| /* |
| * 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 |
| */ |
| public class TraXLiaison implements XSLTLiaison4, ErrorListener, XSLTLoggerAware { |
| |
| /** |
| * Helper for transforming filenames to URIs. |
| * |
| * @since Ant 1.7 |
| */ |
| private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); |
| |
| /** |
| * The current <code>Project</code> |
| */ |
| private Project project; |
| |
| /** |
| * the name of the factory implementation class to use |
| * or null for default JAXP lookup. |
| */ |
| private String factoryName = null; |
| |
| /** The trax TransformerFactory */ |
| private TransformerFactory tfactory = null; |
| |
| /** stylesheet to use for transformation */ |
| private Resource stylesheet; |
| |
| private XSLTLogger logger; |
| |
| /** possible resolver for publicIds */ |
| private EntityResolver entityResolver; |
| |
| /** transformer to use for processing files */ |
| private Transformer transformer; |
| |
| /** The In memory version of the stylesheet */ |
| private Templates templates; |
| |
| /** |
| * The modification time of the stylesheet from which the templates |
| * are read |
| */ |
| private long templatesModTime; |
| |
| /** possible resolver for URIs */ |
| private URIResolver uriResolver; |
| |
| /** transformer output properties */ |
| private final Vector<String[]> outputProperties = new Vector<>(); |
| |
| /** stylesheet parameters */ |
| private final Hashtable<String, Object> params = new Hashtable<>(); |
| |
| /** factory attributes */ |
| private final List<Object[]> attributes = new ArrayList<>(); |
| |
| /** factory features */ |
| private final Map<String, Boolean> features = new HashMap<>(); |
| |
| /** whether to suppress warnings */ |
| private boolean suppressWarnings = false; |
| |
| /** optional trace configuration. */ |
| private XSLTProcess.TraceConfiguration traceConfiguration = null; |
| |
| /** |
| * Constructor for TraXLiaison. |
| * @throws Exception never |
| */ |
| public TraXLiaison() throws Exception { //NOSONAR |
| } |
| |
| /** |
| * 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. |
| * @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. |
| * @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. |
| * @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 |
| */ |
| 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 #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.getDeclaredConstructor().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. |
| */ |
| private void setTransformationParameters() { |
| params.forEach((key, value) -> transformer.setParameter(key, value)); |
| } |
| |
| /** |
| * 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.getDeclaredConstructor().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. |
| * @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. |
| * @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. |
| * @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. |
| * @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. |
| * @param aResolver the resolver class. |
| */ |
| public void setEntityResolver(final EntityResolver aResolver) { |
| entityResolver = aResolver; |
| } |
| |
| /** |
| * 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. |
| * @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. |
| * @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. |
| * @param l a logger. |
| */ |
| public void setLogger(final XSLTLogger l) { |
| logger = l; |
| } |
| |
| /** |
| * Log an error. |
| * @param e the exception to log. |
| */ |
| public void error(final TransformerException e) { |
| logError(e, "Error"); |
| } |
| |
| /** |
| * 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. |
| * @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 StringBuilder msg = new StringBuilder(); |
| 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 |
| /** |
| * @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. |
| * @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); |
| } |
| } |
| } |
| } |
| |
| } |