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.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 * Templates to use for buildStylesheet(). 59 */ 60 private Templates builtTemplates; 61 62 /** 63 * Get a general description of this wrapper itself. 64 * 65 * @return Uses TrAX to perform transforms from DOMSource(node) 66 */ 67 public String getDescription() { 68 return "Uses TrAX to perform transforms from DOMSource(node)"; 69 } 70 71 /** 72 * Get a specific description of the wrappered 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", "dom"); 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 * implementations 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) throws TransformerConfigurationException { 104 newProcessorOpts = options; 105 reset(false); 106 factory = TransformerFactory.newInstance(); 107 // Verify the factory supports DOM! 108 if (!(factory.getFeature(DOMSource.FEATURE) 109 && factory.getFeature(DOMResult.FEATURE))) { 110 throw new TransformerConfigurationException("TraxDOMWrapper.newProcessor: factory does not support DOM!"); 111 } 112 // Set any of our options as Attributes on the factory 113 TraxWrapperUtils.setAttributes(factory, options); 114 return factory; 115 } 116 117 /** 118 * Transform supplied xmlName file with the stylesheet in the xslName file 119 * into a resultName file. 120 * 121 * Names are assumed to be local path\filename references, and will be 122 * converted to URLs as needed for any underlying processor implementation. 123 * 124 * @param xmlName local path\filename of XML file to transform 125 * @param xslName local path\filename of XSL stylesheet to use 126 * @param resultName local path\filename to put result in 127 * 128 * @throws Exception any underlying exceptions from the wrapped processor 129 * are simply allowed to propagate; throws a RuntimeException if any other 130 * problems prevent us from actually completing the operation 131 */ 132 @Override 133 public void transform(String xmlName, String xslName, String resultName) 134 throws Exception { 135 preventFootShooting(); 136 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 137 dfactory.setNamespaceAware(true); 138 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 139 140 // Timed: read xsl into a DOM 141 Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName))); 142 143 // Untimed: create DOMSource and setSystemId 144 DOMSource xslSource = new DOMSource(xslNode); 145 xslSource.setSystemId(filenameToURL(xslName)); 146 147 // Timed: build Transformer from DOMSource 148 Transformer transformer = factory.newTransformer(xslSource); 149 150 // Timed: read xml into a DOM 151 Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName))); 152 153 // Untimed: create DOMSource and setSystemId 154 DOMSource xmlSource = new DOMSource(xmlNode); 155 xmlSource.setSystemId(filenameToURL(xmlName)); 156 157 // Untimed: create DOMResult 158 Document outDoc = docBuilder.newDocument(); 159 DocumentFragment outNode = outDoc.createDocumentFragment(); 160 DOMResult domResult = new DOMResult(outNode); 161 162 // Untimed: Set any of our options as Attributes on the transformer 163 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 164 165 // Untimed: Apply any parameters needed 166 applyParameters(transformer); 167 168 // Timed: build xml (so to speak) and transform 169 transformer.transform(xmlSource, domResult); 170 171 // Untimed: prepare serializer with outputProperties 172 // from the stylesheet 173 Transformer resultSerializer = factory.newTransformer(); 174 Properties serializationProps = transformer.getOutputProperties(); 175 resultSerializer.setOutputProperties(serializationProps); 176 177 // Timed: writeResults from the DOMResult 178 resultSerializer.transform(new DOMSource(outNode), 179 new StreamResult(resultName)); 180 } 181 182 /** 183 * Pre-build/pre-compile a stylesheet. 184 * 185 * Although the actual mechanics are implementation-dependent, most 186 * processors have some method of pre-setting up the data needed by the 187 * stylesheet itself for later use in transforms. In 188 * TrAX/javax.xml.transform, this equates to creating a Templates object. 189 * 190 * Sets isStylesheetReady() to true if it succeeds. Users can then call 191 * transformWithStylesheet(xmlName, resultName) to actually perform a 192 * transformation with this pre-built stylesheet. 193 * 194 * @param xslName local path\filename of XSL stylesheet to use 195 * 196 * @throws Exception any underlying exceptions from the wrapped processor 197 * are simply allowed to propagate; throws a RuntimeException if any other 198 * problems prevent us from actually completing the operation 199 * 200 * @see #transformWithStylesheet(String xmlName, String resultName) 201 */ 202 @Override 203 public void buildStylesheet(String xslName) throws Exception { 204 preventFootShooting(); 205 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 206 dfactory.setNamespaceAware(true); 207 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 208 209 // Read xsl into a DOM 210 Node xslNode = docBuilder.parse(new InputSource(filenameToURL(xslName))); 211 // Untimed: create DOMSource and setSystemId 212 DOMSource xslSource = new DOMSource(xslNode); 213 xslSource.setSystemId(filenameToURL(xslName)); 214 215 // Build Templates from DOMSource 216 builtTemplates = factory.newTemplates(xslSource); 217 218 m_stylesheetReady = true; 219 } 220 221 /** 222 * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet 223 * into a resultName file. 224 * 225 * User must have called buildStylesheet(xslName) beforehand, obviously. 226 * Names are assumed to be local path\filename references, and will be 227 * converted to URLs as needed. 228 * 229 * @param xmlName local path\filename of XML file to transform 230 * @param resultName local path\filename to put result in 231 * 232 * @throws Exception any underlying exceptions from the wrappered processor 233 * are simply allowed to propagate; throws a RuntimeException if any other 234 * problems prevent us from actually completing the operation; throws an 235 * IllegalStateException if isStylesheetReady() == false. 236 * 237 * @see #buildStylesheet(String xslName) 238 */ 239 @Override 240 public void transformWithStylesheet(String xmlName, String resultName) 241 throws Exception { 242 if (!isStylesheetReady()) { 243 throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); 244 } 245 246 preventFootShooting(); 247 248 // Get Transformer from Templates 249 Transformer transformer = builtTemplates.newTransformer(); 250 251 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 252 dfactory.setNamespaceAware(true); 253 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 254 255 // Read xml into a DOM 256 Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName))); 257 258 // Create DOMSource and setSystemId 259 DOMSource xmlSource = new DOMSource(xmlNode); 260 xmlSource.setSystemId(filenameToURL(xmlName)); 261 262 // Create DOMResult 263 Document outNode = docBuilder.newDocument(); 264 DOMResult domResult = new DOMResult(outNode); 265 266 // Set any of our options as Attributes on the transformer 267 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 268 269 // Apply any parameters needed 270 applyParameters(transformer); 271 272 // Build xml (so to speak) and transform 273 transformer.transform(xmlSource, domResult); 274 275 // Prepare serializer with outputProperties from the stylesheet 276 Transformer resultSerializer = factory.newTransformer(); 277 Properties serializationProps = transformer.getOutputProperties(); 278 resultSerializer.setOutputProperties(serializationProps); 279 280 // writeResults from the DOMResult 281 resultSerializer.transform(new DOMSource(outNode), 282 new StreamResult(resultName)); 283 } 284 285 /** 286 * Transform supplied xmlName file with a stylesheet found in an 287 * xml-stylesheet PI into a resultName file. 288 * 289 * Names are assumed to be local path\filename references, and will be 290 * converted to URLs as needed. Implementations will use whatever facilities 291 * exist in their wrapped processor to fetch and build the stylesheet to 292 * use for the transform. 293 * 294 * @param xmlName local path\filename of XML file to transform 295 * @param resultName local path\filename to put result in 296 * 297 * @throws Exception any underlying exceptions from the wrapped processor 298 * are simply allowed to propagate; throws a RuntimeException if any other 299 * problems prevent us from actually completing the operation 300 */ 301 @Override 302 public void transformEmbedded(String xmlName, String resultName) 303 throws Exception { 304 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 305 dfactory.setNamespaceAware(true); 306 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 307 308 // read xml into a DOM 309 Node xmlNode = docBuilder.parse(new InputSource(filenameToURL(xmlName))); 310 311 // create DOMSource and setSystemId 312 DOMSource xmlSource = new DOMSource(xmlNode); 313 xmlSource.setSystemId(filenameToURL(xmlName)); 314 315 // readxsl from the xml document 316 Source xslSource = factory.getAssociatedStylesheet(xmlSource, 317 null, null, null); 318 319 // build Transformer from Source 320 Transformer transformer = factory.newTransformer(xslSource); 321 322 // create DOMResult 323 Document outNode = docBuilder.newDocument(); 324 DOMResult domResult = new DOMResult(outNode); 325 326 // Set any of our options as Attributes on the transformer 327 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 328 329 // Apply any parameters needed 330 applyParameters(transformer); 331 332 // Build xml (so to speak) and transform 333 transformer.transform(xmlSource, domResult); 334 335 // Prepare serializer with outputProperties from the stylesheet 336 Transformer resultSerializer = factory.newTransformer(); 337 Properties serializationProps = transformer.getOutputProperties(); 338 resultSerializer.setOutputProperties(serializationProps); 339 340 // writeResults from the DOMResult 341 resultSerializer.transform(new DOMSource(outNode), 342 new StreamResult(resultName)); 343 } 344 345 /** 346 * Reset our parameters and wrapper state, and optionally force creation of 347 * a new underlying processor implementation. 348 * 349 * This always clears our built stylesheet and any parameters that have been 350 * set. If newProcessor is true, also forces a re-creation of our underlying 351 * processor as if by calling newProcessor(). 352 * 353 * @param newProcessor if we should reset our underlying processor 354 * implementation as well 355 */ 356 @Override 357 public void reset(boolean newProcessor) { 358 super.reset(newProcessor); // clears indent and parameters 359 m_stylesheetReady = false; 360 builtTemplates = null; 361 if (newProcessor) { 362 try { 363 newProcessor(newProcessorOpts); 364 } catch (TransformerConfigurationException ignore) {} 365 } 366 } 367 368 /** 369 * Apply a single parameter to a Transformer. 370 * 371 * Overridden to take a Transformer and call setParameter(). 372 * 373 * @param transformer a Transformer object. 374 * @param namespace for the parameter, may be null 375 * @param name for the parameter, should not be null 376 * @param value for the parameter, may be null 377 */ 378 @Override 379 protected void applyParameter(Transformer transformer, String namespace, 380 String name, String value) { 381 try { 382 // Munge the namespace into the name per 383 // javax.xml.transform.Transformer.setParameter() 384 if (null != namespace) { 385 name = "{" + namespace + "}" + name; 386 } 387 transformer.setParameter(name, value); 388 } catch (Exception e) { 389 throw new IllegalArgumentException("applyParameter threw: " + e.toString()); 390 } 391 } 392 }