| package org.apache.maven.archetype.ui.generation; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.maven.archetype.ArchetypeGenerationRequest; |
| import org.apache.maven.archetype.common.ArchetypeArtifactManager; |
| import org.apache.maven.archetype.common.Constants; |
| import org.apache.maven.archetype.exception.ArchetypeGenerationConfigurationFailure; |
| import org.apache.maven.archetype.exception.ArchetypeNotConfigured; |
| import org.apache.maven.archetype.exception.ArchetypeNotDefined; |
| import org.apache.maven.archetype.exception.UnknownArchetype; |
| import org.apache.maven.archetype.old.OldArchetype; |
| import org.apache.maven.archetype.ui.ArchetypeConfiguration; |
| import org.apache.maven.archetype.ui.ArchetypeDefinition; |
| import org.apache.maven.archetype.ui.ArchetypeFactory; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; |
| import org.apache.maven.artifact.repository.MavenArtifactRepository; |
| import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout; |
| import org.apache.velocity.VelocityContext; |
| import org.apache.velocity.app.Velocity; |
| import org.apache.velocity.context.Context; |
| import org.apache.velocity.runtime.RuntimeSingleton; |
| import org.apache.velocity.runtime.parser.ParseException; |
| import org.apache.velocity.runtime.parser.node.ASTReference; |
| import org.apache.velocity.runtime.parser.node.SimpleNode; |
| import org.apache.velocity.runtime.visitor.BaseVisitor; |
| import org.codehaus.plexus.component.annotations.Component; |
| import org.codehaus.plexus.component.annotations.Requirement; |
| import org.codehaus.plexus.components.interactivity.PrompterException; |
| import org.codehaus.plexus.logging.AbstractLogEnabled; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| // TODO: this seems to have more responsibilities than just a configurator |
| @Component( role = ArchetypeGenerationConfigurator.class, hint = "default" ) |
| public class DefaultArchetypeGenerationConfigurator |
| extends AbstractLogEnabled |
| implements ArchetypeGenerationConfigurator |
| { |
| @Requirement |
| OldArchetype oldArchetype; |
| |
| @Requirement |
| private ArchetypeArtifactManager archetypeArtifactManager; |
| |
| @Requirement |
| private ArchetypeFactory archetypeFactory; |
| |
| @Requirement |
| private ArchetypeGenerationQueryer archetypeGenerationQueryer; |
| |
| /** |
| * Determines whether the layout is legacy or not. |
| */ |
| @Requirement |
| private ArtifactRepositoryLayout defaultArtifactRepositoryLayout; |
| |
| public void setArchetypeArtifactManager( ArchetypeArtifactManager archetypeArtifactManager ) |
| { |
| this.archetypeArtifactManager = archetypeArtifactManager; |
| } |
| |
| @Override |
| public void configureArchetype( ArchetypeGenerationRequest request, Boolean interactiveMode, |
| Properties executionProperties ) |
| throws ArchetypeNotDefined, UnknownArchetype, ArchetypeNotConfigured, IOException, PrompterException, |
| ArchetypeGenerationConfigurationFailure |
| { |
| ArtifactRepository localRepository = request.getLocalRepository(); |
| |
| ArtifactRepository archetypeRepository = null; |
| |
| List<ArtifactRepository> repositories = new ArrayList<>(); |
| |
| Properties properties = new Properties( executionProperties ); |
| |
| ArchetypeDefinition ad = new ArchetypeDefinition( request ); |
| |
| if ( !ad.isDefined() ) |
| { |
| if ( !interactiveMode.booleanValue() ) |
| { |
| throw new ArchetypeNotDefined( "No archetype was chosen" ); |
| } |
| else |
| { |
| throw new ArchetypeNotDefined( "The archetype is not defined" ); |
| } |
| } |
| if ( request.getArchetypeRepository() != null ) |
| { |
| archetypeRepository = createRepository( request.getArchetypeRepository(), |
| ad.getArtifactId() + "-repo" ); |
| repositories.add( archetypeRepository ); |
| } |
| if ( request.getRemoteArtifactRepositories() != null ) |
| { |
| repositories.addAll( request.getRemoteArtifactRepositories() ); |
| } |
| |
| if ( !archetypeArtifactManager.exists( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(), |
| archetypeRepository, localRepository, repositories, |
| request.getProjectBuildingRequest() ) ) |
| { |
| throw new UnknownArchetype( "The desired archetype does not exist (" + ad.getGroupId() + ":" |
| + ad.getArtifactId() + ":" + ad.getVersion() + ")" ); |
| } |
| |
| request.setArchetypeVersion( ad.getVersion() ); |
| |
| ArchetypeConfiguration archetypeConfiguration; |
| |
| if ( archetypeArtifactManager.isFileSetArchetype( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(), |
| archetypeRepository, localRepository, repositories, |
| request.getProjectBuildingRequest() ) ) |
| { |
| org.apache.maven.archetype.metadata.ArchetypeDescriptor archetypeDescriptor = |
| archetypeArtifactManager.getFileSetArchetypeDescriptor( ad.getGroupId(), ad.getArtifactId(), |
| ad.getVersion(), archetypeRepository, |
| localRepository, repositories, |
| request.getProjectBuildingRequest() ); |
| |
| archetypeConfiguration = archetypeFactory.createArchetypeConfiguration( archetypeDescriptor, properties ); |
| } |
| else if ( archetypeArtifactManager.isOldArchetype( ad.getGroupId(), ad.getArtifactId(), ad.getVersion(), |
| archetypeRepository, localRepository, repositories, |
| request.getProjectBuildingRequest() ) ) |
| { |
| org.apache.maven.archetype.old.descriptor.ArchetypeDescriptor archetypeDescriptor = |
| archetypeArtifactManager.getOldArchetypeDescriptor( ad.getGroupId(), ad.getArtifactId(), |
| ad.getVersion(), archetypeRepository, |
| localRepository, repositories, |
| request.getProjectBuildingRequest() ); |
| |
| archetypeConfiguration = archetypeFactory.createArchetypeConfiguration( archetypeDescriptor, properties ); |
| } |
| else |
| { |
| throw new ArchetypeGenerationConfigurationFailure( "The defined artifact is not an archetype" ); |
| } |
| |
| List<String> propertiesRequired = archetypeConfiguration.getRequiredProperties(); |
| Collections.sort( propertiesRequired, new RequiredPropertyComparator( archetypeConfiguration ) ); |
| |
| Context context = new VelocityContext(); |
| if ( interactiveMode.booleanValue() ) |
| { |
| boolean confirmed = false; |
| context.put( Constants.GROUP_ID, ad.getGroupId() ); |
| context.put( Constants.ARTIFACT_ID, ad.getArtifactId() ); |
| context.put( Constants.VERSION, ad.getVersion() ); |
| while ( !confirmed ) |
| { |
| if ( archetypeConfiguration.isConfigured() ) |
| { |
| for ( String requiredProperty : propertiesRequired ) |
| { |
| getLogger().info( |
| "Using property: " + requiredProperty + " = " + archetypeConfiguration.getProperty( |
| requiredProperty ) ); |
| } |
| } |
| else |
| { |
| for ( String requiredProperty : propertiesRequired ) |
| { |
| String value; |
| |
| if ( archetypeConfiguration.isConfigured( requiredProperty ) ) |
| { |
| getLogger().info( |
| "Using property: " + requiredProperty + " = " + archetypeConfiguration.getProperty( |
| requiredProperty ) ); |
| |
| value = archetypeConfiguration.getProperty( requiredProperty ); |
| } |
| else |
| { |
| String defaultValue = archetypeConfiguration.getDefaultValue( requiredProperty ); |
| |
| if ( Constants.PACKAGE.equals( requiredProperty ) && StringUtils.isEmpty( defaultValue ) ) |
| { |
| defaultValue = archetypeConfiguration.getProperty( Constants.GROUP_ID ); |
| } |
| value = archetypeGenerationQueryer.getPropertyValue( requiredProperty, |
| expandEmbeddedTemplateExpressions( defaultValue, requiredProperty, context ), |
| archetypeConfiguration.getPropertyValidationRegex( requiredProperty ) ); |
| } |
| |
| archetypeConfiguration.setProperty( requiredProperty, value ); |
| |
| context.put( requiredProperty, value ); |
| } |
| } |
| |
| if ( !archetypeConfiguration.isConfigured() ) |
| { |
| getLogger().warn( "Archetype is not fully configured" ); |
| } |
| else if ( !archetypeGenerationQueryer.confirmConfiguration( archetypeConfiguration ) ) |
| { |
| getLogger().debug( "Archetype generation configuration not confirmed" ); |
| archetypeConfiguration.reset(); |
| restoreCommandLineProperties( archetypeConfiguration, executionProperties ); |
| } |
| else |
| { |
| getLogger().debug( "Archetype generation configuration confirmed" ); |
| |
| confirmed = true; |
| } |
| } |
| } |
| else |
| { |
| if ( !archetypeConfiguration.isConfigured() ) |
| { |
| for ( String requiredProperty : propertiesRequired ) |
| { |
| if ( archetypeConfiguration.isConfigured( requiredProperty ) ) |
| { |
| context.put( requiredProperty, archetypeConfiguration.getProperty( requiredProperty ) ); |
| } |
| else |
| { |
| String defaultValue = archetypeConfiguration.getDefaultValue( requiredProperty ); |
| |
| if ( defaultValue != null ) |
| { |
| String value = expandEmbeddedTemplateExpressions( defaultValue, requiredProperty, context ); |
| archetypeConfiguration.setProperty( requiredProperty, value ); |
| context.put( requiredProperty, value ); |
| } |
| } |
| } |
| |
| // in batch mode, we assume the defaults, and if still not configured fail |
| if ( !archetypeConfiguration.isConfigured() ) |
| { |
| StringBuilder exceptionMessage = new StringBuilder(); |
| exceptionMessage.append( "Archetype " ); |
| exceptionMessage.append( request.getArchetypeGroupId() ); |
| exceptionMessage.append( ":" ); |
| exceptionMessage.append( request.getArchetypeArtifactId() ); |
| exceptionMessage.append( ":" ); |
| exceptionMessage.append( request.getArchetypeVersion() ); |
| exceptionMessage.append( " is not configured" ); |
| |
| List<String> missingProperties = new ArrayList<>( 0 ); |
| for ( String requiredProperty : archetypeConfiguration.getRequiredProperties() ) |
| { |
| if ( !archetypeConfiguration.isConfigured( requiredProperty ) ) |
| { |
| exceptionMessage.append( "\n\tProperty " ); |
| exceptionMessage.append( requiredProperty ); |
| missingProperties.add( requiredProperty ); |
| exceptionMessage.append( " is missing." ); |
| getLogger().warn( "Property " + requiredProperty + " is missing. Add -D" + requiredProperty |
| + "=someValue" ); |
| } |
| } |
| |
| throw new ArchetypeNotConfigured( exceptionMessage.toString(), missingProperties ); |
| } |
| } |
| } |
| |
| request.setGroupId( archetypeConfiguration.getProperty( Constants.GROUP_ID ) ); |
| |
| request.setArtifactId( archetypeConfiguration.getProperty( Constants.ARTIFACT_ID ) ); |
| |
| request.setVersion( archetypeConfiguration.getProperty( Constants.VERSION ) ); |
| |
| request.setPackage( archetypeConfiguration.getProperty( Constants.PACKAGE ) ); |
| |
| properties = archetypeConfiguration.getProperties(); |
| |
| request.setProperties( properties ); |
| } |
| |
| private static String expandEmbeddedTemplateExpressions( String originalText, String textDescription, Context context ) |
| { |
| if ( StringUtils.contains( originalText, "${" ) ) |
| { |
| try ( StringWriter target = new StringWriter() ) |
| { |
| Velocity.evaluate( context, target, textDescription, originalText ); |
| return target.toString(); |
| } |
| catch ( IOException ex ) |
| { |
| // closing StringWriter shouldn't actually generate any exception |
| throw new RuntimeException( "Exception closing StringWriter", ex ); |
| } |
| } |
| return originalText; |
| } |
| |
| private void restoreCommandLineProperties( ArchetypeConfiguration archetypeConfiguration, |
| Properties executionProperties ) |
| { |
| getLogger().debug( "Restoring command line properties" ); |
| |
| for ( String property : archetypeConfiguration.getRequiredProperties() ) |
| { |
| if ( executionProperties.containsKey( property ) ) |
| { |
| archetypeConfiguration.setProperty( property, executionProperties.getProperty( property ) ); |
| getLogger().debug( "Restored " + property + "=" + archetypeConfiguration.getProperty( property ) ); |
| } |
| } |
| } |
| |
| void setArchetypeGenerationQueryer( ArchetypeGenerationQueryer archetypeGenerationQueryer ) |
| { |
| this.archetypeGenerationQueryer = archetypeGenerationQueryer; |
| } |
| |
| public static class RequiredPropertyComparator |
| implements Comparator<String> |
| { |
| private final ArchetypeConfiguration archetypeConfiguration; |
| |
| private Map<String, Set<String>> propertyReferenceMap; |
| |
| public RequiredPropertyComparator( ArchetypeConfiguration archetypeConfiguration ) |
| { |
| this.archetypeConfiguration = archetypeConfiguration; |
| propertyReferenceMap = computePropertyReferences(); |
| } |
| |
| @Override |
| public int compare( String left, String right ) |
| { |
| if ( references( right, left ) ) |
| { |
| return 1; |
| } |
| |
| if ( references( left, right ) ) |
| { |
| return -1; |
| } |
| |
| return Integer.compare( propertyReferenceMap.get( left ).size(), propertyReferenceMap.get( right ).size() ); |
| } |
| |
| private Map<String, Set<String>> computePropertyReferences() |
| { |
| Map<String, Set<String>> result = new HashMap<>(); |
| |
| List<String> requiredProperties = archetypeConfiguration.getRequiredProperties(); |
| |
| for ( String propertyName : requiredProperties ) |
| { |
| final Set<String> referencedPropertyNames = new LinkedHashSet<>(); |
| |
| String defaultValue = archetypeConfiguration.getDefaultValue( propertyName ); |
| if ( StringUtils.contains( defaultValue, "${" ) ) |
| { |
| try |
| { |
| final boolean dumpNamespace = false; |
| SimpleNode node = RuntimeSingleton.parse( |
| new StringReader( defaultValue ), propertyName + ".default", dumpNamespace ); |
| node.jjtAccept( new BaseVisitor() |
| { |
| @SuppressWarnings( "unchecked" ) |
| @Override |
| public Object visit( ASTReference node, Object data ) |
| { |
| ( ( Set<String> ) data ).add( node.getFirstToken().next.image ); |
| return super.visit( node, data ); |
| } |
| }, referencedPropertyNames ); |
| } |
| catch ( ParseException e ) |
| { |
| throw new IllegalStateException( "Unparsable default value for property " + propertyName, e ); |
| } |
| } |
| |
| result.put( propertyName, referencedPropertyNames ); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Learn whether one property references another. Semantically, "references |
| * {@code targetProperty}, {@code sourceProperty} (does)." |
| * |
| * @param targetProperty {@link String} denoting property for which the state of |
| * being-referenced-by-the-property-denoted-by {@code sourceProperty} is desired |
| * @param sourceProperty {@link String} denoting property for which the state of |
| * references-the-property-denoted-by {@code targetProperty} is desired |
| * @return {@code boolean} |
| */ |
| private boolean references( String targetProperty, String sourceProperty ) |
| { |
| if ( targetProperty.equals( sourceProperty ) ) |
| { |
| return false; |
| } |
| synchronized ( this ) |
| { |
| if ( ! propertyReferenceMap.containsKey( sourceProperty ) ) |
| // something has changed |
| { |
| this.propertyReferenceMap = computePropertyReferences(); |
| } |
| } |
| Set<String> referencedProperties = propertyReferenceMap.get( sourceProperty ); |
| if ( referencedProperties.contains( targetProperty ) ) |
| { |
| return true; |
| } |
| for ( String referencedProperty : referencedProperties ) |
| { |
| if ( references( targetProperty, referencedProperty ) ) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| private ArtifactRepository createRepository( String url, String repositoryId ) |
| { |
| |
| |
| // snapshots vs releases |
| // offline = to turning the update policy off |
| |
| // TODO: we'll need to allow finer grained creation of repositories but this will do for now |
| |
| String updatePolicyFlag = ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS; |
| |
| String checksumPolicyFlag = ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN; |
| |
| ArtifactRepositoryPolicy snapshotsPolicy = |
| new ArtifactRepositoryPolicy( true, updatePolicyFlag, checksumPolicyFlag ); |
| |
| ArtifactRepositoryPolicy releasesPolicy = |
| new ArtifactRepositoryPolicy( true, updatePolicyFlag, checksumPolicyFlag ); |
| |
| return new MavenArtifactRepository( repositoryId, url, defaultArtifactRepositoryLayout, snapshotsPolicy, |
| releasesPolicy ); |
| } |
| |
| } |