1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
64
65
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
75
76
77
78 private VelocityComponent velocity;
79
80
81 private ArtifactResolver artifactResolver;
82
83
84 private InputHandler inputHandler;
85
86
87 private ArtifactFactory artifactFactory;
88
89
90
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
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
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
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
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
207
208
209
210 boolean inInteractiveMode = false;
211 try
212 {
213 inInteractiveMode = settingsBuilder.buildSettings().getInteractiveMode().booleanValue();
214
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
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
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
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
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
311
312
313
314
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
328
329
330
331
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
339 boolean not = false;
340
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
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
478 processVariables(var.getVariables().iterator(), context, false);
479
480 }
481 }
482 else if (var.getVariables() != null)
483 {
484
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
548
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
574
575
576
577
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