View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.archetype.generator;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  import javax.xml.parsers.ParserConfigurationException;
25  import javax.xml.transform.TransformerException;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.io.OutputStreamWriter;
32  import java.io.StringWriter;
33  import java.io.Writer;
34  import java.nio.file.Files;
35  import java.util.ArrayList;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Properties;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  import java.util.zip.ZipEntry;
43  import java.util.zip.ZipFile;
44  
45  import groovy.lang.Binding;
46  import groovy.lang.GroovyShell;
47  import org.apache.maven.archetype.ArchetypeGenerationRequest;
48  import org.apache.maven.archetype.common.ArchetypeArtifactManager;
49  import org.apache.maven.archetype.common.ArchetypeFilesResolver;
50  import org.apache.maven.archetype.common.Constants;
51  import org.apache.maven.archetype.common.PomManager;
52  import org.apache.maven.archetype.exception.ArchetypeGenerationFailure;
53  import org.apache.maven.archetype.exception.ArchetypeNotConfigured;
54  import org.apache.maven.archetype.exception.InvalidPackaging;
55  import org.apache.maven.archetype.exception.OutputFileExists;
56  import org.apache.maven.archetype.exception.PomFileExists;
57  import org.apache.maven.archetype.exception.ProjectDirectoryExists;
58  import org.apache.maven.archetype.exception.UnknownArchetype;
59  import org.apache.maven.archetype.metadata.AbstractArchetypeDescriptor;
60  import org.apache.maven.archetype.metadata.ArchetypeDescriptor;
61  import org.apache.maven.archetype.metadata.FileSet;
62  import org.apache.maven.archetype.metadata.ModuleDescriptor;
63  import org.apache.maven.archetype.metadata.RequiredProperty;
64  import org.apache.velocity.VelocityContext;
65  import org.apache.velocity.context.Context;
66  import org.codehaus.plexus.util.FileUtils;
67  import org.codehaus.plexus.util.IOUtil;
68  import org.codehaus.plexus.util.StringUtils;
69  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
70  import org.codehaus.plexus.velocity.VelocityComponent;
71  import org.slf4j.Logger;
72  import org.slf4j.LoggerFactory;
73  import org.xml.sax.SAXException;
74  
75  @Named
76  @Singleton
77  public class DefaultFilesetArchetypeGenerator implements FilesetArchetypeGenerator {
78      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFilesetArchetypeGenerator.class);
79  
80      private ArchetypeArtifactManager archetypeArtifactManager;
81  
82      private ArchetypeFilesResolver archetypeFilesResolver;
83  
84      private PomManager pomManager;
85  
86      private VelocityComponent velocity;
87  
88      @Inject
89      public DefaultFilesetArchetypeGenerator(
90              ArchetypeArtifactManager archetypeArtifactManager,
91              ArchetypeFilesResolver archetypeFilesResolver,
92              PomManager pomManager,
93              VelocityComponent velocity) {
94          this.archetypeArtifactManager = archetypeArtifactManager;
95          this.archetypeFilesResolver = archetypeFilesResolver;
96          this.pomManager = pomManager;
97          this.velocity = velocity;
98      }
99  
100     /**
101      * Pattern used to detect tokens in a string. Tokens are any text surrounded
102      * by the delimiter <code>__</code>.
103      */
104     private static final Pattern TOKEN_PATTERN = Pattern.compile("__((?:[^_]+_)*[^_]+)__");
105 
106     @Override
107     @SuppressWarnings("checkstyle:MethodLength")
108     public void generateArchetype(ArchetypeGenerationRequest request, File archetypeFile)
109             throws UnknownArchetype, ArchetypeNotConfigured, ProjectDirectoryExists, PomFileExists, OutputFileExists,
110                     ArchetypeGenerationFailure, InvalidPackaging {
111         ClassLoader old = Thread.currentThread().getContextClassLoader();
112 
113         try {
114             ArchetypeDescriptor archetypeDescriptor =
115                     archetypeArtifactManager.getFileSetArchetypeDescriptor(archetypeFile);
116 
117             if (!isArchetypeConfigured(archetypeDescriptor, request)) {
118                 if (request.isInteractiveMode()) {
119                     throw new ArchetypeNotConfigured("No archetype was chosen.", null);
120                 }
121 
122                 StringBuilder exceptionMessage = new StringBuilder(
123                         "Archetype " + request.getArchetypeGroupId() + ":" + request.getArchetypeArtifactId() + ":"
124                                 + request.getArchetypeVersion() + " is not configured");
125 
126                 List<String> missingProperties = new ArrayList<>(0);
127                 for (RequiredProperty requiredProperty : archetypeDescriptor.getRequiredProperties()) {
128                     if (StringUtils.isEmpty(request.getProperties().getProperty(requiredProperty.getKey()))) {
129                         exceptionMessage.append("\n\tProperty " + requiredProperty.getKey() + " is missing.");
130 
131                         missingProperties.add(requiredProperty.getKey());
132                     }
133                 }
134 
135                 throw new ArchetypeNotConfigured(exceptionMessage.toString(), missingProperties);
136             }
137 
138             Context context = prepareVelocityContext(request);
139 
140             String packageName = request.getPackage();
141             String artifactId = request.getArtifactId();
142             File outputDirectoryFile = new File(request.getOutputDirectory(), artifactId);
143             File basedirPom = new File(request.getOutputDirectory(), Constants.ARCHETYPE_POM);
144             File pom = new File(outputDirectoryFile, Constants.ARCHETYPE_POM);
145 
146             List<String> archetypeResources = archetypeArtifactManager.getFilesetArchetypeResources(archetypeFile);
147 
148             ZipFile archetypeZipFile = archetypeArtifactManager.getArchetypeZipFile(archetypeFile);
149 
150             ClassLoader archetypeJarLoader = archetypeArtifactManager.getArchetypeJarLoader(archetypeFile);
151 
152             Thread.currentThread().setContextClassLoader(archetypeJarLoader);
153 
154             if (archetypeDescriptor.isPartial()) {
155                 LOGGER.debug("Processing partial archetype " + archetypeDescriptor.getName());
156                 if (outputDirectoryFile.exists()) {
157                     if (!pom.exists()) {
158                         throw new PomFileExists("This is a partial archetype and the pom.xml file doesn't exist.");
159                     }
160 
161                     processPomWithMerge(context, pom, "");
162 
163                     processArchetypeTemplatesWithWarning(
164                             archetypeDescriptor,
165                             archetypeResources,
166                             archetypeZipFile,
167                             "",
168                             context,
169                             packageName,
170                             outputDirectoryFile);
171                 } else {
172                     if (basedirPom.exists()) {
173                         processPomWithMerge(context, basedirPom, "");
174 
175                         processArchetypeTemplatesWithWarning(
176                                 archetypeDescriptor,
177                                 archetypeResources,
178                                 archetypeZipFile,
179                                 "",
180                                 context,
181                                 packageName,
182                                 new File(request.getOutputDirectory()));
183                     } else {
184                         processPom(context, pom, "");
185 
186                         processArchetypeTemplates(
187                                 archetypeDescriptor,
188                                 archetypeResources,
189                                 archetypeZipFile,
190                                 "",
191                                 context,
192                                 packageName,
193                                 outputDirectoryFile);
194                     }
195                 }
196 
197                 if (!archetypeDescriptor.getModules().isEmpty()) {
198                     LOGGER.info("Modules ignored in partial mode");
199                 }
200             } else {
201                 LOGGER.debug("Processing complete archetype " + archetypeDescriptor.getName());
202                 if (outputDirectoryFile.exists() && pom.exists()) {
203                     throw new ProjectDirectoryExists(
204                             "A Maven project already exists in the directory " + outputDirectoryFile.getPath());
205                 }
206 
207                 if (outputDirectoryFile.exists()) {
208                     LOGGER.warn("The directory " + outputDirectoryFile.getPath() + " already exists.");
209                 }
210 
211                 context.put("rootArtifactId", artifactId);
212 
213                 processFilesetModule(
214                         artifactId,
215                         artifactId,
216                         archetypeResources,
217                         pom,
218                         archetypeZipFile,
219                         "",
220                         basedirPom,
221                         outputDirectoryFile,
222                         packageName,
223                         archetypeDescriptor,
224                         context);
225             }
226 
227             String postGenerationScript = archetypeArtifactManager.getPostGenerationScript(archetypeFile);
228             if (postGenerationScript != null) {
229                 LOGGER.info("Executing " + Constants.ARCHETYPE_POST_GENERATION_SCRIPT + " post-generation script");
230 
231                 Binding binding = new Binding();
232 
233                 final Properties archetypeGeneratorProperties = new Properties();
234                 archetypeGeneratorProperties.putAll(System.getProperties());
235 
236                 if (request.getProperties() != null) {
237                     archetypeGeneratorProperties.putAll(request.getProperties());
238                 }
239 
240                 for (Map.Entry<Object, Object> entry : archetypeGeneratorProperties.entrySet()) {
241                     binding.setVariable(entry.getKey().toString(), entry.getValue());
242                 }
243 
244                 binding.setVariable("request", request);
245 
246                 GroovyShell shell = new GroovyShell(binding);
247                 shell.evaluate(postGenerationScript);
248             }
249 
250             // ----------------------------------------------------------------------
251             // Log message on OldArchetype creation
252             // ----------------------------------------------------------------------
253             if (LOGGER.isInfoEnabled()) {
254                 LOGGER.info("Project created from Archetype in dir: " + outputDirectoryFile.getAbsolutePath());
255             }
256         } catch (IOException
257                 | XmlPullParserException
258                 | ParserConfigurationException
259                 | TransformerException
260                 | SAXException e) {
261             throw new ArchetypeGenerationFailure(e);
262         } finally {
263             Thread.currentThread().setContextClassLoader(old);
264         }
265     }
266 
267     public String getPackageAsDirectory(String packageName) {
268         return StringUtils.replace(packageName, ".", "/");
269     }
270 
271     private boolean copyFile(
272             final File outFile, final String template, final boolean failIfExists, final ZipFile archetypeZipFile)
273             throws OutputFileExists, IOException {
274         LOGGER.debug("Copying file " + template);
275 
276         if (failIfExists && outFile.exists()) {
277             throw new OutputFileExists("Don't rewrite file " + outFile.getName());
278         } else if (outFile.exists()) {
279             LOGGER.warn("CP Don't override file " + outFile);
280 
281             return false;
282         }
283 
284         ZipEntry input = archetypeZipFile.getEntry(Constants.ARCHETYPE_RESOURCES + "/" + template);
285 
286         if (input.isDirectory()) {
287             outFile.mkdirs();
288         } else {
289             outFile.getParentFile().mkdirs();
290             if (!outFile.exists() && !outFile.createNewFile()) {
291                 LOGGER.warn("Could not create new file \"" + outFile.getPath() + "\" or the file already exists.");
292             }
293 
294             try (InputStream inputStream = archetypeZipFile.getInputStream(input);
295                     OutputStream out = Files.newOutputStream(outFile.toPath())) {
296                 IOUtil.copy(inputStream, out);
297             }
298         }
299 
300         return true;
301     }
302 
303     @SuppressWarnings("checkstyle:ParameterNumber")
304     private int copyFiles(
305             String directory,
306             List<String> fileSetResources,
307             boolean packaged,
308             String packageName,
309             File outputDirectoryFile,
310             ZipFile archetypeZipFile,
311             String moduleOffset,
312             boolean failIfExists,
313             Context context)
314             throws OutputFileExists, IOException {
315         int count = 0;
316 
317         for (String template : fileSetResources) {
318             File outputFile = getOutputFile(
319                     template, directory, outputDirectoryFile, packaged, packageName, moduleOffset, context);
320 
321             if (copyFile(outputFile, template, failIfExists, archetypeZipFile)) {
322                 count++;
323             }
324         }
325 
326         return count;
327     }
328 
329     private String getEncoding(String archetypeEncoding) {
330         return (archetypeEncoding == null || archetypeEncoding.isEmpty()) ? "UTF-8" : archetypeEncoding;
331     }
332 
333     private String getOffsetSeparator(String moduleOffset) {
334         return (moduleOffset == null || moduleOffset.isEmpty()) ? "/" : ("/" + moduleOffset + "/");
335     }
336 
337     private File getOutputFile(
338             String template,
339             String directory,
340             File outputDirectoryFile,
341             boolean packaged,
342             String packageName,
343             String moduleOffset,
344             Context context) {
345         String templateName = StringUtils.replaceOnce(template, directory, "");
346 
347         String outputFileName = directory + "/" + (packaged ? getPackageAsDirectory(packageName) : "") + "/"
348                 + templateName.substring(moduleOffset.length());
349 
350         outputFileName = replaceFilenameTokens(outputFileName, context);
351 
352         return new File(outputDirectoryFile, outputFileName);
353     }
354 
355     /**
356      * Replaces all tokens (text matching {@link #TOKEN_PATTERN}) within
357      * the given string, using properties contained within the context. If a
358      * property does not exist in the context, the token is left unmodified
359      * and a warning is logged.
360      *
361      * @param filePath the file name and path to be interpolated
362      * @param context  contains the available properties
363      */
364     private String replaceFilenameTokens(final String filePath, final Context context) {
365         StringBuffer interpolatedResult = new StringBuffer();
366         Matcher matcher = TOKEN_PATTERN.matcher(filePath);
367 
368         while (matcher.find()) {
369             String propertyToken = matcher.group(1);
370             String contextPropertyValue = (String) context.get(propertyToken);
371             if (contextPropertyValue != null && !contextPropertyValue.trim().isEmpty()) {
372                 if (LOGGER.isDebugEnabled()) {
373                     LOGGER.debug("Replacing property '" + propertyToken + "' in file path '" + filePath
374                             + "' with value '" + contextPropertyValue + "'.");
375                 }
376                 matcher.appendReplacement(interpolatedResult, contextPropertyValue);
377             } else {
378                 // Need to skip the undefined property
379                 LOGGER.warn("Property '" + propertyToken + "' was not specified, so the token in '" + filePath
380                         + "' is not being replaced.");
381             }
382         }
383 
384         matcher.appendTail(interpolatedResult);
385 
386         if (LOGGER.isDebugEnabled()) {
387             LOGGER.debug("Final interpolated file path: '" + interpolatedResult + "'");
388         }
389 
390         return interpolatedResult.toString();
391     }
392 
393     private String getPackageInPathFormat(String aPackage) {
394         return StringUtils.replace(aPackage, ".", "/");
395     }
396 
397     private boolean isArchetypeConfigured(ArchetypeDescriptor archetypeDescriptor, ArchetypeGenerationRequest request) {
398         for (RequiredProperty requiredProperty : archetypeDescriptor.getRequiredProperties()) {
399             if (StringUtils.isEmpty(request.getProperties().getProperty(requiredProperty.getKey()))) {
400                 return false;
401             }
402         }
403 
404         return true;
405     }
406 
407     private void setParentArtifactId(Context context, String artifactId) {
408         context.put(Constants.PARENT_ARTIFACT_ID, artifactId);
409     }
410 
411     private Context prepareVelocityContext(ArchetypeGenerationRequest request) {
412         Context context = new VelocityContext();
413         context.put(Constants.GROUP_ID, request.getGroupId());
414         context.put(Constants.ARTIFACT_ID, request.getArtifactId());
415         context.put(Constants.VERSION, request.getVersion());
416         context.put(Constants.PACKAGE, request.getPackage());
417         final String packageInPathFormat = getPackageInPathFormat(request.getPackage());
418         context.put(Constants.PACKAGE_IN_PATH_FORMAT, packageInPathFormat);
419 
420         if (LOGGER.isInfoEnabled()) {
421             LOGGER.info("----------------------------------------------------------------------------");
422 
423             LOGGER.info("Using following parameters for creating project from Archetype: "
424                     + request.getArchetypeArtifactId() + ":" + request.getArchetypeVersion());
425 
426             LOGGER.info("----------------------------------------------------------------------------");
427             LOGGER.info("Parameter: " + Constants.GROUP_ID + ", Value: " + request.getGroupId());
428             LOGGER.info("Parameter: " + Constants.ARTIFACT_ID + ", Value: " + request.getArtifactId());
429             LOGGER.info("Parameter: " + Constants.VERSION + ", Value: " + request.getVersion());
430             LOGGER.info("Parameter: " + Constants.PACKAGE + ", Value: " + request.getPackage());
431             LOGGER.info("Parameter: " + Constants.PACKAGE_IN_PATH_FORMAT + ", Value: " + packageInPathFormat);
432         }
433 
434         for (Iterator<?> iterator = request.getProperties().keySet().iterator(); iterator.hasNext(); ) {
435             String key = (String) iterator.next();
436 
437             String value = request.getProperties().getProperty(key);
438 
439             if (maybeVelocityExpression(value)) {
440                 value = evaluateExpression(context, key, value);
441             }
442 
443             context.put(key, value);
444 
445             if (LOGGER.isInfoEnabled()) {
446                 LOGGER.info("Parameter: " + key + ", Value: " + value);
447             }
448         }
449         return context;
450     }
451 
452     private boolean maybeVelocityExpression(String value) {
453         return value != null && value.contains("${");
454     }
455 
456     private String evaluateExpression(Context context, String key, String value) {
457         try (StringWriter stringWriter = new StringWriter()) {
458             velocity.getEngine().evaluate(context, stringWriter, key, value);
459             return stringWriter.toString();
460         } catch (Exception ex) {
461             return value;
462         }
463     }
464 
465     private void processArchetypeTemplates(
466             AbstractArchetypeDescriptor archetypeDescriptor,
467             List<String> archetypeResources,
468             ZipFile archetypeZipFile,
469             String moduleOffset,
470             Context context,
471             String packageName,
472             File outputDirectoryFile)
473             throws OutputFileExists, ArchetypeGenerationFailure, IOException {
474         processTemplates(
475                 packageName,
476                 outputDirectoryFile,
477                 context,
478                 archetypeDescriptor,
479                 archetypeResources,
480                 archetypeZipFile,
481                 moduleOffset,
482                 false);
483     }
484 
485     private void processArchetypeTemplatesWithWarning(
486             ArchetypeDescriptor archetypeDescriptor,
487             List<String> archetypeResources,
488             ZipFile archetypeZipFile,
489             String moduleOffset,
490             Context context,
491             String packageName,
492             File outputDirectoryFile)
493             throws OutputFileExists, ArchetypeGenerationFailure, IOException {
494         processTemplates(
495                 packageName,
496                 outputDirectoryFile,
497                 context,
498                 archetypeDescriptor,
499                 archetypeResources,
500                 archetypeZipFile,
501                 moduleOffset,
502                 true);
503     }
504 
505     @SuppressWarnings("checkstyle:ParameterNumber")
506     private int processFileSet(
507             String directory,
508             List<String> fileSetResources,
509             boolean packaged,
510             String packageName,
511             Context context,
512             File outputDirectoryFile,
513             String moduleOffset,
514             String archetypeEncoding,
515             boolean failIfExists)
516             throws IOException, OutputFileExists, ArchetypeGenerationFailure {
517         int count = 0;
518 
519         for (String template : fileSetResources) {
520             File outputFile = getOutputFile(
521                     template, directory, outputDirectoryFile, packaged, packageName, moduleOffset, context);
522 
523             if (processTemplate(
524                     outputFile,
525                     context,
526                     Constants.ARCHETYPE_RESOURCES + "/" + template,
527                     archetypeEncoding,
528                     failIfExists)) {
529                 count++;
530             }
531         }
532 
533         return count;
534     }
535 
536     @SuppressWarnings("checkstyle:ParameterNumber")
537     private void processFilesetModule(
538             final String rootArtifactId,
539             final String artifactId,
540             final List<String> archetypeResources,
541             File pom,
542             final ZipFile archetypeZipFile,
543             String moduleOffset,
544             File basedirPom,
545             File outputDirectoryFile,
546             final String packageName,
547             final AbstractArchetypeDescriptor archetypeDescriptor,
548             final Context context)
549             throws XmlPullParserException, IOException, ParserConfigurationException, SAXException,
550                     TransformerException, OutputFileExists, ArchetypeGenerationFailure, InvalidPackaging {
551         outputDirectoryFile.mkdirs();
552         LOGGER.debug("Processing module " + artifactId);
553         LOGGER.debug("Processing module rootArtifactId " + rootArtifactId);
554         LOGGER.debug("Processing module pom " + pom);
555         LOGGER.debug("Processing module moduleOffset " + moduleOffset);
556         LOGGER.debug("Processing module outputDirectoryFile " + outputDirectoryFile);
557 
558         processFilesetProject(
559                 archetypeDescriptor,
560                 StringUtils.replace(artifactId, "${rootArtifactId}", rootArtifactId),
561                 archetypeResources,
562                 pom,
563                 archetypeZipFile,
564                 moduleOffset,
565                 context,
566                 packageName,
567                 outputDirectoryFile,
568                 basedirPom);
569 
570         String parentArtifactId = (String) context.get(Constants.PARENT_ARTIFACT_ID);
571 
572         Iterator<ModuleDescriptor> subprojects =
573                 archetypeDescriptor.getModules().iterator();
574 
575         if (subprojects.hasNext()) {
576             LOGGER.debug(artifactId + " has modules (" + archetypeDescriptor.getModules() + ")");
577 
578             setParentArtifactId(context, StringUtils.replace(artifactId, "${rootArtifactId}", rootArtifactId));
579         }
580 
581         while (subprojects.hasNext()) {
582             ModuleDescriptor project = subprojects.next();
583 
584             String modulePath = StringUtils.replace(project.getDir(), "__rootArtifactId__", rootArtifactId);
585             modulePath = replaceFilenameTokens(modulePath, context);
586 
587             File moduleOutputDirectoryFile = new File(outputDirectoryFile, modulePath);
588 
589             context.put(
590                     Constants.ARTIFACT_ID, StringUtils.replace(project.getId(), "${rootArtifactId}", rootArtifactId));
591 
592             String moduleArtifactId = StringUtils.replace(project.getDir(), "__rootArtifactId__", rootArtifactId);
593             moduleArtifactId = replaceFilenameTokens(moduleArtifactId, context);
594 
595             processFilesetModule(
596                     rootArtifactId,
597                     moduleArtifactId,
598                     archetypeResources,
599                     new File(moduleOutputDirectoryFile, Constants.ARCHETYPE_POM),
600                     archetypeZipFile,
601                     ((moduleOffset == null || moduleOffset.isEmpty()) ? "" : (moduleOffset + "/"))
602                             + StringUtils.replace(project.getDir(), "${rootArtifactId}", rootArtifactId),
603                     pom,
604                     moduleOutputDirectoryFile,
605                     packageName,
606                     project,
607                     context);
608         }
609 
610         restoreParentArtifactId(context, parentArtifactId);
611 
612         LOGGER.debug("Processed " + artifactId);
613     }
614 
615     @SuppressWarnings("checkstyle:ParameterNumber")
616     private void processFilesetProject(
617             final AbstractArchetypeDescriptor archetypeDescriptor,
618             final String moduleId,
619             final List<String> archetypeResources,
620             final File pom,
621             final ZipFile archetypeZipFile,
622             String moduleOffset,
623             final Context context,
624             final String packageName,
625             final File outputDirectoryFile,
626             final File basedirPom)
627             throws XmlPullParserException, IOException, ParserConfigurationException, SAXException,
628                     TransformerException, OutputFileExists, ArchetypeGenerationFailure, InvalidPackaging {
629         LOGGER.debug("Processing fileset project moduleId " + moduleId);
630         LOGGER.debug("Processing fileset project pom " + pom);
631         LOGGER.debug("Processing fileset project moduleOffset " + moduleOffset);
632         LOGGER.debug("Processing fileset project outputDirectoryFile " + outputDirectoryFile);
633         LOGGER.debug("Processing fileset project basedirPom " + basedirPom);
634 
635         if (basedirPom.exists()) {
636             processPomWithParent(context, pom, moduleOffset, basedirPom, moduleId);
637         } else {
638             processPom(context, pom, moduleOffset);
639         }
640 
641         processArchetypeTemplates(
642                 archetypeDescriptor,
643                 archetypeResources,
644                 archetypeZipFile,
645                 moduleOffset,
646                 context,
647                 packageName,
648                 outputDirectoryFile);
649     }
650 
651     private void processPom(Context context, File pom, String moduleOffset)
652             throws IOException, OutputFileExists, ArchetypeGenerationFailure {
653         LOGGER.debug("Processing pom " + pom);
654 
655         processTemplate(
656                 pom,
657                 context,
658                 Constants.ARCHETYPE_RESOURCES + getOffsetSeparator(moduleOffset) + Constants.ARCHETYPE_POM,
659                 getEncoding(null),
660                 true);
661     }
662 
663     private void processPomWithMerge(Context context, File pom, String moduleOffset)
664             throws OutputFileExists, IOException, XmlPullParserException, ArchetypeGenerationFailure {
665         LOGGER.debug("Processing pom " + pom + " with merge");
666 
667         File temporaryPom = getTemporaryFile(pom);
668 
669         processTemplate(
670                 temporaryPom,
671                 context,
672                 Constants.ARCHETYPE_RESOURCES + getOffsetSeparator(moduleOffset) + Constants.ARCHETYPE_POM,
673                 getEncoding(null),
674                 true);
675 
676         pomManager.mergePoms(pom, temporaryPom);
677 
678         // getTemporaryFile sets deleteOnExit. Lets try to delete and then make sure deleteOnExit is
679         // still set. Windows has issues deleting files with certain JDKs.
680         try {
681             FileUtils.forceDelete(temporaryPom);
682         } catch (IOException e) {
683             temporaryPom.deleteOnExit();
684         }
685     }
686 
687     private void processPomWithParent(Context context, File pom, String moduleOffset, File basedirPom, String moduleId)
688             throws XmlPullParserException, IOException, ParserConfigurationException, SAXException,
689                     TransformerException, OutputFileExists, ArchetypeGenerationFailure, InvalidPackaging {
690         LOGGER.debug("Processing pom " + pom + " with parent " + basedirPom);
691 
692         processTemplate(
693                 pom,
694                 context,
695                 Constants.ARCHETYPE_RESOURCES + getOffsetSeparator(moduleOffset) + Constants.ARCHETYPE_POM,
696                 getEncoding(null),
697                 true);
698 
699         LOGGER.debug("Adding module " + moduleId);
700 
701         pomManager.addModule(basedirPom, moduleId);
702 
703         pomManager.addParent(pom, basedirPom);
704     }
705 
706     @SuppressWarnings("deprecation")
707     private boolean processTemplate(
708             File outFile, Context context, String templateFileName, String encoding, boolean failIfExists)
709             throws IOException, OutputFileExists, ArchetypeGenerationFailure {
710         templateFileName = templateFileName.replace(File.separatorChar, '/');
711 
712         String localTemplateFileName = templateFileName.replace('/', File.separatorChar);
713         if (!templateFileName.equals(localTemplateFileName)
714                 && !velocity.getEngine().resourceExists(templateFileName)
715                 && velocity.getEngine().resourceExists(localTemplateFileName)) {
716             templateFileName = localTemplateFileName;
717         }
718 
719         LOGGER.debug("Processing template " + templateFileName);
720 
721         if (outFile.exists()) {
722             if (failIfExists) {
723                 throw new OutputFileExists("Don't override file " + outFile.getAbsolutePath());
724             }
725 
726             LOGGER.warn("Don't override file " + outFile);
727 
728             return false;
729         }
730 
731         if (templateFileName.endsWith("/")) {
732             LOGGER.debug("Creating directory " + outFile);
733 
734             outFile.mkdirs();
735 
736             return true;
737         }
738 
739         if (!outFile.getParentFile().exists()) {
740             outFile.getParentFile().mkdirs();
741         }
742 
743         if (!outFile.exists() && !outFile.createNewFile()) {
744             LOGGER.warn("Could not create new file \"" + outFile.getPath() + "\" or the file already exists.");
745         }
746 
747         LOGGER.debug("Merging into " + outFile);
748 
749         try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outFile.toPath()), encoding)) {
750             StringWriter stringWriter = new StringWriter();
751 
752             velocity.getEngine().mergeTemplate(templateFileName, encoding, context, stringWriter);
753 
754             writer.write(StringUtils.unifyLineSeparators(stringWriter.toString()));
755         } catch (Exception e) {
756             throw new ArchetypeGenerationFailure("Error merging velocity templates: " + e.getMessage(), e);
757         }
758 
759         return true;
760     }
761 
762     @SuppressWarnings("checkstyle:ParameterNumber")
763     private void processTemplates(
764             String packageName,
765             File outputDirectoryFile,
766             Context context,
767             AbstractArchetypeDescriptor archetypeDescriptor,
768             List<String> archetypeResources,
769             ZipFile archetypeZipFile,
770             String moduleOffset,
771             boolean failIfExists)
772             throws OutputFileExists, ArchetypeGenerationFailure, IOException {
773         Iterator<FileSet> iterator = archetypeDescriptor.getFileSets().iterator();
774         if (iterator.hasNext()) {
775             LOGGER.debug("Processing filesets" + "\n  " + archetypeResources);
776         }
777 
778         int count = 0;
779         while (iterator.hasNext()) {
780             FileSet fileSet = iterator.next();
781             count++;
782 
783             final String includeCondition = fileSet.getIncludeCondition();
784             if (includeCondition != null && !includeCondition.isEmpty()) {
785                 final String evaluatedCondition = evaluateExpression(context, "includeCondition", includeCondition);
786                 if (!Boolean.parseBoolean(evaluatedCondition)) {
787                     LOGGER.debug(String.format(
788                             "Skipping fileset %s due to includeCondition: %s being: %s",
789                             fileSet, includeCondition, evaluatedCondition));
790                     continue;
791                 }
792             }
793 
794             List<String> fileSetResources =
795                     archetypeFilesResolver.filterFiles(moduleOffset, fileSet, archetypeResources);
796 
797             // This creates an empty directory, even if there is no file to process
798             // Fix for ARCHETYPE-57
799             getOutputFile(
800                             moduleOffset,
801                             fileSet.getDirectory(),
802                             outputDirectoryFile,
803                             fileSet.isPackaged(),
804                             packageName,
805                             moduleOffset,
806                             context)
807                     .mkdirs();
808 
809             if (fileSet.isFiltered()) {
810                 LOGGER.debug("    Processing fileset " + fileSet + " -> " + fileSetResources.size() + ":\n      "
811                         + fileSetResources);
812 
813                 int processed = processFileSet(
814                         fileSet.getDirectory(),
815                         fileSetResources,
816                         fileSet.isPackaged(),
817                         packageName,
818                         context,
819                         outputDirectoryFile,
820                         moduleOffset,
821                         getEncoding(fileSet.getEncoding()),
822                         failIfExists);
823 
824                 LOGGER.debug("    Processed " + processed + " files.");
825             } else {
826                 LOGGER.debug("    Copying fileset " + fileSet + " -> " + fileSetResources.size() + ":\n      "
827                         + fileSetResources);
828 
829                 int copied = copyFiles(
830                         fileSet.getDirectory(),
831                         fileSetResources,
832                         fileSet.isPackaged(),
833                         packageName,
834                         outputDirectoryFile,
835                         archetypeZipFile,
836                         moduleOffset,
837                         failIfExists,
838                         context);
839 
840                 LOGGER.debug("    Copied " + copied + " files.");
841             }
842         }
843 
844         LOGGER.debug("Processed " + count + " filesets");
845     }
846 
847     private void restoreParentArtifactId(Context context, String parentArtifactId) {
848         if (parentArtifactId == null || parentArtifactId.isEmpty()) {
849             context.remove(Constants.PARENT_ARTIFACT_ID);
850         } else {
851             context.put(Constants.PARENT_ARTIFACT_ID, parentArtifactId);
852         }
853     }
854 
855     private File getTemporaryFile(File file) {
856         File tmp = FileUtils.createTempFile(file.getName(), Constants.TMP, file.getParentFile());
857 
858         tmp.deleteOnExit();
859 
860         return tmp;
861     }
862 }