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