root/trunk/src/java/org/jcoderz/commons/taskdefs/XsltBasedTask.java

Revision 1633, 18.5 kB (checked in by amandel, 2 years ago)

#81 open the stream directly in StreamSource? to avoid whitespace in path issue.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.commons.taskdefs;
34
35
36import java.io.File;
37import java.io.FileInputStream;
38import java.io.FileNotFoundException;
39import java.io.IOException;
40import java.io.InputStream;
41import java.net.URISyntaxException;
42import java.net.URL;
43import java.util.Properties;
44
45import javax.xml.transform.ErrorListener;
46import javax.xml.transform.Source;
47import javax.xml.transform.Transformer;
48import javax.xml.transform.TransformerException;
49import javax.xml.transform.TransformerFactory;
50import javax.xml.transform.sax.SAXSource;
51import javax.xml.transform.stream.StreamResult;
52import javax.xml.transform.stream.StreamSource;
53
54import org.apache.tools.ant.AntClassLoader;
55import org.apache.tools.ant.BuildException;
56import org.apache.tools.ant.Project;
57import org.apache.tools.ant.Task;
58import org.apache.xerces.util.XMLCatalogResolver;
59import org.jcoderz.commons.util.IoUtil;
60import org.jcoderz.commons.util.StringUtil;
61import org.jcoderz.commons.util.XmlUtil;
62import org.xml.sax.EntityResolver;
63import org.xml.sax.InputSource;
64import org.xml.sax.SAXException;
65
66
67/**
68 * This class implements common functionality for XSLT based Ant tasks.
69 *
70 * @author Michael Griffel
71 */
72public abstract class XsltBasedTask
73    extends Task
74{
75    /** System property for the XML Parser Configuration (Xalan2). */
76    private static final String XML_PARSER_CONFIGURATION_PROPERTY =
77        "org.apache.xerces.xni.parser.XMLParserConfiguration";
78
79    /** Xalan2 XML Parser Configuration w/ XInclude support. */
80    private static final String XML_PARSER_CONFIG_WITH_XINCLUDE =
81        "org.apache.xerces.parsers.XIncludeParserConfiguration";
82
83    /** The fawkeZ VERSION file. */
84    private static final String FAWKEZ_VERSION_FILE =
85        "/org/jcoderz/commons/VERSION";
86
87    /** The destination directory. */
88    private File mDestDir = null;
89
90    /** The XSL stylesheet file. */
91    private String mXslFile = null;
92
93    /** The Input XML document (log message info file) to be used. */
94    private File mInFile = null;
95
96    /** The Output file. */
97    private File mOutFile = null;
98
99    /** force output of target files even if they already exist. */
100    private boolean mForce = false;
101
102    /** terminate ant build on error. */
103    private boolean mFailOnError = false;
104
105    /** Log level. */
106    private int mLogLevel = Project.MSG_INFO;
107
108    private boolean mResolveExternalEntities = true;
109
110    /**
111     * AntClassLoader for the nested <classpath> - if set.
112     * <p>
113     * We keep this here in order to reset the context classloader in
114     * execute. We can't use liaison.getClass().getClassLoader() since
115     * the actual liaison class may have been loaded by a loader higher
116     * up (system classloader, for example).
117     * </p>
118     *
119     * @since Ant 1.6.2
120     */
121    private AntClassLoader mClassLoader = null;
122
123    /**
124     * Set the destination directory into which the XSL result files
125     * should be copied to. This parameter is required.
126     *
127     * @param dir the name of the destination directory.
128     */
129    public void setDestdir (File dir)
130    {
131        mDestDir = dir;
132    }
133
134    /**
135     * Sets the XSL file that is used to generate the log message info
136     * classes.
137     *
138     * @param s the XSL file to use.
139     */
140    public void setXsl (String s)
141    {
142        mXslFile = s;
143    }
144
145    /**
146     * Sets the XML input file that contains the log message info
147     * document.
148     *
149     * @param f the XML input file (log message info).
150     */
151    public void setIn (File f)
152    {
153        mInFile = f;
154    }
155
156    /**
157     * Sets the output file.
158     *
159     * @param f The output file.
160     */
161    public void setOut (File f)
162    {
163        mOutFile = f;
164    }
165
166    /**
167     * Sets the force output of target files flag to the given value.
168     *
169     * @param b Whether we should force the generation of output files.
170     */
171    public void setForce (boolean b)
172    {
173        mForce = b;
174    }
175
176    /**
177     * Set whether we should fail on an error.
178     *
179     * @param b Whether we should fail on an error.
180     */
181    public void setFailonerror (boolean b)
182    {
183        mFailOnError = b;
184    }
185
186    /**
187     * Sets the log level.
188     *
189     * @param level the new log level
190     */
191    public void setLogLevel (int level)
192    {
193        mLogLevel = level;
194    }
195
196    /**
197     * Execute this task.
198     *
199     * @throws BuildException An building exception occurred.
200     */
201    public void execute ()
202        throws BuildException
203    {
204        try
205        {
206            checkAttributes();
207            if (mForce || mInFile.lastModified() > mOutFile.lastModified())
208            {
209                if (mDestDir != null)
210                {
211                    log("Generating files to directory " + mDestDir,
212                        Project.MSG_VERBOSE);
213                }
214                log("Processing " + mInFile + " to " + mOutFile
215                    + " using stylesheet " + mXslFile, mLogLevel);
216                transform();
217                postExecute();
218            }
219        }
220        catch (BuildException e)
221        {
222            if (mFailOnError)
223            {
224                throw e;
225            }
226            log(e.getMessage(), Project.MSG_ERR);
227        }
228    }
229
230    /**
231     * @return the fawkez version used for build.
232     */
233    public String getFawkezVersionAsString ()
234    {
235        final StringBuffer version = new StringBuffer();
236        try
237        {
238            final Properties fawkezProps = getFawkezVersionProperties();
239            version.append("fawkeZ ");
240            version.append(fawkezProps.getProperty("version"));
241            version.append(", [");
242            version.append(fawkezProps.getProperty("cvs_name"));
243            version.append(']');
244        }
245        catch (Exception x)
246        {
247            // sorry, we cannot read fawkeZ VERSION file
248            version.append("unknown");
249        }
250        return version.toString();
251    }
252
253    /**
254     * If set to <tt>false</tt>, external entities will not be
255     * resolved.
256     *
257     * @param b new value.
258     */
259    public void resolveExternalEntities (boolean b)
260    {
261        mResolveExternalEntities = b;
262    }
263
264    static void checkXercesVersion (Task task)
265    {
266        final String xercesVersion = org.apache.xerces.impl.Version
267            .getVersion();
268        if (StringUtil.contains(xercesVersion, ("2.6.2")))
269        {
270            task.log("Found " + xercesVersion + " on classpath.",
271                Project.MSG_WARN);
272            task.log("This Version only supports the outdated 2003 "
273                + "namespace for XInclude ", Project.MSG_WARN);
274            task.log("please put a newer version of xerces on your classpath"
275                + "or use", Project.MSG_WARN);
276            task.log("at least ANT 1.7.0.", Project.MSG_WARN);
277        }
278    }
279
280    /**
281     * Returns the build-in default stylesheet file name that should be
282     * used by XSL transformer.
283     * <p>
284     * The stylesheet must be stored in the
285     * <tt>/org/jcoderz/commons/taskdefs</tt> directory.
286     *
287     * @return the default stylesheet file name.
288     */
289    abstract String getDefaultStyleSheet ();
290
291    /**
292     * This method can be overwritten by subclasses to set additional
293     * transformer parameters.
294     *
295     * @param transformer the XSL transformer.
296     */
297    void setAdditionalTransformerParameters (Transformer transformer)
298    {
299        // NOP
300    }
301
302    File getInFile ()
303    {
304        return mInFile;
305    }
306
307    File getOutFile ()
308    {
309        return mOutFile;
310    }
311
312    File getDestDir ()
313    {
314        return mDestDir;
315    }
316
317    boolean getFailOnError ()
318    {
319        return mFailOnError;
320    }
321
322    /**
323     * Checks the attributes provided by this class.
324     *
325     * @throws BuildException
326     */
327    void checkAttributes ()
328        throws BuildException
329    {
330        checkAttributeInFile();
331        checkAttributeOutFile();
332        checkAttributeDestDir();
333        checkAttributeXslFile();
334        checkXercesVersion(this);
335    }
336
337    void checkAttributeXslFile ()
338    {
339        if (mXslFile == null || !new File(mXslFile).exists())
340        {
341            mXslFile = getDefaultStyleSheet();
342        }
343    }
344
345    void checkAttributeDestDir ()
346    {
347        if (mDestDir == null)
348        {
349            throw new BuildException("Missing mandatory attribute 'outdir'.",
350                getLocation());
351        }
352        AntTaskUtil.ensureDirectory(mDestDir);
353    }
354
355    void checkAttributeOutFile ()
356    {
357        if (mOutFile == null)
358        {
359            throw new BuildException("Missing mandatory attribute 'out'.",
360                getLocation());
361        }
362        AntTaskUtil.ensureDirectoryForFile(mOutFile);
363    }
364
365    void checkAttributeInFile ()
366    {
367        if (mInFile == null)
368        {
369            throw new BuildException("Missing mandatory attribute 'in'.",
370                getLocation());
371        }
372        if (!mInFile.exists())
373        {
374            throw new BuildException("Input file '" + mInFile + "' not found.",
375                getLocation());
376        }
377    }
378
379    /**
380     * This method is the last callback in the execute method. Can be
381     * overwritten by subclasses.
382     */
383    void postExecute ()
384    {
385        // NOP
386    }
387
388    /**
389     * Execute the XSL transformation.
390     *
391     * @throws BuildException if an error during transformation occurs.
392     */
393    void transform ()
394        throws BuildException
395    {
396        StreamResult out = null;
397        try
398        {
399            final String xmlParserConfig
400                = System.getProperty(XML_PARSER_CONFIGURATION_PROPERTY);
401            if (!XML_PARSER_CONFIG_WITH_XINCLUDE.equals(xmlParserConfig))
402            {
403                System.setProperty(
404                    XML_PARSER_CONFIGURATION_PROPERTY,
405                    XML_PARSER_CONFIG_WITH_XINCLUDE);
406                log("Using XML Parser configuration "
407                    + XML_PARSER_CONFIG_WITH_XINCLUDE, Project.MSG_VERBOSE);
408            }
409            // Xalan2 transformer is required,
410            // that why we explicit use this factory
411            final TransformerFactory factory
412                = (TransformerFactory)
413                    (loadClass(
414                        "org.apache.xalan.processor.TransformerFactoryImpl")
415                            .newInstance());
416
417            factory.setURIResolver(new JarArchiveUriResolver(this));
418            final StreamSource source = getXslFileAsSource();
419            final Transformer transformer
420                = factory.newTransformer(source);
421            setAdditionalTransformerParameters(transformer);
422            transformer.setParameter("outdir", mDestDir != null ? mDestDir
423                .getAbsolutePath() : "");
424            final Source xml = getInAsStreamSource();
425            out = XmlUtil.createStreamResult(mOutFile);
426            transformer.setErrorListener(new MyErrorListener());
427            transformer.transform(xml, out);
428        }
429        catch (Exception e)
430        {
431            throw new BuildException("Error during transformation: " + e, e);
432        }
433        finally
434        {
435            if (out != null)
436            {
437                IoUtil.close(out.getOutputStream());
438            }
439            if (mClassLoader != null)
440            {
441                mClassLoader.resetThreadContextLoader();
442                mClassLoader.cleanup();
443                mClassLoader = null;
444            }
445        }
446    }
447
448    private Class loadClass (String classname)
449        throws ClassNotFoundException
450    {
451        final Class result;
452        if (getClass().getClassLoader() instanceof AntClassLoader)
453        {
454            mClassLoader = (AntClassLoader) getClass().getClassLoader();
455            mClassLoader.setThreadContextLoader();
456            result = Class.forName(classname, true, mClassLoader);
457            log("Loading '" + classname + "' via " + mClassLoader,
458                Project.MSG_VERBOSE);
459        }
460        else // if (mClassPath == null)
461        {
462            result = Class.forName(classname);
463            log("No ant-classloader found to load '" + classname + "',"
464                + "using 'normal' Class.forName(classname).",
465                Project.MSG_VERBOSE);
466        }
467        return result;
468    }
469
470    private StreamSource getXslFileAsSource ()
471    {
472        final StreamSource result;
473        final InputStream xslStream
474            = XsltBasedTask.class.getResourceAsStream(mXslFile);
475        if (xslStream == null)
476        {
477            try
478            {
479                final File file = new File(mXslFile);
480                final InputStream xslFile = new FileInputStream(file);
481                result = new StreamSource(xslFile);
482                result.setSystemId(file.toURI().toASCIIString());
483            }
484            catch (FileNotFoundException e)
485            {
486                throw new BuildException("Cannot locate stylesheet "
487                    + mXslFile, e);
488            }
489        }
490        else
491        {
492            result = new StreamSource(xslStream);
493            final URL url = XsltBasedTask.class.getResource(mXslFile);
494            if (url != null)
495            {
496                try
497                {
498                    result.setSystemId(url.toURI().toASCIIString());
499                }
500                catch (URISyntaxException ex)
501                {
502                    log("Failed to set systemId. Got " + ex,
503                            Project.MSG_VERBOSE);
504                }
505            }
506        }
507        return result;
508    }
509   
510    /**
511     * Instantiates xml resolver for xerces xml parser.
512     *
513     * If xml-resolver.jar is available on the boot classpath of ant, the
514     * implementation of an xml catalog resolver will be returned otherwise
515     * the dummy resolver implementation will be provided
516     *
517     * @return EntityResolver entity resolver
518     */
519    private EntityResolver getEntityResolver()
520    {
521        EntityResolver resolver = new DummyEntityResolver(this);
522        try 
523        {
524            String [] catalogs = {"src/xml/catalog.xml"};
525            System.getProperties().put("xml.catalog.verbosity", "1000");
526           
527            log("Instantiating xml catalog resolver .", Project.MSG_INFO);
528            // Create catalog resolver and set a catalog list.
529            XMLCatalogResolver xmlResolver = new XMLCatalogResolver();
530           
531            xmlResolver.setPreferPublic(false);
532            xmlResolver.setCatalogList(catalogs);
533            resolver = xmlResolver;
534        }
535        catch (NoClassDefFoundError e)
536        {
537            // The most secure way to check for non-existence of the CatalogReader
538            // class is within ant class loaders is to catch the NoClassDefFoundError.
539            log("Class CatalogReader (xml-resolver.jar) could not be found " +
540                        " within bootstrap classpath. No entity resolver is " + 
541                        " available, setting dummy resolver.", Project.MSG_WARN);
542        }
543       
544        return resolver;
545    }
546
547    /**
548     * @return a resource stream from in file.
549     * @throws FileNotFoundException
550     */
551    Source getInAsStreamSource ()
552    {
553        final Source result;
554        if (!mResolveExternalEntities)
555        {
556            final org.xml.sax.XMLReader reader;
557            try
558            {     
559                EntityResolver resolver = getEntityResolver();
560               
561                // reader = XMLReaderFactory.createXMLReader(
562                // "org.apache.xerces.parsers.SAXParser");
563                reader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
564                reader.setEntityResolver(resolver);
565                result = new SAXSource(reader, new InputSource(
566                    new FileInputStream(mInFile)));
567            }
568            catch (SAXException e)
569            {
570                throw new BuildException("Cannot create SAX XML Reader: " + e,
571                    e);
572            }
573            catch (FileNotFoundException e)
574            {
575                throw new BuildException("Ups, cannot open file: " + e, e);
576            }
577        }
578        else
579        {
580            result = new StreamSource(mInFile);
581        }
582        return result;
583    }
584
585    /**
586     * Returns the VERSION file properties.
587     *
588     * @return the VERSION file properties.
589     * @throws IOException if the VERSION file cannot be found or read.
590     */
591    private Properties getFawkezVersionProperties ()
592        throws IOException
593    {
594        final Properties props = new Properties();
595        final InputStream in
596            = XsltBasedTask.class.getResourceAsStream(FAWKEZ_VERSION_FILE);
597        try
598        {
599            props.load(in);
600        }
601        finally
602        {
603            IoUtil.close(in);
604        }
605        return props;
606    }
607
608    private static class MyErrorListener
609        implements ErrorListener
610    {
611        /** {@inheritDoc} */
612        public void warning (TransformerException arg0)
613            throws TransformerException
614        {
615            throw arg0;
616        }
617
618        /** {@inheritDoc} */
619        public void error (TransformerException arg0)
620            throws TransformerException
621        {
622            throw arg0;
623        }
624
625        /** {@inheritDoc} */
626        public void fatalError (TransformerException arg0)
627            throws TransformerException
628        {
629            throw arg0;
630        }
631    }
632}
Note: See TracBrowser for help on using the browser.