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