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