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.util.Properties;
  23 import javax.xml.parsers.DocumentBuilder;
  24 import javax.xml.parsers.DocumentBuilderFactory;
  25 import javax.xml.transform.Source;
  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.dom.DOMResult;
  31 import javax.xml.transform.dom.DOMSource;
  32 import javax.xml.transform.stream.StreamResult;
  33 import static jaxp.library.JAXPTestUtilities.filenameToURL;
  34 import org.w3c.dom.Document;
  35 import org.w3c.dom.DocumentFragment;
  36 import org.w3c.dom.Node;
  37 import org.xml.sax.InputSource;
  38 
  39 /**
  40  * Implementation of TransformWrapper that uses the TrAX API and uses DOMs for
  41  * it's sources.
  42  *
  43  * This implementation records separate times for xslRead (time to parse the xsl
  44  * and build a DOM) and xslBuild (time to take the DOMSource object until it's
  45  * built the templates); xmlRead (time to parse the xml and build a DOM). Note
  46  * xmlBuild is not timed since it's not easily measureable in TrAX. The
  47  * transform time is just the time to create the DOMResult object; the
  48  * resultsWrite is the separate time it takes to serialize that to disk.
  49  *
  50  * <b>Important!</b> The underlying System property of
  51  * javax.xml.transform.TransformerFactory will determine the actual TrAX
  52  * implementation used. This value will be reported out in our
  53  * getProcessorInfo() method.
  54  *
  55  */
  56 public class TraxDOMWrapper extends TransformWrapperHelper {
  57 
  58     /**
  59      * TransformerFactory to use; constructed in newProcessor().
  60      */
  61     private  TransformerFactory factory;
  62 
  63     /**
  64      * Templates to use for buildStylesheet().
  65      */
  66     private Templates builtTemplates;
  67 
  68     /**
  69      * Cached copy of newProcessor() properties.
  70      */
  71     private Properties newProcessorOpts;
  72 
  73     /**
  74      * Get a general description of this wrapper itself.
  75      *
  76      * @return Uses TrAX to perform transforms from DOMSource(node)
  77      */
  78     public String getDescription() {
  79         return "Uses TrAX to perform transforms from DOMSource(node)";
  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", "dom");
  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. For Xalan-J 1.x this creates an XSLTProcessor. Other
 103      * implementations may or may not actually do any work in this method.
 104      *
 105      * @param options Properties of options, unused.
 106      *
 107      * @return (Object)getProcessor() as a side-effect, this will be null if
 108      * there was any problem creating the processor OR if the underlying
 109      * implementation doesn't use this
 110      * @throws javax.xml.transform.TransformerConfigurationException when 
 111      * actual implementation doesn't support StreamSource or StreamResult.
 112      */
 113     @Override
 114     public TransformerFactory newProcessor(Properties options) throws TransformerConfigurationException {
 115         newProcessorOpts = options;
 116         reset(false);
 117         factory = TransformerFactory.newInstance();
 118         // Verify the factory supports DOM!
 119         if (!(factory.getFeature(DOMSource.FEATURE)
 120                 && factory.getFeature(DOMResult.FEATURE))) {
 121             throw new TransformerConfigurationException("TraxDOMWrapper.newProcessor: factory does not support DOM!");
 122         }
 123         // Set any of our options as Attributes on the factory
 124         TraxWrapperUtils.setAttributes(factory, options);
 125         return factory;
 126     }
 127 
 128     /**
 129      * Transform supplied xmlName file with the stylesheet in the xslName file
 130      * into a resultName file.
 131      *
 132      * Names are assumed to be local path\filename references, and will be
 133      * converted to URLs as needed for any underlying processor implementation.
 134      *
 135      * @param xmlName local path\filename of XML file to transform
 136      * @param xslName local path\filename of XSL stylesheet to use
 137      * @param resultName local path\filename to put result in
 138      *
 139      * @throws Exception any underlying exceptions from the wrapped processor
 140      * are simply allowed to propagate; throws a RuntimeException if any other
 141      * problems prevent us from actually completing the operation
 142      */
 143     @Override
 144     public void transform(String xmlName, String xslName, String resultName)
 145             throws Exception {
 146         preventFootShooting();
 147         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 148         dfactory.setNamespaceAware(true);
 149         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 150 
 151         // Timed: read xsl into a DOM
 152         Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName)));
 153 
 154         // Untimed: create DOMSource and setSystemId
 155         DOMSource xslSource = new DOMSource(xslNode);
 156         xslSource.setSystemId(filenameToURL(xslName));
 157 
 158         // Timed: build Transformer from DOMSource
 159         Transformer transformer = factory.newTransformer(xslSource);
 160 
 161         // Timed: read xml into a DOM
 162         Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName)));
 163 
 164         // Untimed: create DOMSource and setSystemId
 165         DOMSource xmlSource = new DOMSource(xmlNode);
 166         xmlSource.setSystemId(filenameToURL(xmlName));
 167 
 168         // Untimed: create DOMResult
 169         Document outDoc = docBuilder.newDocument();
 170         DocumentFragment outNode = outDoc.createDocumentFragment();
 171         DOMResult domResult = new DOMResult(outNode);
 172 
 173         // Untimed: Set any of our options as Attributes on the transformer
 174         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 175 
 176         // Untimed: Apply any parameters needed
 177         applyParameters(transformer);
 178 
 179         // Timed: build xml (so to speak) and transform
 180         transformer.transform(xmlSource, domResult);
 181 
 182         // Untimed: prepare serializer with outputProperties
 183         //  from the stylesheet
 184         Transformer resultSerializer = factory.newTransformer();
 185         Properties serializationProps = transformer.getOutputProperties();
 186         resultSerializer.setOutputProperties(serializationProps);
 187 
 188         // Timed: writeResults from the DOMResult
 189         resultSerializer.transform(new DOMSource(outNode),
 190                 new StreamResult(resultName));
 191     }
 192 
 193     /**
 194      * Pre-build/pre-compile a stylesheet.
 195      *
 196      * Although the actual mechanics are implementation-dependent, most
 197      * processors have some method of pre-setting up the data needed by the
 198      * stylesheet itself for later use in transforms. In
 199      * TrAX/javax.xml.transform, this equates to creating a Templates object.
 200      *
 201      * Sets isStylesheetReady() to true if it succeeds. Users can then call
 202      * transformWithStylesheet(xmlName, resultName) to actually perform a
 203      * transformation with this pre-built stylesheet.
 204      *
 205      * @param xslName local path\filename of XSL stylesheet to use
 206      *
 207      * @throws Exception any underlying exceptions from the wrapped processor
 208      * are simply allowed to propagate; throws a RuntimeException if any other
 209      * problems prevent us from actually completing the operation
 210      *
 211      * @see #transformWithStylesheet(String xmlName, String resultName)
 212      */
 213     @Override
 214     public void buildStylesheet(String xslName) throws Exception {
 215         preventFootShooting();
 216         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 217         dfactory.setNamespaceAware(true);
 218         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 219 
 220         // Read xsl into a DOM
 221         Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName)));
 222         // Untimed: create DOMSource and setSystemId
 223         DOMSource xslSource = new DOMSource(xslNode);
 224         xslSource.setSystemId(filenameToURL(xslName));
 225 
 226         // Build Templates from DOMSource
 227         builtTemplates = factory.newTemplates(xslSource);
 228 
 229         m_stylesheetReady = true;
 230     }
 231 
 232     /**
 233      * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet
 234      * into a resultName file.
 235      *
 236      * User must have called buildStylesheet(xslName) beforehand, obviously.
 237      * Names are assumed to be local path\filename references, and will be
 238      * converted to URLs as needed.
 239      *
 240      * @param xmlName local path\filename of XML file to transform
 241      * @param resultName local path\filename to put result in
 242      *
 243      * @throws Exception any underlying exceptions from the wrappered processor
 244      * are simply allowed to propagate; throws a RuntimeException if any other
 245      * problems prevent us from actually completing the operation; throws an
 246      * IllegalStateException if isStylesheetReady() == false.
 247      *
 248      * @see #buildStylesheet(String xslName)
 249      */
 250     @Override
 251     public void transformWithStylesheet(String xmlName, String resultName)
 252             throws Exception {
 253         if (!isStylesheetReady()) {
 254             throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false");
 255         }
 256 
 257         preventFootShooting();
 258 
 259         // Get Transformer from Templates
 260         Transformer transformer = builtTemplates.newTransformer();
 261 
 262         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 263         dfactory.setNamespaceAware(true);
 264         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 265 
 266         // Read xml into a DOM
 267         Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName)));
 268 
 269         // Create DOMSource and setSystemId
 270         DOMSource xmlSource = new DOMSource(xmlNode);
 271         xmlSource.setSystemId(filenameToURL(xmlName));
 272 
 273         // Create DOMResult
 274         Document outNode = docBuilder.newDocument();
 275         DOMResult domResult = new DOMResult(outNode);
 276 
 277         // Set any of our options as Attributes on the transformer
 278         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 279 
 280         // Apply any parameters needed
 281         applyParameters(transformer);
 282 
 283         // Build xml (so to speak) and transform
 284         transformer.transform(xmlSource, domResult);
 285 
 286         // Prepare serializer with outputProperties from the stylesheet
 287         Transformer resultSerializer = factory.newTransformer();
 288         Properties serializationProps = transformer.getOutputProperties();
 289         resultSerializer.setOutputProperties(serializationProps);
 290 
 291         // writeResults from the DOMResult
 292         resultSerializer.transform(new DOMSource(outNode),
 293                 new StreamResult(resultName));
 294     }
 295 
 296     /**
 297      * Transform supplied xmlName file with a stylesheet found in an
 298      * xml-stylesheet PI into a resultName file.
 299      *
 300      * Names are assumed to be local path\filename references, and will be
 301      * converted to URLs as needed. Implementations will use whatever facilities
 302      * exist in their wrapped processor to fetch and build the stylesheet to
 303      * use for the transform.
 304      *
 305      * @param xmlName local path\filename of XML file to transform
 306      * @param resultName local path\filename to put result in
 307      *
 308      * @throws Exception any underlying exceptions from the wrapped processor
 309      * are simply allowed to propagate; throws a RuntimeException if any other
 310      * problems prevent us from actually completing the operation
 311      */
 312     @Override
 313     public void transformEmbedded(String xmlName, String resultName)
 314             throws Exception {
 315         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 316         dfactory.setNamespaceAware(true);
 317         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 318 
 319         // read xml into a DOM
 320         Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName)));
 321 
 322         // create DOMSource and setSystemId
 323         DOMSource xmlSource = new DOMSource(xmlNode);
 324         xmlSource.setSystemId(filenameToURL(xmlName));
 325 
 326         // readxsl from the xml document
 327         Source xslSource = factory.getAssociatedStylesheet(xmlSource,
 328                 null, null, null);
 329 
 330         // build Transformer from Source
 331         Transformer transformer = factory.newTransformer(xslSource);
 332 
 333         // create DOMResult
 334         Document outNode = docBuilder.newDocument();
 335         DOMResult domResult = new DOMResult(outNode);
 336 
 337         // Set any of our options as Attributes on the transformer
 338         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 339 
 340         // Apply any parameters needed
 341         applyParameters(transformer);
 342 
 343         // Build xml (so to speak) and transform
 344         transformer.transform(xmlSource, domResult);
 345 
 346         // Prepare serializer with outputProperties from the stylesheet
 347         Transformer resultSerializer = factory.newTransformer();
 348         Properties serializationProps = transformer.getOutputProperties();
 349         resultSerializer.setOutputProperties(serializationProps);
 350 
 351         // writeResults from the DOMResult
 352         resultSerializer.transform(new DOMSource(outNode),
 353                 new StreamResult(resultName));
 354     }
 355 
 356     /**
 357      * Reset our parameters and wrapper state, and optionally force creation of
 358      * a new underlying processor implementation.
 359      *
 360      * This always clears our built stylesheet and any parameters that have been
 361      * set. If newProcessor is true, also forces a re-creation of our underlying
 362      * processor as if by calling newProcessor().
 363      *
 364      * @param newProcessor if we should reset our underlying processor
 365      * implementation as well
 366      */
 367     @Override
 368     public void reset(boolean newProcessor) {
 369         super.reset(newProcessor); // clears indent and parameters
 370         m_stylesheetReady = false;
 371         builtTemplates = null;
 372         if (newProcessor) {
 373             try {
 374                 newProcessor(newProcessorOpts);
 375             } catch (TransformerConfigurationException ignore) {}
 376         }
 377     }
 378 
 379     /**
 380      * Apply a single parameter to a Transformer.
 381      *
 382      * Overridden to take a Transformer and call setParameter().
 383      *
 384      * @param transformer a Transformer object.
 385      * @param namespace for the parameter, may be null
 386      * @param name for the parameter, should not be null
 387      * @param value for the parameter, may be null
 388      */
 389     @Override
 390     protected void applyParameter(Transformer transformer, String namespace,
 391             String name, String value) {
 392         try {
 393             // Munge the namespace into the name per
 394             //  javax.xml.transform.Transformer.setParameter()
 395             if (null != namespace) {
 396                 name = "{" + namespace + "}" + name;
 397             }
 398             transformer.setParameter(name, value);
 399         } catch (Exception e) {
 400             throw new IllegalArgumentException("applyParameter threw: " + e.toString());
 401         }
 402     }
 403 
 404     /**
 405      * Ensure newProcessor has been called when needed.
 406      *
 407      * Prevent users from shooting themselves in the foot by calling a
 408      * transform* API before newProcessor().
 409      *
 410      * @throws java.lang.Exception
 411      */
 412     public void preventFootShooting() throws Exception {
 413         if (null == factory) {
 414             newProcessor(newProcessorOpts);
 415         }
 416     }
 417 }