/*
* $Id$
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons.taskdefs;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Properties;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.xerces.util.XMLCatalogResolver;
import org.jcoderz.commons.util.IoUtil;
import org.jcoderz.commons.util.StringUtil;
import org.jcoderz.commons.util.XmlUtil;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* This class implements common functionality for XSLT based Ant tasks.
*
* @author Michael Griffel
*/
public abstract class XsltBasedTask
extends Task
{
/** System property for the XML Parser Configuration (Xalan2). */
private static final String XML_PARSER_CONFIGURATION_PROPERTY =
"org.apache.xerces.xni.parser.XMLParserConfiguration";
/** Xalan2 XML Parser Configuration w/ XInclude support. */
private static final String XML_PARSER_CONFIG_WITH_XINCLUDE =
"org.apache.xerces.parsers.XIncludeParserConfiguration";
/** The fawkeZ VERSION file. */
private static final String FAWKEZ_VERSION_FILE =
"/org/jcoderz/commons/VERSION";
/** The destination directory. */
private File mDestDir = null;
/** The XSL stylesheet file. */
private String mXslFile = null;
/** The Input XML document (log message info file) to be used. */
private File mInFile = null;
/** The Output file. */
private File mOutFile = null;
/** force output of target files even if they already exist. */
private boolean mForce = false;
/** terminate ant build on error. */
private boolean mFailOnError = false;
/** Log level. */
private int mLogLevel = Project.MSG_INFO;
private boolean mResolveExternalEntities = true;
/**
* AntClassLoader for the nested <classpath> - if set.
* <p>
* We keep this here in order to reset the context classloader in
* execute. We can't use liaison.getClass().getClassLoader() since
* the actual liaison class may have been loaded by a loader higher
* up (system classloader, for example).
* </p>
*
* @since Ant 1.6.2
*/
private AntClassLoader mClassLoader = null;
/**
* Set the destination directory into which the XSL result files
* should be copied to. This parameter is required.
*
* @param dir the name of the destination directory.
*/
public void setDestdir (File dir)
{
mDestDir = dir;
}
/**
* Sets the XSL file that is used to generate the log message info
* classes.
*
* @param s the XSL file to use.
*/
public void setXsl (String s)
{
mXslFile = s;
}
/**
* Sets the XML input file that contains the log message info
* document.
*
* @param f the XML input file (log message info).
*/
public void setIn (File f)
{
mInFile = f;
}
/**
* Sets the output file.
*
* @param f The output file.
*/
public void setOut (File f)
{
mOutFile = f;
}
/**
* Sets the force output of target files flag to the given value.
*
* @param b Whether we should force the generation of output files.
*/
public void setForce (boolean b)
{
mForce = b;
}
/**
* Set whether we should fail on an error.
*
* @param b Whether we should fail on an error.
*/
public void setFailonerror (boolean b)
{
mFailOnError = b;
}
/**
* Sets the log level.
*
* @param level the new log level
*/
public void setLogLevel (int level)
{
mLogLevel = level;
}
/**
* Execute this task.
*
* @throws BuildException An building exception occurred.
*/
public void execute ()
throws BuildException
{
try
{
checkAttributes();
if (mForce || mInFile.lastModified() > mOutFile.lastModified())
{
if (mDestDir != null)
{
log("Generating files to directory " + mDestDir,
Project.MSG_VERBOSE);
}
log("Processing " + mInFile + " to " + mOutFile
+ " using stylesheet " + mXslFile, mLogLevel);
transform();
postExecute();
}
}
catch (BuildException e)
{
if (mFailOnError)
{
throw e;
}
log(e.getMessage(), Project.MSG_ERR);
}
}
/**
* @return the fawkez version used for build.
*/
public String getFawkezVersionAsString ()
{
final StringBuffer version = new StringBuffer();
try
{
final Properties fawkezProps = getFawkezVersionProperties();
version.append("fawkeZ ");
version.append(fawkezProps.getProperty("version"));
version.append(", [");
version.append(fawkezProps.getProperty("cvs_name"));
version.append(']');
}
catch (Exception x)
{
// sorry, we cannot read fawkeZ VERSION file
version.append("unknown");
}
return version.toString();
}
/**
* If set to <tt>false</tt>, external entities will not be
* resolved.
*
* @param b new value.
*/
public void resolveExternalEntities (boolean b)
{
mResolveExternalEntities = b;
}
static void checkXercesVersion (Task task)
{
final String xercesVersion = org.apache.xerces.impl.Version
.getVersion();
if (StringUtil.contains(xercesVersion, ("2.6.2")))
{
task.log("Found " + xercesVersion + " on classpath.",
Project.MSG_WARN);
task.log("This Version only supports the outdated 2003 "
+ "namespace for XInclude ", Project.MSG_WARN);
task.log("please put a newer version of xerces on your classpath"
+ "or use", Project.MSG_WARN);
task.log("at least ANT 1.7.0.", Project.MSG_WARN);
}
}
/**
* Returns the build-in default stylesheet file name that should be
* used by XSL transformer.
* <p>
* The stylesheet must be stored in the
* <tt>/org/jcoderz/commons/taskdefs</tt> directory.
*
* @return the default stylesheet file name.
*/
abstract String getDefaultStyleSheet ();
/**
* This method can be overwritten by subclasses to set additional
* transformer parameters.
*
* @param transformer the XSL transformer.
*/
void setAdditionalTransformerParameters (Transformer transformer)
{
// NOP
}
File getInFile ()
{
return mInFile;
}
File getOutFile ()
{
return mOutFile;
}
File getDestDir ()
{
return mDestDir;
}
boolean getFailOnError ()
{
return mFailOnError;
}
/**
* Checks the attributes provided by this class.
*
* @throws BuildException
*/
void checkAttributes ()
throws BuildException
{
checkAttributeInFile();
checkAttributeOutFile();
checkAttributeDestDir();
checkAttributeXslFile();
checkXercesVersion(this);
}
void checkAttributeXslFile ()
{
if (mXslFile == null || !new File(mXslFile).exists())
{
mXslFile = getDefaultStyleSheet();
}
}
void checkAttributeDestDir ()
{
if (mDestDir == null)
{
throw new BuildException("Missing mandatory attribute 'outdir'.",
getLocation());
}
AntTaskUtil.ensureDirectory(mDestDir);
}
void checkAttributeOutFile ()
{
if (mOutFile == null)
{
throw new BuildException("Missing mandatory attribute 'out'.",
getLocation());
}
AntTaskUtil.ensureDirectoryForFile(mOutFile);
}
void checkAttributeInFile ()
{
if (mInFile == null)
{
throw new BuildException("Missing mandatory attribute 'in'.",
getLocation());
}
if (!mInFile.exists())
{
throw new BuildException("Input file '" + mInFile + "' not found.",
getLocation());
}
}
/**
* This method is the last callback in the execute method. Can be
* overwritten by subclasses.
*/
void postExecute ()
{
// NOP
}
/**
* Execute the XSL transformation.
*
* @throws BuildException if an error during transformation occurs.
*/
void transform ()
throws BuildException
{
StreamResult out = null;
try
{
final String xmlParserConfig
= System.getProperty(XML_PARSER_CONFIGURATION_PROPERTY);
if (!XML_PARSER_CONFIG_WITH_XINCLUDE.equals(xmlParserConfig))
{
System.setProperty(
XML_PARSER_CONFIGURATION_PROPERTY,
XML_PARSER_CONFIG_WITH_XINCLUDE);
log("Using XML Parser configuration "
+ XML_PARSER_CONFIG_WITH_XINCLUDE, Project.MSG_VERBOSE);
}
// Xalan2 transformer is required,
// that why we explicit use this factory
final TransformerFactory factory
= (TransformerFactory)
(loadClass(
"org.apache.xalan.processor.TransformerFactoryImpl")
.newInstance());
factory.setURIResolver(new JarArchiveUriResolver(this));
final StreamSource source = getXslFileAsSource();
final Transformer transformer
= factory.newTransformer(source);
setAdditionalTransformerParameters(transformer);
transformer.setParameter("outdir", mDestDir != null ? mDestDir
.getAbsolutePath() : "");
final Source xml = getInAsStreamSource();
out = XmlUtil.createStreamResult(mOutFile);
transformer.setErrorListener(new MyErrorListener());
transformer.transform(xml, out);
}
catch (Exception e)
{
throw new BuildException("Error during transformation: " + e, e);
}
finally
{
if (out != null)
{
IoUtil.close(out.getOutputStream());
}
if (mClassLoader != null)
{
mClassLoader.resetThreadContextLoader();
mClassLoader.cleanup();
mClassLoader = null;
}
}
}
private Class loadClass (String classname)
throws ClassNotFoundException
{
final Class result;
if (getClass().getClassLoader() instanceof AntClassLoader)
{
mClassLoader = (AntClassLoader) getClass().getClassLoader();
mClassLoader.setThreadContextLoader();
result = Class.forName(classname, true, mClassLoader);
log("Loading '" + classname + "' via " + mClassLoader,
Project.MSG_VERBOSE);
}
else // if (mClassPath == null)
{
result = Class.forName(classname);
log("No ant-classloader found to load '" + classname + "',"
+ "using 'normal' Class.forName(classname).",
Project.MSG_VERBOSE);
}
return result;
}
private StreamSource getXslFileAsSource ()
{
final StreamSource result;
final InputStream xslStream
= XsltBasedTask.class.getResourceAsStream(mXslFile);
if (xslStream == null)
{
try
{
final File file = new File(mXslFile);
final InputStream xslFile = new FileInputStream(file);
result = new StreamSource(xslFile);
result.setSystemId(file.toURI().toASCIIString());
}
catch (FileNotFoundException e)
{
throw new BuildException("Cannot locate stylesheet "
+ mXslFile, e);
}
}
else
{
result = new StreamSource(xslStream);
final URL url = XsltBasedTask.class.getResource(mXslFile);
if (url != null)
{
try
{
result.setSystemId(url.toURI().toASCIIString());
}
catch (URISyntaxException ex)
{
log("Failed to set systemId. Got " + ex,
Project.MSG_VERBOSE);
}
}
}
return result;
}
/**
* Instantiates xml resolver for xerces xml parser.
*
* If xml-resolver.jar is available on the boot classpath of ant, the
* implementation of an xml catalog resolver will be returned otherwise
* the dummy resolver implementation will be provided
*
* @return EntityResolver entity resolver
*/
private EntityResolver getEntityResolver()
{
EntityResolver resolver = new DummyEntityResolver(this);
try
{
String [] catalogs = {"src/xml/catalog.xml"};
System.getProperties().put("xml.catalog.verbosity", "1000");
log("Instantiating xml catalog resolver .", Project.MSG_INFO);
// Create catalog resolver and set a catalog list.
XMLCatalogResolver xmlResolver = new XMLCatalogResolver();
xmlResolver.setPreferPublic(false);
xmlResolver.setCatalogList(catalogs);
resolver = xmlResolver;
}
catch (NoClassDefFoundError e)
{
// The most secure way to check for non-existence of the CatalogReader
// class is within ant class loaders is to catch the NoClassDefFoundError.
log("Class CatalogReader (xml-resolver.jar) could not be found " +
" within bootstrap classpath. No entity resolver is " +
" available, setting dummy resolver.", Project.MSG_WARN);
}
return resolver;
}
/**
* @return a resource stream from in file.
* @throws FileNotFoundException
*/
Source getInAsStreamSource ()
{
final Source result;
if (!mResolveExternalEntities)
{
final org.xml.sax.XMLReader reader;
try
{
EntityResolver resolver = getEntityResolver();
// reader = XMLReaderFactory.createXMLReader(
// "org.apache.xerces.parsers.SAXParser");
reader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
reader.setEntityResolver(resolver);
result = new SAXSource(reader, new InputSource(
new FileInputStream(mInFile)));
}
catch (SAXException e)
{
throw new BuildException("Cannot create SAX XML Reader: " + e,
e);
}
catch (FileNotFoundException e)
{
throw new BuildException("Ups, cannot open file: " + e, e);
}
}
else
{
result = new StreamSource(mInFile);
}
return result;
}
/**
* Returns the VERSION file properties.
*
* @return the VERSION file properties.
* @throws IOException if the VERSION file cannot be found or read.
*/
private Properties getFawkezVersionProperties ()
throws IOException
{
final Properties props = new Properties();
final InputStream in
= XsltBasedTask.class.getResourceAsStream(FAWKEZ_VERSION_FILE);
try
{
props.load(in);
}
finally
{
IoUtil.close(in);
}
return props;
}
private static class MyErrorListener
implements ErrorListener
{
/** {@inheritDoc} */
public void warning (TransformerException arg0)
throws TransformerException
{
throw arg0;
}
/** {@inheritDoc} */
public void error (TransformerException arg0)
throws TransformerException
{
throw arg0;
}
/** {@inheritDoc} */
public void fatalError (TransformerException arg0)
throws TransformerException
{
throw arg0;
}
}
}