1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 /* 19 * $Id$ 20 */ 21 package org.apache.qetest.xslwrapper; 22 import java.util.Hashtable; 23 import java.util.Properties; 24 25 import javax.xml.parsers.DocumentBuilder; 26 import javax.xml.parsers.DocumentBuilderFactory; 27 import javax.xml.transform.Source; 28 import javax.xml.transform.Templates; 29 import javax.xml.transform.Transformer; 30 import javax.xml.transform.TransformerConfigurationException; 31 import javax.xml.transform.TransformerFactory; 32 import javax.xml.transform.dom.DOMResult; 33 import javax.xml.transform.dom.DOMSource; 34 import javax.xml.transform.stream.StreamResult; 35 36 import org.apache.qetest.QetestUtils; 37 import org.apache.xml.utils.DefaultErrorHandler; 38 import org.w3c.dom.Document; 39 import org.w3c.dom.DocumentFragment; 40 import org.w3c.dom.Node; 41 import org.xml.sax.InputSource; 42 43 /** 44 * Implementation of TransformWrapper that uses the TrAX API and 45 * uses DOMs for it's sources. 46 * 47 * This implementation records separate times for xslRead (time to 48 * parse the xsl and build a DOM) and xslBuild (time to take the 49 * DOMSource object until it's built the templates); xmlRead (time 50 * to parse the xml and build a DOM). Note xmlBuild is not timed 51 * since it's not easily measureable in TrAX. 52 * The transform time is just the time to create the DOMResult 53 * object; the resultsWrite is the separate time it takes to 54 * serialize that to disk. 55 * 56 * <b>Important!</b> The underlying System property of 57 * javax.xml.transform.TransformerFactory will determine the actual 58 * TrAX implementation used. This value will be reported out in 59 * our getProcessorInfo() method. 60 * 61 * //@todo add in checks for factory.getFeature(DOMSource.FEATURE) 62 * 63 * @author Shane Curcuru 64 * @version $Id$ 65 */ 66 public class TraxDOMWrapper extends TransformWrapperHelper 67 { 68 69 /** 70 * TransformerFactory to use; constructed in newProcessor(). 71 */ 72 protected TransformerFactory factory = null; 73 74 75 /** 76 * Templates to use for buildStylesheet(). 77 */ 78 protected Templates builtTemplates = null; 79 80 81 /** 82 * Cached copy of newProcessor() Hashtable. 83 */ 84 protected Hashtable newProcessorOpts = null; 85 86 87 /** 88 * Get a general description of this wrapper itself. 89 * 90 * @return Uses TrAX to perform transforms from DOMSource(node) 91 */ getDescription()92 public String getDescription() 93 { 94 return "Uses TrAX to perform transforms from DOMSource(node)"; 95 } 96 97 98 /** 99 * Get a specific description of the wrappered processor. 100 * 101 * @return specific description of the underlying processor or 102 * transformer implementation: this should include both the 103 * general product name, as well as specific version info. If 104 * possible, should be implemented without actively creating 105 * an underlying processor. 106 */ getProcessorInfo()107 public Properties getProcessorInfo() 108 { 109 Properties p = TraxWrapperUtils.getTraxInfo(); 110 p.put("traxwrapper.method", "dom"); 111 p.put("traxwrapper.desc", getDescription()); 112 return p; 113 } 114 115 116 /** 117 * Actually create/initialize an underlying processor or factory. 118 * 119 * For TrAX/javax.xml.transform implementations, this creates 120 * a new TransformerFactory. For Xalan-J 1.x this creates an 121 * XSLTProcessor. Other implmentations may or may not actually 122 * do any work in this method. 123 * 124 * @param options Hashtable of options, unused. 125 * 126 * @return (Object)getProcessor() as a side-effect, this will 127 * be null if there was any problem creating the processor OR 128 * if the underlying implementation doesn't use this 129 * 130 * @throws Exception covers any underlying exceptions thrown 131 * by the actual implementation 132 */ newProcessor(Hashtable options)133 public Object newProcessor(Hashtable options) throws Exception 134 { 135 newProcessorOpts = options; 136 //@todo do we need to do any other cleanup? 137 reset(false); 138 factory = TransformerFactory.newInstance(); 139 factory.setErrorListener(new DefaultErrorHandler()); 140 // Verify the factory supports DOM! 141 if (!(factory.getFeature(DOMSource.FEATURE) 142 && factory.getFeature(DOMResult.FEATURE))) 143 { 144 throw new TransformerConfigurationException("TraxDOMWrapper.newProcessor: factory does not support DOM!"); 145 } 146 // Set any of our options as Attributes on the factory 147 TraxWrapperUtils.setAttributes(factory, options); 148 return (Object)factory; 149 } 150 151 152 /** 153 * Transform supplied xmlName file with the stylesheet in the 154 * xslName file into a resultName file. 155 * 156 * Names are assumed to be local path\filename references, and 157 * will be converted to URLs as needed for any underlying 158 * processor implementation. 159 * 160 * @param xmlName local path\filename of XML file to transform 161 * @param xslName local path\filename of XSL stylesheet to use 162 * @param resultName local path\filename to put result in 163 * 164 * @return array of longs denoting timing of all parts of 165 * our operation 166 * 167 * @throws Exception any underlying exceptions from the 168 * wrappered processor are simply allowed to propagate; throws 169 * a RuntimeException if any other problems prevent us from 170 * actually completing the operation 171 */ transform(String xmlName, String xslName, String resultName)172 public long[] transform(String xmlName, String xslName, String resultName) 173 throws Exception 174 { 175 preventFootShooting(); 176 long startTime = 0; 177 long xslRead = 0; 178 long xslBuild = 0; 179 long xmlRead = 0; 180 long transform = 0; 181 long resultWrite = 0; 182 183 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 184 dfactory.setNamespaceAware(true); 185 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 186 187 // Timed: read xsl into a DOM 188 startTime = System.currentTimeMillis(); 189 Node xslNode = docBuilder.parse(new InputSource(QetestUtils.filenameToURL(xslName))); 190 xslRead = System.currentTimeMillis() - startTime; 191 192 // Untimed: create DOMSource and setSystemId 193 DOMSource xslSource = new DOMSource(xslNode); 194 xslSource.setSystemId(QetestUtils.filenameToURL(xslName)); 195 196 // Timed: build Transformer from DOMSource 197 startTime = System.currentTimeMillis(); 198 Transformer transformer = factory.newTransformer(xslSource); 199 transformer.setErrorListener(new DefaultErrorHandler()); 200 xslBuild = System.currentTimeMillis() - startTime; 201 202 // Timed: read xml into a DOM 203 startTime = System.currentTimeMillis(); 204 Node xmlNode = docBuilder.parse(new InputSource(QetestUtils.filenameToURL(xmlName))); 205 xmlRead = System.currentTimeMillis() - startTime; 206 207 // Untimed: create DOMSource and setSystemId 208 DOMSource xmlSource = new DOMSource(xmlNode); 209 xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName)); 210 211 // Untimed: create DOMResult 212 Document outDoc = docBuilder.newDocument(); 213 DocumentFragment outNode = outDoc.createDocumentFragment(); 214 DOMResult domResult = new DOMResult(outNode); 215 216 // Untimed: Set any of our options as Attributes on the transformer 217 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 218 219 // Untimed: Apply any parameters needed 220 applyParameters(transformer); 221 222 // Timed: build xml (so to speak) and transform 223 startTime = System.currentTimeMillis(); 224 transformer.transform(xmlSource, domResult); 225 transform = System.currentTimeMillis() - startTime; 226 227 // Untimed: prepare serializer with outputProperties 228 // from the stylesheet 229 Transformer resultSerializer = factory.newTransformer(); 230 resultSerializer.setErrorListener(new DefaultErrorHandler()); 231 Properties serializationProps = transformer.getOutputProperties(); 232 resultSerializer.setOutputProperties(serializationProps); 233 234 // Timed: writeResults from the DOMResult 235 startTime = System.currentTimeMillis(); 236 resultSerializer.transform(new DOMSource(outNode), 237 new StreamResult(resultName)); 238 resultWrite = System.currentTimeMillis() - startTime; 239 240 long[] times = getTimeArray(); 241 times[IDX_OVERALL] = xslRead + xslBuild + xmlRead 242 + transform + resultWrite; 243 times[IDX_XSLREAD] = xslRead; 244 times[IDX_XSLBUILD] = xslBuild; 245 times[IDX_XMLREAD] = xmlRead; 246 times[IDX_TRANSFORM] = transform; 247 times[IDX_RESULTWRITE] = resultWrite; 248 return times; 249 } 250 251 252 /** 253 * Pre-build/pre-compile a stylesheet. 254 * 255 * Although the actual mechanics are implementation-dependent, 256 * most processors have some method of pre-setting up the data 257 * needed by the stylesheet itself for later use in transforms. 258 * In TrAX/javax.xml.transform, this equates to creating a 259 * Templates object. 260 * 261 * Sets isStylesheetReady() to true if it succeeds. Users can 262 * then call transformWithStylesheet(xmlName, resultName) to 263 * actually perform a transformation with this pre-built 264 * stylesheet. 265 * 266 * @param xslName local path\filename of XSL stylesheet to use 267 * 268 * @return array of longs denoting timing of only these parts of 269 * our operation: IDX_OVERALL, IDX_XSLREAD, IDX_XSLBUILD 270 * 271 * @throws Exception any underlying exceptions from the 272 * wrappered processor are simply allowed to propagate; throws 273 * a RuntimeException if any other problems prevent us from 274 * actually completing the operation 275 * 276 * @see #transformWithStylesheet(String xmlName, String resultName) 277 */ buildStylesheet(String xslName)278 public long[] buildStylesheet(String xslName) throws Exception 279 { 280 preventFootShooting(); 281 long startTime = 0; 282 long xslRead = 0; 283 long xslBuild = 0; 284 285 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 286 dfactory.setNamespaceAware(true); 287 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 288 289 // Timed: read xsl into a DOM 290 startTime = System.currentTimeMillis(); 291 Node xslNode = docBuilder.parse(new InputSource(QetestUtils.filenameToURL(xslName))); 292 xslRead = System.currentTimeMillis() - startTime; 293 294 // Untimed: create DOMSource and setSystemId 295 DOMSource xslSource = new DOMSource(xslNode); 296 xslSource.setSystemId(QetestUtils.filenameToURL(xslName)); 297 298 // Timed: build Templates from DOMSource 299 startTime = System.currentTimeMillis(); 300 builtTemplates = factory.newTemplates(xslSource); 301 xslBuild = System.currentTimeMillis() - startTime; 302 303 m_stylesheetReady = true; 304 305 long[] times = getTimeArray(); 306 times[IDX_OVERALL] = xslRead + xslBuild; 307 times[IDX_XSLREAD] = xslRead; 308 times[IDX_XSLBUILD] = xslBuild; 309 return times; 310 } 311 312 313 /** 314 * Transform supplied xmlName file with a pre-built/pre-compiled 315 * stylesheet into a resultName file. 316 * 317 * User must have called buildStylesheet(xslName) beforehand, 318 * obviously. 319 * Names are assumed to be local path\filename references, and 320 * will be converted to URLs as needed. 321 * 322 * @param xmlName local path\filename of XML file to transform 323 * @param resultName local path\filename to put result in 324 * 325 * @return array of longs denoting timing of only these parts of 326 * our operation: IDX_OVERALL, IDX_XMLREAD, 327 * IDX_TRANSFORM, IDX_RESULTWRITE 328 * 329 * @throws Exception any underlying exceptions from the 330 * wrappered processor are simply allowed to propagate; throws 331 * a RuntimeException if any other problems prevent us from 332 * actually completing the operation; throws an 333 * IllegalStateException if isStylesheetReady() == false. 334 * 335 * @see #buildStylesheet(String xslName) 336 */ transformWithStylesheet(String xmlName, String resultName)337 public long[] transformWithStylesheet(String xmlName, String resultName) 338 throws Exception 339 { 340 if (!isStylesheetReady()) 341 throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); 342 343 preventFootShooting(); 344 long startTime = 0; 345 long xmlRead = 0; 346 long transform = 0; 347 long resultWrite = 0; 348 349 // Untimed: get Transformer from Templates 350 Transformer transformer = builtTemplates.newTransformer(); 351 transformer.setErrorListener(new DefaultErrorHandler()); 352 353 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 354 dfactory.setNamespaceAware(true); 355 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 356 357 // Timed: read xml into a DOM 358 startTime = System.currentTimeMillis(); 359 Node xmlNode = docBuilder.parse(new InputSource(QetestUtils.filenameToURL(xmlName))); 360 xmlRead = System.currentTimeMillis() - startTime; 361 362 // Untimed: create DOMSource and setSystemId 363 DOMSource xmlSource = new DOMSource(xmlNode); 364 xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName)); 365 366 // Untimed: create DOMResult 367 Document outNode = docBuilder.newDocument(); 368 DOMResult domResult = new DOMResult(outNode); 369 370 // Untimed: Set any of our options as Attributes on the transformer 371 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 372 373 // Untimed: Apply any parameters needed 374 applyParameters(transformer); 375 376 // Timed: build xml (so to speak) and transform 377 startTime = System.currentTimeMillis(); 378 transformer.transform(xmlSource, domResult); 379 transform = System.currentTimeMillis() - startTime; 380 381 // Untimed: prepare serializer with outputProperties 382 // from the stylesheet 383 Transformer resultSerializer = factory.newTransformer(); 384 resultSerializer.setErrorListener(new DefaultErrorHandler()); 385 Properties serializationProps = transformer.getOutputProperties(); 386 resultSerializer.setOutputProperties(serializationProps); 387 388 // Timed: writeResults from the DOMResult 389 startTime = System.currentTimeMillis(); 390 resultSerializer.transform(new DOMSource(outNode), 391 new StreamResult(resultName)); 392 resultWrite = System.currentTimeMillis() - startTime; 393 394 long[] times = getTimeArray(); 395 times[IDX_OVERALL] = xmlRead + transform + resultWrite; 396 times[IDX_XMLREAD] = xmlRead; 397 times[IDX_TRANSFORM] = transform; 398 times[IDX_RESULTWRITE] = resultWrite; 399 return times; 400 } 401 402 403 /** 404 * Transform supplied xmlName file with a stylesheet found in an 405 * xml-stylesheet PI into a resultName file. 406 * 407 * Names are assumed to be local path\filename references, and 408 * will be converted to URLs as needed. Implementations will 409 * use whatever facilities exist in their wrappered processor 410 * to fetch and build the stylesheet to use for the transform. 411 * 412 * @param xmlName local path\filename of XML file to transform 413 * @param resultName local path\filename to put result in 414 * 415 * @return array of longs denoting timing of only these parts of 416 * our operation: IDX_OVERALL, IDX_XSLREAD (time to find XSL 417 * reference from the xml-stylesheet PI), IDX_XSLBUILD, (time 418 * to then build the Transformer therefrom), IDX_TRANSFORM, 419 * and IDX_RESULTWRITE 420 * 421 * @throws Exception any underlying exceptions from the 422 * wrappered processor are simply allowed to propagate; throws 423 * a RuntimeException if any other problems prevent us from 424 * actually completing the operation 425 */ transformEmbedded(String xmlName, String resultName)426 public long[] transformEmbedded(String xmlName, String resultName) 427 throws Exception 428 { 429 long startTime = 0; 430 long xslRead = 0; 431 long xslBuild = 0; 432 long xmlRead = 0; 433 long transform = 0; 434 long resultWrite = 0; 435 436 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 437 dfactory.setNamespaceAware(true); 438 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 439 440 // Timed: read xml into a DOM 441 startTime = System.currentTimeMillis(); 442 Node xmlNode = docBuilder.parse(new InputSource(QetestUtils.filenameToURL(xmlName))); 443 xmlRead = System.currentTimeMillis() - startTime; 444 445 // Untimed: create DOMSource and setSystemId 446 DOMSource xmlSource = new DOMSource(xmlNode); 447 xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName)); 448 449 // Timed: readxsl from the xml document 450 startTime = System.currentTimeMillis(); 451 Source xslSource = factory.getAssociatedStylesheet(xmlSource, 452 null, null, null); 453 xslRead = System.currentTimeMillis() - startTime; 454 455 // Timed: build Transformer from Source 456 startTime = System.currentTimeMillis(); 457 Transformer transformer = factory.newTransformer(xslSource); 458 transformer.setErrorListener(new DefaultErrorHandler()); 459 xslBuild = System.currentTimeMillis() - startTime; 460 461 // Untimed: create DOMResult 462 Document outNode = docBuilder.newDocument(); 463 DOMResult domResult = new DOMResult(outNode); 464 465 // Untimed: Set any of our options as Attributes on the transformer 466 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 467 468 // Untimed: Apply any parameters needed 469 applyParameters(transformer); 470 471 // Timed: build xml (so to speak) and transform 472 startTime = System.currentTimeMillis(); 473 transformer.transform(xmlSource, domResult); 474 transform = System.currentTimeMillis() - startTime; 475 476 // Untimed: prepare serializer with outputProperties 477 // from the stylesheet 478 Transformer resultSerializer = factory.newTransformer(); 479 resultSerializer.setErrorListener(new DefaultErrorHandler()); 480 Properties serializationProps = transformer.getOutputProperties(); 481 resultSerializer.setOutputProperties(serializationProps); 482 483 // Timed: writeResults from the DOMResult 484 startTime = System.currentTimeMillis(); 485 resultSerializer.transform(new DOMSource(outNode), 486 new StreamResult(resultName)); 487 resultWrite = System.currentTimeMillis() - startTime; 488 489 long[] times = getTimeArray(); 490 times[IDX_OVERALL] = xslRead + xslBuild + xmlRead 491 + transform + resultWrite; 492 times[IDX_XSLREAD] = xslRead; 493 times[IDX_XSLBUILD] = xslBuild; 494 times[IDX_XMLREAD] = xmlRead; 495 times[IDX_TRANSFORM] = transform; 496 times[IDX_RESULTWRITE] = resultWrite; 497 return times; 498 } 499 500 501 /** 502 * Reset our parameters and wrapper state, and optionally 503 * force creation of a new underlying processor implementation. 504 * 505 * This always clears our built stylesheet and any parameters 506 * that have been set. If newProcessor is true, also forces a 507 * re-creation of our underlying processor as if by calling 508 * newProcessor(). 509 * 510 * @param newProcessor if we should reset our underlying 511 * processor implementation as well 512 */ reset(boolean newProcessor)513 public void reset(boolean newProcessor) 514 { 515 super.reset(newProcessor); // clears indent and parameters 516 m_stylesheetReady = false; 517 builtTemplates = null; 518 if (newProcessor) 519 { 520 try 521 { 522 newProcessor(newProcessorOpts); 523 } 524 catch (Exception e) 525 { 526 //@todo Hmm: what should we do here? 527 } 528 } 529 } 530 531 532 /** 533 * Apply a single parameter to a Transformer. 534 * 535 * Overridden to take a Transformer and call setParameter(). 536 * 537 * @param passThru to be passed to each applyParameter() method 538 * call - for TrAX, you might pass a Transformer object. 539 * @param namespace for the parameter, may be null 540 * @param name for the parameter, should not be null 541 * @param value for the parameter, may be null 542 */ applyParameter(Object passThru, String namespace, String name, Object value)543 protected void applyParameter(Object passThru, String namespace, 544 String name, Object value) 545 { 546 try 547 { 548 Transformer t = (Transformer)passThru; 549 // Munge the namespace into the name per 550 // javax.xml.transform.Transformer.setParameter() 551 if (null != namespace) 552 { 553 name = "{" + namespace + "}" + name; 554 } 555 t.setParameter(name, value); 556 } 557 catch (Exception e) 558 { 559 throw new IllegalArgumentException("applyParameter threw: " + e.toString()); 560 } 561 } 562 563 564 /** 565 * Ensure newProcessor has been called when needed. 566 * 567 * Prevent users from shooting themselves in the foot by 568 * calling a transform* API before newProcessor(). 569 * 570 * (Sorry, I couldn't resist) 571 */ preventFootShooting()572 public void preventFootShooting() throws Exception 573 { 574 if (null == factory) 575 newProcessor(newProcessorOpts); 576 } 577 } 578