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.io.ByteArrayOutputStream; 23 import java.io.FileOutputStream; 24 import java.util.Hashtable; 25 import java.util.Properties; 26 27 import javax.xml.parsers.SAXParser; 28 import javax.xml.parsers.SAXParserFactory; 29 import javax.xml.transform.Templates; 30 import javax.xml.transform.Transformer; 31 import javax.xml.transform.TransformerConfigurationException; 32 import javax.xml.transform.TransformerFactory; 33 import javax.xml.transform.sax.SAXResult; 34 import javax.xml.transform.sax.SAXSource; 35 import javax.xml.transform.sax.SAXTransformerFactory; 36 import javax.xml.transform.sax.TemplatesHandler; 37 import javax.xml.transform.sax.TransformerHandler; 38 import javax.xml.transform.stream.StreamResult; 39 40 import org.apache.qetest.QetestUtils; 41 import org.apache.xml.utils.DefaultErrorHandler; 42 import org.xml.sax.SAXException; 43 import org.xml.sax.XMLReader; 44 45 /** 46 * Implementation of TransformWrapper that uses the TrAX API and 47 * uses SAXSource/SAXResult whenever possible. 48 * 49 * <p>This implementation uses SAX to build the stylesheet and 50 * to perform the transformation.</p> 51 * 52 * <p><b>Important!</b> The underlying System property of 53 * javax.xml.transform.TransformerFactory will determine the actual 54 * TrAX implementation used. This value will be reported out in 55 * our getProcessorInfo() method.</p> 56 * 57 * @author Shane Curcuru 58 * @version $Id$ 59 */ 60 public class TraxSAXWrapper extends TransformWrapperHelper 61 { 62 63 /** 64 * TransformerFactory to use; constructed in newProcessor(). 65 */ 66 protected TransformerFactory factory = null; 67 68 69 /** 70 * SAXTransformerFactory we actually use; constructed in newProcessor(). 71 */ 72 protected SAXTransformerFactory saxFactory = 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 SAXSource(systemId) 91 */ getDescription()92 public String getDescription() 93 { 94 return "Uses TrAX to perform transforms from SAXSource(stream)"; 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", "sax"); 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. 121 * 122 * @param options Hashtable of options, unused. 123 * 124 * @return (Object)getProcessor() as a side-effect, this will 125 * be null if there was any problem creating the processor OR 126 * if the underlying implementation doesn't use this 127 * 128 * @throws Exception covers any underlying exceptions thrown 129 * by the actual implementation 130 */ newProcessor(Hashtable options)131 public Object newProcessor(Hashtable options) throws Exception 132 { 133 newProcessorOpts = options; 134 //@todo do we need to do any other cleanup? 135 reset(false); 136 factory = TransformerFactory.newInstance(); 137 factory.setErrorListener(new DefaultErrorHandler()); 138 // Verify the factory supports SAX! 139 if (!(factory.getFeature(SAXSource.FEATURE) 140 && factory.getFeature(SAXResult.FEATURE))) 141 { 142 throw new TransformerConfigurationException("TraxSAXWrapper.newProcessor: factory does not support SAX!"); 143 } 144 // Set any of our options as Attributes on the factory 145 TraxWrapperUtils.setAttributes(factory, options); 146 saxFactory = (SAXTransformerFactory)factory; 147 return (Object)saxFactory; 148 } 149 150 151 /** 152 * Transform supplied xmlName file with the stylesheet in the 153 * xslName file into a resultName file using SAX. 154 * 155 * Pseudocode: 156 * <code> 157 * // Read/build stylesheet 158 * xslReader.setContentHandler(templatesHandler); 159 * xslReader.parse(xslName); 160 * 161 * xslOutputProps = templates.getOutputProperties(); 162 * // Set features and tie in DTD, lexical, etc. handling 163 * 164 * serializingHandler.getTransformer().setOutputProperties(xslOutputProps); 165 * serializingHandler.setResult(new StreamResult(outBytes)); 166 * stylesheetHandler.setResult(new SAXResult(serializingHandler)); 167 * xmlReader.setContentHandler(stylesheetHandler); 168 * // Perform Transform 169 * xmlReader.parse(xmlName); 170 * // Separately: write bytes to disk 171 * </code> 172 * 173 * @param xmlName local path\filename of XML file to transform 174 * @param xslName local path\filename of XSL stylesheet to use 175 * @param resultName local path\filename to put result in 176 * 177 * @return array of longs denoting timing of all parts of 178 * our operation: IDX_OVERALL, IDX_XSLBUILD, 179 * IDX_TRANSFORM, IDX_RESULTWRITE 180 * 181 * @throws Exception any underlying exceptions from the 182 * wrappered processor are simply allowed to propagate; throws 183 * a RuntimeException if any other problems prevent us from 184 * actually completing the operation 185 */ transform(String xmlName, String xslName, String resultName)186 public long[] transform(String xmlName, String xslName, String resultName) 187 throws Exception 188 { 189 preventFootShooting(); 190 long startTime = 0; 191 long xslBuild = 0; 192 long transform = 0; 193 long resultWrite = 0; 194 195 // Create a ContentHandler to handle parsing of the xsl 196 TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler(); 197 198 // Create an XMLReader and set its ContentHandler. 199 // Be sure to use the JAXP methods only! 200 XMLReader xslReader = getJAXPXMLReader(); 201 xslReader.setContentHandler(templatesHandler); 202 203 // Timed: read/build Templates from StreamSource 204 startTime = System.currentTimeMillis(); 205 xslReader.parse(QetestUtils.filenameToURL(xslName)); 206 xslBuild = System.currentTimeMillis() - startTime; 207 208 // Get the Templates object from the ContentHandler. 209 Templates templates = templatesHandler.getTemplates(); 210 // Get the outputProperties from the stylesheet, so 211 // we can later use them for the serialization 212 Properties xslOutputProps = templates.getOutputProperties(); 213 214 // Create a ContentHandler to handle parsing of the XML 215 TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(templates); 216 // Also set systemId to the stylesheet 217 stylesheetHandler.setSystemId(QetestUtils.filenameToURL(xslName)); 218 219 // Untimed: Set any of our options as Attributes on the transformer 220 TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts); 221 222 // Apply any parameters needed 223 applyParameters(stylesheetHandler.getTransformer()); 224 225 // Use a new XMLReader to parse the XML document 226 XMLReader xmlReader = getJAXPXMLReader(); 227 xmlReader.setContentHandler(stylesheetHandler); 228 229 // Set the ContentHandler to also function as LexicalHandler, 230 // includes "lexical" events (e.g., comments and CDATA). 231 xmlReader.setProperty( 232 "http://xml.org/sax/properties/lexical-handler", 233 stylesheetHandler); 234 235 // Also attempt to set as a DeclHandler, which Xalan-J 236 // supports even though it is not required by JAXP 237 // Ignore exceptions for other processors since this 238 // is not a required setting 239 try 240 { 241 xmlReader.setProperty( 242 "http://xml.org/sax/properties/declaration-handler", 243 stylesheetHandler); 244 } 245 catch (SAXException se) { /* no-op - ignore */ } 246 247 // added by sb. Tie together DTD and other handling 248 xmlReader.setDTDHandler(stylesheetHandler); 249 try 250 { 251 xmlReader.setFeature( 252 "http://xml.org/sax/features/namespace-prefixes", 253 true); 254 } 255 catch (SAXException se) { /* no-op - ignore */ } 256 try 257 { 258 xmlReader.setFeature( 259 "http://apache.org/xml/features/validation/dynamic", 260 true); 261 } 262 catch (SAXException se) { /* no-op - ignore */ } 263 264 // Create a 'pipe'-like identity transformer, so we can 265 // easily get our results serialized to disk 266 TransformerHandler serializingHandler = saxFactory.newTransformerHandler(); 267 // Set the stylesheet's output properties into the output 268 // via it's Transformer 269 serializingHandler.getTransformer().setOutputProperties(xslOutputProps); 270 271 // Create StreamResult to byte stream in memory 272 ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); 273 StreamResult byteResult = new StreamResult(outBytes); 274 serializingHandler.setResult(byteResult); 275 276 // Create a SAXResult dumping into our 'pipe' serializer 277 // and tie in lexical handling (is any other handling needed?) 278 SAXResult saxResult = new SAXResult(serializingHandler); 279 saxResult.setLexicalHandler(serializingHandler); 280 281 // Set the original stylesheet to dump into our result 282 stylesheetHandler.setResult(saxResult); 283 284 // Timed: Parse the XML input document and do transform 285 startTime = System.currentTimeMillis(); 286 xmlReader.parse(QetestUtils.filenameToURL(xmlName)); 287 transform = System.currentTimeMillis() - startTime; 288 289 // Timed: writeResults from the byte array 290 startTime = System.currentTimeMillis(); 291 byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? 292 // @see TraxStreamWrapper 293 FileOutputStream writeStream = new FileOutputStream(resultName); 294 writeStream.write(writeBytes); 295 writeStream.close(); 296 resultWrite = System.currentTimeMillis() - startTime; 297 298 long[] times = getTimeArray(); 299 times[IDX_OVERALL] = xslBuild + transform + resultWrite; 300 times[IDX_XSLBUILD] = xslBuild; 301 times[IDX_TRANSFORM] = transform; 302 times[IDX_RESULTWRITE] = resultWrite; 303 return times; 304 } 305 306 307 /** 308 * Pre-build/pre-compile a stylesheet. 309 * 310 * Although the actual mechanics are implementation-dependent, 311 * most processors have some method of pre-setting up the data 312 * needed by the stylesheet itself for later use in transforms. 313 * In TrAX/javax.xml.transform, this equates to creating a 314 * Templates object. 315 * 316 * Sets isStylesheetReady() to true if it succeeds. Users can 317 * then call transformWithStylesheet(xmlName, resultName) to 318 * actually perform a transformation with this pre-built 319 * stylesheet. 320 * 321 * @param xslName local path\filename of XSL stylesheet to use 322 * 323 * @return array of longs denoting timing of all parts of 324 * our operation: IDX_OVERALL, IDX_XSLBUILD 325 * 326 * @throws Exception any underlying exceptions from the 327 * wrappered processor are simply allowed to propagate; throws 328 * a RuntimeException if any other problems prevent us from 329 * actually completing the operation 330 * 331 * @see #transformWithStylesheet(String xmlName, String resultName) 332 */ buildStylesheet(String xslName)333 public long[] buildStylesheet(String xslName) throws Exception 334 { 335 preventFootShooting(); 336 long startTime = 0; 337 long xslBuild = 0; 338 339 // Create a ContentHandler to handle parsing of the xsl 340 TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler(); 341 342 // Create an XMLReader and set its ContentHandler. 343 XMLReader xslReader = getJAXPXMLReader(); 344 xslReader.setContentHandler(templatesHandler); 345 346 // Timed: read/build Templates from StreamSource 347 startTime = System.currentTimeMillis(); 348 xslReader.parse(QetestUtils.filenameToURL(xslName)); 349 xslBuild = System.currentTimeMillis() - startTime; 350 351 // Also set systemId to the stylesheet 352 templatesHandler.setSystemId(QetestUtils.filenameToURL(xslName)); 353 354 // Get the Templates object from the ContentHandler. 355 builtTemplates = templatesHandler.getTemplates(); 356 m_stylesheetReady = true; 357 358 long[] times = getTimeArray(); 359 times[IDX_OVERALL] = xslBuild; 360 times[IDX_XSLBUILD] = xslBuild; 361 return times; 362 } 363 364 365 /** 366 * Transform supplied xmlName file with a pre-built/pre-compiled 367 * stylesheet into a resultName file. 368 * 369 * User must have called buildStylesheet(xslName) beforehand, 370 * obviously. 371 * Names are assumed to be local path\filename references, and 372 * will be converted to URLs as needed. 373 * 374 * @param xmlName local path\filename of XML file to transform 375 * @param resultName local path\filename to put result in 376 * 377 * @return array of longs denoting timing of all parts of 378 * our operation: IDX_OVERALL, 379 * IDX_XMLREAD, IDX_TRANSFORM, IDX_RESULTWRITE 380 * 381 * @throws Exception any underlying exceptions from the 382 * wrappered processor are simply allowed to propagate; throws 383 * a RuntimeException if any other problems prevent us from 384 * actually completing the operation; throws an 385 * IllegalStateException if isStylesheetReady() == false. 386 * 387 * @see #buildStylesheet(String xslName) 388 */ transformWithStylesheet(String xmlName, String resultName)389 public long[] transformWithStylesheet(String xmlName, String resultName) 390 throws Exception 391 { 392 if (!isStylesheetReady()) 393 throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); 394 395 preventFootShooting(); 396 long startTime = 0; 397 long xslRead = 0; 398 long xslBuild = 0; 399 long xmlRead = 0; 400 long transform = 0; 401 long resultWrite = 0; 402 403 // Get the outputProperties from the stylesheet, so 404 // we can later use them for the serialization 405 Properties xslOutputProps = builtTemplates.getOutputProperties(); 406 407 // Create a ContentHandler to handle parsing of the XML 408 TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(builtTemplates); 409 410 // Untimed: Set any of our options as Attributes on the transformer 411 TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts); 412 413 // Apply any parameters needed 414 applyParameters(stylesheetHandler.getTransformer()); 415 416 // Use a new XMLReader to parse the XML document 417 XMLReader xmlReader = getJAXPXMLReader(); 418 xmlReader.setContentHandler(stylesheetHandler); 419 420 // Set the ContentHandler to also function as LexicalHandler, 421 // includes "lexical" events (e.g., comments and CDATA). 422 xmlReader.setProperty( 423 "http://xml.org/sax/properties/lexical-handler", 424 stylesheetHandler); 425 xmlReader.setProperty( 426 "http://xml.org/sax/properties/declaration-handler", 427 stylesheetHandler); 428 429 // added by sb. Tie together DTD and other handling 430 xmlReader.setDTDHandler(stylesheetHandler); 431 try 432 { 433 xmlReader.setFeature( 434 "http://xml.org/sax/features/namespace-prefixes", 435 true); 436 } 437 catch (SAXException se) { /* no-op - ignore */ } 438 try 439 { 440 xmlReader.setFeature( 441 "http://apache.org/xml/features/validation/dynamic", 442 true); 443 } 444 catch (SAXException se) { /* no-op - ignore */ } 445 446 // Create a 'pipe'-like identity transformer, so we can 447 // easily get our results serialized to disk 448 TransformerHandler serializingHandler = saxFactory.newTransformerHandler(); 449 // Set the stylesheet's output properties into the output 450 // via it's Transformer 451 serializingHandler.getTransformer().setOutputProperties(xslOutputProps); 452 453 // Create StreamResult to byte stream in memory 454 ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); 455 StreamResult byteResult = new StreamResult(outBytes); 456 serializingHandler.setResult(byteResult); 457 458 // Create a SAXResult dumping into our 'pipe' serializer 459 // and tie in lexical handling (is any other handling needed?) 460 SAXResult saxResult = new SAXResult(serializingHandler); 461 saxResult.setLexicalHandler(serializingHandler); 462 463 // Set the original stylesheet to dump into our result 464 stylesheetHandler.setResult(saxResult); 465 466 // Timed: Parse the XML input document and do transform 467 startTime = System.currentTimeMillis(); 468 xmlReader.parse(QetestUtils.filenameToURL(xmlName)); 469 transform = System.currentTimeMillis() - startTime; 470 471 // Timed: writeResults from the byte array 472 startTime = System.currentTimeMillis(); 473 byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? 474 // @see TraxStreamWrapper 475 FileOutputStream writeStream = new FileOutputStream(resultName); 476 writeStream.write(writeBytes); 477 writeStream.close(); 478 resultWrite = System.currentTimeMillis() - startTime; 479 480 long[] times = getTimeArray(); 481 times[IDX_OVERALL] = transform + resultWrite; 482 times[IDX_TRANSFORM] = transform; 483 times[IDX_RESULTWRITE] = resultWrite; 484 return times; 485 } 486 487 488 /** 489 * Transform supplied xmlName file with a stylesheet found in an 490 * xml-stylesheet PI into a resultName file. 491 * 492 * Names are assumed to be local path\filename references, and 493 * will be converted to URLs as needed. Implementations will 494 * use whatever facilities exist in their wrappered processor 495 * to fetch and build the stylesheet to use for the transform. 496 * 497 * @param xmlName local path\filename of XML file to transform 498 * @param resultName local path\filename to put result in 499 * 500 * @return array of longs denoting timing of only these parts of 501 * our operation: IDX_OVERALL, IDX_XSLREAD (time to find XSL 502 * reference from the xml-stylesheet PI), IDX_XSLBUILD, (time 503 * to then build the Transformer therefrom), IDX_TRANSFORM 504 * 505 * @throws Exception any underlying exceptions from the 506 * wrappered processor are simply allowed to propagate; throws 507 * a RuntimeException if any other problems prevent us from 508 * actually completing the operation 509 */ transformEmbedded(String xmlName, String resultName)510 public long[] transformEmbedded(String xmlName, String resultName) 511 throws Exception 512 { 513 preventFootShooting(); 514 long startTime = 0; 515 long xslRead = 0; 516 long xslBuild = 0; 517 long transform = 0; 518 519 throw new RuntimeException("TraxSAXWrapper.transformEmbedded not implemented yet!"); 520 } 521 522 523 /** 524 * Reset our parameters and wrapper state, and optionally 525 * force creation of a new underlying processor implementation. 526 * 527 * This always clears our built stylesheet and any parameters 528 * that have been set. If newProcessor is true, also forces a 529 * re-creation of our underlying processor as if by calling 530 * newProcessor(). 531 * 532 * @param newProcessor if we should reset our underlying 533 * processor implementation as well 534 */ reset(boolean newProcessor)535 public void reset(boolean newProcessor) 536 { 537 super.reset(newProcessor); // clears indent and parameters 538 m_stylesheetReady = false; 539 builtTemplates = null; 540 if (newProcessor) 541 { 542 try 543 { 544 newProcessor(newProcessorOpts); 545 } 546 catch (Exception e) 547 { 548 //@todo Hmm: what should we do here? 549 } 550 } 551 } 552 553 554 /** 555 * Apply a single parameter to a Transformer. 556 * 557 * Overridden to take a Transformer and call setParameter(). 558 * 559 * @param passThru to be passed to each applyParameter() method 560 * call - for TrAX, you might pass a Transformer object. 561 * @param namespace for the parameter, may be null 562 * @param name for the parameter, should not be null 563 * @param value for the parameter, may be null 564 */ applyParameter(Object passThru, String namespace, String name, Object value)565 protected void applyParameter(Object passThru, String namespace, 566 String name, Object value) 567 { 568 try 569 { 570 Transformer t = (Transformer)passThru; 571 // Munge the namespace into the name per 572 // javax.xml.transform.Transformer.setParameter() 573 if (null != namespace) 574 { 575 name = "{" + namespace + "}" + name; 576 } 577 t.setParameter(name, value); 578 } 579 catch (Exception e) 580 { 581 throw new IllegalArgumentException("applyParameter threw: " + e.toString()); 582 } 583 } 584 585 586 /** 587 * Ensure newProcessor has been called when needed. 588 * 589 * Prevent users from shooting themselves in the foot by 590 * calling a transform* API before newProcessor(). 591 * 592 * (Sorry, I couldn't resist) 593 */ preventFootShooting()594 public void preventFootShooting() throws Exception 595 { 596 if (null == factory) 597 newProcessor(newProcessorOpts); 598 } 599 600 601 /** 602 * Worker method to get an XMLReader. 603 * 604 * Not the most efficient of methods, but makes the code simpler. 605 * 606 * @return a new XMLReader for use, with setNamespaceAware(true) 607 */ getJAXPXMLReader()608 protected XMLReader getJAXPXMLReader() 609 throws Exception 610 { 611 // Be sure to use the JAXP methods only! 612 SAXParserFactory factory = SAXParserFactory.newInstance(); 613 factory.setNamespaceAware(true); 614 SAXParser saxParser = factory.newSAXParser(); 615 return saxParser.getXMLReader(); 616 } 617 } 618