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.parsers.DocumentBuilder; 24 import javax.xml.parsers.DocumentBuilderFactory; 25 import javax.xml.transform.Source; 26 import javax.xml.transform.Templates; 27 import javax.xml.transform.Transformer; 28 import javax.xml.transform.TransformerConfigurationException; 29 import javax.xml.transform.TransformerFactory; 30 import javax.xml.transform.dom.DOMResult; 31 import javax.xml.transform.dom.DOMSource; 32 import javax.xml.transform.stream.StreamResult; 33 import static jaxp.library.JAXPTestUtilities.filenameToURL; 34 import org.w3c.dom.Document; 35 import org.w3c.dom.DocumentFragment; 36 import org.w3c.dom.Node; 37 import org.xml.sax.InputSource; 38 39 /** 40 * Implementation of TransformWrapper that uses the TrAX API and uses DOMs for 41 * it's sources. 42 * 43 * This implementation records separate times for xslRead (time to parse the xsl 44 * and build a DOM) and xslBuild (time to take the DOMSource object until it's 45 * built the templates); xmlRead (time to parse the xml and build a DOM). Note 46 * xmlBuild is not timed since it's not easily measureable in TrAX. The 47 * transform time is just the time to create the DOMResult object; the 48 * resultsWrite is the separate time it takes to serialize that to disk. 49 * 50 * <b>Important!</b> The underlying System property of 51 * javax.xml.transform.TransformerFactory will determine the actual TrAX 52 * implementation used. This value will be reported out in our 53 * getProcessorInfo() method. 54 * 55 */ 56 public class TraxDOMWrapper extends TransformWrapperHelper { 57 58 /** 59 * TransformerFactory to use; constructed in newProcessor(). 60 */ 61 private TransformerFactory factory; 62 63 /** 64 * Templates to use for buildStylesheet(). 65 */ 66 private Templates builtTemplates; 67 68 /** 69 * Cached copy of newProcessor() properties. 70 */ 71 private Properties newProcessorOpts; 72 73 /** 74 * Get a general description of this wrapper itself. 75 * 76 * @return Uses TrAX to perform transforms from DOMSource(node) 77 */ 78 public String getDescription() { 79 return "Uses TrAX to perform transforms from DOMSource(node)"; 80 } 81 82 /** 83 * Get a specific description of the wrappered processor. 84 * 85 * @return specific description of the underlying processor or transformer 86 * implementation: this should include both the general product name, as 87 * well as specific version info. If possible, should be implemented without 88 * actively creating an underlying processor. 89 */ 90 @Override 91 public Properties getProcessorInfo() { 92 Properties p = TraxWrapperUtils.getTraxInfo(); 93 p.put("traxwrapper.method", "dom"); 94 p.put("traxwrapper.desc", getDescription()); 95 return p; 96 } 97 98 /** 99 * Actually create/initialize an underlying processor or factory. 100 * 101 * For TrAX/javax.xml.transform implementations, this creates a new 102 * TransformerFactory. For Xalan-J 1.x this creates an XSLTProcessor. Other 103 * implementations may or may not actually do any work in this method. 104 * 105 * @param options Properties of options, unused. 106 * 107 * @return (Object)getProcessor() as a side-effect, this will be null if 108 * there was any problem creating the processor OR if the underlying 109 * implementation doesn't use this 110 * @throws javax.xml.transform.TransformerConfigurationException when 111 * actual implementation doesn't support StreamSource or StreamResult. 112 */ 113 @Override 114 public TransformerFactory newProcessor(Properties options) throws TransformerConfigurationException { 115 newProcessorOpts = options; 116 reset(false); 117 factory = TransformerFactory.newInstance(); 118 // Verify the factory supports DOM! 119 if (!(factory.getFeature(DOMSource.FEATURE) 120 && factory.getFeature(DOMResult.FEATURE))) { 121 throw new TransformerConfigurationException("TraxDOMWrapper.newProcessor: factory does not support DOM!"); 122 } 123 // Set any of our options as Attributes on the factory 124 TraxWrapperUtils.setAttributes(factory, options); 125 return factory; 126 } 127 128 /** 129 * Transform supplied xmlName file with the stylesheet in the xslName file 130 * into a resultName file. 131 * 132 * Names are assumed to be local path\filename references, and will be 133 * converted to URLs as needed for any underlying processor implementation. 134 * 135 * @param xmlName local path\filename of XML file to transform 136 * @param xslName local path\filename of XSL stylesheet to use 137 * @param resultName local path\filename to put result in 138 * 139 * @throws Exception any underlying exceptions from the wrapped processor 140 * are simply allowed to propagate; throws a RuntimeException if any other 141 * problems prevent us from actually completing the operation 142 */ 143 @Override 144 public void transform(String xmlName, String xslName, String resultName) 145 throws Exception { 146 preventFootShooting(); 147 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 148 dfactory.setNamespaceAware(true); 149 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 150 151 // Timed: read xsl into a DOM 152 Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName))); 153 154 // Untimed: create DOMSource and setSystemId 155 DOMSource xslSource = new DOMSource(xslNode); 156 xslSource.setSystemId(filenameToURL(xslName)); 157 158 // Timed: build Transformer from DOMSource 159 Transformer transformer = factory.newTransformer(xslSource); 160 161 // Timed: read xml into a DOM 162 Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName))); 163 164 // Untimed: create DOMSource and setSystemId 165 DOMSource xmlSource = new DOMSource(xmlNode); 166 xmlSource.setSystemId(filenameToURL(xmlName)); 167 168 // Untimed: create DOMResult 169 Document outDoc = docBuilder.newDocument(); 170 DocumentFragment outNode = outDoc.createDocumentFragment(); 171 DOMResult domResult = new DOMResult(outNode); 172 173 // Untimed: Set any of our options as Attributes on the transformer 174 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 175 176 // Untimed: Apply any parameters needed 177 applyParameters(transformer); 178 179 // Timed: build xml (so to speak) and transform 180 transformer.transform(xmlSource, domResult); 181 182 // Untimed: prepare serializer with outputProperties 183 // from the stylesheet 184 Transformer resultSerializer = factory.newTransformer(); 185 Properties serializationProps = transformer.getOutputProperties(); 186 resultSerializer.setOutputProperties(serializationProps); 187 188 // Timed: writeResults from the DOMResult 189 resultSerializer.transform(new DOMSource(outNode), 190 new StreamResult(resultName)); 191 } 192 193 /** 194 * Pre-build/pre-compile a stylesheet. 195 * 196 * Although the actual mechanics are implementation-dependent, most 197 * processors have some method of pre-setting up the data needed by the 198 * stylesheet itself for later use in transforms. In 199 * TrAX/javax.xml.transform, this equates to creating a Templates object. 200 * 201 * Sets isStylesheetReady() to true if it succeeds. Users can then call 202 * transformWithStylesheet(xmlName, resultName) to actually perform a 203 * transformation with this pre-built stylesheet. 204 * 205 * @param xslName local path\filename of XSL stylesheet to use 206 * 207 * @throws Exception any underlying exceptions from the wrapped processor 208 * are simply allowed to propagate; throws a RuntimeException if any other 209 * problems prevent us from actually completing the operation 210 * 211 * @see #transformWithStylesheet(String xmlName, String resultName) 212 */ 213 @Override 214 public void buildStylesheet(String xslName) throws Exception { 215 preventFootShooting(); 216 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 217 dfactory.setNamespaceAware(true); 218 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 219 220 // Read xsl into a DOM 221 Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName))); 222 // Untimed: create DOMSource and setSystemId 223 DOMSource xslSource = new DOMSource(xslNode); 224 xslSource.setSystemId(filenameToURL(xslName)); 225 226 // Build Templates from DOMSource 227 builtTemplates = factory.newTemplates(xslSource); 228 229 m_stylesheetReady = true; 230 } 231 232 /** 233 * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet 234 * into a resultName file. 235 * 236 * User must have called buildStylesheet(xslName) beforehand, obviously. 237 * Names are assumed to be local path\filename references, and will be 238 * converted to URLs as needed. 239 * 240 * @param xmlName local path\filename of XML file to transform 241 * @param resultName local path\filename to put result in 242 * 243 * @throws Exception any underlying exceptions from the wrappered processor 244 * are simply allowed to propagate; throws a RuntimeException if any other 245 * problems prevent us from actually completing the operation; throws an 246 * IllegalStateException if isStylesheetReady() == false. 247 * 248 * @see #buildStylesheet(String xslName) 249 */ 250 @Override 251 public void transformWithStylesheet(String xmlName, String resultName) 252 throws Exception { 253 if (!isStylesheetReady()) { 254 throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); 255 } 256 257 preventFootShooting(); 258 259 // Get Transformer from Templates 260 Transformer transformer = builtTemplates.newTransformer(); 261 262 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 263 dfactory.setNamespaceAware(true); 264 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 265 266 // Read xml into a DOM 267 Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName))); 268 269 // Create DOMSource and setSystemId 270 DOMSource xmlSource = new DOMSource(xmlNode); 271 xmlSource.setSystemId(filenameToURL(xmlName)); 272 273 // Create DOMResult 274 Document outNode = docBuilder.newDocument(); 275 DOMResult domResult = new DOMResult(outNode); 276 277 // Set any of our options as Attributes on the transformer 278 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 279 280 // Apply any parameters needed 281 applyParameters(transformer); 282 283 // Build xml (so to speak) and transform 284 transformer.transform(xmlSource, domResult); 285 286 // Prepare serializer with outputProperties from the stylesheet 287 Transformer resultSerializer = factory.newTransformer(); 288 Properties serializationProps = transformer.getOutputProperties(); 289 resultSerializer.setOutputProperties(serializationProps); 290 291 // writeResults from the DOMResult 292 resultSerializer.transform(new DOMSource(outNode), 293 new StreamResult(resultName)); 294 } 295 296 /** 297 * Transform supplied xmlName file with a stylesheet found in an 298 * xml-stylesheet PI into a resultName file. 299 * 300 * Names are assumed to be local path\filename references, and will be 301 * converted to URLs as needed. Implementations will use whatever facilities 302 * exist in their wrapped processor to fetch and build the stylesheet to 303 * use for the transform. 304 * 305 * @param xmlName local path\filename of XML file to transform 306 * @param resultName local path\filename to put result in 307 * 308 * @throws Exception any underlying exceptions from the wrapped processor 309 * are simply allowed to propagate; throws a RuntimeException if any other 310 * problems prevent us from actually completing the operation 311 */ 312 @Override 313 public void transformEmbedded(String xmlName, String resultName) 314 throws Exception { 315 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 316 dfactory.setNamespaceAware(true); 317 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 318 319 // read xml into a DOM 320 Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName))); 321 322 // create DOMSource and setSystemId 323 DOMSource xmlSource = new DOMSource(xmlNode); 324 xmlSource.setSystemId(filenameToURL(xmlName)); 325 326 // readxsl from the xml document 327 Source xslSource = factory.getAssociatedStylesheet(xmlSource, 328 null, null, null); 329 330 // build Transformer from Source 331 Transformer transformer = factory.newTransformer(xslSource); 332 333 // create DOMResult 334 Document outNode = docBuilder.newDocument(); 335 DOMResult domResult = new DOMResult(outNode); 336 337 // Set any of our options as Attributes on the transformer 338 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 339 340 // Apply any parameters needed 341 applyParameters(transformer); 342 343 // Build xml (so to speak) and transform 344 transformer.transform(xmlSource, domResult); 345 346 // Prepare serializer with outputProperties from the stylesheet 347 Transformer resultSerializer = factory.newTransformer(); 348 Properties serializationProps = transformer.getOutputProperties(); 349 resultSerializer.setOutputProperties(serializationProps); 350 351 // writeResults from the DOMResult 352 resultSerializer.transform(new DOMSource(outNode), 353 new StreamResult(resultName)); 354 } 355 356 /** 357 * Reset our parameters and wrapper state, and optionally force creation of 358 * a new underlying processor implementation. 359 * 360 * This always clears our built stylesheet and any parameters that have been 361 * set. If newProcessor is true, also forces a re-creation of our underlying 362 * processor as if by calling newProcessor(). 363 * 364 * @param newProcessor if we should reset our underlying processor 365 * implementation as well 366 */ 367 @Override 368 public void reset(boolean newProcessor) { 369 super.reset(newProcessor); // clears indent and parameters 370 m_stylesheetReady = false; 371 builtTemplates = null; 372 if (newProcessor) { 373 try { 374 newProcessor(newProcessorOpts); 375 } catch (TransformerConfigurationException ignore) {} 376 } 377 } 378 379 /** 380 * Apply a single parameter to a Transformer. 381 * 382 * Overridden to take a Transformer and call setParameter(). 383 * 384 * @param transformer a Transformer object. 385 * @param namespace for the parameter, may be null 386 * @param name for the parameter, should not be null 387 * @param value for the parameter, may be null 388 */ 389 @Override 390 protected void applyParameter(Transformer transformer, String namespace, 391 String name, String value) { 392 try { 393 // Munge the namespace into the name per 394 // javax.xml.transform.Transformer.setParameter() 395 if (null != namespace) { 396 name = "{" + namespace + "}" + name; 397 } 398 transformer.setParameter(name, value); 399 } catch (Exception e) { 400 throw new IllegalArgumentException("applyParameter threw: " + e.toString()); 401 } 402 } 403 404 /** 405 * Ensure newProcessor has been called when needed. 406 * 407 * Prevent users from shooting themselves in the foot by calling a 408 * transform* API before newProcessor(). 409 * 410 * @throws java.lang.Exception 411 */ 412 public void preventFootShooting() throws Exception { 413 if (null == factory) { 414 newProcessor(newProcessorOpts); 415 } 416 } 417 }