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.old;
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.Reader;
33  import java.io.StringWriter;
34  import java.io.Writer;
35  import java.net.MalformedURLException;
36  import java.net.URL;
37  import java.net.URLClassLoader;
38  import java.nio.file.Files;
39  import java.util.HashMap;
40  import java.util.Iterator;
41  import java.util.Map;
42  
43  import org.apache.maven.archetype.ArchetypeGenerationRequest;
44  import org.apache.maven.archetype.common.ArchetypeArtifactManager;
45  import org.apache.maven.archetype.common.Constants;
46  import org.apache.maven.archetype.common.util.PomUtils;
47  import org.apache.maven.archetype.exception.InvalidPackaging;
48  import org.apache.maven.archetype.exception.UnknownArchetype;
49  import org.apache.maven.archetype.old.descriptor.ArchetypeDescriptor;
50  import org.apache.maven.archetype.old.descriptor.ArchetypeDescriptorBuilder;
51  import org.apache.maven.archetype.old.descriptor.TemplateDescriptor;
52  import org.apache.maven.model.Build;
53  import org.apache.maven.model.Model;
54  import org.apache.maven.model.Parent;
55  import org.apache.maven.model.Resource;
56  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
57  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
58  import org.apache.velocity.VelocityContext;
59  import org.apache.velocity.context.Context;
60  import org.codehaus.plexus.util.FileUtils;
61  import org.codehaus.plexus.util.IOUtil;
62  import org.codehaus.plexus.util.StringUtils;
63  import org.codehaus.plexus.util.xml.XmlStreamReader;
64  import org.codehaus.plexus.util.xml.XmlStreamWriter;
65  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
66  import org.codehaus.plexus.velocity.VelocityComponent;
67  import org.slf4j.Logger;
68  import org.slf4j.LoggerFactory;
69  import org.xml.sax.SAXException;
70  
71  /**
72   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
73   * @version $Id$
74   */
75  @Named
76  @Singleton
77  public class DefaultOldArchetype implements OldArchetype {
78      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOldArchetype.class);
79      private static final String DEFAULT_TEST_RESOURCE_DIR = "/src/test/resources";
80  
81      private static final String DEFAULT_TEST_SOURCE_DIR = "/src/test/java";
82  
83      private static final String DEFAULT_RESOURCE_DIR = "/src/main/resources";
84  
85      private static final String DEFAULT_SOURCE_DIR = "/src/main/java";
86  
87      // ----------------------------------------------------------------------
88      // Components
89      // ----------------------------------------------------------------------
90  
91      private VelocityComponent velocity;
92  
93      private ArchetypeArtifactManager archetypeArtifactManager;
94  
95      @Inject
96      public DefaultOldArchetype(VelocityComponent velocity, ArchetypeArtifactManager archetypeArtifactManager) {
97          this.archetypeArtifactManager = archetypeArtifactManager;
98          this.velocity = velocity;
99      }
100 
101     // ----------------------------------------------------------------------
102     // Implementation
103     // ----------------------------------------------------------------------
104 
105     // groupId = maven
106     // artifactId = maven-foo-archetype
107     // version = latest
108 
109     @Override
110     public void createArchetype(ArchetypeGenerationRequest request)
111             throws UnknownArchetype, ArchetypeDescriptorException, ArchetypeTemplateProcessingException,
112                     InvalidPackaging {
113         // ----------------------------------------------------------------------
114         // Download the archetype
115         // ----------------------------------------------------------------------
116 
117         File archetypeFile = archetypeArtifactManager.getArchetypeFile(
118                 request.getArchetypeGroupId(),
119                 request.getArchetypeArtifactId(),
120                 request.getArchetypeVersion(),
121                 request.getRemoteRepositories(),
122                 request.getRepositorySession());
123 
124         createArchetype(request, archetypeFile);
125     }
126 
127     @Override
128     @SuppressWarnings("checkstyle:MethodLength")
129     public void createArchetype(ArchetypeGenerationRequest request, File archetypeFile)
130             throws ArchetypeDescriptorException, ArchetypeTemplateProcessingException, InvalidPackaging {
131         Map<String, String> parameters = new HashMap<>();
132 
133         parameters.put("basedir", request.getOutputDirectory());
134 
135         parameters.put(Constants.PACKAGE, request.getPackage());
136 
137         parameters.put("packageName", request.getPackage());
138 
139         parameters.put(Constants.GROUP_ID, request.getGroupId());
140 
141         parameters.put(Constants.ARTIFACT_ID, request.getArtifactId());
142 
143         parameters.put(Constants.VERSION, request.getVersion());
144 
145         // ---------------------------------------------------------------------
146         // Get Logger and display all parameters used
147         // ---------------------------------------------------------------------
148         if (LOGGER.isInfoEnabled()) {
149             LOGGER.info("----------------------------------------------------------------------------");
150 
151             LOGGER.info("Using following parameters for creating project from Old (1.x) Archetype: "
152                     + request.getArchetypeArtifactId() + ":" + request.getArchetypeVersion());
153 
154             LOGGER.info("----------------------------------------------------------------------------");
155 
156             for (Map.Entry<String, String> entry : parameters.entrySet()) {
157                 String parameterName = entry.getKey();
158 
159                 String parameterValue = entry.getValue();
160 
161                 LOGGER.info("Parameter: " + parameterName + ", Value: " + parameterValue);
162             }
163         }
164 
165         // ----------------------------------------------------------------------
166         // Load the descriptor
167         // ----------------------------------------------------------------------
168 
169         ArchetypeDescriptorBuilder builder = new ArchetypeDescriptorBuilder();
170 
171         ArchetypeDescriptor descriptor;
172 
173         URLClassLoader archetypeJarLoader;
174 
175         URL[] urls;
176         try {
177             urls = new URL[] {archetypeFile.toURI().toURL()};
178         } catch (MalformedURLException e) {
179             throw new ArchetypeDescriptorException(e.getMessage());
180         }
181 
182         archetypeJarLoader = new URLClassLoader(urls);
183 
184         try (InputStream is = getDescriptorInputStream(archetypeJarLoader)) {
185             descriptor = builder.build(new XmlStreamReader(is));
186         } catch (IOException | XmlPullParserException e) {
187             throw new ArchetypeDescriptorException("Error reading the " + ARCHETYPE_DESCRIPTOR + " descriptor.", e);
188         }
189 
190         // ----------------------------------------------------------------------
191         //
192         // ----------------------------------------------------------------------
193 
194         String artifactId = request.getArtifactId();
195 
196         File parentPomFile = new File(request.getOutputDirectory(), ARCHETYPE_POM);
197 
198         File outputDirectoryFile;
199 
200         boolean creating;
201         File pomFile;
202         if (parentPomFile.exists() && descriptor.isAllowPartial() && artifactId == null) {
203             outputDirectoryFile = new File(request.getOutputDirectory());
204             creating = false;
205             pomFile = parentPomFile;
206         } else {
207             if (artifactId == null) {
208                 throw new ArchetypeTemplateProcessingException(
209                         "Artifact ID must be specified when creating a new project from an archetype.");
210             }
211 
212             outputDirectoryFile = new File(request.getOutputDirectory(), artifactId);
213             creating = true;
214 
215             if (outputDirectoryFile.exists()) {
216                 if (descriptor.isAllowPartial()) {
217                     creating = false;
218                 } else {
219                     throw new ArchetypeTemplateProcessingException("Directory " + outputDirectoryFile.getName()
220                             + " already exists - please run from a clean directory");
221                 }
222             }
223 
224             pomFile = new File(outputDirectoryFile, ARCHETYPE_POM);
225         }
226 
227         if (creating) {
228             if (request.getGroupId() == null) {
229                 throw new ArchetypeTemplateProcessingException(
230                         "Group ID must be specified when creating a new project from an archetype.");
231             }
232 
233             if (request.getVersion() == null) {
234                 throw new ArchetypeTemplateProcessingException(
235                         "Version must be specified when creating a new project from an archetype.");
236             }
237         }
238 
239         String outputDirectory = outputDirectoryFile.getAbsolutePath();
240 
241         String packageName = request.getPackage();
242 
243         // ----------------------------------------------------------------------
244         // Set up the Velocity context
245         // ----------------------------------------------------------------------
246 
247         Context context = new VelocityContext();
248 
249         context.put(Constants.PACKAGE, packageName);
250 
251         for (Map.Entry<String, String> entry : parameters.entrySet()) {
252             context.put(entry.getKey(), entry.getValue());
253         }
254 
255         // ----------------------------------------------------------------------
256         // Process the templates
257         // ----------------------------------------------------------------------
258 
259         ClassLoader old = Thread.currentThread().getContextClassLoader();
260 
261         Thread.currentThread().setContextClassLoader(archetypeJarLoader);
262 
263         Model parentModel = null;
264         if (creating) {
265             if (parentPomFile.exists()) {
266                 try (Reader fileReader = new XmlStreamReader(parentPomFile)) {
267                     MavenXpp3Reader reader = new MavenXpp3Reader();
268                     parentModel = reader.read(fileReader);
269                     if (!"pom".equals(parentModel.getPackaging())) {
270                         throw new ArchetypeTemplateProcessingException(
271                                 "Unable to add module to the current project as it is not of packaging type 'pom'");
272                     }
273                 } catch (IOException | XmlPullParserException e) {
274                     throw new ArchetypeTemplateProcessingException("Unable to read parent POM", e);
275                 }
276                 parentModel.getModules().add(artifactId);
277             }
278         }
279 
280         try {
281             processTemplates(pomFile, outputDirectory, context, descriptor, packageName, parentModel);
282         } catch (IOException e) {
283             throw new ArchetypeTemplateProcessingException("Unable to process template", e);
284         } finally {
285             Thread.currentThread().setContextClassLoader(old);
286         }
287 
288         if (parentModel != null) {
289             /*
290                     // TODO: would be nice to just write out with the xpp3 writer again, except that it loses a bunch of info and
291                     // reformats, so the module is just baked in as a string instead.
292                         FileWriter fileWriter = null;
293 
294                         try
295                         {
296                             fileWriter = new FileWriter( parentPomFile );
297 
298                             MavenXpp3Writer writer = new MavenXpp3Writer();
299                             writer.write( fileWriter, parentModel );
300                         }
301                         catch ( IOException e )
302                         {
303                             throw new ArchetypeTemplateProcessingException( "Unable to rewrite parent POM", e );
304                         }
305                         finally
306                         {
307                             IOUtil.close( fileWriter );
308                         }
309             */
310 
311             boolean added;
312             StringWriter w = new StringWriter();
313             try (Reader fileReader = new XmlStreamReader(parentPomFile)) {
314                 added = addModuleToParentPom(artifactId, fileReader, w);
315             } catch (IOException | SAXException | ParserConfigurationException | TransformerException e) {
316                 throw new ArchetypeTemplateProcessingException("Unable to rewrite parent POM", e);
317             }
318 
319             if (added) {
320                 try (Writer out = new XmlStreamWriter(parentPomFile)) {
321                     IOUtil.copy(w.toString(), out);
322                 } catch (IOException e) {
323                     throw new ArchetypeTemplateProcessingException("Unable to rewrite parent POM", e);
324                 }
325             }
326         }
327 
328         // ----------------------------------------------------------------------
329         // Log message on OldArchetype creation
330         // ----------------------------------------------------------------------
331         if (LOGGER.isInfoEnabled()) {
332             LOGGER.info("project created from Old (1.x) Archetype in dir: " + outputDirectory);
333         }
334     }
335 
336     private InputStream getDescriptorInputStream(ClassLoader archetypeJarLoader) throws ArchetypeDescriptorException {
337         InputStream is = getStream(ARCHETYPE_DESCRIPTOR, archetypeJarLoader);
338 
339         if (is == null) {
340             is = getStream(ARCHETYPE_OLD_DESCRIPTOR, archetypeJarLoader);
341         }
342 
343         if (is == null) {
344             throw new ArchetypeDescriptorException("The " + ARCHETYPE_DESCRIPTOR + " descriptor cannot be found.");
345         }
346 
347         return is;
348     }
349 
350     static boolean addModuleToParentPom(String artifactId, Reader fileReader, Writer fileWriter)
351             throws ArchetypeTemplateProcessingException, InvalidPackaging, IOException, ParserConfigurationException,
352                     SAXException, TransformerException {
353         return PomUtils.addNewModule(artifactId, fileReader, fileWriter);
354     }
355 
356     @SuppressWarnings("checkstyle:MethodLength")
357     private void processTemplates(
358             File pomFile,
359             String outputDirectory,
360             Context context,
361             ArchetypeDescriptor descriptor,
362             String packageName,
363             Model parentModel)
364             throws ArchetypeTemplateProcessingException, IOException {
365         if (!pomFile.exists()) {
366             processTemplate(outputDirectory, context, ARCHETYPE_POM, new TemplateDescriptor(), false, null);
367         }
368 
369         // ---------------------------------------------------------------------
370         // Model generated for the new archetype, so process it now
371         // ---------------------------------------------------------------------
372 
373         Model generatedModel;
374 
375         try (Reader pomReader = new XmlStreamReader(pomFile)) {
376             MavenXpp3Reader reader = new MavenXpp3Reader();
377 
378             generatedModel = reader.read(pomReader);
379         } catch (IOException | XmlPullParserException e) {
380             throw new ArchetypeTemplateProcessingException("Error reading POM", e);
381         }
382 
383         if (parentModel != null) {
384             Parent parent = new Parent();
385             parent.setGroupId(parentModel.getGroupId());
386             if (parent.getGroupId() == null) {
387                 parent.setGroupId(parentModel.getParent().getGroupId());
388             }
389             parent.setArtifactId(parentModel.getArtifactId());
390             parent.setVersion(parentModel.getVersion());
391             if (parent.getVersion() == null) {
392                 parent.setVersion(parentModel.getParent().getVersion());
393             }
394             generatedModel.setParent(parent);
395 
396             try (Writer pomWriter = new XmlStreamWriter(pomFile)) {
397                 MavenXpp3Writer writer = new MavenXpp3Writer();
398                 writer.write(pomWriter, generatedModel);
399             } catch (IOException e) {
400                 throw new ArchetypeTemplateProcessingException("Error rewriting POM", e);
401             }
402         }
403 
404         // XXX: Following POM processing block may be a candidate for
405         // refactoring out into service methods or moving to
406         // createProjectDirectoryStructure(outputDirectory)
407         Build build = generatedModel.getBuild();
408 
409         boolean overrideSrcDir = false;
410 
411         boolean overrideResourceDir = false;
412 
413         boolean overrideTestSrcDir = false;
414 
415         boolean overrideTestResourceDir = false;
416 
417         boolean foundBuildElement = build != null;
418 
419         if (LOGGER.isDebugEnabled()) {
420             LOGGER.debug(
421                     "********************* Debug info for resources created from generated Model ***********************");
422             LOGGER.debug("Was build element found in generated POM?: " + foundBuildElement);
423         }
424 
425         // create source directory if specified in POM
426         if (foundBuildElement && null != build.getSourceDirectory()) {
427             LOGGER.debug("Overriding default source directory ");
428 
429             overrideSrcDir = true;
430 
431             String srcDirectory = build.getSourceDirectory();
432 
433             srcDirectory = StringUtils.replace(srcDirectory, "\\", "/");
434 
435             FileUtils.mkdir(getOutputDirectory(outputDirectory, srcDirectory));
436         }
437 
438         // create script source directory if specified in POM
439         if (foundBuildElement && null != build.getScriptSourceDirectory()) {
440             LOGGER.debug("Overriding default script source directory ");
441 
442             String scriptSourceDirectory = build.getScriptSourceDirectory();
443 
444             scriptSourceDirectory = StringUtils.replace(scriptSourceDirectory, "\\", "/");
445 
446             FileUtils.mkdir(getOutputDirectory(outputDirectory, scriptSourceDirectory));
447         }
448 
449         // create resource director(y/ies) if specified in POM
450         if (foundBuildElement && !build.getResources().isEmpty()) {
451             LOGGER.debug("Overriding default resource directory ");
452 
453             overrideResourceDir = true;
454 
455             Iterator<?> resourceItr = build.getResources().iterator();
456 
457             while (resourceItr.hasNext()) {
458                 Resource resource = (Resource) resourceItr.next();
459 
460                 String resourceDirectory = resource.getDirectory();
461 
462                 resourceDirectory = StringUtils.replace(resourceDirectory, "\\", "/");
463 
464                 FileUtils.mkdir(getOutputDirectory(outputDirectory, resourceDirectory));
465             }
466         }
467         // create test source directory if specified in POM
468         if (foundBuildElement && null != build.getTestSourceDirectory()) {
469             LOGGER.debug("Overriding default test directory ");
470 
471             overrideTestSrcDir = true;
472 
473             String testDirectory = build.getTestSourceDirectory();
474 
475             testDirectory = StringUtils.replace(testDirectory, "\\", "/");
476 
477             FileUtils.mkdir(getOutputDirectory(outputDirectory, testDirectory));
478         }
479 
480         // create test resource directory if specified in POM
481         if (foundBuildElement && !build.getTestResources().isEmpty()) {
482             LOGGER.debug("Overriding default test resource directory ");
483 
484             overrideTestResourceDir = true;
485 
486             Iterator<?> testResourceItr = build.getTestResources().iterator();
487 
488             while (testResourceItr.hasNext()) {
489                 Resource resource = (Resource) testResourceItr.next();
490 
491                 String testResourceDirectory = resource.getDirectory();
492 
493                 testResourceDirectory = StringUtils.replace(testResourceDirectory, "\\", "/");
494 
495                 FileUtils.mkdir(getOutputDirectory(outputDirectory, testResourceDirectory));
496             }
497         }
498 
499         LOGGER.debug(
500                 "********************* End of debug info from resources from generated POM ***********************");
501 
502         // ----------------------------------------------------------------------
503         // Main
504         // ----------------------------------------------------------------------
505 
506         if (!descriptor.getSources().isEmpty()) {
507             if (!overrideSrcDir) {
508                 FileUtils.mkdir(outputDirectory + DEFAULT_SOURCE_DIR);
509                 processSources(outputDirectory, context, descriptor, packageName, DEFAULT_SOURCE_DIR);
510             } else {
511                 processSources(outputDirectory, context, descriptor, packageName, build.getSourceDirectory());
512             }
513         }
514 
515         if (!descriptor.getResources().isEmpty()) {
516             if (!overrideResourceDir) {
517                 FileUtils.mkdir(outputDirectory + DEFAULT_RESOURCE_DIR);
518             }
519             processResources(outputDirectory, context, descriptor, packageName);
520         }
521 
522         // ----------------------------------------------------------------------
523         // Test
524         // ----------------------------------------------------------------------
525 
526         if (!descriptor.getTestSources().isEmpty()) {
527             if (!overrideTestSrcDir) {
528                 FileUtils.mkdir(outputDirectory + DEFAULT_TEST_SOURCE_DIR);
529                 processTestSources(outputDirectory, context, descriptor, packageName, DEFAULT_TEST_SOURCE_DIR);
530             } else {
531                 processTestSources(outputDirectory, context, descriptor, packageName, build.getTestSourceDirectory());
532             }
533         }
534 
535         if (!descriptor.getTestResources().isEmpty()) {
536             if (!overrideTestResourceDir) {
537                 FileUtils.mkdir(outputDirectory + DEFAULT_TEST_RESOURCE_DIR);
538             }
539             processTestResources(outputDirectory, context, descriptor, packageName);
540         }
541 
542         // ----------------------------------------------------------------------
543         // Site
544         // ----------------------------------------------------------------------
545 
546         if (!descriptor.getSiteResources().isEmpty()) {
547             processSiteResources(outputDirectory, context, descriptor, packageName);
548         }
549     }
550 
551     private void processTemplate(
552             String outputDirectory,
553             Context context,
554             String template,
555             TemplateDescriptor descriptor,
556             boolean packageInFileName,
557             String packageName)
558             throws ArchetypeTemplateProcessingException, IOException {
559         processTemplate(outputDirectory, context, template, descriptor, packageInFileName, packageName, null);
560     }
561 
562     private String getOutputDirectory(String outputDirectory, String testResourceDirectory) {
563         return outputDirectory
564                 + (testResourceDirectory.startsWith("/") ? testResourceDirectory : "/" + testResourceDirectory);
565     }
566 
567     // ----------------------------------------------------------------------
568     //
569     // ----------------------------------------------------------------------
570 
571     protected void processSources(
572             String outputDirectory,
573             Context context,
574             ArchetypeDescriptor descriptor,
575             String packageName,
576             String sourceDirectory)
577             throws ArchetypeTemplateProcessingException, IOException {
578         for (String template : descriptor.getSources()) {
579             processTemplate(
580                     outputDirectory,
581                     context,
582                     template,
583                     descriptor.getSourceDescriptor(template),
584                     true,
585                     packageName,
586                     sourceDirectory);
587         }
588     }
589 
590     protected void processTestSources(
591             String outputDirectory,
592             Context context,
593             ArchetypeDescriptor descriptor,
594             String packageName,
595             String testSourceDirectory)
596             throws ArchetypeTemplateProcessingException, IOException {
597         for (String template : descriptor.getTestSources()) {
598             processTemplate(
599                     outputDirectory,
600                     context,
601                     template,
602                     descriptor.getTestSourceDescriptor(template),
603                     true,
604                     packageName,
605                     testSourceDirectory);
606         }
607     }
608 
609     protected void processResources(
610             String outputDirectory, Context context, ArchetypeDescriptor descriptor, String packageName)
611             throws IOException, ArchetypeTemplateProcessingException {
612         for (String template : descriptor.getResources()) {
613             processTemplate(
614                     outputDirectory, context, template, descriptor.getResourceDescriptor(template), false, packageName);
615         }
616     }
617 
618     protected void processTestResources(
619             String outputDirectory, Context context, ArchetypeDescriptor descriptor, String packageName)
620             throws IOException, ArchetypeTemplateProcessingException {
621         for (String template : descriptor.getTestResources()) {
622             processTemplate(
623                     outputDirectory,
624                     context,
625                     template,
626                     descriptor.getTestResourceDescriptor(template),
627                     false,
628                     packageName);
629         }
630     }
631 
632     protected void processSiteResources(
633             String outputDirectory, Context context, ArchetypeDescriptor descriptor, String packageName)
634             throws IOException, ArchetypeTemplateProcessingException {
635         for (String template : descriptor.getSiteResources()) {
636             processTemplate(
637                     outputDirectory,
638                     context,
639                     template,
640                     descriptor.getSiteResourceDescriptor(template),
641                     false,
642                     packageName);
643         }
644     }
645 
646     protected void processTemplate(
647             String outputDirectory,
648             Context context,
649             String template,
650             TemplateDescriptor descriptor,
651             boolean packageInFileName,
652             String packageName,
653             String sourceDirectory)
654             throws IOException, ArchetypeTemplateProcessingException {
655         File f;
656 
657         template = StringUtils.replace(template, "\\", "/");
658 
659         if (packageInFileName && packageName != null) {
660             String templateFileName = StringUtils.replace(template, "/", File.separator);
661 
662             String path = packageName.replace('.', '/');
663 
664             String filename = FileUtils.filename(templateFileName);
665 
666             String dirname = FileUtils.dirname(templateFileName).replace('\\', '/');
667 
668             sourceDirectory = sourceDirectory.replace('\\', '/');
669             if (sourceDirectory.startsWith("/")) {
670                 sourceDirectory = sourceDirectory.substring(1);
671             }
672 
673             if (!dirname.startsWith(sourceDirectory)) {
674                 throw new ArchetypeTemplateProcessingException(
675                         "Template '" + template + "' not in directory '" + sourceDirectory + "'");
676             }
677 
678             String extraPackages = dirname.substring(sourceDirectory.length());
679             if (extraPackages.startsWith("/")) {
680                 extraPackages = extraPackages.substring(1);
681             }
682             if (!extraPackages.isEmpty()) {
683                 path += "/" + extraPackages;
684             }
685 
686             f = new File(new File(new File(outputDirectory, sourceDirectory), path), filename);
687         } else {
688             f = new File(outputDirectory, template);
689         }
690 
691         if (!f.getParentFile().exists()) {
692             f.getParentFile().mkdirs();
693         }
694 
695         if (!f.exists() && !f.createNewFile()) {
696             LOGGER.warn("Could not create new file \"" + f.getPath() + "\" or the file already exists.");
697         }
698 
699         if (descriptor.isFiltered()) {
700             try (Writer writer = new OutputStreamWriter(Files.newOutputStream(f.toPath()), descriptor.getEncoding())) {
701                 StringWriter stringWriter = new StringWriter();
702 
703                 template = ARCHETYPE_RESOURCES + "/" + template;
704 
705                 velocity.getEngine().mergeTemplate(template, descriptor.getEncoding(), context, stringWriter);
706 
707                 writer.write(StringUtils.unifyLineSeparators(stringWriter.toString()));
708             } catch (Exception e) {
709                 throw new ArchetypeTemplateProcessingException("Error merging velocity templates", e);
710             }
711         } else {
712             try (InputStream is = getStream(ARCHETYPE_RESOURCES + "/" + template, null);
713                     OutputStream fos = Files.newOutputStream(f.toPath())) {
714                 IOUtil.copy(is, fos);
715             } catch (Exception e) {
716                 throw new ArchetypeTemplateProcessingException("Error copying file", e);
717             }
718         }
719     }
720 
721     protected void createProjectDirectoryStructure(String outputDirectory) {}
722 
723     private InputStream getStream(String name, ClassLoader loader) {
724         if (loader == null) {
725             return Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
726         }
727         return loader.getResourceAsStream(name);
728     }
729 }