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.codehaus.plexus.component.annotations.Component;
41  import org.codehaus.plexus.component.annotations.Requirement;
42  import org.codehaus.plexus.components.interactivity.PrompterException;
43  import org.codehaus.plexus.logging.AbstractLogEnabled;
44  import org.codehaus.plexus.util.StringUtils;
45  
46  import java.io.IOException;
47  import java.io.StringWriter;
48  import java.util.ArrayList;
49  import java.util.Collections;
50  import java.util.Comparator;
51  import java.util.List;
52  import java.util.Properties;
53  
54  // TODO: this seems to have more responsibilities than just a configurator
55  @Component( role = ArchetypeGenerationConfigurator.class, hint = "default" )
56  public class DefaultArchetypeGenerationConfigurator
57      extends AbstractLogEnabled
58      implements ArchetypeGenerationConfigurator
59  {
60      @Requirement
61      OldArchetype oldArchetype;
62  
63      @Requirement
64      private ArchetypeArtifactManager archetypeArtifactManager;
65  
66      @Requirement
67      private ArchetypeFactory archetypeFactory;
68  
69      @Requirement
70      private ArchetypeGenerationQueryer archetypeGenerationQueryer;
71  
72      /**
73       * Determines whether the layout is legacy or not.
74       */
75      @Requirement
76      private ArtifactRepositoryLayout defaultArtifactRepositoryLayout;
77  
78      public void setArchetypeArtifactManager( ArchetypeArtifactManager archetypeArtifactManager )
79      {
80          this.archetypeArtifactManager = archetypeArtifactManager;
81      }
82  
83      @Override
84      public void configureArchetype( ArchetypeGenerationRequest request, Boolean interactiveMode,
85                                      Properties executionProperties )
86          throws ArchetypeNotDefined, UnknownArchetype, ArchetypeNotConfigured, IOException, PrompterException,
87          ArchetypeGenerationConfigurationFailure
88      {
89          ArtifactRepository localRepository = request.getLocalRepository();
90  
91          ArtifactRepository archetypeRepository = null;
92  
93          List<ArtifactRepository> repositories = new ArrayList<>();
94  
95          Properties properties = new Properties( executionProperties );
96  
97          ArchetypeDefinition ad = new ArchetypeDefinition( request );
98  
99          if ( !ad.isDefined() )
100         {
101             if ( !interactiveMode.booleanValue() )
102             {
103                 throw new ArchetypeNotDefined( "No archetype was chosen" );
104             }
105             else
106             {
107                 throw new ArchetypeNotDefined( "The archetype is not defined" );
108             }
109         }
110         if ( request.getArchetypeRepository() != null )
111         {
112             archetypeRepository = createRepository( request.getArchetypeRepository(),
113                                                                              ad.getArtifactId() + "-repo" );
114             repositories.add( archetypeRepository );
115         }
116         if ( request.getRemoteArtifactRepositories() != null )
117         {
118             repositories.addAll( request.getRemoteArtifactRepositories() );
119         }
120 
121         if ( !archetypeArtifactManager.exists( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(),
122                                                archetypeRepository, localRepository, repositories,
123                                                request.getProjectBuildingRequest() ) )
124         {
125             throw new UnknownArchetype( "The desired archetype does not exist (" + ad.getGroupId() + ":"
126                 + ad.getArtifactId() + ":" + ad.getVersion() + ")" );
127         }
128 
129         request.setArchetypeVersion( ad.getVersion() );
130 
131         ArchetypeConfiguration archetypeConfiguration;
132 
133         if ( archetypeArtifactManager.isFileSetArchetype( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(),
134                                                           archetypeRepository, localRepository, repositories,
135                                                           request.getProjectBuildingRequest() ) )
136         {
137             org.apache.maven.archetype.metadata.ArchetypeDescriptor archetypeDescriptor =
138                 archetypeArtifactManager.getFileSetArchetypeDescriptor( ad.getGroupId(), ad.getArtifactId(),
139                                                                         ad.getVersion(), archetypeRepository,
140                                                                         localRepository, repositories,
141                                                                         request.getProjectBuildingRequest() );
142 
143             archetypeConfiguration = archetypeFactory.createArchetypeConfiguration( archetypeDescriptor, properties );
144         }
145         else if ( archetypeArtifactManager.isOldArchetype( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(),
146                                                            archetypeRepository, localRepository, repositories,
147                                                            request.getProjectBuildingRequest() ) )
148         {
149             org.apache.maven.archetype.old.descriptor.ArchetypeDescriptor archetypeDescriptor =
150                 archetypeArtifactManager.getOldArchetypeDescriptor( ad.getGroupId(), ad.getArtifactId(),
151                                                                     ad.getVersion(), archetypeRepository,
152                                                                     localRepository, repositories,
153                                                                     request.getProjectBuildingRequest() );
154 
155             archetypeConfiguration = archetypeFactory.createArchetypeConfiguration( archetypeDescriptor, properties );
156         }
157         else
158         {
159             throw new ArchetypeGenerationConfigurationFailure( "The defined artifact is not an archetype" );
160         }
161 
162         Context context = new VelocityContext();
163         if ( interactiveMode.booleanValue() )
164         {
165             boolean confirmed = false;
166             context.put( Constants.GROUP_ID, ad.getGroupId() );
167             context.put( Constants.ARTIFACT_ID, ad.getArtifactId() );
168             context.put( Constants.VERSION, ad.getVersion() );
169             while ( !confirmed )
170             {
171                 List<String> propertiesRequired = archetypeConfiguration.getRequiredProperties();
172                 getLogger().debug( "Required properties before content sort: " + propertiesRequired );
173                 Collections.sort( propertiesRequired, new RequiredPropertyComparator( archetypeConfiguration ) );
174                 getLogger().debug( "Required properties after content sort: " + propertiesRequired );
175 
176                 if ( !archetypeConfiguration.isConfigured() )
177                 {
178                     for ( String requiredProperty : propertiesRequired )
179                     {
180                         if ( !archetypeConfiguration.isConfigured( requiredProperty ) )
181                         {
182                             if ( "package".equals( requiredProperty ) )
183                             {
184                                 // if the asked property is 'package', then
185                                 // use its default and if not defined,
186                                 // use the 'groupId' property value.
187                                 String packageDefault = archetypeConfiguration.getDefaultValue( requiredProperty );
188                                 packageDefault = ( null == packageDefault || "".equals( packageDefault ) )
189                                     ? archetypeConfiguration.getProperty( "groupId" )
190                                     : archetypeConfiguration.getDefaultValue( requiredProperty );
191 
192                                 String value =
193                                     getTransitiveDefaultValue( packageDefault, archetypeConfiguration, requiredProperty,
194                                                                context );
195 
196                                 value = archetypeGenerationQueryer.getPropertyValue( requiredProperty, value, null );
197 
198                                 archetypeConfiguration.setProperty( requiredProperty, value );
199 
200                                 context.put( Constants.PACKAGE, value );
201                             }
202                             else
203                             {
204                                 String value = archetypeConfiguration.getDefaultValue( requiredProperty );
205 
206                                 value = getTransitiveDefaultValue( value, archetypeConfiguration, requiredProperty,
207                                                                    context );
208 
209                                 value = archetypeGenerationQueryer.getPropertyValue( requiredProperty, value,
210                                     archetypeConfiguration.getPropertyValidationRegex( requiredProperty ) );
211 
212                                 archetypeConfiguration.setProperty( requiredProperty, value );
213 
214                                 context.put( requiredProperty, value );
215                             }
216                         }
217                         else
218                         {
219                             getLogger().info(
220                                 "Using property: " + requiredProperty + " = " + archetypeConfiguration.getProperty(
221                                     requiredProperty ) );
222                             archetypeConfiguration.setProperty( requiredProperty, archetypeConfiguration.getProperty(
223                                 requiredProperty ) );
224                         }
225                     }
226                 }
227                 else
228                 {
229 
230                     for ( String requiredProperty : propertiesRequired )
231                     {
232                         getLogger().info(
233                             "Using property: " + requiredProperty + " = " + archetypeConfiguration.getProperty(
234                                 requiredProperty ) );
235                     }
236                 }
237 
238                 if ( !archetypeConfiguration.isConfigured() )
239                 {
240                     getLogger().warn( "Archetype is not fully configured" );
241                 }
242                 else if ( !archetypeGenerationQueryer.confirmConfiguration( archetypeConfiguration ) )
243                 {
244                     getLogger().debug( "Archetype generation configuration not confirmed" );
245                     archetypeConfiguration.reset();
246                     restoreCommandLineProperties( archetypeConfiguration, executionProperties );
247                 }
248                 else
249                 {
250                     getLogger().debug( "Archetype generation configuration confirmed" );
251 
252                     confirmed = true;
253                 }
254             }
255         }
256         else
257         {
258             if ( !archetypeConfiguration.isConfigured() )
259             {
260                 for ( String requiredProperty : archetypeConfiguration.getRequiredProperties() )
261                 {
262                     if ( !archetypeConfiguration.isConfigured( requiredProperty ) && (
263                         archetypeConfiguration.getDefaultValue( requiredProperty ) != null ) )
264                     {
265                         String value = archetypeConfiguration.getDefaultValue( requiredProperty );
266                         value = getTransitiveDefaultValue( value, archetypeConfiguration, requiredProperty, context );
267                         archetypeConfiguration.setProperty( requiredProperty, value );
268                         context.put( requiredProperty, value );
269                     }
270                 }
271 
272                 // in batch mode, we assume the defaults, and if still not configured fail
273                 if ( !archetypeConfiguration.isConfigured() )
274                 {
275                     StringBuilder exceptionMessage = new StringBuilder();
276                     exceptionMessage.append( "Archetype " );
277                     exceptionMessage.append( request.getArchetypeGroupId() );
278                     exceptionMessage.append( ":" );
279                     exceptionMessage.append( request.getArchetypeArtifactId() );
280                     exceptionMessage.append( ":" );
281                     exceptionMessage.append( request.getArchetypeVersion() );
282                     exceptionMessage.append( " is not configured" );
283 
284                     List<String> missingProperties = new ArrayList<>( 0 );
285                     for ( String requiredProperty : archetypeConfiguration.getRequiredProperties() )
286                     {
287                         if ( !archetypeConfiguration.isConfigured( requiredProperty ) )
288                         {
289                             exceptionMessage.append( "\n\tProperty " );
290                             exceptionMessage.append( requiredProperty );
291                             missingProperties.add( requiredProperty );
292                             exceptionMessage.append( " is missing." );
293                             getLogger().warn( "Property " + requiredProperty + " is missing. Add -D" + requiredProperty
294                                                   + "=someValue" );
295                         }
296                     }
297 
298                     throw new ArchetypeNotConfigured( exceptionMessage.toString(), missingProperties );
299                 }
300             }
301         }
302 
303         request.setGroupId( archetypeConfiguration.getProperty( Constants.GROUP_ID ) );
304 
305         request.setArtifactId( archetypeConfiguration.getProperty( Constants.ARTIFACT_ID ) );
306 
307         request.setVersion( archetypeConfiguration.getProperty( Constants.VERSION ) );
308 
309         request.setPackage( archetypeConfiguration.getProperty( Constants.PACKAGE ) );
310 
311         properties = archetypeConfiguration.getProperties();
312 
313         request.setProperties( properties );
314     }
315 
316     private String getTransitiveDefaultValue( String defaultValue, ArchetypeConfiguration archetypeConfiguration,
317                                               String requiredProperty, Context context )
318     {
319         String result = defaultValue;
320         if ( null == result )
321         {
322             return null;
323         }
324         for ( String property : archetypeConfiguration.getRequiredProperties() )
325         {
326             if ( result.indexOf( "${" + property + "}" ) >= 0 )
327             {
328 
329                 result = StringUtils.replace( result, "${" + property + "}",
330                                               archetypeConfiguration.getProperty( property ) );
331             }
332         }
333         if ( result.contains( "${" ) )
334         {
335             result = evaluateProperty( context, requiredProperty, defaultValue );
336         }
337         return result;
338     }
339 
340 
341     private String evaluateProperty( Context context, String property, String value )
342     {
343         
344         try ( StringWriter stringWriter = new StringWriter() )
345         {
346             Velocity.evaluate( context, stringWriter, property, value );
347             return stringWriter.toString();
348         }
349         catch ( Exception ex )
350         {
351             return value;
352         }
353     }
354 
355 
356     private void restoreCommandLineProperties( ArchetypeConfiguration archetypeConfiguration,
357                                                Properties executionProperties )
358     {
359         getLogger().debug( "Restoring command line properties" );
360 
361         for ( String property : archetypeConfiguration.getRequiredProperties() )
362         {
363             if ( executionProperties.containsKey( property ) )
364             {
365                 archetypeConfiguration.setProperty( property, executionProperties.getProperty( property ) );
366                 getLogger().debug( "Restored " + property + "=" + archetypeConfiguration.getProperty( property ) );
367             }
368         }
369     }
370 
371     public static class RequiredPropertyComparator
372         implements Comparator<String>
373     {
374         private final ArchetypeConfiguration archetypeConfiguration;
375 
376         public RequiredPropertyComparator( ArchetypeConfiguration archetypeConfiguration )
377         {
378             this.archetypeConfiguration = archetypeConfiguration;
379         }
380 
381         @Override
382         public int compare( String left, String right )
383         {
384             String leftDefault = archetypeConfiguration.getDefaultValue( left );
385 
386             if ( ( leftDefault != null ) && leftDefault.indexOf( "${" + right + "}" ) >= 0 )
387             { //left contains right
388                 return 1;
389             }
390 
391             String rightDefault = archetypeConfiguration.getDefaultValue( right );
392 
393             if ( ( rightDefault != null ) && rightDefault.indexOf( "${" + left + "}" ) >= 0 )
394             { //right contains left
395                 return -1;
396             }
397 
398             return comparePropertyName( left, right );
399         }
400 
401         private int comparePropertyName( String left, String right )
402         {
403             if ( "groupId".equals( left ) )
404             {
405                 return -1;
406             }
407             if ( "groupId".equals( right ) )
408             {
409                 return 1;
410             }
411             if ( "artifactId".equals( left ) )
412             {
413                 return -1;
414             }
415             if ( "artifactId".equals( right ) )
416             {
417                 return 1;
418             }
419             if ( "version".equals( left ) )
420             {
421                 return -1;
422             }
423             if ( "version".equals( right ) )
424             {
425                 return 1;
426             }
427             if ( "package".equals( left ) )
428             {
429                 return -1;
430             }
431             if ( "package".equals( right ) )
432             {
433                 return 1;
434             }
435             return left.compareTo( right );
436         }
437     }
438     
439     private ArtifactRepository createRepository( String url, String repositoryId )
440     {
441         
442         
443         // snapshots vs releases
444         // offline = to turning the update policy off
445 
446         // TODO: we'll need to allow finer grained creation of repositories but this will do for now
447 
448         String updatePolicyFlag = ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS;
449 
450         String checksumPolicyFlag = ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN;
451 
452         ArtifactRepositoryPolicy snapshotsPolicy =
453             new ArtifactRepositoryPolicy( true, updatePolicyFlag, checksumPolicyFlag );
454 
455         ArtifactRepositoryPolicy releasesPolicy =
456             new ArtifactRepositoryPolicy( true, updatePolicyFlag, checksumPolicyFlag );
457         
458         return new MavenArtifactRepository( repositoryId, url, defaultArtifactRepositoryLayout, snapshotsPolicy,
459                                             releasesPolicy );
460     }
461 
462 }