View Javadoc
1   /*
2    * Copyright 2001-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.javaforge.bobber.plugin.archetype;
18  
19  import com.javaforge.bobber.archetype.model.Template;
20  import com.javaforge.bobber.archetype.model.Variable;
21  import com.javaforge.bobber.archetype.model.io.xpp3.BobberArchetypeXpp3Reader;
22  
23  import java.io.File;
24  import java.io.FileOutputStream;
25  import java.io.FileWriter;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InputStreamReader;
29  import java.io.StringWriter;
30  import java.io.Writer;
31  import java.net.MalformedURLException;
32  import java.net.URL;
33  import java.net.URLClassLoader;
34  import java.util.ArrayList;
35  import java.util.Enumeration;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.StringTokenizer;
40  import java.util.jar.JarEntry;
41  import java.util.jar.JarFile;
42  import java.util.zip.ZipEntry;
43  
44  import org.apache.maven.archetype.Archetype;
45  import org.apache.maven.archetype.ArchetypeDescriptorException;
46  import org.apache.maven.archetype.ArchetypeNotFoundException;
47  import org.apache.maven.archetype.ArchetypeTemplateProcessingException;
48  import org.apache.maven.artifact.Artifact;
49  import org.apache.maven.artifact.factory.ArtifactFactory;
50  import org.apache.maven.artifact.repository.ArtifactRepository;
51  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
52  import org.apache.maven.artifact.resolver.ArtifactResolver;
53  import org.apache.maven.settings.MavenSettingsBuilder;
54  import org.apache.velocity.VelocityContext;
55  import org.codehaus.plexus.components.interactivity.InputHandler;
56  import org.codehaus.plexus.logging.AbstractLogEnabled;
57  import org.codehaus.plexus.util.FileUtils;
58  import org.codehaus.plexus.util.IOUtil;
59  import org.codehaus.plexus.util.StringUtils;
60  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
61  import org.codehaus.plexus.velocity.VelocityComponent;
62  
63  //source blatantly copied from org.apache.maven.archetype.DefaultArchetype. Also stole code from DefaultPluginVersionManager.java
64  
65  //and maven-model for the mdo
66  public class BobberArchetype
67          extends AbstractLogEnabled
68          implements Archetype
69  {
70  
71      private static final String NEW_LINE = System.getProperty("line.separator");
72  
73      // ----------------------------------------------------------------------
74      // Components
75      // ----------------------------------------------------------------------
76  
77      /** @component */
78      private VelocityComponent velocity;
79  
80      /** @component */
81      private ArtifactResolver artifactResolver;
82  
83      /** @component */
84      private InputHandler inputHandler;
85  
86      /** @component */
87      private ArtifactFactory artifactFactory;
88  
89  
90      /** @component */
91      private MavenSettingsBuilder settingsBuilder;
92      private static final int MESSAGE_LINE_LENGTH = 80;
93  
94  
95      public void createArchetype(String archetypeGroupId, String archetypeArtifactId, String archetypeVersion,
96                                  ArtifactRepository localRepository, List remoteRepositories, Map parameters)
97              throws ArchetypeNotFoundException, ArchetypeDescriptorException, ArchetypeTemplateProcessingException
98      {
99  
100         // ---------------------------------------------------------------------
101         //locate the archetype file
102         // ---------------------------------------------------------------------
103         Artifact archetypeArtifact = artifactFactory.createArtifact(archetypeGroupId, archetypeArtifactId,
104                 archetypeVersion, Artifact.SCOPE_RUNTIME, "jar");
105 
106         try
107         {
108             artifactResolver.resolve(archetypeArtifact, remoteRepositories, localRepository);
109         }
110         catch (ArtifactResolutionException e)
111         {
112             throw new ArchetypeDescriptorException("Error attempting to download archetype: " + e.getMessage(), e);
113         }
114 
115         // ----------------------------------------------------------------------
116         // Load the archetype descriptor
117         // ----------------------------------------------------------------------
118 
119         BobberArchetypeXpp3Reader builder = new BobberArchetypeXpp3Reader();
120 
121         com.javaforge.bobber.archetype.model.BobberArchetype archetype;
122 
123         JarFile archetypeJarFile;
124 
125         try
126         {
127 
128             archetypeJarFile = new JarFile(archetypeArtifact.getFile());
129 
130             final ZipEntry zipEntry = archetypeJarFile.getEntry(ARCHETYPE_DESCRIPTOR);
131             InputStream is = archetypeJarFile.getInputStream(zipEntry);
132 
133             if (is == null)
134             {
135                 throw new ArchetypeDescriptorException(
136                         "The " + ARCHETYPE_DESCRIPTOR + " descriptor cannot be found.");
137             }
138 
139             archetype = builder.read(new InputStreamReader(is));
140 
141             archetypeJarFile.close();
142         }
143         catch (IOException e)
144         {
145             throw new ArchetypeDescriptorException("Error reading the " + ARCHETYPE_DESCRIPTOR + " descriptor.", e);
146         }
147         catch (XmlPullParserException e)
148         {
149             throw new ArchetypeDescriptorException("Error reading the " + ARCHETYPE_DESCRIPTOR + " descriptor.", e);
150         }
151 
152         // ----------------------------------------------------------------------
153         //
154         // ----------------------------------------------------------------------
155 
156         String basedir = (String) parameters.get("basedir");
157 
158         String artifactId = (String) parameters.get("artifactId");
159 
160         File pomFile = new File(basedir, ARCHETYPE_POM);
161 
162         File outputDirectoryFile;
163 
164         if (pomFile.exists() && archetype.isAllowPartial())
165         {
166             outputDirectoryFile = new File(basedir);
167         }
168         else
169         {
170             outputDirectoryFile = new File(basedir, artifactId);
171 
172             // TODO temporarily allow partial generation, remove it later
173             if (!archetype.isAllowPartial() &&
174                     outputDirectoryFile.exists() &&
175                     outputDirectoryFile.listFiles().length > 0)
176             {
177                 throw new ArchetypeTemplateProcessingException(
178                         outputDirectoryFile.getName() + " already exists - please run from a clean directory");
179             }
180 
181             outputDirectoryFile.mkdir();
182 
183         }
184 
185         String outputDirectory = outputDirectoryFile.getAbsolutePath();
186 
187         // ----------------------------------------------------------------------
188         // Set up the Velocity context
189         // ----------------------------------------------------------------------
190 
191         VelocityContext context = new VelocityContext();
192 
193         String packageName = (String) parameters.get("package");
194 
195         addParamToContext("package", packageName, context);
196         addParamToContext("packagePath", StringUtils.replace(packageName, ".", "/"), context);
197 
198         for (Iterator iterator = parameters.keySet().iterator(); iterator.hasNext();)
199         {
200             String key = (String) iterator.next();
201 
202             Object value = parameters.get(key);
203             addParamToContext(key, value, context);
204         }
205 
206         //add in the specified system properties
207         //if this were a mojo could have set the settings using the ${settings} expression. Since it is not need to get it from the settings builder
208 
209 
210         boolean inInteractiveMode = false;
211         try
212         {
213             inInteractiveMode = settingsBuilder.buildSettings().getInteractiveMode().booleanValue();
214             //TODO there must be a cleaner way of doing this
215             String temp = System.getProperty("interactive", null);
216             if (temp != null)
217             {
218                 inInteractiveMode = Boolean.valueOf(temp).booleanValue();
219             }
220             getLogger().info("Interactive is: " + inInteractiveMode);
221         }
222         catch (Exception ie)
223         {
224             throw new ArchetypeTemplateProcessingException("unable to read settings ", ie);
225         }
226         if (inInteractiveMode)
227         {
228             getLogger().info("Please enter the values for the following archetype variables:");
229         }
230 
231 
232         final List variables = archetype.getVariables();
233         processVariables(variables.iterator(), context, inInteractiveMode);
234 
235         // ---------------------------------------------------------------------
236         // Get Logger and display all parameters used
237         // ---------------------------------------------------------------------
238         if (getLogger().isInfoEnabled())
239         {
240             Object[] keys = context.getKeys();
241             if (keys.length > 0)
242             {
243                 getLogger().info("----------------------------------------------------------------------------");
244 
245                 getLogger().info("Using following parameters for creating Archetype: " + archetypeArtifactId + ":" +
246                         archetypeVersion);
247 
248                 getLogger().info("----------------------------------------------------------------------------");
249 
250                 for (int i = 0; i < keys.length; i++)
251                 {
252 
253                     String parameterName = (String) keys[i];
254 
255                     Object parameterValue = context.get(parameterName);
256 
257                     getLogger().info("Parameter: " + parameterName + " = " + parameterValue);
258                 }
259             }
260             else
261             {
262                 getLogger().info("No Parameters found for creating Archetype");
263             }
264         }
265 
266         // ----------------------------------------------------------------------
267         // Extract the archetype to the chosen directory
268         // ----------------------------------------------------------------------
269 
270         try
271         {
272             archetypeJarFile = new JarFile(archetypeArtifact.getFile());
273             Enumeration entries = archetypeJarFile.entries();
274 
275             while (entries.hasMoreElements())
276             {
277                 JarEntry entry = (JarEntry) entries.nextElement();
278                 String path = entry.getName();
279                 if (!path.startsWith(ARCHETYPE_RESOURCES) || path.endsWith(".vm"))
280                 {
281                     continue;
282                 }
283 
284                 File t = new File(outputDirectory, path.substring(19));
285                 if (entry.isDirectory())
286                 {
287                     // Assume directories are stored parents first then children.
288                     getLogger().debug("Extracting directory: " + entry.getName() + " to " + t.getAbsolutePath());
289                     t.mkdir();
290                     continue;
291                 }
292 
293                 getLogger().debug("Extracting file: " + entry.getName() + " to " + t.getAbsolutePath());
294                 t.createNewFile();
295                 IOUtil.copy(archetypeJarFile.getInputStream(entry), new FileOutputStream(t));
296             }
297 
298             archetypeJarFile.close();
299 
300             //remove the archetype descriptor
301             File t = new File(outputDirectory, ARCHETYPE_DESCRIPTOR);
302             t.delete();
303         }
304         catch (IOException ioe)
305         {
306             throw new ArchetypeTemplateProcessingException("Error extracting archetype", ioe);
307         }
308 
309         // ----------------------------------------------------------------------
310         // Process the templates
311         // ----------------------------------------------------------------------
312 
313         // use the out of the box codehaus velocity component that loads templates
314         //from the class path
315         ClassLoader old = Thread.currentThread().getContextClassLoader();
316         try
317         {
318             URL[] urls = new URL[1];
319             urls[0] = archetypeArtifact.getFile().toURI().toURL();
320             URLClassLoader archetypeJarLoader = new URLClassLoader(urls);
321 
322             Thread.currentThread().setContextClassLoader(archetypeJarLoader);
323             for (Iterator i = archetype.getTemplates().iterator(); i.hasNext();)
324             {
325                 final Template template = (Template) i.next();
326 
327                 // Check the optional 'condition' property on the template.
328                 // If present and the variable it points to is 'true', then
329                 // continue processing. If it's false, then skip.
330                 // If condition is not specified, assume the template should
331                 // be processed.
332                 boolean shouldProcess = true;
333                 String condition = template.getDependsOnVar();
334                 String requiredValue = null;
335                 List options = new ArrayList();
336                 if (StringUtils.isNotEmpty(condition))
337                 {
338                     //Crappy logic processing -- for now
339                     boolean not = false;
340                     //Allow very simple matching logic to match templates against variable values
341                     int x = condition.indexOf("!=");
342                     getLogger().debug("Processing Condition : " + condition);
343                     if (x > -1)
344                     {
345                         not = true;
346                         requiredValue = condition.substring(x + 2).trim();
347                         options = getListOfValues(requiredValue);
348                         condition = condition.substring(0, x).trim();
349                     }
350                     else
351                     {
352                         x = condition.indexOf("=");
353                         if (x > -1)
354                         {
355                             requiredValue = condition.substring(x + 1);
356                             options = getListOfValues(requiredValue);
357                             condition = condition.substring(0, x);
358                         }
359                     }
360                     getLogger().debug("Not Expr: " + not);
361                     getLogger().debug("Condition Value: '" + condition + "'");
362                     getLogger().debug("Required Value: '" + requiredValue + "'");
363                     final Variable var = (Variable) findVariable(condition, variables);
364                     if (var != null)
365                     {
366                         final String strValue = (String) context.get(var.getName());
367                         getLogger().debug("Variable Value is: '" + strValue + "'");
368                         if (requiredValue == null)
369                         {
370                             if (!Boolean.valueOf(strValue).booleanValue())
371                             {
372                                 shouldProcess = false;
373                             }
374                         }
375                         else
376                         {
377                             if (!options.contains(strValue))
378                             {
379                                 shouldProcess = false;
380                             }
381                         }
382 
383                     }
384                     else
385                     {
386                         getLogger().debug("Variable Value is: null");
387                         shouldProcess = false;
388                     }
389                     if (not)
390                     {
391                         shouldProcess = !shouldProcess;
392                     }
393                 }
394 
395                 if (shouldProcess)
396                 {
397                     processTemplate(template, outputDirectory, context);
398                 }
399                 else
400                 {
401                     getLogger().debug("Condition not met, skipping " + template.getOutput());
402                 }
403             }
404 
405         }
406         catch (MalformedURLException mfe)
407         {
408             throw new ArchetypeTemplateProcessingException("Error loading archetype resources into the classpath", mfe);
409         }
410         finally
411         {
412             Thread.currentThread().setContextClassLoader(old);
413         }
414 
415         // ----------------------------------------------------------------------
416         // Log message on Archetype creation
417         // ----------------------------------------------------------------------
418         if (getLogger().isInfoEnabled())
419         {
420             getLogger().info("Archetype created in dir: " + outputDirectory);
421         }
422 
423     }
424 
425     protected void addParamToContext(String key, Object value, VelocityContext context)
426     {
427         getLogger().info("Adding Parameter to template Context: " + key + "=" + value);
428         context.put(key, value);
429 
430     }
431 
432     protected void processVariables(Iterator variables, VelocityContext context, final boolean interactiveMode) throws ArchetypeTemplateProcessingException
433     {
434         while (variables.hasNext())
435         {
436             Variable var = (Variable) variables.next();
437             String val = System.getProperty(var.getName(), var.getDefvalue());
438 
439             if (interactiveMode)
440             {
441 
442                 StringBuffer message = new StringBuffer();
443                 message.append(var.getName()).append(": ")
444                         .append(NEW_LINE)
445                         .append(StringUtils.repeat("*", MESSAGE_LINE_LENGTH))
446                         .append(NEW_LINE)
447                         .append(NEW_LINE)
448                         .append(StringUtils.center(var.getDescription(), MESSAGE_LINE_LENGTH))
449                         .append(NEW_LINE)
450                         .append(StringUtils.leftPad("[default: " + val + "]", MESSAGE_LINE_LENGTH))
451                         .append(NEW_LINE)
452                         .append(StringUtils.repeat("*", MESSAGE_LINE_LENGTH));
453                 getLogger().info(message.toString());
454                 try
455                 {
456                     String answer = inputHandler.readLine();
457                     if (!StringUtils.isEmpty(answer))
458                     {
459                         val = answer;
460                     }
461                 }
462                 catch (IOException ie)
463                 {
464                     throw new ArchetypeTemplateProcessingException(ie);
465                 }
466                 context.put(var.getName(), val);
467             }
468             else
469             {
470                 context.put(var.getName(), val);
471             }
472 
473             if (val.toLowerCase().equals("false") || val.toLowerCase().equals("n"))
474             {
475                 if (var.getVariables() != null)
476                 {
477                     //keep processing the variables picking up the default values
478                     processVariables(var.getVariables().iterator(), context, false);
479 
480                 }
481             }
482             else if (var.getVariables() != null)
483             {
484                 //keep processing the variables in the current interaction mode
485                 processVariables(var.getVariables().iterator(), context, interactiveMode);
486 
487             }
488         }
489 
490     }
491 
492     protected List getListOfValues(String s)
493     {
494         List options = new ArrayList();
495         for (StringTokenizer stringTokenizer = new StringTokenizer(s, "|"); stringTokenizer.hasMoreTokens();)
496         {
497             options.add(stringTokenizer.nextToken());
498         }
499         return options;
500     }
501 
502     protected void processTemplate(Template template, String outputDirectory, VelocityContext context)
503             throws ArchetypeTemplateProcessingException
504     {
505         File outFile;
506 
507 
508         try
509         {
510             StringWriter wout = new StringWriter();
511 
512             velocity.getEngine().evaluate(context, wout, "output value", template.getOutput());
513             outFile = new File(outputDirectory, wout.toString());
514             getLogger().debug(outFile.getAbsolutePath());
515             FileUtils.forceMkdir(outFile.getParentFile());
516             getLogger().debug("Created directory: " + outFile.getParentFile() + ", Dir exists = " + outFile.getParentFile().exists());
517 
518         }
519         catch (Exception e)
520         {
521             e.printStackTrace();
522             throw new ArchetypeTemplateProcessingException("error evaluating output file name " + template.getOutput(), e);
523         }
524 
525 
526         Writer writer = null;
527         try
528         {
529             getLogger().info("Processing Template: " + template.getFile());
530             String templateLocation = ARCHETYPE_RESOURCES + "/" + template.getFile();
531 
532             writer = new FileWriter(outFile);
533             velocity.getEngine().mergeTemplate(templateLocation, context, writer);
534             writer.flush();
535 
536         }
537         catch (Exception e)
538         {
539             throw new ArchetypeTemplateProcessingException("Error merging velocity templates", e);
540         }
541         finally
542         {
543             IOUtil.close(writer);
544             getLogger().info("Written Template to: " + outFile + ", file exists = " + outFile.exists());
545         }
546 
547         // Delete archetype-originated folders in case the output path is also templated.
548         // Otherwise, there will be a processed folder AND the original folder.
549         try
550         {
551             final File templateFile = new File(outputDirectory, template.getFile());
552             final String templateDir = FileUtils.dirname(templateFile.getCanonicalPath());
553             final String outputDir = FileUtils.dirname(outFile.getCanonicalPath());
554             if (getLogger().isDebugEnabled())
555             {
556                 getLogger().debug("TemplateDir=" + templateDir);
557                 getLogger().debug("OutputDir=" + outputDir);
558             }
559             if (!outputDir.startsWith(templateDir))
560             {
561                 getLogger().debug("Deleting Template Dir:" + templateDir);
562                 FileUtils.forceDelete(templateDir);
563             }
564         }
565         catch (IOException e)
566         {
567             throw new ArchetypeTemplateProcessingException("Failed to cleanup the working dir.", e);
568         }
569     }
570 
571 
572     /**
573      * Find the  variable.
574      *
575      * @param variableName name
576      * @param variables    all variables of the artifact
577      * @return variable value or null of not found
578      */
579     protected Object findVariable(String variableName, List variables)
580     {
581 
582         for (int i = 0; i < variables.size(); i++)
583         {
584             Variable var = (Variable) variables.get(i);
585             if (variableName.equals(var.getName()))
586             {
587                 return var;
588             }
589             else if (var.getVariables() != null)
590             {
591                 Object o = findVariable(variableName, var.getVariables());
592                 if (o != null)
593                 {
594                     return o;
595                 }
596             }
597         }
598 
599         return null;
600     }
601 
602 }
603 
604 
605