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   * </dl>
82   * <p>
83   * Note that you may need to edit the results of this goal. This goal has no way to exclude unwanted files,
84   * or add copyright notices to the Velocity templates, or add more complex elements to the archetype metadata file.
85   * </p>
86   * <p>
87   * This goal also generates a simple integration-test that exercises the generated archetype.
88   * </p>
89   *
90   * @author rafale
91   */
92  @Mojo( name = "create-from-project", requiresProject = true, aggregator = true )
93  @Execute( phase = LifecyclePhase.GENERATE_SOURCES )
94  public class CreateArchetypeFromProjectMojo
95      extends AbstractMojo
96  {
97  
98      @Component
99      private ArchetypeCreationConfigurator configurator;
100 
101     /**
102      * Enable the interactive mode to define the archetype from the project.
103      */
104     @Parameter( property = "interactive", defaultValue = "false" )
105     private boolean interactive;
106 
107     @Component
108     private ArchetypeManager manager;
109 
110     /**
111      * File extensions which are checked for project's text files (vs binary files).
112      */
113     @Parameter( property = "archetype.filteredExtentions" )
114     private String archetypeFilteredExtentions;
115 
116     /**
117      * Directory names which are checked for project's sources main package.
118      */
119     @Parameter( property = "archetype.languages" )
120     private String archetypeLanguages;
121 
122     /**
123      * The location of the registry file.
124      */
125     @Parameter( defaultValue = "${user.home}/.m2/archetype.xml" )
126     private File archetypeRegistryFile;
127 
128     /**
129      * Velocity templates encoding.
130      */
131     @Parameter( property = "archetype.encoding", defaultValue = "UTF-8" )
132     private String defaultEncoding;
133 
134     /**
135      * Create a partial archetype.
136      */
137     @Parameter( property = "archetype.partialArchetype" )
138     private boolean partialArchetype = false;
139 
140     /**
141      * Create pom's velocity templates with CDATA preservation. This uses the <code>String.replaceAll()</code>
142      * method and risks to have some overly replacement capabilities (beware of '1.0' value).
143      */
144     @Parameter( property = "archetype.preserveCData" )
145     private boolean preserveCData = false;
146 
147     @Parameter( defaultValue = "${localRepository}", readonly = true )
148     private ArtifactRepository localRepository;
149 
150     /**
151      * POMs in archetype are created with their initial parent.
152      * This property is ignored when preserveCData is true.
153      */
154     @Parameter( property = "archetype.keepParent" )
155     private boolean keepParent = true;
156 
157     /**
158      * The Maven project to create an archetype from.
159      */
160     @Parameter( defaultValue = "${project}", readonly = true, required = true )
161     private MavenProject project;
162 
163     /**
164      * The property file that holds the plugin configuration. If this is provided, then
165      * the plugin reads properties from here. The properties in here can be standard
166      * properties listed below or custom properties for this archetype. The standard properties
167      * are below. Several of them overlap parameters of this goal; it's better to just
168      * set the parameter.
169      * <p/>
170      * <dl><dt>package</dt><dd>See the packageName parameter.</dd>
171      * <dt>archetype.languages</dt><dd>See the archetypeLanguages parameter.</dd>
172      * <dt>groupId</dt><dd>The default groupId of the generated project.</dd>
173      * <dt>artifactId</dt><dd>The default artifactId of the generated project.</dd>
174      * <dt>version</dt><dd>The default version of the generated project.</dd>
175      * <dt>archetype.filteredExtensions</dt><dd>See the filteredExensions parameter.</dd>
176      * </dl>
177      * <strong>Custom Properties</strong>
178      * <p>
179      * Custom properties allow you to replace some constant values in the project's files
180      * with Velocity macro references. When a user generates a project from your archetype
181      * he or she gets the opportunity to replace the value from the source project.
182      * </p>
183      * <p>
184      * Custom property names <strong>may not contain the '.' character</strong>.
185      * </p>
186      * <p>
187      * For example, if you include a line like the following in your property file:
188      * <pre>cxf-version=2.5.1-SNAPSHOT</pre>
189      * the plugin will search your files for the string <pre>2.5.1-SNAPSHOT</pre> and
190      * replace them with references to a velocity macro <pre>cxf-version</pre>. It will
191      * then list <pre>cxf-version</pre> as a <pre>requiredProperty</pre> in the
192      * archetype-metadata.xml, with <pre>2.5.1-SNAPSHOT</pre> as the default value.
193      * </p>
194      */
195     @Parameter( property = "archetype.properties" )
196     private File propertyFile;
197 
198     /**
199      * The property telling which phase to call on the generated archetype.
200      * Interesting values are: <code>package</code>, <code>integration-test</code>, <code>install</code> and <code>deploy</code>.
201      */
202     @Parameter( property = "archetype.postPhase", defaultValue = "package" )
203     private String archetypePostPhase;
204 
205     /**
206      * The directory where the archetype should be created.
207      */
208     @Parameter( defaultValue = "${project.build.directory}/generated-sources/archetype" )
209     private File outputDirectory;
210 
211     @Parameter( property = "testMode" )
212     private boolean testMode;
213 
214     /**
215      * The package name for Java source files to be incorporated in the archetype and
216      * and relocated to the package that the user selects.
217      */
218     @Parameter( property = "packageName" )
219     private String packageName; //Find a better way to resolve the package!!! enforce usage of the configurator
220 
221     @Parameter( defaultValue = "${session}", readonly = true, required = true )
222     private MavenSession session;
223 
224     public void execute()
225         throws MojoExecutionException, MojoFailureException
226     {
227         Properties executionProperties = session.getExecutionProperties();
228         try
229         {
230             if ( propertyFile != null )
231             {
232                 propertyFile.getParentFile().mkdirs();
233             }
234 
235             List<String> languages = getLanguages( archetypeLanguages, propertyFile );
236 
237             Properties properties =
238                 configurator.configureArchetypeCreation( project, Boolean.valueOf( interactive ), executionProperties,
239                                                          propertyFile, languages );
240 
241             List<String> filtereds = getFilteredExtensions( archetypeFilteredExtentions, propertyFile );
242 
243             ArchetypeCreationRequest request =
244                 new ArchetypeCreationRequest().setDefaultEncoding( defaultEncoding ).setProject( project )
245                 /* Used when in interactive mode */.setProperties( properties ).setLanguages( languages )
246                 /* Should be refactored to use some ant patterns */.setFiltereds( filtereds )
247                 /* This should be correctly handled */.setPreserveCData( preserveCData ).setKeepParent(
248                     keepParent ).setPartialArchetype( partialArchetype )
249                 /* This should be used before there and use only languages and filtereds */.setArchetypeRegistryFile(
250                     archetypeRegistryFile ).setLocalRepository( localRepository )
251                 /* this should be resolved and asked for user to verify */.setPackageName( packageName ).setPostPhase(
252                     archetypePostPhase ).setOutputDirectory( outputDirectory );
253 
254             ArchetypeCreationResult result = manager.createArchetypeFromProject( request );
255 
256             if ( result.getCause() != null )
257             {
258                 throw new MojoFailureException( result.getCause(), result.getCause().getMessage(),
259                                                 result.getCause().getMessage() );
260             }
261 
262             getLog().info( "Archetype created in " + outputDirectory );
263 
264             if ( testMode )
265             {
266                 // Now here a properties file would be useful to write so that we could automate
267                 // some functional tests where we string together an:
268                 //
269                 // archetype create from project -> deploy it into a test repo
270                 // project create from archetype -> use the repository we deployed to archetype to
271                 // generate
272                 // test the output
273                 //
274                 // This of course would be strung together from the outside.
275             }
276 
277         }
278         catch ( MojoFailureException ex )
279         {
280             throw ex;
281         }
282         catch ( Exception ex )
283         {
284             throw new MojoFailureException( ex, ex.getMessage(), ex.getMessage() );
285         }
286     }
287 
288     private List<String> getFilteredExtensions( String archetypeFilteredExtentions, File propertyFile )
289     {
290         List<String> filteredExtensions = new ArrayList<String>();
291 
292         if ( StringUtils.isNotEmpty( archetypeFilteredExtentions ) )
293         {
294             filteredExtensions.addAll( Arrays.asList( StringUtils.split( archetypeFilteredExtentions, "," ) ) );
295 
296             getLog().debug( "Found in command line extensions = " + filteredExtensions );
297         }
298 
299         if ( filteredExtensions.isEmpty() && propertyFile != null && propertyFile.exists() )
300         {
301             Properties properties = PropertyUtils.loadProperties( propertyFile );
302 
303             String extensions = properties.getProperty( Constants.ARCHETYPE_FILTERED_EXTENSIONS );
304             if ( StringUtils.isNotEmpty( extensions ) )
305             {
306                 filteredExtensions.addAll( Arrays.asList( StringUtils.split( extensions, "," ) ) );
307             }
308 
309             getLog().debug( "Found in propertyFile " + propertyFile.getName() + " extensions = " + filteredExtensions );
310         }
311 
312         if ( filteredExtensions.isEmpty() )
313         {
314             filteredExtensions.addAll( Constants.DEFAULT_FILTERED_EXTENSIONS );
315 
316             getLog().debug( "Using default extensions = " + filteredExtensions );
317         }
318 
319         return filteredExtensions;
320     }
321 
322     private List<String> getLanguages( String archetypeLanguages, File propertyFile )
323     {
324         List<String> resultingLanguages = new ArrayList<String>();
325 
326         if ( StringUtils.isNotEmpty( archetypeLanguages ) )
327         {
328             resultingLanguages.addAll( Arrays.asList( StringUtils.split( archetypeLanguages, "," ) ) );
329 
330             getLog().debug( "Found in command line languages = " + resultingLanguages );
331         }
332 
333         if ( resultingLanguages.isEmpty() && propertyFile != null && propertyFile.exists() )
334         {
335             Properties properties = PropertyUtils.loadProperties( propertyFile );
336 
337             String languages = properties.getProperty( Constants.ARCHETYPE_LANGUAGES );
338             if ( StringUtils.isNotEmpty( languages ) )
339             {
340                 resultingLanguages.addAll( Arrays.asList( StringUtils.split( languages, "," ) ) );
341             }
342 
343             getLog().debug( "Found in propertyFile " + propertyFile.getName() + " languages = " + resultingLanguages );
344         }
345 
346         if ( resultingLanguages.isEmpty() )
347         {
348             resultingLanguages.addAll( Constants.DEFAULT_LANGUAGES );
349 
350             getLog().debug( "Using default languages = " + resultingLanguages );
351         }
352 
353         return resultingLanguages;
354     }
355 }