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