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