001: package org.apache.velocity.texen.ant;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.io.Writer;
027: import java.util.Date;
028: import java.util.Iterator;
029: import java.util.StringTokenizer;
030:
031: import org.apache.commons.collections.ExtendedProperties;
032: import org.apache.tools.ant.BuildException;
033: import org.apache.tools.ant.Project;
034: import org.apache.tools.ant.Task;
035: import org.apache.velocity.VelocityContext;
036: import org.apache.velocity.app.VelocityEngine;
037: import org.apache.velocity.context.Context;
038: import org.apache.velocity.exception.MethodInvocationException;
039: import org.apache.velocity.exception.ParseErrorException;
040: import org.apache.velocity.exception.ResourceNotFoundException;
041: import org.apache.velocity.runtime.RuntimeConstants;
042: import org.apache.velocity.texen.Generator;
043: import org.apache.velocity.util.StringUtils;
044:
045: /**
046: * An ant task for generating output by using Velocity
047: *
048: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
049: * @author <a href="robertdonkin@mac.com">Robert Burrell Donkin</a>
050: * @version $Id: TexenTask.java 463298 2006-10-12 16:10:32Z henning $
051: */
052: public class TexenTask extends Task {
053: /**
054: * This message fragment (telling users to consult the log or
055: * invoke ant with the -debug flag) is appended to rethrown
056: * exception messages.
057: */
058: private final static String ERR_MSG_FRAGMENT = ". For more information consult the velocity log, or invoke ant "
059: + "with the -debug flag.";
060:
061: /**
062: * This is the control template that governs the output.
063: * It may or may not invoke the services of worker
064: * templates.
065: */
066: protected String controlTemplate;
067:
068: /**
069: * This is where Velocity will look for templates
070: * using the file template loader.
071: */
072: protected String templatePath;
073:
074: /**
075: * This is where texen will place all the output
076: * that is a product of the generation process.
077: */
078: protected String outputDirectory;
079:
080: /**
081: * This is the file where the generated text
082: * will be placed.
083: */
084: protected String outputFile;
085:
086: /**
087: * This is the encoding for the output file(s).
088: */
089: protected String outputEncoding;
090:
091: /**
092: * This is the encoding for the input file(s)
093: * (templates).
094: */
095: protected String inputEncoding;
096:
097: /**
098: * <p>
099: * These are properties that are fed into the
100: * initial context from a properties file. This
101: * is simply a convenient way to set some values
102: * that you wish to make available in the context.
103: * </p>
104: * <p>
105: * These values are not critical, like the template path
106: * or output path, but allow a convenient way to
107: * set a value that may be specific to a particular
108: * generation task.
109: * </p>
110: * <p>
111: * For example, if you are generating scripts to allow
112: * user to automatically create a database, then
113: * you might want the <code>$databaseName</code>
114: * to be placed
115: * in the initial context so that it is available
116: * in a script that might look something like the
117: * following:
118: * <code><pre>
119: * #!bin/sh
120: *
121: * echo y | mysqladmin create $databaseName
122: * </pre></code>
123: * The value of <code>$databaseName</code> isn't critical to
124: * output, and you obviously don't want to change
125: * the ant task to simply take a database name.
126: * So initial context values can be set with
127: * properties file.
128: */
129: protected ExtendedProperties contextProperties;
130:
131: /**
132: * Property which controls whether the classpath
133: * will be used when trying to locate templates.
134: */
135: protected boolean useClasspath;
136:
137: /**
138: * The LogFile (incl. path) to log to.
139: */
140: protected String logFile;
141:
142: /**
143: * Property which controls whether the resource
144: * loader will be told to cache. Default false
145: */
146:
147: protected String useResourceLoaderCache = "false";
148: /**
149: *
150: */
151: protected String resourceLoaderModificationCheckInterval = "2";
152:
153: /**
154: * [REQUIRED] Set the control template for the
155: * generating process.
156: * @param controlTemplate
157: */
158: public void setControlTemplate(String controlTemplate) {
159: this .controlTemplate = controlTemplate;
160: }
161:
162: /**
163: * Get the control template for the
164: * generating process.
165: * @return The current control template.
166: */
167: public String getControlTemplate() {
168: return controlTemplate;
169: }
170:
171: /**
172: * [REQUIRED] Set the path where Velocity will look
173: * for templates using the file template
174: * loader.
175: * @param templatePath
176: * @throws Exception
177: */
178:
179: public void setTemplatePath(String templatePath) throws Exception {
180: StringBuffer resolvedPath = new StringBuffer();
181: StringTokenizer st = new StringTokenizer(templatePath, ",");
182: while (st.hasMoreTokens()) {
183: // resolve relative path from basedir and leave
184: // absolute path untouched.
185: File fullPath = project.resolveFile(st.nextToken());
186: resolvedPath.append(fullPath.getCanonicalPath());
187: if (st.hasMoreTokens()) {
188: resolvedPath.append(",");
189: }
190: }
191: this .templatePath = resolvedPath.toString();
192:
193: System.out.println(templatePath);
194: }
195:
196: /**
197: * Get the path where Velocity will look
198: * for templates using the file template
199: * loader.
200: * @return The template path.
201: */
202: public String getTemplatePath() {
203: return templatePath;
204: }
205:
206: /**
207: * [REQUIRED] Set the output directory. It will be
208: * created if it doesn't exist.
209: * @param outputDirectory
210: */
211: public void setOutputDirectory(File outputDirectory) {
212: try {
213: this .outputDirectory = outputDirectory.getCanonicalPath();
214: } catch (java.io.IOException ioe) {
215: throw new BuildException(ioe);
216: }
217: }
218:
219: /**
220: * Get the output directory.
221: * @return The output directory.
222: */
223: public String getOutputDirectory() {
224: return outputDirectory;
225: }
226:
227: /**
228: * [REQUIRED] Set the output file for the
229: * generation process.
230: * @param outputFile
231: */
232: public void setOutputFile(String outputFile) {
233: this .outputFile = outputFile;
234: }
235:
236: /**
237: * Set the output encoding.
238: * @param outputEncoding
239: */
240: public void setOutputEncoding(String outputEncoding) {
241: this .outputEncoding = outputEncoding;
242: }
243:
244: /**
245: * Set the input (template) encoding.
246: * @param inputEncoding
247: */
248: public void setInputEncoding(String inputEncoding) {
249: this .inputEncoding = inputEncoding;
250: }
251:
252: /**
253: * Get the output file for the
254: * generation process.
255: * @return The output file.
256: */
257: public String getOutputFile() {
258: return outputFile;
259: }
260:
261: /**
262: * Sets the log file.
263: * @param log
264: */
265: public void setLogFile(String log) {
266: this .logFile = log;
267: }
268:
269: /**
270: * Gets the log file.
271: * @return The log file.
272: */
273: public String getLogFile() {
274: return this .logFile;
275: }
276:
277: /**
278: * Set the context properties that will be
279: * fed into the initial context be the
280: * generating process starts.
281: * @param file
282: */
283: public void setContextProperties(String file) {
284: String[] sources = StringUtils.split(file, ",");
285: contextProperties = new ExtendedProperties();
286:
287: // Always try to get the context properties resource
288: // from a file first. Templates may be taken from a JAR
289: // file but the context properties resource may be a
290: // resource in the filesystem. If this fails than attempt
291: // to get the context properties resource from the
292: // classpath.
293: for (int i = 0; i < sources.length; i++) {
294: ExtendedProperties source = new ExtendedProperties();
295:
296: try {
297: // resolve relative path from basedir and leave
298: // absolute path untouched.
299: File fullPath = project.resolveFile(sources[i]);
300: log("Using contextProperties file: " + fullPath);
301: source.load(new FileInputStream(fullPath));
302: } catch (IOException e) {
303: ClassLoader classLoader = this .getClass()
304: .getClassLoader();
305:
306: try {
307: InputStream inputStream = classLoader
308: .getResourceAsStream(sources[i]);
309:
310: if (inputStream == null) {
311: throw new BuildException(
312: "Context properties file "
313: + sources[i]
314: + " could not be found in the file system or on the classpath!");
315: } else {
316: source.load(inputStream);
317: }
318: } catch (IOException ioe) {
319: source = null;
320: }
321: }
322:
323: if (source != null) {
324: for (Iterator j = source.getKeys(); j.hasNext();) {
325: String name = (String) j.next();
326: String value = StringUtils.nullTrim(source
327: .getString(name));
328: contextProperties.setProperty(name, value);
329: }
330: }
331: }
332: }
333:
334: /**
335: * Get the context properties that will be
336: * fed into the initial context be the
337: * generating process starts.
338: * @return The current context properties.
339: */
340: public ExtendedProperties getContextProperties() {
341: return contextProperties;
342: }
343:
344: /**
345: * Set the use of the classpath in locating templates
346: *
347: * @param useClasspath true means the classpath will be used.
348: */
349: public void setUseClasspath(boolean useClasspath) {
350: this .useClasspath = useClasspath;
351: }
352:
353: /**
354: * @param useResourceLoaderCache
355: */
356: public void setUseResourceLoaderCache(String useResourceLoaderCache) {
357: this .useResourceLoaderCache = useResourceLoaderCache;
358: }
359:
360: /**
361: * @param resourceLoaderModificationCheckInterval
362: */
363: public void setResourceLoaderModificationCheckInterval(
364: String resourceLoaderModificationCheckInterval) {
365: this .resourceLoaderModificationCheckInterval = resourceLoaderModificationCheckInterval;
366: }
367:
368: /**
369: * Creates a VelocityContext.
370: *
371: * @return new Context
372: * @throws Exception the execute method will catch
373: * and rethrow as a <code>BuildException</code>
374: */
375: public Context initControlContext() throws Exception {
376: return new VelocityContext();
377: }
378:
379: /**
380: * Execute the input script with Velocity
381: *
382: * @throws BuildException
383: * BuildExceptions are thrown when required attributes are missing.
384: * Exceptions thrown by Velocity are rethrown as BuildExceptions.
385: */
386: public void execute() throws BuildException {
387: // Make sure the template path is set.
388: if (templatePath == null && useClasspath == false) {
389: throw new BuildException(
390: "The template path needs to be defined if you are not using "
391: + "the classpath for locating templates!");
392: }
393:
394: // Make sure the control template is set.
395: if (controlTemplate == null) {
396: throw new BuildException(
397: "The control template needs to be defined!");
398: }
399:
400: // Make sure the output directory is set.
401: if (outputDirectory == null) {
402: throw new BuildException(
403: "The output directory needs to be defined!");
404: }
405:
406: // Make sure there is an output file.
407: if (outputFile == null) {
408: throw new BuildException(
409: "The output file needs to be defined!");
410: }
411:
412: VelocityEngine ve = new VelocityEngine();
413:
414: try {
415: // Setup the Velocity Runtime.
416: if (templatePath != null) {
417: log("Using templatePath: " + templatePath,
418: Project.MSG_VERBOSE);
419: ve.setProperty(
420: RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
421: templatePath);
422: }
423:
424: if (useClasspath) {
425: log("Using classpath");
426: ve.addProperty(VelocityEngine.RESOURCE_LOADER,
427: "classpath");
428:
429: ve
430: .setProperty("classpath."
431: + VelocityEngine.RESOURCE_LOADER
432: + ".class",
433: "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
434:
435: ve.setProperty("classpath."
436: + VelocityEngine.RESOURCE_LOADER + ".cache",
437: useResourceLoaderCache);
438:
439: ve.setProperty("classpath."
440: + VelocityEngine.RESOURCE_LOADER
441: + ".modificationCheckInterval",
442: resourceLoaderModificationCheckInterval);
443: }
444:
445: if (this .logFile != null) {
446: ve.setProperty(RuntimeConstants.RUNTIME_LOG,
447: this .logFile);
448: }
449:
450: ve.init();
451:
452: // Create the text generator.
453: Generator generator = Generator.getInstance();
454: generator.setVelocityEngine(ve);
455: generator.setOutputPath(outputDirectory);
456: generator.setInputEncoding(inputEncoding);
457: generator.setOutputEncoding(outputEncoding);
458:
459: if (templatePath != null) {
460: generator.setTemplatePath(templatePath);
461: }
462:
463: // Make sure the output directory exists, if it doesn't
464: // then create it.
465: File file = new File(outputDirectory);
466: if (!file.exists()) {
467: file.mkdirs();
468: }
469:
470: String path = outputDirectory + File.separator + outputFile;
471: log("Generating to file " + path, Project.MSG_INFO);
472: Writer writer = generator.getWriter(path, outputEncoding);
473:
474: // The generator and the output path should
475: // be placed in the init context here and
476: // not in the generator class itself.
477: Context c = initControlContext();
478:
479: // Everything in the generator class should be
480: // pulled out and placed in here. What the generator
481: // class does can probably be added to the Velocity
482: // class and the generator class can probably
483: // be removed all together.
484: populateInitialContext(c);
485:
486: // Feed all the options into the initial
487: // control context so they are available
488: // in the control/worker templates.
489: if (contextProperties != null) {
490: Iterator i = contextProperties.getKeys();
491:
492: while (i.hasNext()) {
493: String property = (String) i.next();
494: String value = StringUtils
495: .nullTrim(contextProperties
496: .getString(property));
497:
498: // Now lets quickly check to see if what
499: // we have is numeric and try to put it
500: // into the context as an Integer.
501: try {
502: c.put(property, new Integer(value));
503: } catch (NumberFormatException nfe) {
504: // Now we will try to place the value into
505: // the context as a boolean value if it
506: // maps to a valid boolean value.
507: String booleanString = contextProperties
508: .testBoolean(value);
509:
510: if (booleanString != null) {
511: c.put(property, Boolean
512: .valueOf(booleanString));
513: } else {
514: // We are going to do something special
515: // for properties that have a "file.contents"
516: // suffix: for these properties will pull
517: // in the contents of the file and make
518: // them available in the context. So for
519: // a line like the following in a properties file:
520: //
521: // license.file.contents = license.txt
522: //
523: // We will pull in the contents of license.txt
524: // and make it available in the context as
525: // $license. This should make texen a little
526: // more flexible.
527: if (property.endsWith("file.contents")) {
528: // We need to turn the license file from relative to
529: // absolute, and let Ant help :)
530: value = StringUtils
531: .fileContentsToString(project
532: .resolveFile(value)
533: .getCanonicalPath());
534:
535: property = property
536: .substring(
537: 0,
538: property
539: .indexOf("file.contents") - 1);
540: }
541:
542: c.put(property, value);
543: }
544: }
545: }
546: }
547:
548: writer.write(generator.parse(controlTemplate, c));
549: writer.flush();
550: writer.close();
551: generator.shutdown();
552: cleanup();
553: } catch (BuildException e) {
554: throw e;
555: } catch (MethodInvocationException e) {
556: throw new BuildException("Exception thrown by '"
557: + e.getReferenceName() + "." + e.getMethodName()
558: + "'" + ERR_MSG_FRAGMENT, e.getWrappedThrowable());
559: } catch (ParseErrorException e) {
560: throw new BuildException("Velocity syntax error"
561: + ERR_MSG_FRAGMENT, e);
562: } catch (ResourceNotFoundException e) {
563: throw new BuildException("Resource not found"
564: + ERR_MSG_FRAGMENT, e);
565: } catch (Exception e) {
566: throw new BuildException("Generation failed"
567: + ERR_MSG_FRAGMENT, e);
568: }
569: }
570:
571: /**
572: * <p>Place useful objects into the initial context.</p>
573: *
574: * <p>TexenTask places <code>Date().toString()</code> into the
575: * context as <code>$now</code>. Subclasses who want to vary the
576: * objects in the context should override this method.</p>
577: *
578: * <p><code>$generator</code> is not put into the context in this
579: * method.</p>
580: *
581: * @param context The context to populate, as retrieved from
582: * {@link #initControlContext()}.
583: *
584: * @throws Exception Error while populating context. The {@link
585: * #execute()} method will catch and rethrow as a
586: * <code>BuildException</code>.
587: */
588: protected void populateInitialContext(Context context)
589: throws Exception {
590: context.put("now", new Date().toString());
591: }
592:
593: /**
594: * A hook method called at the end of {@link #execute()} which can
595: * be overridden to perform any necessary cleanup activities (such
596: * as the release of database connections, etc.). By default,
597: * does nothing.
598: *
599: * @exception Exception Problem cleaning up.
600: */
601: protected void cleanup() throws Exception {
602: }
603: }
|