KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > ext > ant > FreemarkerXmlTask


1 /*
2  * Copyright (c) 2003 The Visigoth Software Society. All rights
3  * reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in
14  * the documentation and/or other materials provided with the
15  * distribution.
16  *
17  * 3. The end-user documentation included with the redistribution, if
18  * any, must include the following acknowledgement:
19  * "This product includes software developed by the
20  * Visigoth Software Society (http://www.visigoths.org/)."
21  * Alternately, this acknowledgement may appear in the software itself,
22  * if and wherever such third-party acknowledgements normally appear.
23  *
24  * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25  * project contributors may be used to endorse or promote products derived
26  * from this software without prior written permission. For written
27  * permission, please contact visigoths@visigoths.org.
28  *
29  * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30  * nor may "FreeMarker" or "Visigoth" appear in their names
31  * without prior written permission of the Visigoth Software Society.
32  *
33  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36  * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44  * SUCH DAMAGE.
45  * ====================================================================
46  *
47  * This software consists of voluntary contributions made by many
48  * individuals on behalf of the Visigoth Software Society. For more
49  * information on the Visigoth Software Society, please see
50  * http://www.visigoths.org/
51  */

52
53 package freemarker.ext.ant;
54
55 import java.io.*;
56 import java.util.*;
57
58 import org.w3c.dom.*;
59 import org.xml.sax.SAXParseException JavaDoc;
60 import javax.xml.parsers.DocumentBuilder JavaDoc;
61 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
62 import javax.xml.parsers.ParserConfigurationException JavaDoc;
63
64 import org.apache.tools.ant.BuildException;
65 import org.apache.tools.ant.DirectoryScanner;
66 import org.apache.tools.ant.Project;
67 import org.apache.tools.ant.taskdefs.MatchingTask;
68 import freemarker.ext.xml.NodeListModel;
69 import freemarker.ext.dom.NodeModel;
70 import freemarker.template.utility.ClassUtil;
71 import freemarker.template.utility.SecurityUtilities;
72 import freemarker.template.*;
73
74
75 /**
76  * <p>This is an <a HREF="http://jakarta.apache.org/ant/" target="_top">Ant</a> task for transforming
77  * XML documents using FreeMarker templates. It uses the adapter class
78  * {@link NodeListModel}. It will read a set of XML documents, and pass them to
79  * the template for processing, building the corresponding output files in the
80  * destination directory.</p>
81  * <p>It makes the following variables available to the template in the data model:</p>
82  * <ul>
83  * <li><tt>document</tt>: <em>Deprecated!</em> The DOM tree of the currently processed XML file wrapped
84       with the legacy {@link freemarker.ext.xml.NodeListModel}.
85       For new projects you should use the <tt>.node</tt> instead, which initially
86       contains the DOM Document wrapped with {@link freemarker.ext.dom.NodeModel}.</li>
87  * <li><tt>properties</tt>: a {@link freemarker.template.SimpleHash} containing
88  * properties of the project that executes the task</li>
89  * <li><tt>userProperties</tt>: a {@link freemarker.template.SimpleHash} containing
90  * user properties of the project that executes the task</li>
91  * <li><tt>project</tt>: the DOM tree of the XML file specified by the
92  * <tt>projectfile</tt>. It will not be available if you didn't specify the
93  * <tt>projectfile</tt> attribute.</li>
94  * <li>further custom models can be instantiated and made available to the
95  * templates using the <tt>models</tt> attribute.</li>
96  * </ul>
97  * <p>It supports the following attributes:</p>
98  * <table border="1" cellpadding="2" cellspacing="0">
99  * <tr>
100  * <th valign="top" align="left">Attribute</th>
101  * <th valign="top" align="left">Description</th>
102  * <th valign="top">Required</th>
103  * </tr>
104  * <tr>
105  * <td valign="top">basedir</td>
106  * <td valign="top">location of the XML files. Defaults to the project's
107  * basedir.</td>
108  * <td align="center" valign="top">No</td>
109  * </tr>
110  * <tr>
111  * <td valign="top">destdir</td>
112  * <td valign="top">location to store the generated files.</td>
113  * <td align="center" valign="top">Yes</td>
114  * </tr>
115  * <tr>
116  * <td valign="top">includes</td>
117  * <td valign="top">comma-separated list of patterns of files that must be
118  * included; all files are included when omitted.</td>
119  * <td valign="top" align="center">No</td>
120  * </tr>
121  * <tr>
122  * <td valign="top">includesfile</td>
123  * <td valign="top">the name of a file that contains
124  * include patterns.</td>
125  * <td valign="top" align="center">No</td>
126  * </tr>
127  * <tr>
128  * <td valign="top">excludes</td>
129  * <td valign="top">comma-separated list of patterns of files that must be
130  * excluded; no files (except default excludes) are excluded when omitted.</td>
131  * <td valign="top" align="center">No</td>
132  * </tr>
133  * <tr>
134  * <td valign="top">excludesfile</td>
135  * <td valign="top">the name of a file that contains
136  * exclude patterns.</td>
137  * <td valign="top" align="center">No</td>
138  * </tr>
139  * <tr>
140  * <td valign="top">defaultexcludes</td>
141  * <td valign="top">indicates whether default excludes should be used
142  * (<code>yes</code> | <code>no</code>); default excludes are used when omitted.</td>
143  * <td valign="top" align="center">No</td>
144  * </tr>
145  * <tr>
146  * <td valign="top">extension</td>
147  * <td valign="top">extension of generated files. Defaults to .html.</td>
148  * <td valign="top" align="center">No</td>
149  * </tr>
150  * <tr>
151  * <td valign="top">template</td>
152  * <td valign="top">name of the FreeMarker template file that will be
153  * applied by default to XML files</td>
154  * <td valign="top" align="center">No</td>
155  * </tr>
156  * <tr>
157  * <td valign="top">templateDir</td>
158  * <td valign="top">location of the FreeMarker template(s) to be used, defaults
159  * to the project's baseDir</td>
160  * <td valign="top" align="center">No</td>
161  * </tr>
162  * <tr>
163  * <td valign="top">projectfile</td>
164  * <td valign="top">path to the project file. The poject file must be an XML file.
165  * If omitted, it will not be available to templates </td>
166  * <td valign="top" align="center">No</td>
167  * </tr>
168  * <tr>
169  * <td valign="top">incremental</td>
170  * <td valign="top">indicates whether all files should be regenerated (no), or
171  * only those that are older than the XML file, the template file, or the
172  * project file (yes). Defaults to yes. </td>
173  * <td valign="top" align="center">No</td>
174  * </tr>
175  * <tr>
176  * <td valign="top">encoding</td>
177  * <td valign="top">The encoding of the output files. Defaults to platform
178  * default encoding.</td>
179  * <td valign="top" align="center">No</td>
180  * </tr>
181  * <tr>
182  * <td valign="top">templateEncoding</td>
183  * <td valign="top">The encoding of the template files. Defaults to platform
184  * default encoding.</td>
185  * <td valign="top" align="center">No</td>
186  * </tr>
187  * <tr>
188  * <td valign="top">validation</td>
189  * <td valign="top">Whether to validate the XML input. Defaults to off.</td>
190  * <td valign="top" align="center">No</td>
191  * </tr>
192  * <tr>
193  * <td valign="top">models</td>
194  * <td valign="top">A list of [name=]className pairs separated by spaces,
195  * commas, or semicolons that specifies further models that should be
196  * available to templates. If name is omitted, the unqualified class name
197  * is used as the name. Every class that is specified must implement the
198  * TemplateModel interface and have a no-args constructor.</td>
199  * <td valign="top" align="center">No</td>
200  * </tr>
201  * </table>
202  *
203  * <p>It supports the following nesed elements:</p>
204  * <table border="1" cellpadding="2" cellspacing="0">
205  * <tr>
206  * <th valign="top" align="left">Element</th>
207  * <th valign="top" align="left">Description</th>
208  * <th valign="top">Required</th>
209  * </tr>
210  * <tr>
211  * <td valign="top">prepareModel</td>
212  * <td valign="top">
213  * This element executes Jython script before the processing of each XML
214  * files, that you can use to modify the data model.
215  * You either enter the Jython script directly nested into this
216  * element, or specify a Jython script file with the <tt>file</tt>
217  * attribute.
218  * The following variables are added to the Jython runtime's local
219  * namespace before the script is invoked:
220  * <ul>
221  * <li><tt>model</tt>: The data model as <code>java.util.HashMap</code>.
222  * You can read and modify the data model with this variable.
223  * <li><tt>doc</tt>: The XML document as <code>org.w3c.dom.Document</code>.
224  * <li><tt>project</tt>: The project document (if used) as
225  * <code>org.w3c.dom.Document</code>.
226  * </ul>
227  * <i>If this element is used, Jython classes (tried with Jython 2.1)
228  * must be available.</i>
229  * </td>
230  * <td valign="top" align="center">No</td>
231  * </tr>
232  * <tr>
233  * <td valign="top">prepareEnvironment</td>
234  * <td valign="top">This element executes Jython script before the processing
235  * of each XML files, that you can use to modify the freemarker environment
236  * ({@link freemarker.core.Environment}). The script is executed after the
237  * <tt>prepareModel</tt> element. The accessible Jython variables are the
238  * same as with the <tt>prepareModel</tt> element, except that there is no
239  * <tt>model</tt> variable, but there is <tt>env</tt> variable, which is
240  * the FreeMarker environment ({@link freemarker.core.Environment}).
241  * <i>If this element is used, Jython classes (tried with Jython 2.1)
242  * must be available.</i>
243  * </td>
244  * <td valign="top" align="center">No</td>
245  * </tr>
246  * </table>
247  *
248  * @author Attila Szegedi
249  * @author Jonathan Revusky, jon@revusky.com
250  * @version $Id: FreemarkerXmlTask.java,v 1.58 2004/11/11 20:34:35 ddekany Exp $
251  */

