1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 package org.apache.qetest.xslwrapper;
  21 
  22 import java.io.ByteArrayOutputStream;
  23 import java.io.FileOutputStream;
  24 import java.util.Properties;
  25 import javax.xml.parsers.SAXParserFactory;
  26 import javax.xml.transform.Templates;
  27 import javax.xml.transform.Transformer;
  28 import javax.xml.transform.TransformerConfigurationException;
  29 import javax.xml.transform.TransformerFactory;
  30 import javax.xml.transform.sax.SAXResult;
  31 import javax.xml.transform.sax.SAXSource;
  32 import javax.xml.transform.sax.SAXTransformerFactory;
  33 import javax.xml.transform.sax.TemplatesHandler;
  34 import javax.xml.transform.sax.TransformerHandler;
  35 import javax.xml.transform.stream.StreamResult;
  36 import static jaxp.library.JAXPTestUtilities.filenameToURL;
  37 import org.xml.sax.SAXException;
  38 import org.xml.sax.XMLReader;
  39 
  40 /**
  41  * Implementation of TransformWrapper that uses the TrAX API and uses
  42  * SAXSource/SAXResult whenever possible.
  43  * This implementation uses SAX to build the stylesheet and to perform the
  44  * transformation.
  45  * <p><b>Important!</b> The underlying System property of
  46  * javax.xml.transform.TransformerFactory will determine the actual TrAX
  47  * implementation used. This value will be reported out in our
  48  * getProcessorInfo() method.</p>
  49  *
  50  */
  51 public class TraxSAXWrapper extends TransformWrapperHelper {
  52 
  53     /**
  54      * TransformerFactory to use; constructed in newProcessor().
  55      */
  56     private TransformerFactory factory;
  57 
  58     /**
  59      * SAXTransformerFactory we actually use; constructed in newProcessor().
  60      */
  61     private SAXTransformerFactory saxFactory;
  62 
  63     /**
  64      * Templates to use for buildStylesheet().
  65      */
  66     private Templates builtTemplates;
  67 
  68     /**
  69      * Cached copy of newProcessor() properties.
  70      */
  71     protected Properties newProcessorOpts = null;
  72 
  73     /**
  74      * Get a general description of this wrapper itself.
  75      *
  76      * @return Uses TrAX to perform transforms from SAXSource(systemId)
  77      */
  78     public String getDescription() {
  79         return "Uses TrAX to perform transforms from SAXSource(stream)";
  80     }
  81 
  82     /**
  83      * Get a specific description of the wrappered processor.
  84      *
  85      * @return specific description of the underlying processor or transformer
  86      * implementation: this should include both the general product name, as
  87      * well as specific version info. If possible, should be implemented without
  88      * actively creating an underlying processor.
  89      */
  90     @Override
  91     public Properties getProcessorInfo() {
  92         Properties p = TraxWrapperUtils.getTraxInfo();
  93         p.put("traxwrapper.method", "sax");
  94         p.put("traxwrapper.desc", getDescription());
  95         return p;
  96     }
  97 
  98     /**
  99      * Actually create/initialize an underlying processor or factory.
 100      *
 101      * For TrAX/javax.xml.transform implementations, this creates a new
 102      * TransformerFactory.
 103      *
 104      * @param options Hashtable of options, unused.
 105      *
 106      * @return (Object)getProcessor() as a side-effect, this will be null if
 107      * there was any problem creating the processor OR if the underlying
 108      * implementation doesn't use this
 109      * @throws javax.xml.transform.TransformerConfigurationException when 
 110      * actual implementation doesn't support StreamSource or StreamResult.
 111      */
 112     @Override
 113     public TransformerFactory newProcessor(Properties options) throws TransformerConfigurationException {
 114         newProcessorOpts = options;
 115         reset(false);
 116         factory = TransformerFactory.newInstance();
 117         // Verify the factory supports SAX!
 118         if (!(factory.getFeature(SAXSource.FEATURE)
 119                 && factory.getFeature(SAXResult.FEATURE))) {
 120             throw new TransformerConfigurationException("TraxSAXWrapper.newProcessor: factory does not support SAX!");
 121         }
 122         // Set any of our options as Attributes on the factory
 123         TraxWrapperUtils.setAttributes(factory, options);
 124         saxFactory = (SAXTransformerFactory) factory;
 125         return saxFactory;
 126     }
 127 
 128     /**
 129      * Transform supplied xmlName file with the stylesheet in the xslName file
 130      * into a resultName file using SAX.
 131      *
 132      * Pseudocode:      <code>
 133      *   // Read/build stylesheet
 134      *   xslReader.setContentHandler(templatesHandler);
 135      *   xslReader.parse(xslName);
 136      *
 137      *   xslOutputProps = templates.getOutputProperties();
 138      *   // Set features and tie in DTD, lexical, etc. handling
 139      *
 140      *   serializingHandler.getTransformer().setOutputProperties(xslOutputProps);
 141      *   serializingHandler.setResult(new StreamResult(outBytes));
 142      *   stylesheetHandler.setResult(new SAXResult(serializingHandler));
 143      *   xmlReader.setContentHandler(stylesheetHandler);
 144      *   // Perform Transform
 145      *   xmlReader.parse(xmlName);
 146      *   // Separately: write bytes to disk
 147      * </code>
 148      *
 149      * @param xmlName local path\filename of XML file to transform
 150      * @param xslName local path\filename of XSL stylesheet to use
 151      * @param resultName local path\filename to put result in
 152      *
 153      * @throws Exception any underlying exceptions from the wrappered processor
 154      * are simply allowed to propagate; throws a RuntimeException if any other
 155      * problems prevent us from actually completing the operation
 156      */
 157     @Override
 158     public void transform(String xmlName, String xslName, String resultName)
 159             throws Exception {
 160         preventFootShooting();
 161 
 162         // Create a ContentHandler to handle parsing of the xsl
 163         TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler();
 164 
 165         // Create an XMLReader and set its ContentHandler.
 166         // Be sure to use the JAXP methods only!
 167         XMLReader xslReader = getJAXPXMLReader();
 168         xslReader.setContentHandler(templatesHandler);
 169 
 170         // read/build Templates from StreamSource
 171         xslReader.parse(filenameToURL(xslName));
 172 
 173         // Get the Templates object from the ContentHandler.
 174         Templates templates = templatesHandler.getTemplates();
 175         // Get the outputProperties from the stylesheet, so
 176         //  we can later use them for the serialization
 177         Properties xslOutputProps = templates.getOutputProperties();
 178 
 179         // Create a ContentHandler to handle parsing of the XML
 180         TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(templates);
 181         // Also set systemId to the stylesheet
 182         stylesheetHandler.setSystemId(filenameToURL(xslName));
 183 
 184         // Untimed: Set any of our options as Attributes on the transformer
 185         TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts);
 186 
 187         // Apply any parameters needed
 188         applyParameters(stylesheetHandler.getTransformer());
 189 
 190         // Use a new XMLReader to parse the XML document
 191         XMLReader xmlReader = getJAXPXMLReader();
 192         xmlReader.setContentHandler(stylesheetHandler);
 193 
 194         // Set the ContentHandler to also function as LexicalHandler,
 195         // includes "lexical" events (e.g., comments and CDATA).
 196         xmlReader.setProperty(
 197                 "http://xml.org/sax/properties/lexical-handler",
 198                 stylesheetHandler);
 199 
 200         // Also attempt to set as a DeclHandler, which Xalan-J
 201         //  supports even though it is not required by JAXP
 202         // Ignore exceptions for other processors since this
 203         //  is not a required setting
 204         try {
 205             xmlReader.setProperty(
 206                     "http://xml.org/sax/properties/declaration-handler",
 207                     stylesheetHandler);
 208         } catch (SAXException se) { /* no-op - ignore */ }
 209 
 210         // added by sb. Tie together DTD and other handling
 211         xmlReader.setDTDHandler(stylesheetHandler);
 212         try {
 213             xmlReader.setFeature(
 214                     "http://xml.org/sax/features/namespace-prefixes",
 215                     true);
 216         } catch (SAXException se) { /* no-op - ignore */ }
 217         try {
 218             xmlReader.setFeature(
 219                     "http://apache.org/xml/features/validation/dynamic",
 220                     true);
 221         } catch (SAXException se) { /* no-op - ignore */ }
 222 
 223         // Create a 'pipe'-like identity transformer, so we can
 224         //  easily get our results serialized to disk
 225         TransformerHandler serializingHandler = saxFactory.newTransformerHandler();
 226         // Set the stylesheet's output properties into the output
 227         //  via it's Transformer
 228         serializingHandler.getTransformer().setOutputProperties(xslOutputProps);
 229 
 230         // Create StreamResult to byte stream in memory
 231         ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
 232         StreamResult byteResult = new StreamResult(outBytes);
 233         serializingHandler.setResult(byteResult);
 234 
 235         // Create a SAXResult dumping into our 'pipe' serializer
 236         //  and tie in lexical handling (is any other handling needed?)
 237         SAXResult saxResult = new SAXResult(serializingHandler);
 238         saxResult.setLexicalHandler(serializingHandler);
 239 
 240         // Set the original stylesheet to dump into our result
 241         stylesheetHandler.setResult(saxResult);
 242 
 243         // Parse the XML input document and do transform
 244         xmlReader.parse(filenameToURL(xmlName));
 245 
 246         // writeResults from the byte array
 247         byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not?
 248         try (FileOutputStream writeStream = new FileOutputStream(resultName)) {
 249             writeStream.write(writeBytes);
 250         }
 251     }
 252 
 253     /**
 254      * Pre-build/pre-compile a stylesheet.
 255      *
 256      * Although the actual mechanics are implementation-dependent, most
 257      * processors have some method of pre-setting up the data needed by the
 258      * stylesheet itself for later use in transforms. In
 259      * TrAX/javax.xml.transform, this equates to creating a Templates object.
 260      *
 261      * Sets isStylesheetReady() to true if it succeeds. Users can then call
 262      * transformWithStylesheet(xmlName, resultName) to actually perform a
 263      * transformation with this pre-built stylesheet.
 264      *
 265      * @param xslName local path\filename of XSL stylesheet to use
 266      *
 267      * @throws Exception any underlying exceptions from the wrapped processor
 268      * are simply allowed to propagate; throws a RuntimeException if any other
 269      * problems prevent us from actually completing the operation
 270      *
 271      * @see #transformWithStylesheet(String xmlName, String resultName)
 272      */
 273     @Override
 274     public void buildStylesheet(String xslName) throws Exception {
 275         preventFootShooting();
 276 
 277         // Create a ContentHandler to handle parsing of the xsl
 278         TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler();
 279 
 280         // Create an XMLReader and set its ContentHandler.
 281         XMLReader xslReader = getJAXPXMLReader();
 282         xslReader.setContentHandler(templatesHandler);
 283 
 284         // read/build Templates from StreamSource
 285         xslReader.parse(filenameToURL(xslName));
 286 
 287         // Also set systemId to the stylesheet
 288         templatesHandler.setSystemId(filenameToURL(xslName));
 289 
 290         // Get the Templates object from the ContentHandler.
 291         builtTemplates = templatesHandler.getTemplates();
 292         m_stylesheetReady = true;
 293     }
 294 
 295     /**
 296      * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet
 297      * into a resultName file.
 298      *
 299      * User must have called buildStylesheet(xslName) beforehand, obviously.
 300      * Names are assumed to be local path\filename references, and will be
 301      * converted to URLs as needed.
 302      *
 303      * @param xmlName local path\filename of XML file to transform
 304      * @param resultName local path\filename to put result in
 305      *
 306      * @throws Exception any underlying exceptions from the wrappered processor
 307      * are simply allowed to propagate; throws a RuntimeException if any other
 308      * problems prevent us from actually completing the operation; throws an
 309      * IllegalStateException if isStylesheetReady() == false.
 310      *
 311      * @see #buildStylesheet(String xslName)
 312      */
 313     @Override
 314     public void transformWithStylesheet(String xmlName, String resultName)
 315             throws Exception {
 316         if (!isStylesheetReady()) {
 317             throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false");
 318         }
 319         preventFootShooting();
 320 
 321         // Get the outputProperties from the stylesheet, so
 322         //  we can later use them for the serialization
 323         Properties xslOutputProps = builtTemplates.getOutputProperties();
 324 
 325         // Create a ContentHandler to handle parsing of the XML
 326         TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(builtTemplates);
 327 
 328         // Untimed: Set any of our options as Attributes on the transformer
 329         TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts);
 330 
 331         // Apply any parameters needed
 332         applyParameters(stylesheetHandler.getTransformer());
 333 
 334         // Use a new XMLReader to parse the XML document
 335         XMLReader xmlReader = getJAXPXMLReader();
 336         xmlReader.setContentHandler(stylesheetHandler);
 337 
 338         // Set the ContentHandler to also function as LexicalHandler,
 339         // includes "lexical" events (e.g., comments and CDATA).
 340         xmlReader.setProperty(
 341                 "http://xml.org/sax/properties/lexical-handler",
 342                 stylesheetHandler);
 343         xmlReader.setProperty(
 344                 "http://xml.org/sax/properties/declaration-handler",
 345                 stylesheetHandler);
 346 
 347         // added by sb. Tie together DTD and other handling
 348         xmlReader.setDTDHandler(stylesheetHandler);
 349         try {
 350             xmlReader.setFeature(
 351                     "http://xml.org/sax/features/namespace-prefixes",
 352                     true);
 353         } catch (SAXException se) { /* no-op - ignore */ }
 354         try {
 355             xmlReader.setFeature(
 356                     "http://apache.org/xml/features/validation/dynamic",
 357                     true);
 358         } catch (SAXException se) { /* no-op - ignore */ }
 359 
 360         // Create a 'pipe'-like identity transformer, so we can
 361         //  easily get our results serialized to disk
 362         TransformerHandler serializingHandler = saxFactory.newTransformerHandler();
 363         // Set the stylesheet's output properties into the output
 364         //  via it's Transformer
 365         serializingHandler.getTransformer().setOutputProperties(xslOutputProps);
 366 
 367         // Create StreamResult to byte stream in memory
 368         ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
 369         StreamResult byteResult = new StreamResult(outBytes);
 370         serializingHandler.setResult(byteResult);
 371 
 372         // Create a SAXResult dumping into our 'pipe' serializer
 373         //  and tie in lexical handling (is any other handling needed?)
 374         SAXResult saxResult = new SAXResult(serializingHandler);
 375         saxResult.setLexicalHandler(serializingHandler);
 376 
 377         // Set the original stylesheet to dump into our result
 378         stylesheetHandler.setResult(saxResult);
 379 
 380         // Timed: Parse the XML input document and do transform
 381         xmlReader.parse(filenameToURL(xmlName));
 382 
 383         // Timed: writeResults from the byte array
 384         byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not?
 385         try (FileOutputStream writeStream = new FileOutputStream(resultName)) {
 386             writeStream.write(writeBytes);
 387         }
 388     }
 389 
 390     /**
 391      * Transform supplied xmlName file with a stylesheet found in an
 392      * xml-stylesheet PI into a resultName file.
 393      *
 394      * Names are assumed to be local path\filename references, and will be
 395      * converted to URLs as needed. Implementations will use whatever facilities
 396      * exist in their wrappered processor to fetch and build the stylesheet to
 397      * use for the transform.
 398      *
 399      * @param xmlName local path\filename of XML file to transform
 400      * @param resultName local path\filename to put result in
 401      *
 402      * @throws Exception any underlying exceptions from the wrappered processor
 403      * are simply allowed to propagate; throws a RuntimeException if any other
 404      * problems prevent us from actually completing the operation
 405      */
 406     @Override
 407     public void transformEmbedded(String xmlName, String resultName)
 408             throws Exception {
 409         throw new RuntimeException("TraxSAXWrapper.transformEmbedded not implemented yet!");
 410     }
 411 
 412     /**
 413      * Reset our parameters and wrapper state, and optionally force creation of
 414      * a new underlying processor implementation.
 415      *
 416      * This always clears our built stylesheet and any parameters that have been
 417      * set. If newProcessor is true, also forces a re-creation of our underlying
 418      * processor as if by calling newProcessor().
 419      *
 420      * @param newProcessor if we should reset our underlying processor
 421      * implementation as well
 422      */
 423     @Override
 424     public void reset(boolean newProcessor) {
 425         super.reset(newProcessor); // clears indent and parameters
 426         m_stylesheetReady = false;
 427         builtTemplates = null;
 428         if (newProcessor) {
 429             try {
 430                 newProcessor(newProcessorOpts);
 431             } catch (TransformerConfigurationException ignore) {}
 432         }
 433     }
 434 
 435     /**
 436      * Apply a single parameter to a Transformer.
 437      *
 438      * Overridden to take a Transformer and call setParameter().
 439      *
 440      * @param transformer a Transformer object.
 441      * @param namespace for the parameter, may be null
 442      * @param name for the parameter, should not be null
 443      * @param value for the parameter, may be null
 444      */
 445     @Override
 446     protected void applyParameter(Transformer transformer, String namespace,
 447             String name, String value) {
 448         try {
 449             // Munge the namespace into the name per
 450             //  javax.xml.transform.Transformer.setParameter()
 451             if (null != namespace) {
 452                 name = "{" + namespace + "}" + name;
 453             }
 454             transformer.setParameter(name, value);
 455         } catch (Exception e) {
 456             throw new IllegalArgumentException("applyParameter threw: " + e.toString());
 457         }
 458     }
 459 
 460     /**
 461      * Ensure newProcessor has been called when needed.
 462      *
 463      * Prevent users from shooting themselves in the foot by calling a
 464      * transform* API before newProcessor().
 465      *
 466      * (Sorry, I couldn't resist)
 467      * @throws java.lang.Exception
 468      */
 469     public void preventFootShooting() throws Exception {
 470         if (null == factory) {
 471             newProcessor(newProcessorOpts);
 472         }
 473     }
 474 
 475     /**
 476      * Worker method to get an XMLReader.
 477      *
 478      * Not the most efficient of methods, but makes the code simpler.
 479      *
 480      * @return a new XMLReader for use, with setNamespaceAware(true)
 481      * @throws java.lang.Exception
 482      */
 483     protected XMLReader getJAXPXMLReader() throws Exception {
 484         // Be sure to use the JAXP methods only!
 485         SAXParserFactory sfactory = SAXParserFactory.newInstance();
 486         sfactory.setNamespaceAware(true);
 487         return sfactory.newSAXParser().getXMLReader();
 488     }
 489 }