View Javadoc
1   package org.apache.maven.archetype.ui.generation;
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.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  // TODO: this seems to have more responsibilities than just a configurator
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       * Determines whether the layout is legacy or not.
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                 // in batch mode, we assume the defaults, and if still not configured fail
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                 // closing StringWriter shouldn't actually generate any exception
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                 // handle the case that a property expression #set()s itself:
425                 referencedPropertyNames.remove( propertyName );
426                 result.put( propertyName, referencedPropertyNames );
427             }
428 
429             return result;
430         }
431 
432         /**
433          * Learn whether one property references another. Semantically, "references
434          * {@code targetProperty}, {@code sourceProperty} (does)."
435          *
436          * @param targetProperty {@link String} denoting property for which the state of
437          *        being-referenced-by-the-property-denoted-by {@code sourceProperty} is desired
438          * @param sourceProperty {@link String} denoting property for which the state of
439          *        references-the-property-denoted-by {@code targetProperty} is desired
440          * @return {@code boolean}
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                 // something has changed
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         // snapshots vs releases
477         // offline = to turning the update policy off
478 
479         // TODO: we'll need to allow finer grained creation of repositories but this will do for now
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 }