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 }