View Javadoc

1   package org.appfuse;
2   
3   import org.apache.maven.artifact.Artifact;
4   import org.apache.maven.artifact.factory.ArtifactFactory;
5   import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
6   import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
7   import org.apache.maven.model.Dependency;
8   import org.apache.maven.plugin.AbstractMojo;
9   import org.apache.maven.plugin.MojoExecutionException;
10  import org.apache.maven.project.MavenProject;
11  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
12  
13  import java.io.File;
14  import java.io.IOException;
15  import java.util.ArrayList;
16  import java.util.HashSet;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Set;
20  
21  /**
22   * <p>
23   * Allows war artifacts to be used as fully fledged dependencies by including the WEB-INF/classes
24   * directory on the compile classpath.
25   * </p>
26   *
27   * <p>
28   * It does this by creating a &quot;fake&quot; dependency on the project
29   * that points to the jar'ed contents of the WEB-INF/classes directory of any war file included on the project
30   * as a dependency of type warpath. The introduced dependency has scope system to prevent it being included in
31   * war files.
32   * </p>
33   *
34   * @author Michael Horwitz
35   *
36   * @goal add-classes
37   * @phase generate-sources
38   * @requiresDependencyResolution compile
39   */
40  public class AddClassesMojo extends AbstractMojo
41  {
42  
43    /**
44     * Location of the warpath working directory. The contents of the WEB-INF/classes directory
45     * from all warpath dependencies will be extracted to jar files located in this directory.
46     *
47     * @parameter expression="${project.build.directory}/warpath"
48     * @required
49     */
50    private File workDirectory;
51  
52    /**
53     * The list of reactor projects.
54     *
55     * @parameter expression="${reactorProjects}"
56     * @readonly
57     */
58    private List reactorProjects;
59  
60   /**
61     * The maven project.
62     *
63     * @parameter expression="${project}"
64     * @required
65     * @readonly
66     */
67    private MavenProject project;
68  
69  
70    /**
71     * Maven's artifact factory
72     *
73     * @parameter expression="${component.org.apache.maven.artifact.factory.ArtifactFactory}"
74     * @required
75     * @readonly
76     */
77    private ArtifactFactory artifactFactory;
78  
79    /**
80     * The filter that determines the resources, from the dependent war's WEB-INF/classes directory, to
81     * include on the classpath. Default is &quot;**&quot;.
82     *
83     * @parameter expression="**"
84     */
85    private String warpathIncludes;
86  
87    /**
88     * The filter that determines the resources, from the dependent war's WEB-INF/classes directory, to
89     * exclude from the classpath. Note that excludes takes priority over includes. The default is the empty
90     * string, i.e. exclude nothing.
91     *
92     * @parameter
93     */
94    private String warpathExcludes;
95  
96  
97    public void execute()
98            throws MojoExecutionException
99    {
100     File f = workDirectory;
101 
102     if (!f.exists())
103     {
104       f.mkdirs();
105     }
106 
107     //process all warpath dependencies, extracting their classpath elements to suitably named jar files in a sub-directory.
108     Set artifacts = project.getArtifacts();
109 
110     List duplicates = findDuplicates(artifacts);
111 
112     Set newDependencies = new HashSet();
113 
114     for (Iterator iterator = artifacts.iterator(); iterator.hasNext();)
115     {
116       Artifact artifact = (Artifact) iterator.next();
117       ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_COMPILE);
118       if ("warpath".equals(artifact.getType()) && filter.include(artifact) && !artifact.isOptional())
119       {
120         //need to include classes directory.
121         String warWorkingDir = getDefaultFinalName(artifact);
122 
123         getLog().debug("Processing war dependency " + warWorkingDir);
124 
125         if (duplicates.contains(warWorkingDir))
126         {
127           getLog().debug("Duplicate war dependency found:" + warWorkingDir);
128           warWorkingDir = artifact.getGroupId() + "-" + warWorkingDir;
129           getLog().debug("Deplicate war dependency renamed to " + warWorkingDir);
130         }
131 
132         File warClassesDirectory = new File(workDirectory, warWorkingDir);
133         try
134         {
135           WarPathUtils.unpackWarClassesIfNewer(artifact.getFile(), warClassesDirectory, warpathIncludes, warpathExcludes);
136         }
137         catch (IOException e)
138         {
139           throw new MojoExecutionException("I/O error while processing WAR dependencies.", e);
140         }
141         getLog().debug("Adding new dependenvy artifact entry for " + warWorkingDir);
142         try
143         {
144           newDependencies.add(getWarClassesDependency(artifact, warClassesDirectory));
145         }
146         catch (OverConstrainedVersionException e)
147         {
148           throw new MojoExecutionException("Failed to created war classes dependency for artifact " + warWorkingDir, e);
149         }
150       }
151     }
152 
153     if (newDependencies.size() > 0)
154     {
155       //set the dependencies as well for ide plugins
156       addDepenciesToProject(project, newDependencies);
157 
158       //Check through the reactor projects and add dependencies to the execution project
159       //in case this project is run in parallel. Fix need to ensure project dependencies
160       //properly resolved for Eclipse plugin. Ugly, any better suggestions welcome....
161       for (int i = 0; i < reactorProjects.size(); i++)
162       {
163         MavenProject mavenProject = (MavenProject) reactorProjects.get(i);
164         if (mavenProject.getArtifactId().equals(project.getArtifactId()) &&
165                 mavenProject.getGroupId().equals(project.getGroupId()) &&
166                 mavenProject != project)
167         {
168           getLog().debug("Adding dependencies to reactor project: " + mavenProject.getGroupId() + "-" + mavenProject.getArtifactId());
169           addDepenciesToProject(mavenProject, newDependencies);
170         }
171       }
172     }
173 
174   }
175 
176   private void addDepenciesToProject(MavenProject project, Set dependencies)
177           throws MojoExecutionException
178   {
179     List amalgamatedDependencies = new ArrayList();
180     amalgamatedDependencies.addAll(dependencies);
181     amalgamatedDependencies.addAll(project.getDependencies());
182     project.setDependencies(amalgamatedDependencies);
183     try
184     {
185       project.setDependencyArtifacts(project.createArtifacts(artifactFactory, null, null));
186     }
187     catch (InvalidDependencyVersionException e)
188     {
189       throw new MojoExecutionException("Failed to resolve dependency artifacts.", e);
190     }
191   }
192 
193   /**
194    * Build a dependency with scope system for the extracted war class files.
195    *
196    * @param artifact            The original war artifact.
197    * @param warClassesDirectory The directory in which the class files have been extracted.
198    * @return A new dependency, set to scope system, to include in the project's compile classpath.
199    */
200   private Dependency getWarClassesDependency(Artifact artifact, File warClassesDirectory) throws OverConstrainedVersionException
201   {
202     Dependency dependency = new Dependency();
203     dependency.setArtifactId(artifact.getArtifactId());
204     dependency.setGroupId(artifact.getGroupId());
205     dependency.setType("classes");
206     dependency.setScope(Artifact.SCOPE_SYSTEM);
207     dependency.setOptional(true);
208     dependency.setVersion(artifact.getSelectedVersion().toString());
209     dependency.setSystemPath(warClassesDirectory.getPath());
210     return dependency;
211   }
212 
213   /**
214    * Searches a set of artifacts for duplicate filenames and returns a list of duplicates.
215    *
216    * @param artifacts set of artifacts
217    * @return List of duplicated artifacts
218    */
219   private List findDuplicates(Set artifacts)
220   {
221     List duplicates = new ArrayList();
222     List identifiers = new ArrayList();
223     for (Iterator iter = artifacts.iterator(); iter.hasNext();)
224     {
225       Artifact artifact = (Artifact) iter.next();
226       String candidate = getDefaultFinalName(artifact);
227       if (identifiers.contains(candidate))
228       {
229         duplicates.add(candidate);
230       }
231       else
232       {
233         identifiers.add(candidate);
234       }
235     }
236     return duplicates;
237   }
238 
239   /**
240    * Converts the filename of an artifact to artifactId-version.type format.
241    *
242    * @param artifact
243    * @return converted filename of the artifact
244    */
245   private String getDefaultFinalName(Artifact artifact)
246   {
247     if ("warpath".equals(artifact.getType()))
248     {
249       return artifact.getArtifactId() + "-" + artifact.getVersion() + "." +
250              artifact.getType() + ".jar";
251     }
252     else
253     {
254       return artifact.getArtifactId() + "-" + artifact.getVersion() + "." +
255               artifact.getArtifactHandler().getExtension();
256     }
257   }
258 
259 }