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