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>excludePatterns</dt><dd>A comma-separated list of paths that will not be included in the resulting
176      * archetype.</dd>
177      * <dt>archetype.filteredExtensions</dt><dd>See the filteredExensions parameter.</dd>
178      * </dl>
179      * <strong>Custom Properties</strong>
180      * <p>
181      * Custom properties allow you to replace some constant values in the project's files
182      * with Velocity macro references. When a user generates a project from your archetype
183      * he or she gets the opportunity to replace the value from the source project.
184      * </p>
185      * <p>
186      * Custom property names <strong>may not contain the '.' character</strong>.
187      * </p>
188      * <p>
189      * For example, if you include a line like the following in your property file:
190      * <pre>cxf-version=2.5.1-SNAPSHOT</pre>
191      * the plugin will search your files for the <code>2.5.1-SNAPSHOT</code> string and
192      * replace them with references to a velocity macro <code>cxf-version</cpde>. It will
193      * then list <code>cxf-version</code> as a <code>requiredProperty</code> in the
194      * <code>archetype-metadata.xml</code>, with <code>2.5.1-SNAPSHOT</code> as the default value.
195      * </p>
196      */
197     @Parameter( property = "archetype.properties" )
198     private File propertyFile;
199 
200     /**
201      * The property telling which phase to call on the generated archetype.
202      * Interesting values are: <code>package</code>, <code>integration-test</code>, <code>install</code> and <code>deploy</code>.
203      */
204     @Parameter( property = "archetype.postPhase", defaultValue = "package" )
205     private String archetypePostPhase;
206 
207     /**
208      * The directory where the archetype should be created.
209      */
210     @Parameter( defaultValue = "${project.build.directory}/generated-sources/archetype" )
211     private File outputDirectory;
212 
213     @Parameter( property = "testMode" )
214     private boolean testMode;
215 
216     /**
217      * The package name for Java source files to be incorporated in the archetype and
218      * and relocated to the package that the user selects.
219      */
220     @Parameter( property = "packageName" )
221     private String packageName; //Find a better way to resolve the package!!! enforce usage of the configurator
222 
223     @Parameter( defaultValue = "${session}", readonly = true, required = true )
224     private MavenSession session;
225 
226     public void execute()
227         throws MojoExecutionException, MojoFailureException
228     {
229         Properties executionProperties = session.getExecutionProperties();
230         try
231         {
232             if ( propertyFile != null )
233             {
234                 propertyFile.getParentFile().mkdirs();
235             }
236 
237             List<String> languages = getLanguages( archetypeLanguages, propertyFile );
238 
239             Properties properties =
240                 configurator.configureArchetypeCreation( project, Boolean.valueOf( interactive ), executionProperties,
241                                                          propertyFile, languages );
242 
243             List<String> filtereds = getFilteredExtensions( archetypeFilteredExtentions, propertyFile );
244 
245             ArchetypeCreationRequest request =
246                 new ArchetypeCreationRequest().setDefaultEncoding( defaultEncoding ).setProject( project )
247                 /* Used when in interactive mode */.setProperties( properties ).setLanguages( languages )
248                 /* Should be refactored to use some ant patterns */.setFiltereds( filtereds )
249                 /* This should be correctly handled */.setPreserveCData( preserveCData ).setKeepParent(
250                     keepParent ).setPartialArchetype( partialArchetype )
251                 /* This should be used before there and use only languages and filtereds */.setArchetypeRegistryFile(
252                     archetypeRegistryFile ).setLocalRepository( localRepository )
253                 /* this should be resolved and asked for user to verify */.setPackageName( packageName ).setPostPhase(
254                     archetypePostPhase ).setOutputDirectory( outputDirectory );
255 
256             ArchetypeCreationResult result = manager.createArchetypeFromProject( request );
257 
258             if ( result.getCause() != null )
259             {
260                 throw new MojoFailureException( result.getCause(), result.getCause().getMessage(),
261                                                 result.getCause().getMessage() );
262             }
263 
264             getLog().info( "Archetype project created in " + outputDirectory );
265 
266             if ( testMode )
267             {
268                 // Now here a properties file would be useful to write so that we could automate
269                 // some functional tests where we string together an:
270                 //
271                 // archetype create from project -> deploy it into a test repo
272                 // project create from archetype -> use the repository we deployed to archetype to
273                 // generate
274                 // test the output
275                 //
276                 // This of course would be strung together from the outside.
277             }
278 
279         }
280         catch ( MojoFailureException ex )
281         {
282             throw ex;
283         }
284         catch ( Exception ex )
285         {
286             throw new MojoFailureException( ex, ex.getMessage(), ex.getMessage() );
287         }
288     }
289 
290     private List<String> getFilteredExtensions( String archetypeFilteredExtentions, File propertyFile )
291     {
292         List<String> filteredExtensions = new ArrayList<String>();
293 
294         if ( StringUtils.isNotEmpty( archetypeFilteredExtentions ) )
295         {
296             filteredExtensions.addAll( Arrays.asList( StringUtils.split( archetypeFilteredExtentions, "," ) ) );
297 
298             getLog().debug( "Found in command line extensions = " + filteredExtensions );
299         }
300 
301         if ( filteredExtensions.isEmpty() && propertyFile != null && propertyFile.exists() )
302         {
303             Properties properties = PropertyUtils.loadProperties( propertyFile );
304 
305             String extensions = properties.getProperty( Constants.ARCHETYPE_FILTERED_EXTENSIONS );
306             if ( StringUtils.isNotEmpty( extensions ) )
307             {
308                 filteredExtensions.addAll( Arrays.asList( StringUtils.split( extensions, "," ) ) );
309             }
310 
311             getLog().debug( "Found in propertyFile " + propertyFile.getName() + " extensions = " + filteredExtensions );
312         }
313 
314         if ( filteredExtensions.isEmpty() )
315         {
316             filteredExtensions.addAll( Constants.DEFAULT_FILTERED_EXTENSIONS );
317 
318             getLog().debug( "Using default extensions = " + filteredExtensions );
319         }
320 
321         return filteredExtensions;
322     }
323 
324     private List<String> getLanguages( String archetypeLanguages, File propertyFile )
325     {
326         List<String> resultingLanguages = new ArrayList<String>();
327 
328         if ( StringUtils.isNotEmpty( archetypeLanguages ) )
329         {
330             resultingLanguages.addAll( Arrays.asList( StringUtils.split( archetypeLanguages, "," ) ) );
331 
332             getLog().debug( "Found in command line languages = " + resultingLanguages );
333         }
334 
335         if ( resultingLanguages.isEmpty() && propertyFile != null && propertyFile.exists() )
336         {
337             Properties properties = PropertyUtils.loadProperties( propertyFile );
338 
339             String languages = properties.getProperty( Constants.ARCHETYPE_LANGUAGES );
340             if ( StringUtils.isNotEmpty( languages ) )
341             {
342                 resultingLanguages.addAll( Arrays.asList( StringUtils.split( languages, "," ) ) );
343             }
344 
345             getLog().debug( "Found in propertyFile " + propertyFile.getName() + " languages = " + resultingLanguages );
346         }
347 
348         if ( resultingLanguages.isEmpty() )
349         {
350             resultingLanguages.addAll( Constants.DEFAULT_LANGUAGES );
351 
352             getLog().debug( "Using default languages = " + resultingLanguages );
353         }
354 
355         return resultingLanguages;
356     }
357 }