252 public class FreemarkerXmlTask
253 extends
254     MatchingTask
255 {
256     private JythonAntTask prepareModel;
257     private JythonAntTask prepareEnvironment;
258     private final DocumentBuilderFactory JavaDoc builderFactory;
259     private DocumentBuilder JavaDoc builder;
260     
261     /** the {@link Configuration} used by this task. */
262     private Configuration cfg = new Configuration();
263     
264     /** the destination directory */
265     private File destDir;
266
267     /** the base directory */
268     private File baseDir;
269
270     //Where the templates live
271

272     private File templateDir;
273     
274     /** the template= attribute */
275     private String JavaDoc templateName;
276
277     /** The template in its parsed form */
278     private Template parsedTemplate;
279
280     /** last modified of the template sheet */
281     private long templateFileLastModified = 0;
282
283     /** the projectFile= attribute */
284     private String JavaDoc projectAttribute = null;
285
286     private File projectFile = null;
287
288     /** The DOM tree of the project wrapped into FreeMarker TemplateModel */
289     private TemplateModel projectTemplate;
290     // The DOM tree wrapped using the freemarker.ext.dom wrapping.
291
private TemplateNodeModel projectNode;
292     private TemplateModel propertiesTemplate;
293     private TemplateModel userPropertiesTemplate;
294
295     /** last modified of the project file if it exists */
296     private long projectFileLastModified = 0;
297
298     /** check the last modified date on files. defaults to true */
299     private boolean incremental = true;
300
301     /** the default output extension is .html */
302     private String JavaDoc extension = ".html";
303
304     private String JavaDoc encoding = SecurityUtilities.getSystemProperty("file.encoding");
305     private String JavaDoc templateEncoding = encoding;
306     private boolean validation = false;
307
308     private String JavaDoc models = "";
309     private final Map modelsMap = new HashMap();
310     
311     
312     
313     /**
314      * Constructor creates the SAXBuilder.
315      */

316     public FreemarkerXmlTask()
317     {
318         builderFactory = DocumentBuilderFactory.newInstance();
319         builderFactory.setNamespaceAware(true);
320     }
321
322     /**
323      * Set the base directory. Defaults to <tt>.</tt>
324      */

325     public void setBasedir(File dir)
326     {
327         baseDir = dir;
328     }
329
330     /**
331      * Set the destination directory into which the generated
332      * files should be copied to
333      * @param dir the name of the destination directory
334      */

335     public void setDestdir(File dir)
336     {
337         destDir = dir;
338     }
339
340     /**
341      * Set the output file extension. <tt>.html</tt> by default.
342      */

343     public void setExtension(String JavaDoc extension)
344     {
345         this.extension = extension;
346     }
347
348     public void setTemplate(String JavaDoc templateName) {
349         this.templateName = templateName;
350     }
351     
352     public void setTemplateDir(File templateDir) throws BuildException {
353         this.templateDir = templateDir;
354         try {
355             cfg.setDirectoryForTemplateLoading(templateDir);
356         } catch (Exception JavaDoc e) {
357             throw new BuildException(e);
358         }
359     }
360
361     /**
362      * Set the path to the project XML file
363      */

364     public void setProjectfile(String JavaDoc projectAttribute)
365     {
366         this.projectAttribute = projectAttribute;
367     }
368
369     /**
370      * Turn on/off incremental processing. On by default
371      */

372     public void setIncremental(String JavaDoc incremental)
373     {
374         this.incremental = !(incremental.equalsIgnoreCase("false") || incremental.equalsIgnoreCase("no") || incremental.equalsIgnoreCase("off"));
375     }
376
377     /**
378      * Set encoding for generated files. Defaults to platform default encoding.
379      */

380     public void setEncoding(String JavaDoc encoding)
381     {
382         this.encoding = encoding;
383     }
384
385     public void setTemplateEncoding(String JavaDoc inputEncoding)
386     {
387         this.templateEncoding = inputEncoding;
388     }
389     
390     /**
391      * Sets whether to validate the XML input.
392      */

393     public void setValidation(boolean validation)
394     {
395         this.validation = validation;
396     }
397
398     public void setModels(String JavaDoc models)
399     {
400         this.models = models;
401     }
402     
403     public void execute() throws BuildException
404     {
405         DirectoryScanner scanner;
406         String JavaDoc[] list;
407
408         if (baseDir == null)
409         {
410             baseDir = getProject().getBaseDir();
411         }
412         if (destDir == null )
413         {
414             String JavaDoc msg = "destdir attribute must be set!";
415             throw new BuildException(msg, getLocation());
416         }
417         
418         File templateFile = null;
419
420         if (templateDir == null) {
421             if (templateName != null) {
422                 templateFile = new File(templateName);
423                 if (!templateFile.isAbsolute()) {
424                     templateFile = new File(getProject().getBaseDir(), templateName);
425                 }
426                 templateDir = templateFile.getParentFile();
427                 templateName = templateFile.getName();
428             }
429             else {
430                 templateDir = baseDir;
431             }
432             setTemplateDir(templateDir);
433         } else if (templateName != null) {
434             if (new File(templateName).isAbsolute()) {
435                 throw new BuildException("Do not specify an absolute location for the template as well as a templateDir");
436             }
437             templateFile = new File(templateDir, templateName);
438         }
439         if (templateFile != null) {
440             templateFileLastModified = templateFile.lastModified();
441         }
442
443         try {
444             if (templateName != null) {
445                 parsedTemplate = cfg.getTemplate(templateName, templateEncoding);
446             }
447         }
448         catch (IOException ioe) {
449             throw new BuildException(ioe.toString());
450         }
451         // get the last modification of the template
452
log("Transforming into: " + destDir.getAbsolutePath(), Project.MSG_INFO);
453
454         // projectFile relative to baseDir
455
if (projectAttribute != null && projectAttribute.length() > 0)
456         {
457             projectFile = new File(baseDir, projectAttribute);
458             if (projectFile.isFile())
459                 projectFileLastModified = projectFile.lastModified();
460             else
461             {
462                 log ("Project file is defined, but could not be located: " +
463                      projectFile.getAbsolutePath(), Project.MSG_INFO );
464                 projectFile = null;
465             }
466         }
467
468         generateModels();
469         
470         // find the files/directories
471
scanner = getDirectoryScanner(baseDir);
472
473         propertiesTemplate = wrapMap(project.getProperties());
474         userPropertiesTemplate = wrapMap(project.getUserProperties());
475
476         builderFactory.setValidating(validation);
477         try
478         {
479             builder = builderFactory.newDocumentBuilder();
480         }
481         catch(ParserConfigurationException JavaDoc e)
482         {
483             throw new BuildException("Could not create document builder", e, getLocation());
484         }
485
486         // get a list of files to work on
487
list = scanner.getIncludedFiles();
488         
489         
490         for (int i = 0;i < list.length; ++i)
491         {
492             process(baseDir, list[i], destDir);
493         }
494     }
495     
496     public void addConfiguredJython(JythonAntTask jythonAntTask) {
497         this.prepareEnvironment = jythonAntTask;
498     }
499
500     public void addConfiguredPrepareModel(JythonAntTask prepareModel) {
501         this.prepareModel = prepareModel;
502     }
503
504     public void addConfiguredPrepareEnvironment(JythonAntTask prepareEnvironment) {
505         this.prepareEnvironment = prepareEnvironment;
506     }
507     
508     /**
509      * Process an XML file using FreeMarker
510      */

511     private void process(File baseDir, String JavaDoc xmlFile, File destDir)
512     throws BuildException
513     {
514         File outFile=null;
515         File inFile=null;
516         try
517         {
518             // the current input file relative to the baseDir
519
inFile = new File(baseDir,xmlFile);
520             // the output file relative to basedir
521
outFile = new File(destDir,
522                                xmlFile.substring(0,
523                                                  xmlFile.lastIndexOf('.')) + extension);
524
525             // only process files that have changed
526
if (!incremental ||
527                 (inFile.lastModified() > outFile.lastModified() ||
528                  templateFileLastModified > outFile.lastModified() ||
529                  projectFileLastModified > outFile.lastModified()))
530             {
531                 ensureDirectoryFor(outFile);
532
533                 //-- command line status
534
log("Input: " + xmlFile, Project.MSG_INFO );
535                 
536                 if (projectTemplate == null && projectFile != null) {
537                     Document doc = builder.parse(projectFile);
538                     projectTemplate = new NodeListModel(builder.parse(projectFile));
539                     projectNode = NodeModel.wrap(doc);
540                 }
541
542                 // Build the file DOM
543
Document docNode = builder.parse(inFile);
544                 
545                 TemplateModel document = new NodeListModel(docNode);
546                 TemplateNodeModel docNodeModel = NodeModel.wrap(docNode);
547                 HashMap root = new HashMap();
548                 root.put("document", document);
549                 insertDefaults(root);
550
551                 // Process the template and write out
552
// the result as the outFile.
553
Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), encoding));
554                 try
555                 {
556                     if (parsedTemplate == null) {
557                         throw new BuildException("No template file specified in build script or in XML file");
558                     }
559                     if (prepareModel != null) {
560                         Map vars = new HashMap();
561                         vars.put("model", root);
562                         vars.put("doc", docNode);
563                         if (projectNode != null) {
564                             vars.put("project", ((NodeModel) projectNode).getNode());
565                         }
566                         prepareModel.execute(vars);
567                     }
568                     freemarker.core.Environment env = parsedTemplate.createProcessingEnvironment(root, writer);
569                     env.setCurrentVisitorNode(docNodeModel);
570                     if (prepareEnvironment != null) {
571                         Map vars = new HashMap();
572                         vars.put("env", env);
573                         vars.put("doc", docNode);
574                         if (projectNode != null) {
575                             vars.put("project", ((NodeModel) projectNode).getNode());
576                         }
577                         prepareEnvironment.execute(vars);
578                     }
579                     env.process();
580                     writer.flush();
581                 }
582                 finally
583                 {
584                     writer.close();
585                 }
586
587                 log("Output: " + outFile, Project.MSG_INFO );
588                 
589             }
590         }
591         catch (SAXParseException JavaDoc spe)
592         {
593             Throwable JavaDoc rootCause = spe;
594             if (spe.getException() != null)
595                 rootCause = spe.getException();
596             log("XML parsing error in " + inFile.getAbsolutePath(), Project.MSG_ERR);
597             log("Line number " + spe.getLineNumber());
598             log("Column number " + spe.getColumnNumber());
599             throw new BuildException(rootCause, getLocation());
600         }
601         catch (Throwable JavaDoc e)
602         {
603             if (outFile != null ) outFile.delete();
604             e.printStackTrace();
605             throw new BuildException(e, getLocation());
606         }
607     }
608
609     private void generateModels()
610     {
611         StringTokenizer modelTokenizer = new StringTokenizer(models, ",; ");
612         while(modelTokenizer.hasMoreTokens())
613         {
614             String JavaDoc modelSpec = modelTokenizer.nextToken();
615             String JavaDoc name = null;
616             String JavaDoc clazz = null;
617             
618             int sep = modelSpec.indexOf('=');
619             if(sep == -1)
620             {
621                 // No explicit name - use unqualified class name
622
clazz = modelSpec;
623                 int dot = clazz.lastIndexOf('.');
624                 if(dot == -1)
625                 {
626                     // clazz in the default package
627
name = clazz;
628                 }
629                 else
630                 {
631                     name = clazz.substring(dot + 1);
632                 }
633             }
634             else
635             {
636                 name = modelSpec.substring(0, sep);
637                 clazz = modelSpec.substring(sep + 1);
638             }
639             try
640             {
641                 modelsMap.put(name, ClassUtil.forName(clazz).newInstance());
642             }
643             catch(Exception JavaDoc e)
644             {
645                 throw new BuildException(e);
646             }
647         }
648     }
649     
650     /**
651      * create directories as needed
652      */

