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