blob: 0d56ee1b9bc92d5c66bd856b7a301cc0a8c963c4 [file] [log] [blame]
/*
* 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.
*/
package org.apache.maven.archetype.ui.generation;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
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;
import org.apache.maven.archetype.ArchetypeGenerationRequest;
import org.apache.maven.archetype.LoggingSupport;
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.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.context.InternalContextAdapterImpl;
import org.apache.velocity.runtime.RuntimeServices;
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.components.interactivity.PrompterException;
import org.codehaus.plexus.util.StringUtils;
// TODO: this seems to have more responsibilities than just a configurator
@Singleton
@Named
public class DefaultArchetypeGenerationConfigurator extends LoggingSupport implements ArchetypeGenerationConfigurator {
private final ArchetypeArtifactManager archetypeArtifactManager;
private final ArchetypeFactory archetypeFactory;
private final ArchetypeGenerationQueryer archetypeGenerationQueryer;
/**
* Determines whether the layout is legacy or not.
*/
private final ArtifactRepositoryLayout defaultArtifactRepositoryLayout;
@Inject
public DefaultArchetypeGenerationConfigurator(
ArchetypeArtifactManager archetypeArtifactManager,
ArchetypeFactory archetypeFactory,
ArchetypeGenerationQueryer archetypeGenerationQueryer,
ArtifactRepositoryLayout defaultArtifactRepositoryLayout) {
this.archetypeArtifactManager = archetypeArtifactManager;
this.archetypeFactory = archetypeFactory;
this.archetypeGenerationQueryer = archetypeGenerationQueryer;
this.defaultArtifactRepositoryLayout = defaultArtifactRepositoryLayout;
}
@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)
&& !request.isAskForDefaultPropertyValues()) {
getLogger()
.info("Using property: " + requiredProperty + " = "
+ archetypeConfiguration.getProperty(requiredProperty));
value = archetypeConfiguration.getProperty(requiredProperty);
} else {
String defaultValue = archetypeConfiguration.getDefaultValue(requiredProperty);
if (Constants.PACKAGE.equals(requiredProperty)
&& (defaultValue == null || defaultValue.isEmpty())) {
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));
}
}
}
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();
final InternalContextAdapterImpl velocityContextAdapter =
new InternalContextAdapterImpl(new VelocityContext());
final RuntimeServices velocityRuntime = RuntimeSingleton.getRuntimeServices();
for (String propertyName : requiredProperties) {
final Set<String> referencedPropertyNames = new LinkedHashSet<>();
String defaultValue = archetypeConfiguration.getDefaultValue(propertyName);
if (StringUtils.contains(defaultValue, "${")) {
try {
SimpleNode node = RuntimeSingleton.parse(
new StringReader(defaultValue), velocityRuntime.getTemplate(propertyName + ".default"));
node.init(velocityContextAdapter, velocityRuntime);
node.jjtAccept(
new BaseVisitor() {
@Override
public Object visit(ASTReference node, Object data) {
referencedPropertyNames.add(node.getRootString());
return super.visit(node, data);
}
},
velocityRuntime);
} catch (ParseException e) {
throw new IllegalStateException("Unparsable default value for property " + propertyName, e);
}
}
referencedPropertyNames.retainAll(archetypeConfiguration.getRequiredProperties());
// handle the case that a property expression #set()s itself:
referencedPropertyNames.remove(propertyName);
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);
}
}