653     private void ensureDirectoryFor( File targetFile ) throws BuildException
654     {
655         File directory = new File( targetFile.getParent() );
656         if (!directory.exists())
657         {
658             if (!directory.mkdirs())
659             {
660                 throw new BuildException("Unable to create directory: "
661                                          + directory.getAbsolutePath(), getLocation());
662             }
663         }
664     }
665
666     private static TemplateModel wrapMap(Map table)
667     {
668         SimpleHash model = new SimpleHash();
669         for (Iterator it = table.keySet().iterator(); it.hasNext();)
670         {
671             Object JavaDoc key = it.next();
672             Object JavaDoc value = table.get(key);
673             model.put(key.toString(), new SimpleScalar(value.toString()));
674         }
675         return model;
676     }
677
678     protected void insertDefaults(Map root)
679     {
680         root.put("properties", propertiesTemplate);
681         root.put("userProperties", userPropertiesTemplate);
682         if (projectTemplate != null) {
683             root.put("project", projectTemplate);
684             root.put("project_node", projectNode);
685         }
686         if(modelsMap.size() > 0)
687         {
688             for (Iterator it = modelsMap.entrySet().iterator(); it.hasNext();)
689             {
690                 Map.Entry entry = (Map.Entry) it.next();
691                 root.put(entry.getKey(), entry.getValue());
692             }
693         }
694     }
695     
696 }
697
Popular Tags