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 }