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