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.File;
  23 import java.util.Properties;
  24 
  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.stream.StreamResult;
  31 import javax.xml.transform.stream.StreamSource;
  32 
  33 /**
  34  * Implementation of TransformWrapper that uses the TrAX API and uses files for
  35  * it's sources.
  36  *
  37  * This is the second most common usage: transformer =
  38  * factory.newTransformer(new StreamSource(new File(xslName)));
  39  * transformer.transform(new StreamSource(new File(xmlName)), new
  40  * StreamResult(resultFileName));
  41  *
  42  * <b>Important!</b> The underlying System property of
  43  * javax.xml.transform.TransformerFactory will determine the actual TrAX
  44  * implementation used. This value will be reported out in our
  45  * getProcessorInfo() method.
  46  *
  47  */
  48 public class TraxFileWrapper extends TransformWrapperHelper {
  49 
  50     /**
  51      * TransformerFactory to use; constructed in newProcessor().
  52      */
  53     private TransformerFactory factory;
  54 
  55     /**
  56      * Templates to use for buildStylesheet().
  57      */
  58     private Templates builtTemplates;
  59 
  60     /**
  61      * Cached copy of newProcessor() properties.
  62      */
  63     private Properties newProcessorOpts;
  64 
  65     /**
  66      * Get a general description of this wrapper itself.
  67      *
  68      * @return Uses TrAX to perform transforms from StreamSource(systemId)
  69      */
  70     public String getDescription() {
  71         return "Uses TrAX to perform transforms from StreamSource(new File(filename))";
  72     }
  73 
  74     /**
  75      * Get a specific description of the wrappered processor.
  76      *
  77      * @return specific description of the underlying processor or transformer
  78      * implementation: this should include both the general product name, as
  79      * well as specific version info. If possible, should be implemented without
  80      * actively creating an underlying processor.
  81      */
  82     @Override
  83     public Properties getProcessorInfo() {
  84         Properties p = TraxWrapperUtils.getTraxInfo();
  85         p.put("traxwrapper.method", "file");
  86         p.put("traxwrapper.desc", getDescription());
  87         return p;
  88     }
  89 
  90     /**
  91      * Actually create/initialize an underlying processor or factory.
  92      *
  93      * For TrAX/javax.xml.transform implementations, this creates a new
  94      * TransformerFactory. For Xalan-J 1.x this creates an XSLTProcessor. Other
  95      * implmentations may or may not actually do any work in this method.
  96      *
  97      * @param options Hashtable of options, unused.
  98      *
  99      * @return (Object)getProcessor() as a side-effect, this will be null if
 100      * there was any problem creating the processor OR if the underlying
 101      * implementation doesn't use this
 102      * @throws javax.xml.transform.TransformerConfigurationException when 
 103      * actual implementation doesn't support StreamSource or StreamResult.
 104      */
 105     @Override
 106     public TransformerFactory newProcessor(Properties options) throws TransformerConfigurationException {
 107         newProcessorOpts = options;
 108         reset(false);
 109         factory = TransformerFactory.newInstance();
 110         // Verify the factory supports Streams!
 111         if (!(factory.getFeature(StreamSource.FEATURE)
 112                 && factory.getFeature(StreamResult.FEATURE))) {
 113             throw new TransformerConfigurationException("TraxFileWrapper.newProcessor: factory does not support Streams!");
 114         }
 115         // Set any of our options as Attributes on the factory
 116         TraxWrapperUtils.setAttributes(factory, options);
 117         return factory;
 118     }
 119 
 120     /**
 121      * Transform supplied xmlName file with the stylesheet in the xslName file
 122      * into a resultName file.
 123      *
 124      * Names are assumed to be local path\filename references, and will be
 125      * converted to URLs as needed for any underlying processor implementation.
 126      *
 127      * @param xmlName local path\filename of XML file to transform
 128      * @param xslName local path\filename of XSL stylesheet to use
 129      * @param resultName local path\filename to put result in
 130      *
 131      * @throws Exception any underlying exceptions from the wrapped processor
 132      * are simply allowed to propagate; throws a RuntimeException if any other
 133      * problems prevent us from actually completing the operation
 134      */
 135     @Override
 136     public void transform(String xmlName, String xslName, String resultName)
 137             throws Exception {
 138         preventFootShooting();
 139 
 140         // read/build xsl from a File
 141         Transformer transformer = factory.newTransformer(new StreamSource(new File(xslName)));
 142 
 143         // Untimed: Set any of our options as Attributes on the transformer
 144         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 145 
 146         // Untimed: Apply any parameters needed
 147         applyParameters(transformer);
 148 
 149         // Timed: read/build xml, transform, and write results
 150         transformer.transform(new StreamSource(new File(xmlName)), new StreamResult(resultName));
 151     }
 152 
 153     /**
 154      * Pre-build/pre-compile a stylesheet.
 155      *
 156      * Although the actual mechanics are implementation-dependent, most
 157      * processors have some method of pre-setting up the data needed by the
 158      * stylesheet itself for later use in transforms. In
 159      * TrAX/javax.xml.transform, this equates to creating a Templates object.
 160      *
 161      * Sets isStylesheetReady() to true if it succeeds. Users can then call
 162      * transformWithStylesheet(xmlName, resultName) to actually perform a
 163      * transformation with this pre-built stylesheet.
 164      *
 165      * @param xslName local path\filename of XSL stylesheet to use
 166      *
 167      * @throws Exception any underlying exceptions from the wrapped processor
 168      * are simply allowed to propagate; throws a RuntimeException if any other
 169      * problems prevent us from actually completing the operation
 170      *
 171      * @see #transformWithStylesheet(String xmlName, String resultName)
 172      */
 173     @Override
 174     public void buildStylesheet(String xslName) throws Exception {
 175         preventFootShooting();
 176 
 177         // read/build xsl from a URL
 178         builtTemplates = factory.newTemplates(new StreamSource(new File(xslName)));
 179         m_stylesheetReady = true;
 180     }
 181 
 182     /**
 183      * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet
 184      * into a resultName file.
 185      *
 186      * User must have called buildStylesheet(xslName) beforehand, obviously.
 187      * Names are assumed to be local path\filename references, and will be
 188      * converted to URLs as needed.
 189      *
 190      * @param xmlName local path\filename of XML file to transform
 191      * @param resultName local path\filename to put result in
 192      *
 193      * @throws Exception any underlying exceptions from the wrapped processor
 194      * are simply allowed to propagate; throws a RuntimeException if any other
 195      * problems prevent us from actually completing the operation; throws an
 196      * IllegalStateException if isStylesheetReady() == false.
 197      *
 198      * @see #buildStylesheet(String xslName)
 199      */
 200     @Override
 201     public void transformWithStylesheet(String xmlName, String resultName)
 202             throws Exception {
 203         if (!isStylesheetReady()) {
 204             throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false");
 205         }
 206 
 207         preventFootShooting();
 208 
 209         // Get Transformer from Templates
 210         Transformer transformer = builtTemplates.newTransformer();
 211 
 212         // Set any of our options as Attributes on the transformer
 213         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 214 
 215         // Apply any parameters needed
 216         applyParameters(transformer);
 217 
 218         // read/build xml, transform, and write results
 219         long startTime = System.currentTimeMillis();
 220         transformer.transform(new StreamSource(new File(xmlName)),
 221                 new StreamResult(resultName));
 222     }
 223 
 224     /**
 225      * Transform supplied xmlName file with a stylesheet found in an
 226      * xml-stylesheet PI into a resultName file.
 227      *
 228      * Names are assumed to be local path\filename references, and will be
 229      * converted to URLs as needed. Implementations will use whatever facilities
 230      * exist in their wrapped processor to fetch and build the stylesheet to
 231      * use for the transform.
 232      *
 233      * @param xmlName local path\filename of XML file to transform
 234      * @param resultName local path\filename to put result in
 235      *
 236      * @throws Exception any underlying exceptions from the wrapped processor
 237      * are simply allowed to propagate; throws a RuntimeException if any other
 238      * problems prevent us from actually completing the operation
 239      */
 240     @Override
 241     public void transformEmbedded(String xmlName, String resultName)
 242             throws Exception {
 243         preventFootShooting();
 244         // Read xsl from the xml document.
 245         Source xslSource = factory.getAssociatedStylesheet(
 246                 new StreamSource(new File(xmlName)),null, null, null);
 247 
 248         // Build xsl from a URL
 249         Transformer transformer = factory.newTransformer(xslSource);
 250 
 251         // Set any of our options as Attributes on the transformer
 252         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 253 
 254         // Apply any parameters needed
 255         applyParameters(transformer);
 256 
 257         // read/build xml, transform, and write results.
 258         transformer.transform(new StreamSource(new File(xmlName)),
 259                 new StreamResult(resultName));
 260     }
 261 
 262     /**
 263      * Reset our parameters and wrapper state, and optionally force creation of
 264      * a new underlying processor implementation.
 265      *
 266      * This always clears our built stylesheet and any parameters that have been
 267      * set. If newProcessor is true, also forces a re-creation of our underlying
 268      * processor as if by calling newProcessor().
 269      *
 270      * @param newProcessor if we should reset our underlying processor
 271      * implementation as well
 272      */
 273     @Override
 274     public void reset(boolean newProcessor) {
 275         super.reset(newProcessor); // clears indent and parameters
 276         m_stylesheetReady = false;
 277         builtTemplates = null;
 278         if (newProcessor) {
 279             try {
 280                 newProcessor(newProcessorOpts);
 281             } catch (TransformerConfigurationException ignore) {}
 282         }
 283     }
 284 
 285     /**
 286      * Apply a single parameter to a Transformer.
 287      *
 288      * Overridden to take a Transformer and call setParameter().
 289      *
 290      * @param transformer a Transformer object.
 291      * @param namespace for the parameter, may be null
 292      * @param name for the parameter, should not be null
 293      * @param value for the parameter, may be null
 294      */
 295     @Override
 296     protected void applyParameter(Transformer transformer, String namespace,
 297             String name, String value) {
 298         try {
 299             // Munge the namespace into the name per
 300             //  javax.xml.transform.Transformer.setParameter()
 301             if (null != namespace) {
 302                 name = "{" + namespace + "}" + name;
 303             }
 304             transformer.setParameter(name, value);
 305         } catch (Exception e) {
 306             throw new IllegalArgumentException("applyParameter threw: " + e.toString());
 307         }
 308     }
 309 
 310     /**
 311      * Ensure newProcessor has been called when needed.
 312      *
 313      * Prevent users from shooting themselves in the foot by calling a
 314      * transform* API before newProcessor().
 315      *
 316      * @throws java.lang.Exception
 317      */
 318     public void preventFootShooting() throws Exception {
 319         if (null == factory) {
 320             newProcessor(newProcessorOpts);
 321         }
 322     }
 323 }