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      private static final String NEW_LINE = System.getProperty("line.separator");
71  
72      // ----------------------------------------------------------------------
73      // Components
74      // ----------------------------------------------------------------------
75  
76      /**
77       * @component
78       */
79      private VelocityComponent velocity;
80  
81      /**
82       * @component
83       */
84      private ArtifactResolver artifactResolver;
85  
86      /**
87       * @component
88       */
89      private InputHandler inputHandler;
90  
91      /**
92       * @component
93       */
94      private ArtifactFactory artifactFactory;
95  
96  
97      /**
98       * @component
99       */
100     private MavenSettingsBuilder settingsBuilder;
101     private static final int MESSAGE_LINE_LENGTH = 80;
102 
103 
104     public void createArchetype (String archetypeGroupId, String archetypeArtifactId, String archetypeVersion,
105                                  ArtifactRepository localRepository, List remoteRepositories, Map parameters)
106             throws ArchetypeNotFoundException, ArchetypeDescriptorException, ArchetypeTemplateProcessingException {
107 
108         // ---------------------------------------------------------------------
109         //locate the archetype file
110         // ---------------------------------------------------------------------
111         Artifact archetypeArtifact = artifactFactory.createArtifact(archetypeGroupId, archetypeArtifactId,
112                 archetypeVersion, Artifact.SCOPE_RUNTIME, "jar");
113 
114         try {
115             artifactResolver.resolve(archetypeArtifact, remoteRepositories, localRepository);
116         } catch (ArtifactResolutionException e) {
117             throw new ArchetypeDescriptorException("Error attempting to download archetype: " + e.getMessage(), e);
118         }
119 
120         // ----------------------------------------------------------------------
121         // Load the archetype descriptor
122         // ----------------------------------------------------------------------
123 
124         BobberArchetypeXpp3Reader builder = new BobberArchetypeXpp3Reader();
125 
126         com.javaforge.bobber.archetype.model.BobberArchetype archetype;
127 
128         JarFile archetypeJarFile;
129 
130         try {
131 
132             archetypeJarFile = new JarFile(archetypeArtifact.getFile());
133 
134             final ZipEntry zipEntry = archetypeJarFile.getEntry(ARCHETYPE_DESCRIPTOR);
135             InputStream is = archetypeJarFile.getInputStream(zipEntry);
136 
137             if (is == null) {
138                 throw new ArchetypeDescriptorException(
139                         "The " + ARCHETYPE_DESCRIPTOR + " descriptor cannot be found.");
140             }
141 
142             archetype = builder.read(new InputStreamReader(is));
143 
144             archetypeJarFile.close();
145         } catch (IOException e) {
146             throw new ArchetypeDescriptorException("Error reading the " + ARCHETYPE_DESCRIPTOR + " descriptor.", e);
147         } catch (XmlPullParserException e) {
148             throw new ArchetypeDescriptorException("Error reading the " + ARCHETYPE_DESCRIPTOR + " descriptor.", e);
149         }
150 
151         // ----------------------------------------------------------------------
152         //
153         // ----------------------------------------------------------------------
154 
155         String basedir = (String) parameters.get("basedir");
156 
157         String artifactId = (String) parameters.get("artifactId");
158 
159         File pomFile = new File(basedir, ARCHETYPE_POM);
160 
161         File outputDirectoryFile;
162 
163         if (pomFile.exists() && archetype.isAllowPartial()) {
164             outputDirectoryFile = new File(basedir);
165         } else {
166             outputDirectoryFile = new File(basedir, artifactId);
167 
168             // TODO temporarily allow partial generation, remove it later
169             if (!archetype.isAllowPartial() &&
170                     outputDirectoryFile.exists() &&
171                     outputDirectoryFile.listFiles().length > 0) {
172                 throw new ArchetypeTemplateProcessingException(
173                         outputDirectoryFile.getName() + " already exists - please run from a clean directory");
174             }
175 
176             outputDirectoryFile.mkdir();
177 
178         }
179 
180         String outputDirectory = outputDirectoryFile.getAbsolutePath();
181 
182         // ----------------------------------------------------------------------
183         // Set up the Velocity context
184         // ----------------------------------------------------------------------
185 
186         VelocityContext context = new VelocityContext();
187 
188         String packageName = (String) parameters.get("package");
189 
190         context.put("package", packageName);
191 
192         context.put("packagePath", StringUtils.replace(packageName, ".", "/"));
193 
194         for (Iterator iterator = parameters.keySet().iterator(); iterator.hasNext();) {
195             String key = (String) iterator.next();
196 
197             Object value = parameters.get(key);
198 
199             context.put(key, value);
200         }
201 
202         //add in the specified system properties
203         //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
204 
205 
206         boolean inInteractiveMode = false;
207         try {
208             inInteractiveMode = settingsBuilder.buildSettings().getInteractiveMode().booleanValue();
209         } catch (Exception ie) {
210             throw new ArchetypeTemplateProcessingException("unable to read settings ", ie);
211         }
212         if (inInteractiveMode) {
213             getLogger().info("Please enter the values for the following archetype variables:");
214         }
215 
216 
217         final List variables = archetype.getVariables();
218         processVariables(variables.iterator(), context, inInteractiveMode);
219 
220 
221         // ---------------------------------------------------------------------
222         // Get Logger and display all parameters used
223         // ---------------------------------------------------------------------
224         if (getLogger().isInfoEnabled()) {
225             Object[] keys = context.getKeys();
226             if (keys.length > 0) {
227                 getLogger().info("----------------------------------------------------------------------------");
228 
229                 getLogger().info("Using following parameters for creating Archetype: " + archetypeArtifactId + ":" +
230                         archetypeVersion);
231 
232                 getLogger().info("----------------------------------------------------------------------------");
233 
234                 for (int i = 0; i < keys.length; i++) {
235 
236                     String parameterName = (String) keys[i];
237 
238                     Object parameterValue = context.get(parameterName);
239 
240                     getLogger().info("Parameter: " + parameterName + " = " + parameterValue);
241                 }
242             } else {
243                 getLogger().info("No Parameters found for creating Archetype");
244             }
245         }
246 
247         // ----------------------------------------------------------------------
248         // Extract the archetype to the chosen directory
249         // ----------------------------------------------------------------------
250 
251         try {
252             archetypeJarFile = new JarFile(archetypeArtifact.getFile());
253             Enumeration entries = archetypeJarFile.entries();
254 
255             while (entries.hasMoreElements()) {
256                 JarEntry entry = (JarEntry) entries.nextElement();
257                 String path = entry.getName();
258                 if (!path.startsWith(ARCHETYPE_RESOURCES) || path.endsWith(".vm")) {
259                     continue;
260                 }
261 
262                 File t = new File(outputDirectory, path.substring(19));
263                 if (entry.isDirectory()) {
264                     // Assume directories are stored parents first then children.
265                     getLogger().debug("Extracting directory: " + entry.getName() + " to " + t.getAbsolutePath());
266                     t.mkdir();
267                     continue;
268                 }
269 
270                 getLogger().debug("Extracting file: " + entry.getName() + " to " + t.getAbsolutePath());
271                 t.createNewFile();
272                 IOUtil.copy(archetypeJarFile.getInputStream(entry), new FileOutputStream(t));
273             }
274 
275             archetypeJarFile.close();
276 
277             //remove the archetype descriptor
278             File t = new File(outputDirectory, ARCHETYPE_DESCRIPTOR);
279             t.delete();
280         } catch (IOException ioe) {
281             throw new ArchetypeTemplateProcessingException("Error extracting archetype", ioe);
282         }
283 
284         // ----------------------------------------------------------------------
285         // Process the templates
286         // ----------------------------------------------------------------------
287 
288         // use the out of the box codehaus velocity component that loads templates
289         //from the class path
290         ClassLoader old = Thread.currentThread().getContextClassLoader();
291         try {
292             URL[] urls = new URL[1];
293             urls[0] = archetypeArtifact.getFile().toURI().toURL();
294             URLClassLoader archetypeJarLoader = new URLClassLoader(urls);
295 
296             Thread.currentThread().setContextClassLoader(archetypeJarLoader);
297             for (Iterator i = archetype.getTemplates().iterator(); i.hasNext();) {
298                 final Template template = (Template) i.next();
299 
300                 // Check the optional 'condition' property on the template.
301                 // If present and the variable it points to is 'true', then
302                 // continue processing. If it's false, then skip.
303                 // If condition is not specified, assume the template should
304                 // be processed.
305                 boolean shouldProcess = true;
306                 String condition = template.getDependsOnVar();
307                 String requiredValue=null;
308                 List options = new ArrayList();
309                 if (StringUtils.isNotEmpty(condition)) {
310                     //Crappy logic processing -- for now
311                     boolean not=false;
312                     //Allow very simple matching logic to match templates against variable values
313                     int x = condition.indexOf("!=");
314                     getLogger().debug("Processing Condition : " + condition);                                        
315                     if(x > -1) {
316                         not=true;
317                         requiredValue = condition.substring(x+2).trim();
318                         options = getListOfValues(requiredValue);
319                         condition = condition.substring(0, x).trim();
320                     }
321                     else {
322                         x = condition.indexOf("=");
323                         if(x > -1) {
324                             requiredValue = condition.substring(x+1);
325                             options = getListOfValues(requiredValue);
326                             condition = condition.substring(0, x);
327                         }
328                     }
329                     getLogger().debug("Not Expr: " + not);
330                     getLogger().debug("Condition Value: '" + condition + "'");
331                     getLogger().debug("Required Value: '" + requiredValue + "'");
332                     final Variable var = (Variable) findVariable(condition, variables);
333                     if (var != null) {
334                         final String strValue = (String) context.get(var.getName());
335                         getLogger().debug("Variable Value is: '" + strValue + "'");
336                         if(requiredValue==null)
337                         {
338                             if (!Boolean.valueOf(strValue).booleanValue()) {
339                                 shouldProcess = false;
340                             }
341                         } else {
342                             if(!options.contains(strValue))
343                             {
344                                 shouldProcess = false;
345                             }
346                         }
347 
348                     } else {
349                         getLogger().debug("Variable Value is: null");                                                
350                         shouldProcess=false;
351                     }
352                     if(not) {
353                         shouldProcess = !shouldProcess;
354                     }
355                 }
356 
357                 if (shouldProcess) {
358                     processTemplate(template, outputDirectory, context);
359                 } else {
360                     getLogger().debug("Condition not met, skipping " + template.getOutput());
361                 }
362             }
363 
364         }
365         catch (MalformedURLException mfe) {
366             throw new ArchetypeTemplateProcessingException("Error loading archetype resources into the classpath", mfe);
367         }
368         finally {
369             Thread.currentThread().setContextClassLoader(old);
370         }
371 
372         // ----------------------------------------------------------------------
373         // Log message on Archetype creation
374         // ----------------------------------------------------------------------
375         if (getLogger().isInfoEnabled()) {
376             getLogger().info("Archetype created in dir: " + outputDirectory);
377         }
378 
379     }
380 
381     protected void processVariables(Iterator variables, VelocityContext context, final boolean interactiveMode) throws ArchetypeTemplateProcessingException
382     {
383         while (variables.hasNext()) {
384             Variable var = (Variable) variables.next();
385             String val = System.getProperty(var.getName(), var.getDefvalue());
386 
387             if (interactiveMode) {
388 
389                     StringBuffer message = new StringBuffer();
390                     message.append(var.getName()).append(": ")
391                             .append(NEW_LINE)
392                             .append(StringUtils.repeat("*", MESSAGE_LINE_LENGTH))
393                             .append(NEW_LINE)
394                             .append(NEW_LINE)
395                             .append(StringUtils.center(var.getDescription(), MESSAGE_LINE_LENGTH))
396                             .append(NEW_LINE)
397                             .append(StringUtils.leftPad("[default: " + val + "]", MESSAGE_LINE_LENGTH))
398                             .append(NEW_LINE)
399                             .append(StringUtils.repeat("*", MESSAGE_LINE_LENGTH));
400                     getLogger().info(message.toString());
401                     try {
402                         String answer = inputHandler.readLine();
403                         if (!StringUtils.isEmpty(answer)) {
404                             val = answer;
405                         }
406                     } catch (IOException ie) {
407                         throw new ArchetypeTemplateProcessingException(ie);
408                     }
409                     context.put(var.getName(), val);
410             }
411             else
412             {
413                 context.put(var.getName(), val);
414             }
415 
416             if(val.toLowerCase().equals("false") || val.toLowerCase().equals("n"))
417             {
418                 if(var.getVariables() !=null)
419                 {
420                     //keep processing the variables picking up the default values
421                     processVariables(var.getVariables().iterator(), context, false);
422 
423                 }
424             } else if(var.getVariables() !=null)
425             {
426                 //keep processing the variables picking up the default values
427                 processVariables(var.getVariables().iterator(), context, true);
428 
429             }
430         }
431 
432     }
433 
434     protected List getListOfValues(String s) {
435         List options = new ArrayList();
436         for (StringTokenizer stringTokenizer = new StringTokenizer(s, "|"); stringTokenizer.hasMoreTokens();)
437         {
438             options.add(stringTokenizer.nextToken());
439         }
440         return options;
441     }
442 
443     protected void processTemplate (Template template, String outputDirectory, VelocityContext context)
444             throws ArchetypeTemplateProcessingException {
445         File outFile;
446 
447 
448         try {
449             StringWriter wout = new StringWriter();
450 
451             velocity.getEngine().evaluate(context, wout, "output value", template.getOutput());
452             outFile = new File(outputDirectory, wout.toString());
453             getLogger().debug(outFile.getAbsolutePath());
454             FileUtils.forceMkdir(outFile.getParentFile());
455             getLogger().debug("Created directory: " + outFile.getParentFile() + ", Dir exists = " + outFile.getParentFile().exists());
456 
457         } catch (Exception e) {
458             e.printStackTrace();
459             throw new ArchetypeTemplateProcessingException("error evaluating output file name " + template.getOutput(), e);
460         }
461 
462 
463         Writer writer = null;
464         try {
465             getLogger().info("Processing Template: " + template.getFile());
466             String templateLocation = ARCHETYPE_RESOURCES + "/" + template.getFile();
467 
468             writer = new FileWriter(outFile);
469             velocity.getEngine().mergeTemplate(templateLocation, context, writer);
470             writer.flush();
471 
472         } catch (Exception e) {
473             throw new ArchetypeTemplateProcessingException("Error merging velocity templates", e);
474         } finally {
475             IOUtil.close(writer);
476             getLogger().info("Written Template to: " + outFile + ", file exists = " + outFile.exists());
477         }
478 
479         // Delete archetype-originated folders in case the output path is also templated.
480         // Otherwise, there will be a processed folder AND the original folder.
481         try {
482             final File templateFile = new File(outputDirectory, template.getFile());
483             final String templateDir = FileUtils.dirname(templateFile.getCanonicalPath());
484             final String outputDir = FileUtils.dirname(outFile.getCanonicalPath());
485             if (getLogger().isDebugEnabled()) {
486                 getLogger().debug("TemplateDir=" + templateDir);
487                 getLogger().debug("OutputDir=" + outputDir);
488             }
489             if (!outputDir.startsWith(templateDir)) {
490                 getLogger().debug("Deleting Template Dir:" + templateDir);
491                 FileUtils.forceDelete(templateDir);
492             }
493         } catch (IOException e) {
494             throw new ArchetypeTemplateProcessingException("Failed to cleanup the working dir.", e);
495         }
496     }
497 
498 
499     /**
500      * Find the  variable.
501      * @param variableName name
502      * @param variables all variables of the artifact
503      * @return variable value or null of not found
504      */
505     protected Object findVariable (String variableName, List variables) {
506 
507         for (int i = 0; i < variables.size(); i++) {
508             Variable var = (Variable) variables.get(i);
509             if (variableName.equals(var.getName())) {
510                 return var;
511             } else if(var.getVariables()!=null) {
512                 Object o = findVariable(variableName, var.getVariables());
513                 if(o!=null) {
514                     return o;
515                 }
516             }
517         }
518 
519         return null;
520     }
521 
522 }
523 
524 
525