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