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.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      * 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 DOMSource(node)
  66      */
  67     public String getDescription() {
  68         return "Uses TrAX to perform transforms from DOMSource(node)";
  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", "dom");
  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. For Xalan-J 1.x this creates an XSLTProcessor. Other
  92      * implementations may or may not actually do any work in this method.
  93      *
  94      * @param options Properties of options, unused.
  95      *
  96      * @return (Object)getProcessor() as a side-effect, this will be null if
  97      * there was any problem creating the processor OR if the underlying
  98      * implementation doesn't use this
  99      * @throws javax.xml.transform.TransformerConfigurationException when 
 100      * actual implementation doesn't support StreamSource or StreamResult.
 101      */
 102     @Override
 103     public TransformerFactory newProcessor(Properties options) throws TransformerConfigurationException {
 104         newProcessorOpts = options;
 105         reset(false);
 106         factory = TransformerFactory.newInstance();
 107         // Verify the factory supports DOM!
 108         if (!(factory.getFeature(DOMSource.FEATURE)
 109                 && factory.getFeature(DOMResult.FEATURE))) {
 110             throw new TransformerConfigurationException("TraxDOMWrapper.newProcessor: factory does not support DOM!");
 111         }
 112         // Set any of our options as Attributes on the factory
 113         TraxWrapperUtils.setAttributes(factory, options);
 114         return factory;
 115     }
 116 
 117     /**
 118      * Transform supplied xmlName file with the stylesheet in the xslName file
 119      * into a resultName file.
 120      *
 121      * Names are assumed to be local path\filename references, and will be
 122      * converted to URLs as needed for any underlying processor implementation.
 123      *
 124      * @param xmlName local path\filename of XML file to transform
 125      * @param xslName local path\filename of XSL stylesheet to use
 126      * @param resultName local path\filename to put result in
 127      *
 128      * @throws Exception any underlying exceptions from the wrapped processor
 129      * are simply allowed to propagate; throws a RuntimeException if any other
 130      * problems prevent us from actually completing the operation
 131      */
 132     @Override
 133     public void transform(String xmlName, String xslName, String resultName)
 134             throws Exception {
 135         preventFootShooting();
 136         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 137         dfactory.setNamespaceAware(true);
 138         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 139 
 140         // Timed: read xsl into a DOM
 141         Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName)));
 142 
 143         // Untimed: create DOMSource and setSystemId
 144         DOMSource xslSource = new DOMSource(xslNode);
 145         xslSource.setSystemId(filenameToURL(xslName));
 146 
 147         // Timed: build Transformer from DOMSource
 148         Transformer transformer = factory.newTransformer(xslSource);
 149 
 150         // Timed: read xml into a DOM
 151         Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName)));
 152 
 153         // Untimed: create DOMSource and setSystemId
 154         DOMSource xmlSource = new DOMSource(xmlNode);
 155         xmlSource.setSystemId(filenameToURL(xmlName));
 156 
 157         // Untimed: create DOMResult
 158         Document outDoc = docBuilder.newDocument();
 159         DocumentFragment outNode = outDoc.createDocumentFragment();
 160         DOMResult domResult = new DOMResult(outNode);
 161 
 162         // Untimed: Set any of our options as Attributes on the transformer
 163         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 164 
 165         // Untimed: Apply any parameters needed
 166         applyParameters(transformer);
 167 
 168         // Timed: build xml (so to speak) and transform
 169         transformer.transform(xmlSource, domResult);
 170 
 171         // Untimed: prepare serializer with outputProperties
 172         //  from the stylesheet
 173         Transformer resultSerializer = factory.newTransformer();
 174         Properties serializationProps = transformer.getOutputProperties();
 175         resultSerializer.setOutputProperties(serializationProps);
 176 
 177         // Timed: writeResults from the DOMResult
 178         resultSerializer.transform(new DOMSource(outNode),
 179                 new StreamResult(resultName));
 180     }
 181 
 182     /**
 183      * Pre-build/pre-compile a stylesheet.
 184      *
 185      * Although the actual mechanics are implementation-dependent, most
 186      * processors have some method of pre-setting up the data needed by the
 187      * stylesheet itself for later use in transforms. In
 188      * TrAX/javax.xml.transform, this equates to creating a Templates object.
 189      *
 190      * Sets isStylesheetReady() to true if it succeeds. Users can then call
 191      * transformWithStylesheet(xmlName, resultName) to actually perform a
 192      * transformation with this pre-built stylesheet.
 193      *
 194      * @param xslName local path\filename of XSL stylesheet to use
 195      *
 196      * @throws Exception any underlying exceptions from the wrapped processor
 197      * are simply allowed to propagate; throws a RuntimeException if any other
 198      * problems prevent us from actually completing the operation
 199      *
 200      * @see #transformWithStylesheet(String xmlName, String resultName)
 201      */
 202     @Override
 203     public void buildStylesheet(String xslName) throws Exception {
 204         preventFootShooting();
 205         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 206         dfactory.setNamespaceAware(true);
 207         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 208 
 209         // Read xsl into a DOM
 210         Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName)));
 211         // Untimed: create DOMSource and setSystemId
 212         DOMSource xslSource = new DOMSource(xslNode);
 213         xslSource.setSystemId(filenameToURL(xslName));
 214 
 215         // Build Templates from DOMSource
 216         builtTemplates = factory.newTemplates(xslSource);
 217 
 218         m_stylesheetReady = true;
 219     }
 220 
 221     /**
 222      * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet
 223      * into a resultName file.
 224      *
 225      * User must have called buildStylesheet(xslName) beforehand, obviously.
 226      * Names are assumed to be local path\filename references, and will be
 227      * converted to URLs as needed.
 228      *
 229      * @param xmlName local path\filename of XML file to transform
 230      * @param resultName local path\filename to put result in
 231      *
 232      * @throws Exception any underlying exceptions from the wrappered processor
 233      * are simply allowed to propagate; throws a RuntimeException if any other
 234      * problems prevent us from actually completing the operation; throws an
 235      * IllegalStateException if isStylesheetReady() == false.
 236      *
 237      * @see #buildStylesheet(String xslName)
 238      */
 239     @Override
 240     public void transformWithStylesheet(String xmlName, String resultName)
 241             throws Exception {
 242         if (!isStylesheetReady()) {
 243             throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false");
 244         }
 245 
 246         preventFootShooting();
 247 
 248         // Get Transformer from Templates
 249         Transformer transformer = builtTemplates.newTransformer();
 250 
 251         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 252         dfactory.setNamespaceAware(true);
 253         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 254 
 255         // Read xml into a DOM
 256         Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName)));
 257 
 258         // Create DOMSource and setSystemId
 259         DOMSource xmlSource = new DOMSource(xmlNode);
 260         xmlSource.setSystemId(filenameToURL(xmlName));
 261 
 262         // Create DOMResult
 263         Document outNode = docBuilder.newDocument();
 264         DOMResult domResult = new DOMResult(outNode);
 265 
 266         // Set any of our options as Attributes on the transformer
 267         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 268 
 269         // Apply any parameters needed
 270         applyParameters(transformer);
 271 
 272         // Build xml (so to speak) and transform
 273         transformer.transform(xmlSource, domResult);
 274 
 275         // Prepare serializer with outputProperties from the stylesheet
 276         Transformer resultSerializer = factory.newTransformer();
 277         Properties serializationProps = transformer.getOutputProperties();
 278         resultSerializer.setOutputProperties(serializationProps);
 279 
 280         // writeResults from the DOMResult
 281         resultSerializer.transform(new DOMSource(outNode),
 282                 new StreamResult(resultName));
 283     }
 284 
 285     /**
 286      * Transform supplied xmlName file with a stylesheet found in an
 287      * xml-stylesheet PI into a resultName file.
 288      *
 289      * Names are assumed to be local path\filename references, and will be
 290      * converted to URLs as needed. Implementations will use whatever facilities
 291      * exist in their wrapped processor to fetch and build the stylesheet to
 292      * use for the transform.
 293      *
 294      * @param xmlName local path\filename of XML file to transform
 295      * @param resultName local path\filename to put result in
 296      *
 297      * @throws Exception any underlying exceptions from the wrapped processor
 298      * are simply allowed to propagate; throws a RuntimeException if any other
 299      * problems prevent us from actually completing the operation
 300      */
 301     @Override
 302     public void transformEmbedded(String xmlName, String resultName)
 303             throws Exception {
 304         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
 305         dfactory.setNamespaceAware(true);
 306         DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
 307 
 308         // read xml into a DOM
 309         Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName)));
 310 
 311         // create DOMSource and setSystemId
 312         DOMSource xmlSource = new DOMSource(xmlNode);
 313         xmlSource.setSystemId(filenameToURL(xmlName));
 314 
 315         // readxsl from the xml document
 316         Source xslSource = factory.getAssociatedStylesheet(xmlSource,
 317                 null, null, null);
 318 
 319         // build Transformer from Source
 320         Transformer transformer = factory.newTransformer(xslSource);
 321 
 322         // create DOMResult
 323         Document outNode = docBuilder.newDocument();
 324         DOMResult domResult = new DOMResult(outNode);
 325 
 326         // Set any of our options as Attributes on the transformer
 327         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 328 
 329         // Apply any parameters needed
 330         applyParameters(transformer);
 331 
 332         // Build xml (so to speak) and transform
 333         transformer.transform(xmlSource, domResult);
 334 
 335         // Prepare serializer with outputProperties from the stylesheet
 336         Transformer resultSerializer = factory.newTransformer();
 337         Properties serializationProps = transformer.getOutputProperties();
 338         resultSerializer.setOutputProperties(serializationProps);
 339 
 340         // writeResults from the DOMResult
 341         resultSerializer.transform(new DOMSource(outNode),
 342                 new StreamResult(resultName));
 343     }
 344 
 345     /**
 346      * Reset our parameters and wrapper state, and optionally force creation of
 347      * a new underlying processor implementation.
 348      *
 349      * This always clears our built stylesheet and any parameters that have been
 350      * set. If newProcessor is true, also forces a re-creation of our underlying
 351      * processor as if by calling newProcessor().
 352      *
 353      * @param newProcessor if we should reset our underlying processor
 354      * implementation as well
 355      */
 356     @Override
 357     public void reset(boolean newProcessor) {
 358         super.reset(newProcessor); // clears indent and parameters
 359         m_stylesheetReady = false;
 360         builtTemplates = null;
 361         if (newProcessor) {
 362             try {
 363                 newProcessor(newProcessorOpts);
 364             } catch (TransformerConfigurationException ignore) {}
 365         }
 366     }
 367 
 368     /**
 369      * Apply a single parameter to a Transformer.
 370      *
 371      * Overridden to take a Transformer and call setParameter().
 372      *
 373      * @param transformer a Transformer object.
 374      * @param namespace for the parameter, may be null
 375      * @param name for the parameter, should not be null
 376      * @param value for the parameter, may be null
 377      */
 378     @Override
 379     protected void applyParameter(Transformer transformer, String namespace,
 380             String name, String value) {
 381         try {
 382             // Munge the namespace into the name per
 383             //  javax.xml.transform.Transformer.setParameter()
 384             if (null != namespace) {
 385                 name = "{" + namespace + "}" + name;
 386             }
 387             transformer.setParameter(name, value);
 388         } catch (Exception e) {
 389             throw new IllegalArgumentException("applyParameter threw: " + e.toString());
 390         }
 391     }
 392 }