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