View Javadoc
1   package org.andromda.maven.plugin.andromdapp;
2   
3   import java.io.File;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.Collection;
7   import java.util.LinkedHashMap;
8   import java.util.LinkedHashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  import org.andromda.core.common.ResourceUtils;
13  import org.andromda.maven.plugin.andromdapp.eclipse.ClasspathWriter;
14  import org.andromda.maven.plugin.andromdapp.eclipse.ProjectWriter;
15  import org.andromda.maven.plugin.andromdapp.eclipse.Variable;
16  import org.andromda.maven.plugin.andromdapp.utils.ProjectUtils;
17  import org.apache.commons.lang.ObjectUtils;
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.maven.artifact.factory.ArtifactFactory;
20  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
21  import org.apache.maven.artifact.repository.ArtifactRepository;
22  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
23  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
24  import org.apache.maven.artifact.resolver.ArtifactResolver;
25  import org.apache.maven.execution.MavenSession;
26  import org.apache.maven.model.Build;
27  import org.apache.maven.model.Plugin;
28  import org.apache.maven.model.PluginExecution;
29  import org.apache.maven.model.PluginManagement;
30  import org.apache.maven.plugin.AbstractMojo;
31  import org.apache.maven.plugin.MojoExecutionException;
32  import org.apache.maven.project.MavenProject;
33  import org.apache.maven.project.MavenProjectBuilder;
34  import org.apache.maven.project.ProjectBuildingException;
35  import org.codehaus.plexus.util.DirectoryScanner;
36  import org.codehaus.plexus.util.xml.Xpp3Dom;
37  
38  /**
39   * Writes the necessary .classpath and .project files
40   * for a new eclipse application.
41   *
42   * @goal eclipse
43   * @phase generate-sources
44   * @author Chad Brandon
45   */
46  public class EclipseMojo
47      extends AbstractMojo
48  {
49      /**
50       * @parameter expression="${session}"
51       */
52      private MavenSession session;
53  
54      /**
55       * @parameter expression="${project}"
56       * @required
57       * @readonly
58       */
59      private MavenProject project;
60  
61      private static final String POM_FILE_NAME = "pom.xml";
62  
63      /**
64       * Defines the POMs to include when generating the eclipse files.
65       *
66       * @parameter
67       */
68      private String[] includes = new String[] {"*/**/" + POM_FILE_NAME};
69  
70      /**
71       * Defines the POMs to exclude when generating the eclipse files.
72       *
73       * @parameter expression="${exclude.poms}"
74       */
75      private String excludePoms;
76  
77      /**
78       * Defines the POMs to exclude when generating the eclipse files.
79       *
80       * @parameter
81       */
82      private String[] excludes = new String[0];
83  
84      /**
85       * Used to construct Maven project instances from POMs.
86       *
87       * @component
88       */
89      private MavenProjectBuilder projectBuilder;
90  
91      /**
92       * The name of the variable that will store the maven repository location.
93       *
94       * @parameter expression="${repository.variable.name}
95       */
96      private String repositoryVariableName = "M2_REPO";
97  
98      /**
99       * Artifact factory, needed to download source jars for inclusion in classpath.
100      *
101      * @component role="org.apache.maven.artifact.factory.ArtifactFactory"
102      * @required
103      * @readonly
104      */
105     private ArtifactFactory artifactFactory;
106 
107     /**
108      * Artifact resolver, needed to download source jars for inclusion in classpath.
109      *
110      * @component role="org.apache.maven.artifact.resolver.ArtifactResolver"
111      * @required
112      * @readonly
113      */
114     private ArtifactResolver artifactResolver;
115 
116     /**
117      * @parameter expression="${localRepository}"
118      * @required
119      * @readonly
120      */
121     protected ArtifactRepository localRepository;
122 
123     /**
124      * @parameter
125      */
126     protected Variable[] variables;
127 
128     /**
129      * @component
130      */
131     private ArtifactMetadataSource artifactMetadataSource;
132 
133     /**
134      * The artifact types which should be included in the generated Eclipse classpath.
135      *
136      * @parameter
137      */
138     private Set<String> classpathArtifactTypes = new LinkedHashSet<String>(Arrays.asList("jar","ejb"));
139 
140     /**
141      * Whether or not transitive dependencies shall be included in any resources (i.e. .classpath
142      * that are generated by this mojo).
143      *
144      * @parameter expression="${resolveTransitiveDependencies}"
145      */
146     private boolean resolveTransitiveDependencies = true;
147 
148     /**
149      * Allows non-generated configuration to be "merged" into the generated .classpath file.
150      *
151      * @parameter
152      */
153     private String classpathMerge;
154 
155     /**
156      * Whether or not processing should be skipped (this is if you just want to force AndroMDA
157      * not to run on your model).
158      *
159      * @parameter expression="${andromda.run.skip}"
160      */
161     private boolean skipProcessing = false;
162 
163     /**
164      * @see org.apache.maven.plugin.Mojo#execute()
165      */
166     public void execute()
167         throws MojoExecutionException
168     {
169         if (!this.skipProcessing)
170         {
171             try
172             {
173                 final MavenProject rootProject = this.getRootProject();
174                 final ProjectWriter projectWriter = new ProjectWriter(rootProject, this.getLog());
175                 projectWriter.write();
176                 final Map<MavenProject, Collection<String>> originalCompileSourceRoots = this.collectProjectCompileSourceRoots();
177                 final List<MavenProject> projects = this.collectProjects();
178                 this.processCompileSourceRoots(projects);
179                 final ClasspathWriter classpathWriter = new ClasspathWriter(rootProject, this.getLog());
180                 classpathWriter.write(
181                     projects,
182                     this.repositoryVariableName,
183                     this.artifactFactory,
184                     this.artifactResolver,
185                     this.localRepository,
186                     this.artifactMetadataSource,
187                     this.classpathArtifactTypes,
188                     this.project.getRemoteArtifactRepositories(),
189                     this.resolveTransitiveDependencies,
190                     this.variables,
191                     this.classpathMerge);
192                 // - reset to the original source roots
193                 for (final MavenProject project : projects)
194                 {
195                     project.getCompileSourceRoots().clear();
196                     project.getCompileSourceRoots().addAll(originalCompileSourceRoots.get(project));
197                 }
198             }
199             catch (Throwable throwable)
200             {
201                 throw new MojoExecutionException("Error creating eclipse configuration", throwable);
202             }
203         }
204     }
205 
206     /**
207      * Collects all existing project compile source roots.
208      *
209      * @return a collection of collections
210      */
211     private Map<MavenProject, Collection<String>> collectProjectCompileSourceRoots()
212         throws Exception
213     {
214         final Map<MavenProject, Collection<String>> sourceRoots = new LinkedHashMap<MavenProject, Collection<String>>();
215         for (final MavenProject project : this.collectProjects())
216         {
217             sourceRoots.put(project, new ArrayList<String>(project.getCompileSourceRoots()));
218         }
219         return sourceRoots;
220     }
221 
222     private List<MavenProject> projects = new ArrayList<MavenProject>();
223 
224     /**
225      * Collects all projects from all POMs within the current project.
226      *
227      * @return all applicable Maven project instances.
228      *
229      * @throws MojoExecutionException
230      */
231     private List<MavenProject> collectProjects()
232         throws Exception
233     {
234         if (projects.isEmpty())
235         {
236             final List<File> poms = this.getPoms();
237             for (File pom : poms)
238             {
239                 try
240                 {
241                     // - first attempt to get the existing project from the session
242                     final MavenProject project = ProjectUtils.getProject(this.projectBuilder, this.session, pom, this.getLog());
243                     if (project != null)
244                     {
245                         this.getLog().info("found project " + project.getId());
246                         projects.add(project);
247                     }
248                     else
249                     {
250                         if (this.getLog().isWarnEnabled())
251                         {
252                             this.getLog().warn("Could not load project from pom: " + pom + " - ignoring");
253                         }
254                     }
255                 }
256                 catch (ProjectBuildingException exception)
257                 {
258                     throw new MojoExecutionException("Error loading " + pom, exception);
259                 }
260             }
261         }
262         return projects;
263     }
264 
265     /**
266      * Processes the project compile source roots (adds all appropriate ones to the projects)
267      * so that they're available to the eclipse mojos.
268      *
269      * @param projects the projects to process.
270      * @throws Exception
271      */
272     private void processCompileSourceRoots(final List<MavenProject> projects)
273         throws Exception
274     {
275         for (final MavenProject project : projects)
276         {
277             final Set<String> compileSourceRoots = new LinkedHashSet<String>(project.getCompileSourceRoots());
278             compileSourceRoots.addAll(this.getExtraSourceDirectories(project));
279             final String testSourceDirectory = project.getBuild().getTestSourceDirectory();
280             if (StringUtils.isNotBlank(testSourceDirectory))
281             {
282                 compileSourceRoots.add(testSourceDirectory);
283             }
284             project.getCompileSourceRoots().clear();
285             project.getCompileSourceRoots().addAll(compileSourceRoots);
286         }
287     }
288 
289     /**
290      * The artifact id for the multi source plugin.
291      */
292     private static final String MULTI_SOURCE_PLUGIN_ARTIFACT_ID = "build-helper-maven-plugin";
293 
294     /**
295      * Retrieves any additional source directories which are defined within the build-helper-maven-plugin.
296      *
297      * @param project the maven project from which to retrieve the extra source directories.
298      * @return the list of extra source directories.
299      */
300     private List<String> getExtraSourceDirectories(final MavenProject project)
301     {
302         final List<String> sourceDirectories = new ArrayList<String>();
303         final Build build = project.getBuild();
304         if (build != null)
305         {
306             final PluginManagement pluginManagement = build.getPluginManagement();
307             if (pluginManagement != null && !pluginManagement.getPlugins().isEmpty())
308             {
309                 Plugin multiSourcePlugin = null;
310                 for (final Plugin plugin : pluginManagement.getPlugins())
311                 {
312                     if (MULTI_SOURCE_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId()))
313                     {
314                         multiSourcePlugin = plugin;
315                         break;
316                     }
317                 }
318                 final Xpp3Dom configuration = this.getConfiguration(multiSourcePlugin);
319                 if (configuration != null && configuration.getChildCount() > 0)
320                 {
321                     final Xpp3Dom directories = configuration.getChild(0);
322                     if (directories != null)
323                     {
324                         final int childCount = directories.getChildCount();
325                         if (childCount > 0)
326                         {
327                             final String baseDirectory =
328                                 ResourceUtils.normalizePath(ObjectUtils.toString(project.getBasedir()) + '/');
329                             final Xpp3Dom[] children = directories.getChildren();
330                             for (int ctr = 0; ctr < childCount; ctr++)
331                             {
332                                 final Xpp3Dom child = children[ctr];
333                                 if (child != null)
334                                 {
335                                     String directoryValue = ResourceUtils.normalizePath(child.getValue());
336                                     if (directoryValue != null)
337                                     {
338                                         if (!directoryValue.startsWith(baseDirectory))
339                                         {
340                                             directoryValue =
341                                                 ResourceUtils.normalizePath(baseDirectory + directoryValue.trim());
342                                         }
343                                         sourceDirectories.add(directoryValue);
344                                     }
345                                 }
346                             }
347                         }
348                     }
349                 }
350             }
351         }
352         return sourceDirectories;
353     }
354 
355     /**
356      * Retrieves the appropriate configuration instance (first tries
357      * to get the configuration from the plugin, then tries from the plugin's
358      * executions.
359      *
360      * @param plugin the plugin from which the retrieve the configuration.
361      * @return the plugin's configuration, or null if not found.
362      */
363     private Xpp3Dom getConfiguration(final Plugin plugin)
364     {
365         Xpp3Dom configuration = null;
366         if (plugin != null)
367         {
368             if (plugin.getConfiguration() != null)
369             {
370                 configuration = (Xpp3Dom)plugin.getConfiguration();
371             }
372             else
373             {
374                 final List<PluginExecution> executions = plugin.getExecutions();
375                 if (executions != null && !executions.isEmpty())
376                 {
377                     // - there should only be one execution so we get the first one
378                     final PluginExecution execution = plugin.getExecutions().iterator().next();
379                     configuration = (Xpp3Dom)execution.getConfiguration();
380                 }
381             }
382         }
383         return configuration;
384     }
385 
386     /**
387      * Stores the root project.
388      */
389     private MavenProject rootProject;
390 
391     /**
392      * Retrieves the root project (i.e. the root parent project)
393      * for this project.
394      *
395      * @return the root project.
396      * @throws MojoExecutionException
397      */
398     private MavenProject getRootProject()
399         throws MojoExecutionException, ArtifactResolutionException, ArtifactNotFoundException
400     {
401         if (this.rootProject == null)
402         {
403             final MavenProject firstParent = this.project.getParent();
404             File rootFile = this.project.getFile();
405             if (firstParent != null && firstParent.getFile() != null )
406             {
407                 for (this.rootProject = firstParent, rootFile = new File(rootFile.getParentFile().getParentFile(), POM_FILE_NAME);
408                      this.rootProject.getParent() != null && this.rootProject.getParent().getFile() != null;
409                      this.rootProject = this.rootProject.getParent(), rootFile = new File(rootFile.getParentFile().getParentFile(), POM_FILE_NAME))
410                 {
411                 }
412                 // - if the project has no file defined, use the rootFile
413                 if (this.rootProject != null && this.rootProject.getFile() == null && rootFile.exists())
414                 {
415                     this.rootProject.setFile(rootFile);
416                 }
417             }
418             else
419             {
420                 this.rootProject = this.project;
421             }
422         }
423         return this.rootProject;
424     }
425 
426     /**
427      * Retrieves all the POMs for the given project.
428      *
429      * @return all poms found.
430      * @throws MojoExecutionException
431      */
432     private List<File> getPoms()
433         throws Exception
434     {
435         final DirectoryScanner scanner = new DirectoryScanner();
436         scanner.setBasedir(this.getRootProject().getBasedir());
437         scanner.setIncludes(this.includes);
438 
439         final List<String> excludes = new ArrayList<String>(Arrays.asList(this.excludes));
440         if (this.excludePoms != null)
441         {
442             excludes.addAll(Arrays.asList(excludePoms.split(",")));
443         }
444         scanner.setExcludes(excludes.toArray(new String[excludes.size()]));
445         scanner.scan();
446 
447         List<File> poms = new ArrayList<File>();
448 
449         for (int ctr = 0; ctr < scanner.getIncludedFiles().length; ctr++)
450         {
451             final File file = new File(
452                 this.getRootProject().getBasedir(),
453                 scanner.getIncludedFiles()[ctr]);
454             if (file.exists())
455             {
456                 poms.add(file);
457             }
458         }
459 
460         return poms;
461     }
462 }