1 package org.apache.maven.archetype.ui.generation;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.archetype.ArchetypeGenerationRequest;
23 import org.apache.maven.archetype.common.ArchetypeArtifactManager;
24 import org.apache.maven.archetype.common.Constants;
25 import org.apache.maven.archetype.exception.ArchetypeGenerationConfigurationFailure;
26 import org.apache.maven.archetype.exception.ArchetypeNotConfigured;
27 import org.apache.maven.archetype.exception.ArchetypeNotDefined;
28 import org.apache.maven.archetype.exception.UnknownArchetype;
29 import org.apache.maven.archetype.old.OldArchetype;
30 import org.apache.maven.archetype.ui.ArchetypeConfiguration;
31 import org.apache.maven.archetype.ui.ArchetypeDefinition;
32 import org.apache.maven.archetype.ui.ArchetypeFactory;
33 import org.apache.maven.artifact.repository.ArtifactRepository;
34 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
35 import org.apache.maven.artifact.repository.MavenArtifactRepository;
36 import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
37 import org.apache.velocity.VelocityContext;
38 import org.apache.velocity.app.Velocity;
39 import org.apache.velocity.context.Context;
40 import org.apache.velocity.context.InternalContextAdapterImpl;
41 import org.apache.velocity.runtime.RuntimeServices;
42 import org.apache.velocity.runtime.RuntimeSingleton;
43 import org.apache.velocity.runtime.parser.ParseException;
44 import org.apache.velocity.runtime.parser.node.ASTReference;
45 import org.apache.velocity.runtime.parser.node.SimpleNode;
46 import org.apache.velocity.runtime.visitor.BaseVisitor;
47 import org.codehaus.plexus.component.annotations.Component;
48 import org.codehaus.plexus.component.annotations.Requirement;
49 import org.codehaus.plexus.components.interactivity.PrompterException;
50 import org.codehaus.plexus.logging.AbstractLogEnabled;
51 import org.codehaus.plexus.util.StringUtils;
52
53 import java.io.IOException;
54 import java.io.StringReader;
55 import java.io.StringWriter;
56 import java.util.ArrayList;
57 import java.util.Collections;
58 import java.util.Comparator;
59 import java.util.HashMap;
60 import java.util.LinkedHashSet;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Properties;
64 import java.util.Set;
65
66
67 @Component( role = ArchetypeGenerationConfigurator.class, hint = "default" )
68 public class DefaultArchetypeGenerationConfigurator
69 extends AbstractLogEnabled
70 implements ArchetypeGenerationConfigurator
71 {
72 @Requirement
73 OldArchetype oldArchetype;
74
75 @Requirement
76 private ArchetypeArtifactManager archetypeArtifactManager;
77
78 @Requirement
79 private ArchetypeFactory archetypeFactory;
80
81 @Requirement
82 private ArchetypeGenerationQueryer archetypeGenerationQueryer;
83
84
85
86
87 @Requirement
88 private ArtifactRepositoryLayout defaultArtifactRepositoryLayout;
89
90 public void setArchetypeArtifactManager( ArchetypeArtifactManager archetypeArtifactManager )
91 {
92 this.archetypeArtifactManager = archetypeArtifactManager;
93 }
94
95 @Override
96 public void configureArchetype( ArchetypeGenerationRequest request, Boolean interactiveMode,
97 Properties executionProperties )
98 throws ArchetypeNotDefined, UnknownArchetype, ArchetypeNotConfigured, IOException, PrompterException,
99 ArchetypeGenerationConfigurationFailure
100 {
101 ArtifactRepository localRepository = request.getLocalRepository();
102
103 ArtifactRepository archetypeRepository = null;
104
105 List<ArtifactRepository> repositories = new ArrayList<>();
106
107 Properties properties = new Properties( executionProperties );
108
109 ArchetypeDefinition ad = new ArchetypeDefinition( request );
110
111 if ( !ad.isDefined() )
112 {
113 if ( !interactiveMode.booleanValue() )
114 {
115 throw new ArchetypeNotDefined( "No archetype was chosen" );
116 }
117 else
118 {
119 throw new ArchetypeNotDefined( "The archetype is not defined" );
120 }
121 }
122 if ( request.getArchetypeRepository() != null )
123 {
124 archetypeRepository = createRepository( request.getArchetypeRepository(),
125 ad.getArtifactId() + "-repo" );
126 repositories.add( archetypeRepository );
127 }
128 if ( request.getRemoteArtifactRepositories() != null )
129 {
130 repositories.addAll( request.getRemoteArtifactRepositories() );
131 }
132
133 if ( !archetypeArtifactManager.exists( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(),
134 archetypeRepository, localRepository, repositories,
135 request.getProjectBuildingRequest() ) )
136 {
137 throw new UnknownArchetype( "The desired archetype does not exist (" + ad.getGroupId() + ":"
138 + ad.getArtifactId() + ":" + ad.getVersion() + ")" );
139 }
140
141 request.setArchetypeVersion( ad.getVersion() );
142
143 ArchetypeConfiguration archetypeConfiguration;
144
145 if ( archetypeArtifactManager.isFileSetArchetype( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(),
146 archetypeRepository, localRepository, repositories,
147 request.getProjectBuildingRequest() ) )
148 {
149 org.apache.maven.archetype.metadata.ArchetypeDescriptor archetypeDescriptor =
150 archetypeArtifactManager.getFileSetArchetypeDescriptor( ad.getGroupId(), ad.getArtifactId(),
151 ad.getVersion(), archetypeRepository,
152 localRepository, repositories,
153 request.getProjectBuildingRequest() );
154
155 archetypeConfiguration = archetypeFactory.createArchetypeConfiguration( archetypeDescriptor, properties );
156 }
157 else if ( archetypeArtifactManager.isOldArchetype( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(),
158 archetypeRepository, localRepository, repositories,
159 request.getProjectBuildingRequest() ) )
160 {
161 org.apache.maven.archetype.old.descriptor.ArchetypeDescriptor archetypeDescriptor =
162 archetypeArtifactManager.getOldArchetypeDescriptor( ad.getGroupId(), ad.getArtifactId(),
163 ad.getVersion(), archetypeRepository,
164 localRepository, repositories,
165 request.getProjectBuildingRequest() );
166
167 archetypeConfiguration = archetypeFactory.createArchetypeConfiguration( archetypeDescriptor, properties );
168 }
169 else
170 {
171 throw new ArchetypeGenerationConfigurationFailure( "The defined artifact is not an archetype" );
172 }
173
174 List<String> propertiesRequired = archetypeConfiguration.getRequiredProperties();
175 Collections.sort( propertiesRequired, new RequiredPropertyComparator( archetypeConfiguration ) );
176
177 Context context = new VelocityContext();
178 if ( interactiveMode.booleanValue() )
179 {
180 boolean confirmed = false;
181 context.put( Constants.GROUP_ID, ad.getGroupId() );
182 context.put( Constants.ARTIFACT_ID, ad.getArtifactId() );
183 context.put( Constants.VERSION, ad.getVersion() );
184 while ( !confirmed )
185 {
186 if ( archetypeConfiguration.isConfigured() )
187 {
188 for ( String requiredProperty : propertiesRequired )
189 {
190 getLogger().info(
191 "Using property: " + requiredProperty + " = " + archetypeConfiguration.getProperty(
192 requiredProperty ) );
193 }
194 }
195 else
196 {
197 for ( String requiredProperty : propertiesRequired )
198 {
199 String value;
200
201 if ( archetypeConfiguration.isConfigured( requiredProperty ) && !request.isAskForDefaultPropertyValues() )
202 {
203 getLogger().info(
204 "Using property: " + requiredProperty + " = " + archetypeConfiguration.getProperty(
205 requiredProperty ) );
206
207 value = archetypeConfiguration.getProperty( requiredProperty );
208 }
209 else
210 {
211 String defaultValue = archetypeConfiguration.getDefaultValue( requiredProperty );
212
213 if ( Constants.PACKAGE.equals( requiredProperty ) && StringUtils.isEmpty( defaultValue ) )
214 {
215 defaultValue = archetypeConfiguration.getProperty( Constants.GROUP_ID );
216 }
217 value = archetypeGenerationQueryer.getPropertyValue( requiredProperty,
218 expandEmbeddedTemplateExpressions( defaultValue, requiredProperty, context ),
219 archetypeConfiguration.getPropertyValidationRegex( requiredProperty ) );
220 }
221
222 archetypeConfiguration.setProperty( requiredProperty, value );
223
224 context.put( requiredProperty, value );
225 }
226 }
227
228 if ( !archetypeConfiguration.isConfigured() )
229 {
230 getLogger().warn( "Archetype is not fully configured" );
231 }
232 else if ( !archetypeGenerationQueryer.confirmConfiguration( archetypeConfiguration ) )
233 {
234 getLogger().debug( "Archetype generation configuration not confirmed" );
235 archetypeConfiguration.reset();
236 restoreCommandLineProperties( archetypeConfiguration, executionProperties );
237 }
238 else
239 {
240 getLogger().debug( "Archetype generation configuration confirmed" );
241
242 confirmed = true;
243 }
244 }
245 }
246 else
247 {
248 if ( !archetypeConfiguration.isConfigured() )
249 {
250 for ( String requiredProperty : propertiesRequired )
251 {
252 if ( archetypeConfiguration.isConfigured( requiredProperty ) )
253 {
254 context.put( requiredProperty, archetypeConfiguration.getProperty( requiredProperty ) );
255 }
256 else
257 {
258 String defaultValue = archetypeConfiguration.getDefaultValue( requiredProperty );
259
260 if ( defaultValue != null )
261 {
262 String value = expandEmbeddedTemplateExpressions( defaultValue, requiredProperty, context );
263 archetypeConfiguration.setProperty( requiredProperty, value );
264 context.put( requiredProperty, value );
265 }
266 }
267 }
268
269
270 if ( !archetypeConfiguration.isConfigured() )
271 {
272 StringBuilder exceptionMessage = new StringBuilder();
273 exceptionMessage.append( "Archetype " );
274 exceptionMessage.append( request.getArchetypeGroupId() );
275 exceptionMessage.append( ":" );
276 exceptionMessage.append( request.getArchetypeArtifactId() );
277 exceptionMessage.append( ":" );
278 exceptionMessage.append( request.getArchetypeVersion() );
279 exceptionMessage.append( " is not configured" );
280
281 List<String> missingProperties = new ArrayList<>( 0 );
282 for ( String requiredProperty : archetypeConfiguration.getRequiredProperties() )
283 {
284 if ( !archetypeConfiguration.isConfigured( requiredProperty ) )
285 {
286 exceptionMessage.append( "\n\tProperty " );
287 exceptionMessage.append( requiredProperty );
288 missingProperties.add( requiredProperty );
289 exceptionMessage.append( " is missing." );
290 getLogger().warn( "Property " + requiredProperty + " is missing. Add -D" + requiredProperty
291 + "=someValue" );
292 }
293 }
294
295 throw new ArchetypeNotConfigured( exceptionMessage.toString(), missingProperties );
296 }
297 }
298 }
299
300 request.setGroupId( archetypeConfiguration.getProperty( Constants.GROUP_ID ) );
301
302 request.setArtifactId( archetypeConfiguration.getProperty( Constants.ARTIFACT_ID ) );
303
304 request.setVersion( archetypeConfiguration.getProperty( Constants.VERSION ) );
305
306 request.setPackage( archetypeConfiguration.getProperty( Constants.PACKAGE ) );
307
308 properties = archetypeConfiguration.getProperties();
309
310 request.setProperties( properties );
311 }
312
313 private static String expandEmbeddedTemplateExpressions( String originalText, String textDescription, Context context )
314 {
315 if ( StringUtils.contains( originalText, "${" ) )
316 {
317 try ( StringWriter target = new StringWriter() )
318 {
319 Velocity.evaluate( context, target, textDescription, originalText );
320 return target.toString();
321 }
322 catch ( IOException ex )
323 {
324
325 throw new RuntimeException( "Exception closing StringWriter", ex );
326 }
327 }
328 return originalText;
329 }
330
331 private void restoreCommandLineProperties( ArchetypeConfiguration archetypeConfiguration,
332 Properties executionProperties )
333 {
334 getLogger().debug( "Restoring command line properties" );
335
336 for ( String property : archetypeConfiguration.getRequiredProperties() )
337 {
338 if ( executionProperties.containsKey( property ) )
339 {
340 archetypeConfiguration.setProperty( property, executionProperties.getProperty( property ) );
341 getLogger().debug( "Restored " + property + "=" + archetypeConfiguration.getProperty( property ) );
342 }
343 }
344 }
345
346 void setArchetypeGenerationQueryer( ArchetypeGenerationQueryer archetypeGenerationQueryer )
347 {
348 this.archetypeGenerationQueryer = archetypeGenerationQueryer;
349 }
350
351 public static class RequiredPropertyComparator
352 implements Comparator<String>
353 {
354 private final ArchetypeConfiguration archetypeConfiguration;
355
356 private Map<String, Set<String>> propertyReferenceMap;
357
358 public RequiredPropertyComparator( ArchetypeConfiguration archetypeConfiguration )
359 {
360 this.archetypeConfiguration = archetypeConfiguration;
361 propertyReferenceMap = computePropertyReferences();
362 }
363
364 @Override
365 public int compare( String left, String right )
366 {
367 if ( references( right, left ) )
368 {
369 return 1;
370 }
371
372 if ( references( left, right ) )
373 {
374 return -1;
375 }
376
377 return Integer.compare( propertyReferenceMap.get( left ).size(), propertyReferenceMap.get( right ).size() );
378 }
379
380 private Map<String, Set<String>> computePropertyReferences()
381 {
382 Map<String, Set<String>> result = new HashMap<>();
383
384 List<String> requiredProperties = archetypeConfiguration.getRequiredProperties();
385
386 final InternalContextAdapterImpl velocityContextAdapter =
387 new InternalContextAdapterImpl( new VelocityContext() );
388
389 final RuntimeServices velocityRuntime = RuntimeSingleton.getRuntimeServices();
390
391 for ( String propertyName : requiredProperties )
392 {
393 final Set<String> referencedPropertyNames = new LinkedHashSet<>();
394
395 String defaultValue = archetypeConfiguration.getDefaultValue( propertyName );
396 if ( StringUtils.contains( defaultValue, "${" ) )
397 {
398 try
399 {
400 final boolean dumpNamespace = false;
401 SimpleNode node = RuntimeSingleton.parse(
402 new StringReader( defaultValue ), propertyName + ".default", dumpNamespace );
403
404 node.init( velocityContextAdapter, velocityRuntime );
405
406 node.jjtAccept( new BaseVisitor()
407 {
408 @Override
409 public Object visit( ASTReference node, Object data )
410 {
411 referencedPropertyNames.add( node.getRootString() );
412 return super.visit( node, data );
413 }
414 }, velocityRuntime );
415 }
416 catch ( ParseException e )
417 {
418 throw new IllegalStateException( "Unparsable default value for property " + propertyName, e );
419 }
420 }
421
422 referencedPropertyNames.retainAll( archetypeConfiguration.getRequiredProperties() );
423
424
425 referencedPropertyNames.remove( propertyName );
426 result.put( propertyName, referencedPropertyNames );
427 }
428
429 return result;
430 }
431
432
433
434
435
436
437
438
439
440
441
442 private boolean references( String targetProperty, String sourceProperty )
443 {
444 if ( targetProperty.equals( sourceProperty ) )
445 {
446 return false;
447 }
448 synchronized ( this )
449 {
450 if ( ! propertyReferenceMap.containsKey( sourceProperty ) )
451
452 {
453 this.propertyReferenceMap = computePropertyReferences();
454 }
455 }
456 Set<String> referencedProperties = propertyReferenceMap.get( sourceProperty );
457 if ( referencedProperties.contains( targetProperty ) )
458 {
459 return true;
460 }
461 for ( String referencedProperty : referencedProperties )
462 {
463 if ( references( targetProperty, referencedProperty ) )
464 {
465 return true;
466 }
467 }
468 return false;
469 }
470 }
471
472 private ArtifactRepository createRepository( String url, String repositoryId )
473 {
474
475
476
477
478
479
480
481 String updatePolicyFlag = ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS;
482
483 String checksumPolicyFlag = ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN;
484
485 ArtifactRepositoryPolicy snapshotsPolicy =
486 new ArtifactRepositoryPolicy( true, updatePolicyFlag, checksumPolicyFlag );
487
488 ArtifactRepositoryPolicy releasesPolicy =
489 new ArtifactRepositoryPolicy( true, updatePolicyFlag, checksumPolicyFlag );
490
491 return new MavenArtifactRepository( repositoryId, url, defaultArtifactRepositoryLayout, snapshotsPolicy,
492 releasesPolicy );
493 }
494
495 }