/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.tools.ant.taskdefs.optional;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.XSLTLiaison4;
import org.apache.tools.ant.taskdefs.XSLTLogger;
import org.apache.tools.ant.taskdefs.XSLTLoggerAware;
import org.apache.tools.ant.taskdefs.XSLTProcess;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.XMLCatalog;
import org.apache.tools.ant.types.resources.FileProvider;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.types.resources.URLProvider;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.JAXPUtils;
import org.apache.tools.ant.util.JavaEnvUtils;
import org.apache.tools.ant.util.StreamUtils;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1)
Since:Ant 1.3
/** * Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1) * * @since Ant 1.3 */
public class TraXLiaison implements XSLTLiaison4, ErrorListener, XSLTLoggerAware {
Helper for transforming filenames to URIs.
Since:Ant 1.7
/** * Helper for transforming filenames to URIs. * * @since Ant 1.7 */
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
The current Project
/** * The current <code>Project</code> */
private Project project;
the name of the factory implementation class to use or null for default JAXP lookup.
/** * the name of the factory implementation class to use * or null for default JAXP lookup. */
private String factoryName = null;
The trax TransformerFactory
/** The trax TransformerFactory */
private TransformerFactory tfactory = null;
stylesheet to use for transformation
/** stylesheet to use for transformation */
private Resource stylesheet; private XSLTLogger logger;
possible resolver for publicIds
/** possible resolver for publicIds */
private EntityResolver entityResolver;
transformer to use for processing files
/** transformer to use for processing files */
private Transformer transformer;
The In memory version of the stylesheet
/** The In memory version of the stylesheet */
private Templates templates;
The modification time of the stylesheet from which the templates are read
/** * The modification time of the stylesheet from which the templates * are read */
private long templatesModTime;
possible resolver for URIs
/** possible resolver for URIs */
private URIResolver uriResolver;
transformer output properties
/** transformer output properties */
private final Vector<String[]> outputProperties = new Vector<>();
stylesheet parameters
/** stylesheet parameters */
private final Hashtable<String, Object> params = new Hashtable<>();
factory attributes
/** factory attributes */
private final List<Object[]> attributes = new ArrayList<>();
factory features
/** factory features */
private final Map<String, Boolean> features = new HashMap<>();
whether to suppress warnings
/** whether to suppress warnings */
private boolean suppressWarnings = false;
optional trace configuration.
/** optional trace configuration. */
private XSLTProcess.TraceConfiguration traceConfiguration = null;
Constructor for TraXLiaison.
Throws:
  • Exception – never
/** * Constructor for TraXLiaison. * @throws Exception never */
public TraXLiaison() throws Exception { //NOSONAR }
Set the stylesheet file.
Params:
  • stylesheet – a File value
Throws:
/** * Set the stylesheet file. * @param stylesheet a <code>File</code> value * @throws Exception on error */
public void setStylesheet(final File stylesheet) throws Exception { final FileResource fr = new FileResource(); fr.setProject(project); fr.setFile(stylesheet); setStylesheet(fr); }
Set the stylesheet file.
Params:
Throws:
/** * Set the stylesheet file. * @param stylesheet a {@link org.apache.tools.ant.types.Resource} value * @throws Exception on error */
public void setStylesheet(final Resource stylesheet) throws Exception { if (this.stylesheet != null) { // resetting the stylesheet - reset transformer transformer = null; // do we need to reset templates as well if (!this.stylesheet.equals(stylesheet) || (stylesheet.getLastModified() != templatesModTime)) { templates = null; } } this.stylesheet = stylesheet; }
Transform an input file.
Params:
  • infile – the file to transform
  • outfile – the result file
Throws:
/** * Transform an input file. * @param infile the file to transform * @param outfile the result file * @throws Exception on error */
public void transform(final File infile, final File outfile) throws Exception { if (transformer == null) { createTransformer(); } // autoclose all handles, otherwise the garbage collector will close them... // and Windows may complain about not being able to delete files. try (InputStream fis = new BufferedInputStream(Files.newInputStream(infile.toPath())); OutputStream fos = new BufferedOutputStream(Files.newOutputStream(outfile.toPath()))) { final StreamResult res = new StreamResult(fos); // not sure what could be the need of this... res.setSystemId(JAXPUtils.getSystemId(outfile)); // set parameters on each transformation, maybe something has changed //(e.g. value of file name parameter) setTransformationParameters(); transformer.transform(getSource(fis, infile), res); } }
Get the source instance from the stream and id of the file.
Params:
  • is – the stream containing the stylesheet data.
  • infile – the file that will be used for the systemid.
Throws:
Returns:the configured source instance matching the stylesheet.
/** * Get the source instance from the stream and id of the file. * @param is the stream containing the stylesheet data. * @param infile the file that will be used for the systemid. * @return the configured source instance matching the stylesheet. * @throws ParserConfigurationException if a parser cannot be created which * satisfies the requested configuration. * @throws SAXException in case of problem detected by the SAX parser. */
private Source getSource(final InputStream is, final File infile) throws ParserConfigurationException, SAXException { // todo: is this comment still relevant ?? // FIXME: need to use a SAXSource as the source for the transform // so we can plug in our own entity resolver Source src = null; if (entityResolver != null) { if (getFactory().getFeature(SAXSource.FEATURE)) { final SAXParserFactory spFactory = SAXParserFactory.newInstance(); spFactory.setNamespaceAware(true); final XMLReader reader = spFactory.newSAXParser().getXMLReader(); reader.setEntityResolver(entityResolver); src = new SAXSource(reader, new InputSource(is)); } else { throw new IllegalStateException("xcatalog specified, but " + "parser doesn't support SAX"); } } else { // WARN: Don't use the StreamSource(File) ctor. It won't work with // xalan prior to 2.2 because of systemid bugs. src = new StreamSource(is); } src.setSystemId(JAXPUtils.getSystemId(infile)); return src; } private Source getSource(final InputStream is, final Resource resource) throws ParserConfigurationException, SAXException { // todo: is this comment still relevant ?? // FIXME: need to use a SAXSource as the source for the transform // so we can plug in our own entity resolver Source src = null; if (entityResolver != null) { if (getFactory().getFeature(SAXSource.FEATURE)) { final SAXParserFactory spFactory = SAXParserFactory.newInstance(); spFactory.setNamespaceAware(true); final XMLReader reader = spFactory.newSAXParser().getXMLReader(); reader.setEntityResolver(entityResolver); src = new SAXSource(reader, new InputSource(is)); } else { throw new IllegalStateException("xcatalog specified, but " + "parser doesn't support SAX"); } } else { // WARN: Don't use the StreamSource(File) ctor. It won't work with // xalan prior to 2.2 because of systemid bugs. src = new StreamSource(is); } // The line below is a hack: the system id must an URI, but it is not // cleat to get the URI of an resource, so just set the name of the // resource as a system id src.setSystemId(resourceToURI(resource)); return src; } private String resourceToURI(final Resource resource) { final FileProvider fp = resource.as(FileProvider.class); if (fp != null) { return FILE_UTILS.toURI(fp.getFile().getAbsolutePath()); } final URLProvider up = resource.as(URLProvider.class); if (up != null) { final URL u = up.getURL(); return String.valueOf(u); } else { return resource.getName(); } }
Read in templates from the stylesheet
/** * Read in templates from the stylesheet */
private void readTemplates() throws IOException, TransformerConfigurationException, ParserConfigurationException, SAXException { // Use a stream so that you can close it yourself quickly // and avoid keeping the handle until the object is garbaged. // (always keep control), otherwise you won't be able to delete // the file quickly on windows. try (InputStream xslStream = new BufferedInputStream(stylesheet.getInputStream())) { templatesModTime = stylesheet.getLastModified(); final Source src = getSource(xslStream, stylesheet); templates = getFactory().newTemplates(src); } }
Create a new transformer based on the liaison settings
See Also:
/** * Create a new transformer based on the liaison settings * @see #setStylesheet(java.io.File) * @see #addParam(java.lang.String, java.lang.String) * @see #setOutputProperty(java.lang.String, java.lang.String) */
private void createTransformer() throws IOException, ParserConfigurationException, SAXException, TransformerException { if (templates == null) { readTemplates(); } transformer = templates.newTransformer(); // configure the transformer... transformer.setErrorListener(this); if (uriResolver != null) { transformer.setURIResolver(uriResolver); } for (final String[] pair : outputProperties) { transformer.setOutputProperty(pair[0], pair[1]); } if (traceConfiguration != null) { if ("org.apache.xalan.transformer.TransformerImpl" //NOSONAR .equals(transformer.getClass().getName())) { try { final Class<?> traceSupport = Class.forName("org.apache.tools.ant.taskdefs.optional." + "Xalan2TraceSupport", true, Thread.currentThread() .getContextClassLoader()); final XSLTTraceSupport ts = (XSLTTraceSupport) traceSupport.newInstance(); ts.configureTrace(transformer, traceConfiguration); } catch (final Exception e) { final String msg = "Failed to enable tracing because of " + e; if (project != null) { project.log(msg, Project.MSG_WARN); } else { System.err.println(msg); } } } else { final String msg = "Not enabling trace support for transformer" + " implementation" + transformer.getClass().getName(); if (project != null) { project.log(msg, Project.MSG_WARN); } else { System.err.println(msg); } } } }
Sets the parameters for the transformer.
/** * Sets the parameters for the transformer. */
private void setTransformationParameters() { params.forEach((key, value) -> transformer.setParameter(key, value)); }
return the Transformer factory associated to this liaison.
Throws:
See Also:
Returns:the Transformer factory associated to this liaison.
Since:Ant 1.5.2
/** * return the Transformer factory associated to this liaison. * @return the Transformer factory associated to this liaison. * @throws BuildException thrown if there is a problem creating * the factory. * @see #setFactory(String) * @since Ant 1.5.2 */
private TransformerFactory getFactory() throws BuildException { if (tfactory != null) { return tfactory; } // not initialized yet, so create the factory if (factoryName == null) { tfactory = TransformerFactory.newInstance(); } else { try { Class<?> clazz = null; try { clazz = Class.forName(factoryName, true, Thread.currentThread() .getContextClassLoader()); } catch (final ClassNotFoundException cnfe) { final String msg = "Failed to load " + factoryName + " via the configured classpath, will try" + " Ant's classpath instead."; if (logger != null) { logger.log(msg); } else if (project != null) { project.log(msg, Project.MSG_WARN); } else { System.err.println(msg); } } if (clazz == null) { clazz = Class.forName(factoryName); } tfactory = (TransformerFactory) clazz.newInstance(); } catch (final Exception e) { throw new BuildException(e); } } applyReflectionHackForExtensionMethods(); tfactory.setErrorListener(this); // specific attributes for the transformer for (final Object[] pair : attributes) { tfactory.setAttribute((String) pair[0], pair[1]); } for (Map.Entry<String, Boolean> feature : features.entrySet()) { try { tfactory.setFeature(feature.getKey(), feature.getValue()); } catch (TransformerConfigurationException ex) { throw new BuildException(ex); } } if (uriResolver != null) { tfactory.setURIResolver(uriResolver); } return tfactory; }
Set the factory name to use instead of JAXP default lookup.
Params:
  • name – the fully qualified class name of the factory to use or null for the default JAXP look up mechanism.
Since:Ant 1.6
/** * Set the factory name to use instead of JAXP default lookup. * @param name the fully qualified class name of the factory to use * or null for the default JAXP look up mechanism. * @since Ant 1.6 */
public void setFactory(final String name) { factoryName = name; }
Set a custom attribute for the JAXP factory implementation.
Params:
  • name – the attribute name.
  • value – the value of the attribute, usually a boolean string or object.
Since:Ant 1.6
/** * Set a custom attribute for the JAXP factory implementation. * @param name the attribute name. * @param value the value of the attribute, usually a boolean * string or object. * @since Ant 1.6 */
public void setAttribute(final String name, final Object value) { final Object[] pair = new Object[]{name, value}; attributes.add(pair); }
Set a custom feature for the JAXP factory implementation.
Params:
  • name – the feature name.
  • value – the value of the feature
Since:Ant 1.9.8
/** * Set a custom feature for the JAXP factory implementation. * @param name the feature name. * @param value the value of the feature * @since Ant 1.9.8 */
public void setFeature(final String name, final boolean value) { features.put(name, value); }
Set the output property for the current transformer. Note that the stylesheet must be set prior to calling this method.
Params:
  • name – the output property name.
  • value – the output property value.
Since:Ant 1.5
Since:Ant 1.5
/** * Set the output property for the current transformer. * Note that the stylesheet must be set prior to calling * this method. * @param name the output property name. * @param value the output property value. * @since Ant 1.5 * @since Ant 1.5 */
public void setOutputProperty(final String name, final String value) { final String[] pair = new String[]{name, value}; outputProperties.addElement(pair); }
Set the class to resolve entities during the transformation.
Params:
  • aResolver – the resolver class.
/** * Set the class to resolve entities during the transformation. * @param aResolver the resolver class. */
public void setEntityResolver(final EntityResolver aResolver) { entityResolver = aResolver; }
Set the class to resolve URIs during the transformation
Params:
  • aResolver – a EntityResolver value
/** * Set the class to resolve URIs during the transformation * @param aResolver a <code>EntityResolver</code> value */
public void setURIResolver(final URIResolver aResolver) { uriResolver = aResolver; }
Add a parameter.
Params:
  • name – the name of the parameter
  • value – the value of the parameter
/** * Add a parameter. * @param name the name of the parameter * @param value the value of the parameter */
public void addParam(final String name, final String value) { params.put(name, value); }
Add a parameter.
Params:
  • name – the name of the parameter
  • value – the value of the parameter
Since:Ant 1.9.3
/** * Add a parameter. * @param name the name of the parameter * @param value the value of the parameter * @since Ant 1.9.3 */
public void addParam(final String name, final Object value) { params.put(name, value); }
Set a logger.
Params:
  • l – a logger.
/** * Set a logger. * @param l a logger. */
public void setLogger(final XSLTLogger l) { logger = l; }
Log an error.
Params:
  • e – the exception to log.
/** * Log an error. * @param e the exception to log. */
public void error(final TransformerException e) { logError(e, "Error"); }
Log a fatal error.
Params:
  • e – the exception to log.
/** * Log a fatal error. * @param e the exception to log. */
public void fatalError(final TransformerException e) { logError(e, "Fatal Error"); throw new BuildException("Fatal error during transformation using " + stylesheet + ": " + e.getMessageAndLocation(), e); }
Log a warning.
Params:
  • e – the exception to log.
/** * Log a warning. * @param e the exception to log. */
public void warning(final TransformerException e) { if (!suppressWarnings) { logError(e, "Warning"); } } private void logError(final TransformerException e, final String type) { if (logger == null) { return; } final StringBuffer msg = new StringBuffer(); final SourceLocator locator = e.getLocator(); if (locator != null) { final String systemid = locator.getSystemId(); if (systemid != null) { String url = systemid; if (url.startsWith("file:")) { url = FileUtils.getFileUtils().fromURI(url); } msg.append(url); } else { msg.append("Unknown file"); } final int line = locator.getLineNumber(); if (line != -1) { msg.append(":"); msg.append(line); final int column = locator.getColumnNumber(); if (column != -1) { msg.append(":"); msg.append(column); } } } msg.append(": "); msg.append(type); msg.append("! "); msg.append(e.getMessage()); if (e.getCause() != null) { msg.append(" Cause: "); msg.append(e.getCause()); } logger.log(msg.toString()); } // kept for backwards compatibility
Params:
  • file – the filename to use for the systemid
Returns:the systemid
Deprecated:since 1.5.x. Use org.apache.tools.ant.util.JAXPUtils#getSystemId instead.
/** * @param file the filename to use for the systemid * @return the systemid * @deprecated since 1.5.x. * Use org.apache.tools.ant.util.JAXPUtils#getSystemId instead. */
@Deprecated protected String getSystemId(final File file) { return JAXPUtils.getSystemId(file); }
Specific configuration for the TRaX liaison.
Params:
  • xsltTask – the XSLTProcess task instance from which this liaison is to be configured.
/** * Specific configuration for the TRaX liaison. * @param xsltTask the XSLTProcess task instance from which this liaison * is to be configured. */
public void configure(final XSLTProcess xsltTask) { project = xsltTask.getProject(); final XSLTProcess.Factory factory = xsltTask.getFactory(); if (factory != null) { setFactory(factory.getName()); // configure factory attributes StreamUtils.enumerationAsStream(factory.getAttributes()) .forEach(attr -> setAttribute(attr.getName(), attr.getValue())); factory.getFeatures() .forEach(feature -> setFeature(feature.getName(), feature.getValue())); } final XMLCatalog xmlCatalog = xsltTask.getXMLCatalog(); // use XMLCatalog as the entity resolver and URI resolver if (xmlCatalog != null) { setEntityResolver(xmlCatalog); setURIResolver(xmlCatalog); } // configure output properties StreamUtils.enumerationAsStream(xsltTask.getOutputProperties()) .forEach(prop -> setOutputProperty(prop.getName(), prop.getValue())); suppressWarnings = xsltTask.getSuppressWarnings(); traceConfiguration = xsltTask.getTraceConfiguration(); } private void applyReflectionHackForExtensionMethods() { // Jigsaw doesn't allow reflection to work, so we can stop trying if (!JavaEnvUtils.isAtLeastJavaVersion(JavaEnvUtils.JAVA_9)) { try { // #51668, #52382 final Field _isNotSecureProcessing = tfactory.getClass().getDeclaredField("_isNotSecureProcessing"); _isNotSecureProcessing.setAccessible(true); _isNotSecureProcessing.set(tfactory, Boolean.TRUE); } catch (final Exception x) { if (project != null) { project.log(x.toString(), Project.MSG_DEBUG); } } } } }