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 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     /**
  48      * TransformerFactory to use; constructed in newProcessor().
  49      */
  50     protected TransformerFactory factory;
  51 
  52     /**
  53      * Templates to use for buildStylesheet().
  54      */
  55     protected Templates builtTemplates;
  56 
  57     /**
  58      * Cached copy of newProcessor() properties.
  59      */
  60     protected Properties newProcessorOpts;
  61 
  62     /**
  63      * Get a general description of this wrapper itself.
  64      *
  65      * @return Uses TrAX to perform transforms from StreamSource(systemId)
  66      */
  67     public String getDescription() {
  68         return "Uses TrAX to perform transforms from StreamSource(systemId)";
  69     }
  70 
  71     /**
  72      * Get a specific description of the wrapped 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", "systemId");
  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      * implmentations 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) 
 104             throws TransformerConfigurationException {
 105         newProcessorOpts = options;
 106         reset(false);
 107         factory = TransformerFactory.newInstance();
 108         // Verify the factory supports Streams!
 109         if (!(factory.getFeature(StreamSource.FEATURE)
 110                 && factory.getFeature(StreamResult.FEATURE))) {
 111             throw new TransformerConfigurationException("TraxSystemIdWrapper.newProcessor: factory does not support Streams!");
 112         }
 113         // Set any of our options as Attributes on the factory
 114         TraxWrapperUtils.setAttributes(factory, newProcessorOpts);
 115         return factory;
 116     }
 117 
 118     /**
 119      * Transform supplied xmlName file with the stylesheet in the xslName file
 120      * into a resultName file.
 121      *
 122      * Names are assumed to be local path\filename references, and will be
 123      * converted to URLs as needed for any underlying processor implementation.
 124      *
 125      * @param xmlName local path\filename of XML file to transform
 126      * @param xslName local path\filename of XSL stylesheet to use
 127      * @param resultName local path\filename to put result in
 128      *
 129      * @throws Exception any underlying exceptions from the wrapped processor
 130      * are simply allowed to propagate; throws a RuntimeException if any other
 131      * problems prevent us from actually completing the operation
 132      */
 133     @Override
 134     public void transform(String xmlName, String xslName, String resultName)
 135             throws Exception {
 136         preventFootShooting();
 137 
 138         // Read/build xsl from a URL
 139         Transformer transformer = factory.newTransformer(
 140                 new StreamSource(filenameToURL(xslName)));
 141 
 142         // Set any of our options as Attributes on the transformer
 143         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 144 
 145         // Apply any parameters needed
 146         applyParameters(transformer);
 147 
 148         // Read/build xml, transform, and write results
 149         transformer.transform(new StreamSource(filenameToURL(xmlName)),
 150                 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(
 179                 new StreamSource(filenameToURL(xslName)));
 180         m_stylesheetReady = true;
 181     }
 182 
 183     /**
 184      * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet
 185      * into a resultName file.
 186      *
 187      * User must have called buildStylesheet(xslName) beforehand, obviously.
 188      * Names are assumed to be local path\filename references, and will be
 189      * converted to URLs as needed.
 190      *
 191      * @param xmlName local path\filename of XML file to transform
 192      * @param resultName local path\filename to put result in
 193      *
 194      * @throws Exception any underlying exceptions from the wrapped processor
 195      * are simply allowed to propagate; throws a RuntimeException if any other
 196      * problems prevent us from actually completing the operation; throws an
 197      * IllegalStateException if isStylesheetReady() == false.
 198      *
 199      * @see #buildStylesheet(String xslName)
 200      */
 201     @Override
 202     public void transformWithStylesheet(String xmlName, String resultName)
 203             throws Exception {
 204         if (!isStylesheetReady()) {
 205             throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false");
 206         }
 207 
 208         preventFootShooting();
 209 
 210         // Get Transformer from Templates
 211         Transformer transformer = builtTemplates.newTransformer();
 212 
 213         // Set any of our options as Attributes on the transformer
 214         TraxWrapperUtils.setAttributes(transformer, newProcessorOpts);
 215 
 216         // Apply any parameters needed
 217         applyParameters(transformer);
 218 
 219         // Read/build xml, transform, and write results
 220         transformer.transform(new StreamSource(filenameToURL(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(new StreamSource(filenameToURL(xmlName)),
 246                 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(filenameToURL(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     /**
 287      * Apply a single parameter to a Transformer.
 288      *
 289      * Overridden to take a Transformer and call setParameter().
 290      *
 291      * @param transformer a Transformer object.
 292      * @param namespace for the parameter, may be null
 293      * @param name for the parameter, should not be null
 294      * @param value for the parameter, may be null
 295      */
 296     @Override
 297     protected void applyParameter(Transformer transformer, String namespace,
 298             String name, String value) {
 299         try {
 300             // Munge the namespace into the name per
 301             //  javax.xml.transform.Transformer.setParameter()
 302             if (null != namespace) {
 303                 name = "{" + namespace + "}" + name;
 304             }
 305             transformer.setParameter(name, value);
 306         } catch (Exception e) {
 307             throw new IllegalArgumentException("applyParameter threw: " + e.toString());
 308         }
 309     }
 310 
 311     /**
 312      * Ensure newProcessor has been called when needed.
 313      *
 314      * Prevent users from shooting themselves in the foot by calling a
 315      * transform* API before newProcessor().
 316      * @throws java.lang.Exception
 317      */
 318     public void preventFootShooting() throws Exception {
 319         if (null == factory) {
 320             newProcessor(newProcessorOpts);
 321         }
 322     }
 323 }