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 }