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