001package org.andromda.maven.plugin.configuration;
002
003import java.io.File;
004import java.io.IOException;
005import java.io.StringReader;
006import java.net.MalformedURLException;
007import java.net.URL;
008import java.net.URLClassLoader;
009import java.util.List;
010import java.util.Properties;
011import org.andromda.core.common.ResourceUtils;
012import org.andromda.core.configuration.Configuration;
013import org.apache.commons.lang.ObjectUtils;
014import org.apache.maven.artifact.Artifact;
015import org.apache.maven.artifact.factory.ArtifactFactory;
016import org.apache.maven.artifact.repository.ArtifactRepository;
017import org.apache.maven.model.Dependency;
018import org.apache.maven.model.Plugin;
019import org.apache.maven.plugin.AbstractMojo;
020import org.apache.maven.project.MavenProject;
021import org.apache.maven.settings.Settings;
022import org.apache.maven.shared.filtering.PropertyUtils;
023import org.codehaus.plexus.util.InterpolationFilterReader;
024
025/**
026 * An abstract Mojo for dealing with the AndroMDA configuration,
027 * if a plugin needs to use the AndroMDA configuration, it should extend this
028 * class.
029 *
030 * @author Chad Brandon
031 * @author Bob Fields
032 */
033public abstract class AbstractConfigurationMojo
034    extends AbstractMojo
035{
036    /**
037     * The path to the mappings within the plugin.
038     */
039    private static final String MAPPINGS_PATH = "META-INF/andromda/mappings";
040
041    /**
042     * Creates the Configuration instance from the {@link URL}
043     *
044     * @param configurationUri
045     * @return the configuration instance
046     * @throws IOException if the URL is invalid.
047     */
048    protected Configuration getConfiguration(final URL configurationUri)
049        throws IOException
050    {
051        final String contents = this.replaceProperties(ResourceUtils.getContents(configurationUri));
052        final Configuration configuration = Configuration.getInstance(contents);
053        final URL mappingsUrl = ResourceUtils.getResource(MAPPINGS_PATH);
054        if (mappingsUrl != null)
055        {
056            configuration.addMappingsSearchLocation(mappingsUrl.toString());
057        }
058        return configuration;
059    }
060
061    /**
062     * Collects and returns all properties as a Properties instance.
063     *
064     * @return the properties including those from the project, settings, etc.
065     * @throws IOException
066     */
067    protected Properties getProperties()
068        throws IOException
069    {
070        // System properties
071        final Properties properties = new Properties();
072
073        properties.put(
074            "settings",
075            this.getSettings());
076
077        // - project properties
078        properties.putAll(this.getProject().getProperties());
079        for (final String propertiesFile : (Iterable<String>) this.getPropertyFiles())
080        {
081            final Properties projectProperties = PropertyUtils.loadPropertyFile(
082                    new File(propertiesFile),
083                    true,
084                    true);
085
086            properties.putAll(projectProperties);
087        }
088
089        for (Object objProperty : properties.keySet())
090        {
091            final String property = (String) objProperty;
092            final String value = this.replaceProperties(
093                    properties,
094                    ObjectUtils.toString(properties.get(property)));
095            properties.put(
096                    property,
097                    value);
098        }
099
100        properties.putAll(System.getProperties());
101
102        return properties;
103    }
104
105    /**
106     * Replaces all properties having the style
107     * <code>${some.property}</code> with the value
108     * of the specified property if there is one.
109     *
110     * @param string the string to perform replacement on.
111     * @return this.replaceProperties(this.getProperties(),string);
112     * @throws IOException
113     */
114    protected String replaceProperties(final String string)
115        throws IOException
116    {
117        return this.replaceProperties(
118            this.getProperties(),
119            string);
120    }
121
122    /**
123     * The begin token for interpolation.
124     */
125    private static final String BEGIN_TOKEN = "${";
126
127    /**
128     * The end token for interpolation.
129     */
130    private static final String END_TOKEN = "}";
131
132    /**
133     * Replaces all properties having the style
134     * <code>${some.property}</code> with the value
135     * of the specified property if there is one.
136     *
137     * @param properties the properties used to perform the replacement.
138     * @param string the fileContents to perform replacement on.
139     */
140    private String replaceProperties(
141        final Properties properties,
142        final String string)
143        throws IOException
144    {
145        final StringReader stringReader = new StringReader(string);
146        InterpolationFilterReader reader = new InterpolationFilterReader(stringReader, properties, "${", "}");
147        reader.reset();
148        reader = new InterpolationFilterReader(
149                reader,
150                new BeanProperties(this.getProject()),
151                BEGIN_TOKEN,
152                END_TOKEN);
153        reader = new InterpolationFilterReader(
154                reader,
155                new BeanProperties(this.getSettings()),
156                BEGIN_TOKEN,
157                END_TOKEN);
158        return ResourceUtils.getContents(reader);
159    }
160
161    /**
162     * Sets the current context class loader from the given runtime classpath elements.
163     *
164     * @param classpathFiles
165     * @throws MalformedURLException
166     */
167    protected void initializeClasspathFromClassPathElements(final List<String> classpathFiles)
168        throws MalformedURLException
169    {
170        if (classpathFiles != null && !classpathFiles.isEmpty())
171        {
172            final URL[] classpathUrls = new URL[classpathFiles.size()];
173
174            for (int ctr = 0; ctr < classpathFiles.size(); ++ctr)
175            {
176                final File file = new File(classpathFiles.get(ctr));
177                if (this.getLog().isDebugEnabled())
178                {
179                    getLog().debug("adding to classpath '" + file + '\'');
180                }
181                classpathUrls[ctr] = file.toURI().toURL();
182            }
183
184            final URLClassLoader loader =
185                new ConfigurationClassLoader(classpathUrls,
186                    Thread.currentThread().getContextClassLoader());
187            Thread.currentThread().setContextClassLoader(loader);
188        }
189    }
190
191    /**
192     * Adds any dependencies to the current project from the plugin
193     * having the given <code>pluginArtifactId</code>.
194     *
195     * @param pluginArtifactId the artifactId of the plugin of which to add its dependencies.
196     * @param scope the artifact scope in which to add them (runtime, compile, etc).
197     */
198    protected void addPluginDependencies(
199        final String pluginArtifactId,
200        final String scope)
201    {
202        if (pluginArtifactId != null)
203        {
204            final List<Plugin> plugins = this.getPlugins();
205            if (plugins != null)
206            {
207                for (final Plugin plugin : plugins)
208                {
209                    if (pluginArtifactId.equals(plugin.getArtifactId()))
210                    {
211                        final List<Dependency> dependencies = plugin.getDependencies();
212                        if (dependencies != null)
213                        {
214                            for (Dependency dependency : plugin.getDependencies())
215                            {
216                                this.addDependency(
217                                        dependency,
218                                        scope);
219                            }
220                        }
221                    }
222                }
223            }
224        }
225    }
226
227    // Can't do anything about raw Collection types in MavenProject dependency
228    @SuppressWarnings("unchecked")
229    /**
230     * Adds a dependency to the current project's dependencies.
231     *
232     * @param dependency
233     */
234    private void addDependency(
235        final Dependency dependency,
236        final String scope)
237    {
238        final ArtifactRepository localRepository = this.getLocalRepository();
239        final MavenProject project = this.getProject();
240        if (project != null && localRepository != null)
241        {
242            if (dependency != null)
243            {
244                final Artifact artifact =
245                    this.getFactory().createArtifact(
246                        dependency.getGroupId(),
247                        dependency.getArtifactId(),
248                        dependency.getVersion(),
249                        scope,
250                        dependency.getType());
251                final File file = new File(
252                        localRepository.getBasedir(),
253                        localRepository.pathOf(artifact));
254                artifact.setFile(file);
255                project.getDependencies().add(dependency);
256                project.getArtifacts().add(artifact);
257            }
258        }
259    }
260
261    /**
262     * Gets the current project.
263     *
264     * @return Returns the project.
265     */
266    protected abstract MavenProject getProject();
267
268    /**
269     * Gets the property files for the profile (the ones defined
270     * in the filters section of the POM).
271     *
272     * @return Returns the propertyFiles.
273     */
274    protected abstract List<String> getPropertyFiles();
275
276    /**
277     * Gets the current settings of the project.
278     *
279     * @return Returns the settings.
280     */
281    protected abstract Settings getSettings();
282
283    /**
284     * Gets the artifact factory used to construct any new required artifacts.
285     * @return ArtifactFactory
286     */
287    protected abstract ArtifactFactory getFactory();
288
289    /**
290     * Gets the current project's registered plugin implementations.
291     * @return pluginList
292     *
293     * @parameter expression="${project.build.plugins}"
294     * @required
295     * @readonly
296     */
297    protected abstract List<Plugin> getPlugins();
298
299    /**
300     * Gets the current local repository instance.
301     *
302     * @return the local repository instance of the current project.
303     */
304    protected abstract ArtifactRepository getLocalRepository();
305
306    /**
307     * Set this to 'true' to bypass cartridge tests entirely. Its use is NOT RECOMMENDED, but quite convenient on occasion.
308     *
309     * @parameter expression="${maven.test.skip}" default-value="false"
310     */
311    protected boolean skip;
312
313    /**
314     *  Set this to 'true' to skip running tests, but still compile them. Its use is NOT RECOMMENDED, but quite convenient on occasion.
315     *
316     * @parameter expression="${skipTests}" default-value="false"
317     */
318    protected boolean skipTests;
319
320    /**
321     * Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite convenient on occasion.
322     *
323     * @parameter expression="${maven.test.failure.ignore}" default-value="false"
324     */
325    protected boolean testFailureIgnore;
326}