View Javadoc
1   package org.apache.maven.archetype.mojos;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.archetype.ArchetypeCreationRequest;
23  import org.apache.maven.archetype.ArchetypeCreationResult;
24  import org.apache.maven.archetype.ArchetypeManager;
25  import org.apache.maven.archetype.common.Constants;
26  import org.apache.maven.archetype.ui.creation.ArchetypeCreationConfigurator;
27  import org.apache.maven.artifact.repository.ArtifactRepository;
28  import org.apache.maven.execution.MavenSession;
29  import org.apache.maven.plugin.AbstractMojo;
30  import org.apache.maven.plugin.MojoExecutionException;
31  import org.apache.maven.plugin.MojoFailureException;
32  import org.apache.maven.plugins.annotations.Component;
33  import org.apache.maven.plugins.annotations.Execute;
34  import org.apache.maven.plugins.annotations.LifecyclePhase;
35  import org.apache.maven.plugins.annotations.Mojo;
36  import org.apache.maven.plugins.annotations.Parameter;
37  import org.apache.maven.project.MavenProject;
38  import org.codehaus.plexus.util.PropertyUtils;
39  import org.codehaus.plexus.util.StringUtils;
40  
41  import java.io.File;
42  import java.util.ArrayList;
43  import java.util.Arrays;
44  import java.util.List;
45  import java.util.Properties;
46  
47  /**
48   * <p>
49   * Creates an archetype project from the current project.
50   * </p>
51   * <p>
52   * This goal reads your source and resource files, the values of its parameters,
53   * and properties you specify in a <code>.property</code> file, and uses them to
54   * create a Maven archetype project using the maven-archetype packaging.
55   * If you build the resulting project, it will create the archetype. You can then
56   * use this archetype to create new projects that resemble the original.
57   * </p>
58   * <p>
59   * The maven-archetype-plugin uses Velocity to expand template files, and this documentation
60   * talks about 'Velocity Properties', which are values substituted into Velocity templates.
61   * See <a href="http://velocity.apache.org/engine/devel/user-guide.html">The Velocity User's Guide</a>
62   * for more information.
63   * </p>
64   * <p>
65   * This goal modifies the text of the files of the current project to form the Velocity template files
66   * that make up the archetype.
67   * </p>
68   * <dl>
69   * <dt>GAV</dt><dd>The GAV values for the current project are replaced by properties: groupId, artifactId, and version.
70   * The user chooses new values for these when generating a project from the archetype.</dd>
71   * <dt>package</dt><dd>All the files under one specified Java (or cognate) package are relocated to a project
72   * that the user chooses when generating a project. References to the class name are replaced by a property reference. For
73   * example, if the current project's sources are in the package <code>org.apache.saltedpeanuts</code>, then
74   * any example of the string <code>org.apache.saltedpeanuts</code> is replaced with the Velocity property
75   * reference <code>${packageName}</code>. When the user generates a project, this is in turn replaced by
76   * his or her choice of a package.
77   * </dd>
78   * <dt>custom properties</dt><dd>You may identify additional strings that should be replaced by parameters.
79   * To add custom properties, you must use the <code>propertyFile</code> parameter to specify a property file.
80   * See the documentation for <code>propertyFile</code> for the details.
81   * </dd>
82   * <dt>integration tests</dt><dd>You may also specify a set of integration tests to be executed right after
83   * you create archetype from the project. Each test consists of a separate folder under src/it/projects folder and
84   * in there you specify <code>archetype.properties</code>, <code>goal.txt</code> and <code>verify.groovy</code> files.
85   * The <code>archetype.properties</code> file is the file used to generate a new project from the newly created archetype
86   * and the <code>goal.txt</code> a single-line file to specify the maven goal to invoke after generation of the test-project.
87   * Finally the <code>verify.groovy</code> is a groovy file that you can use to specify your assertions on the generated project.
88   * </dd>
89   * </dl>
90   * <p>
91   * Note that you may need to edit the results of this goal. This goal has no way to exclude unwanted files,
92   * or add copyright notices to the Velocity templates, or add more complex elements to the archetype metadata file.
93   * </p>
94   * <p>
95   * This goal also generates a simple integration-test that exercises the generated archetype.
96   * </p>
97   *
98   * @author rafale
99   */
100 @Mojo( name = "create-from-project", requiresProject = true, aggregator = true )
101 @Execute( phase = LifecyclePhase.GENERATE_SOURCES )
102 public class CreateArchetypeFromProjectMojo
103     extends AbstractMojo
104 {
105 
106     @Component
107     private ArchetypeCreationConfigurator configurator;
108 
109     /**
110      * Enable the interactive mode to define the archetype from the project.
111      */
112     @Parameter( property = "interactive", defaultValue = "false" )
113     private boolean interactive;
114 
115     @Component
116     private ArchetypeManager manager;
117 
118     /**
119      * File extensions which are checked for project's text files (vs binary files).
120      */
121     @Parameter( property = "archetype.filteredExtentions" )
122     private String archetypeFilteredExtentions;
123 
124     /**
125      * Directory names which are checked for project's sources main package.
126      */
127     @Parameter( property = "archetype.languages" )
128     private String archetypeLanguages;
129 
130     /**
131      * Velocity templates encoding.
132      */
133     @Parameter( property = "archetype.encoding", defaultValue = "UTF-8" )
134     private String defaultEncoding;
135 
136     /**
137      * Create a partial archetype.
138      */
139     @Parameter( property = "archetype.partialArchetype" )
140     private boolean partialArchetype = false;
141 
142     /**
143      * Create pom's velocity templates with CDATA preservation. This uses the <code>String.replaceAll()</code>
144      * method and risks to have some overly replacement capabilities (beware of '1.0' value).
145      */
146     @Parameter( property = "archetype.preserveCData" )
147     private boolean preserveCData = false;
148 
149     @Parameter( defaultValue = "${localRepository}", readonly = true )
150     private ArtifactRepository localRepository;
151 
152     /**
153      * POMs in archetype are created with their initial parent.
154      * This property is ignored when preserveCData is true.
155      */
156     @Parameter( property = "archetype.keepParent" )
157     private boolean keepParent = true;
158 
159     /**
160      * The Maven project to create an archetype from.
161      */
162     @Parameter( defaultValue = "${project}", readonly = true, required = true )
163     private MavenProject project;
164 
165     /**
166      * The property file that holds the plugin configuration. If this is provided, then
167      * the plugin reads properties from here. The properties in here can be standard
168      * properties listed below or custom properties for this archetype. The standard properties
169      * are below. Several of them overlap parameters of this goal; it's better to just
170      * set the parameter.
171      * <p/>
172      * <dl><dt>package</dt><dd>See the packageName parameter.</dd>
173      * <dt>archetype.languages</dt><dd>See the archetypeLanguages parameter.</dd>
174      * <dt>groupId</dt><dd>The default groupId of the generated project.</dd>
175      * <dt>artifactId</dt><dd>The default artifactId of the generated project.</dd>
176      * <dt>version</dt><dd>The default version of the generated project.</dd>
177      * <dt>excludePatterns</dt><dd>A comma-separated list of paths that will not be included in the resulting
178      * archetype.</dd>
179      * <dt>archetype.filteredExtensions</dt><dd>See the filteredExensions parameter.</dd>
180      * </dl>
181      * <strong>Custom Properties</strong>
182      * <p>
183      * Custom properties allow you to replace some constant values in the project's files
184      * with Velocity macro references. When a user generates a project from your archetype
185      * he or she gets the opportunity to replace the value from the source project.
186      * </p>
187      * <p>
188      * Custom property names <strong>may not contain the '.' character</strong>.
189      * </p>
190      * <p>
191      * For example, if you include a line like the following in your property file:
192      * <pre>cxf-version=2.5.1-SNAPSHOT</pre>
193      * the plugin will search your files for the <code>2.5.1-SNAPSHOT</code> string and
194      * replace them with references to a velocity macro <code>cxf-version</cpde>. It will
195      * then list <code>cxf-version</code> as a <code>requiredProperty</code> in the
196      * <code>archetype-metadata.xml</code>, with <code>2.5.1-SNAPSHOT</code> as the default value.
197      * </p>
198      */
199     @Parameter( property = "archetype.properties" )
200     private File propertyFile;
201 
202     /**
203      * The property telling which phase to call on the generated archetype.
204      * Interesting values are: <code>package</code>, <code>integration-test</code>, <code>install</code> and <code>deploy</code>.
205      */
206     @Parameter( property = "archetype.postPhase", defaultValue = "package" )
207     private String archetypePostPhase;
208 
209     /**
210      * The directory where the archetype should be created.
211      */
212     @Parameter( defaultValue = "${project.build.directory}/generated-sources/archetype" )
213     private File outputDirectory;
214 
215     @Parameter( property = "testMode" )
216     private boolean testMode;
217 
218     /**
219      * The package name for Java source files to be incorporated in the archetype and
220      * and relocated to the package that the user selects.
221      */
222     @Parameter( property = "packageName" )
223     private String packageName; //Find a better way to resolve the package!!! enforce usage of the configurator
224 
225     @Parameter( defaultValue = "${session}", readonly = true, required = true )
226     private MavenSession session;
227 
228     public void execute()
229         throws MojoExecutionException, MojoFailureException
230     {
231         Properties executionProperties = session.getExecutionProperties();
232         try
233         {
234             if ( propertyFile != null )
235             {
236                 propertyFile.getParentFile().mkdirs();
237             }
238 
239             List<String> languages = getLanguages( archetypeLanguages, propertyFile );
240 
241             Properties properties =
242                 configurator.configureArchetypeCreation( project, Boolean.valueOf( interactive ), executionProperties,
243                                                          propertyFile, languages );
244 
245             List<String> filtereds = getFilteredExtensions( archetypeFilteredExtentions, propertyFile );
246 
247             ArchetypeCreationRequest request =
248                 new ArchetypeCreationRequest().setDefaultEncoding( defaultEncoding ).setProject( project )
249                 /* Used when in interactive mode */.setProperties( properties ).setLanguages( languages )
250                 /* Should be refactored to use some ant patterns */.setFiltereds( filtereds )
251                 /* This should be correctly handled */.setPreserveCData( preserveCData ).setKeepParent(
252                     keepParent ).setPartialArchetype( partialArchetype )
253                 .setLocalRepository( localRepository ).setProjectBuildingRequest( session.getProjectBuildingRequest() )
254                 /* this should be resolved and asked for user to verify */.setPackageName( packageName ).setPostPhase(
255                     archetypePostPhase ).setOutputDirectory( outputDirectory );
256 
257             ArchetypeCreationResult result = manager.createArchetypeFromProject( request );
258 
259             if ( result.getCause() != null )
260             {
261                 throw new MojoFailureException( result.getCause(), result.getCause().getMessage(),
262                                                 result.getCause().getMessage() );
263             }
264 
265             getLog().info( "Archetype project created in " + outputDirectory );
266 
267             if ( testMode )
268             {
269                 // Now here a properties file would be useful to write so that we could automate
270                 // some functional tests where we string together an:
271                 //
272                 // archetype create from project -> deploy it into a test repo
273                 // project create from archetype -> use the repository we deployed to archetype to
274                 // generate
275                 // test the output
276                 //
277                 // This of course would be strung together from the outside.
278             }
279 
280         }
281         catch ( MojoFailureException ex )
282         {
283             throw ex;
284         }
285         catch ( Exception ex )
286         {
287             throw new MojoFailureException( ex, ex.getMessage(), ex.getMessage() );
288         }
289     }
290 
291     private List<String> getFilteredExtensions( String archetypeFilteredExtentions, File propertyFile )
292     {
293         List<String> filteredExtensions = new ArrayList<String>();
294 
295         if ( StringUtils.isNotEmpty( archetypeFilteredExtentions ) )
296         {
297             filteredExtensions.addAll( Arrays.asList( StringUtils.split( archetypeFilteredExtentions, "," ) ) );
298 
299             getLog().debug( "Found in command line extensions = " + filteredExtensions );
300         }
301 
302         if ( filteredExtensions.isEmpty() && propertyFile != null && propertyFile.exists() )
303         {
304             Properties properties = PropertyUtils.loadProperties( propertyFile );
305 
306             String extensions = properties.getProperty( Constants.ARCHETYPE_FILTERED_EXTENSIONS );
307             if ( StringUtils.isNotEmpty( extensions ) )
308             {
309                 filteredExtensions.addAll( Arrays.asList( StringUtils.split( extensions, "," ) ) );
310             }
311 
312             getLog().debug( "Found in propertyFile " + propertyFile.getName() + " extensions = " + filteredExtensions );
313         }
314 
315         if ( filteredExtensions.isEmpty() )
316         {
317             filteredExtensions.addAll( Constants.DEFAULT_FILTERED_EXTENSIONS );
318 
319             getLog().debug( "Using default extensions = " + filteredExtensions );
320         }
321 
322         return filteredExtensions;
323     }
324 
325     private List<String> getLanguages( String archetypeLanguages, File propertyFile )
326     {
327         List<String> resultingLanguages = new ArrayList<String>();
328 
329         if ( StringUtils.isNotEmpty( archetypeLanguages ) )
330         {
331             resultingLanguages.addAll( Arrays.asList( StringUtils.split( archetypeLanguages, "," ) ) );
332 
333             getLog().debug( "Found in command line languages = " + resultingLanguages );
334         }
335 
336         if ( resultingLanguages.isEmpty() && propertyFile != null && propertyFile.exists() )
337         {
338             Properties properties = PropertyUtils.loadProperties( propertyFile );
339 
340             String languages = properties.getProperty( Constants.ARCHETYPE_LANGUAGES );
341             if ( StringUtils.isNotEmpty( languages ) )
342             {
343                 resultingLanguages.addAll( Arrays.asList( StringUtils.split( languages, "," ) ) );
344             }
345 
346             getLog().debug( "Found in propertyFile " + propertyFile.getName() + " languages = " + resultingLanguages );
347         }
348 
349         if ( resultingLanguages.isEmpty() )
350         {
351             resultingLanguages.addAll( Constants.DEFAULT_LANGUAGES );
352 
353             getLog().debug( "Using default languages = " + resultingLanguages );
354         }
355 
356         return resultingLanguages;
357     }
358 }