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.ByteArrayInputStream; 23 import java.io.ByteArrayOutputStream; 24 import java.io.File; 25 import java.io.FileInputStream; 26 import java.io.FileOutputStream; 27 import java.util.Hashtable; 28 import java.util.Properties; 29 30 import javax.xml.transform.Source; 31 import javax.xml.transform.Templates; 32 import javax.xml.transform.Transformer; 33 import javax.xml.transform.TransformerConfigurationException; 34 import javax.xml.transform.TransformerFactory; 35 import javax.xml.transform.stream.StreamResult; 36 import javax.xml.transform.stream.StreamSource; 37 38 import org.apache.qetest.QetestUtils; 39 import org.apache.xml.utils.DefaultErrorHandler; 40 41 /** 42 * Implementation of TransformWrapper that uses the TrAX API and 43 * uses in-memory streams for it's sources. 44 * 45 * <p>This implementation separates the process of reading xml and xsl 46 * files from disk into byte arrays out from the time processing of 47 * a new StreamSource(byte[]) takes to build a stylesheet. 48 * It also separates the time of performing the transformation 49 * to a StreamResult(byte[]) from the time spent simply sending 50 * the byte[] through a FileOutputStream to disk.</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 * <p>//@todo: currently limited to reading in files whose length 58 * will fit into an int value. The actual file reading routine 59 * should be updated in several of our methods.</p> 60 * 61 * @author Shane Curcuru 62 * @version $Id$ 63 */ 64 public class TraxStreamWrapper extends TransformWrapperHelper 65 { 66 67 /** 68 * TransformerFactory to use; constructed in newProcessor(). 69 */ 70 protected TransformerFactory factory = null; 71 72 73 /** 74 * Templates to use for buildStylesheet(). 75 */ 76 protected Templates builtTemplates = null; 77 78 79 /** 80 * Cached copy of newProcessor() Hashtable. 81 */ 82 protected Hashtable newProcessorOpts = null; 83 84 85 /** 86 * Get a general description of this wrapper itself. 87 * 88 * @return Uses TrAX to perform transforms from StreamSource(systemId) 89 */ getDescription()90 public String getDescription() 91 { 92 return "Uses TrAX to perform transforms from StreamSource(stream)"; 93 } 94 95 96 /** 97 * Get a specific description of the wrappered processor. 98 * 99 * @return specific description of the underlying processor or 100 * transformer implementation: this should include both the 101 * general product name, as well as specific version info. If 102 * possible, should be implemented without actively creating 103 * an underlying processor. 104 */ getProcessorInfo()105 public Properties getProcessorInfo() 106 { 107 Properties p = TraxWrapperUtils.getTraxInfo(); 108 p.put("traxwrapper.method", "streams"); 109 p.put("traxwrapper.desc", getDescription()); 110 return p; 111 } 112 113 114 /** 115 * Actually create/initialize an underlying processor or factory. 116 * 117 * For TrAX/javax.xml.transform implementations, this creates 118 * a new TransformerFactory. 119 * 120 * @param options Hashtable of options, unused. 121 * 122 * @return (Object)getProcessor() as a side-effect, this will 123 * be null if there was any problem creating the processor OR 124 * if the underlying implementation doesn't use this 125 * 126 * @throws Exception covers any underlying exceptions thrown 127 * by the actual implementation 128 */ newProcessor(Hashtable options)129 public Object newProcessor(Hashtable options) throws Exception 130 { 131 newProcessorOpts = options; 132 //@todo do we need to do any other cleanup? 133 reset(false); 134 factory = TransformerFactory.newInstance(); 135 factory.setErrorListener(new DefaultErrorHandler()); 136 // Verify the factory supports Streams! 137 if (!(factory.getFeature(StreamSource.FEATURE) 138 && factory.getFeature(StreamResult.FEATURE))) 139 { 140 throw new TransformerConfigurationException("TraxStreamWrapper.newProcessor: factory does not support Streams!"); 141 } 142 // Set any of our options as Attributes on the factory 143 TraxWrapperUtils.setAttributes(factory, options); 144 return (Object)factory; 145 } 146 147 148 /** 149 * Transform supplied xmlName file with the stylesheet in the 150 * xslName file into a resultName file. 151 * 152 * Names are assumed to be local path\filename references, and 153 * will be read as byte streams before being passed to 154 * underlying StreamSources, etc. 155 * 156 * @param xmlName local path\filename of XML file to transform 157 * @param xslName local path\filename of XSL stylesheet to use 158 * @param resultName local path\filename to put result in 159 * 160 * @return array of longs denoting timing of all parts of 161 * our operation: IDX_OVERALL, IDX_XSLREAD, IDX_XSLBUILD, 162 * IDX_XMLREAD, IDX_TRANSFORM, IDX_RESULTWRITE 163 * 164 * @throws Exception any underlying exceptions from the 165 * wrappered processor are simply allowed to propagate; throws 166 * a RuntimeException if any other problems prevent us from 167 * actually completing the operation 168 */ transform(String xmlName, String xslName, String resultName)169 public long[] transform(String xmlName, String xslName, String resultName) 170 throws Exception 171 { 172 preventFootShooting(); 173 long startTime = 0; 174 long xslRead = 0; 175 long xslBuild = 0; 176 long xmlRead = 0; 177 long transform = 0; 178 long resultWrite = 0; 179 180 File xslFile = new File(xslName); 181 int xslLength = new Long(xslFile.length()).intValue(); //@todo warning: possible overflow 182 byte[] xslBytes = new byte[xslLength]; 183 FileInputStream xslStream = new FileInputStream(xslFile); 184 // Timed: read xsl into a byte array 185 startTime = System.currentTimeMillis(); 186 int xslRetVal = xslStream.read(xslBytes); 187 xslRead = System.currentTimeMillis() - startTime; 188 189 // Untimed: create StreamSource and setSystemId 190 StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes)); 191 // Note that systemIds must be a legal URI 192 xslSource.setSystemId(QetestUtils.filenameToURL(xslName)); 193 194 // Timed: build Transformer from StreamSource 195 startTime = System.currentTimeMillis(); 196 Transformer transformer = factory.newTransformer(xslSource); 197 transformer.setErrorListener(new DefaultErrorHandler()); 198 xslBuild = System.currentTimeMillis() - startTime; 199 200 File xmlFile = new File(xmlName); 201 int xmlLength = new Long(xmlFile.length()).intValue(); //@todo warning: possible overflow 202 byte[] xmlBytes = new byte[xmlLength]; 203 FileInputStream xmlStream = new FileInputStream(xmlFile); 204 // Timed: read xml into a byte array 205 startTime = System.currentTimeMillis(); 206 int xmlRetVal = xmlStream.read(xmlBytes); 207 xmlRead = System.currentTimeMillis() - startTime; 208 209 // Untimed: create StreamSource and setSystemId 210 StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); 211 xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName)); 212 213 // Untimed: create StreamResult 214 ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); 215 StreamResult byteResult = new StreamResult(outBytes); 216 217 // Untimed: Set any of our options as Attributes on the transformer 218 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 219 220 // Untimed: Apply any parameters needed 221 applyParameters(transformer); 222 223 // Timed: build xml (so to speak) and transform 224 startTime = System.currentTimeMillis(); 225 transformer.transform(xmlSource, byteResult); 226 transform = System.currentTimeMillis() - startTime; 227 228 // Timed: writeResults from the byte array 229 startTime = System.currentTimeMillis(); 230 byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? 231 FileOutputStream writeStream = new FileOutputStream(resultName); 232 writeStream.write(writeBytes); 233 writeStream.close(); 234 resultWrite = System.currentTimeMillis() - startTime; 235 236 long[] times = getTimeArray(); 237 times[IDX_OVERALL] = xslRead + xslBuild + xmlRead 238 + transform + resultWrite; 239 times[IDX_XSLREAD] = xslRead; 240 times[IDX_XSLBUILD] = xslBuild; 241 times[IDX_XMLREAD] = xmlRead; 242 times[IDX_TRANSFORM] = transform; 243 times[IDX_RESULTWRITE] = resultWrite; 244 return times; 245 } 246 247 248 /** 249 * Pre-build/pre-compile a stylesheet. 250 * 251 * Although the actual mechanics are implementation-dependent, 252 * most processors have some method of pre-setting up the data 253 * needed by the stylesheet itself for later use in transforms. 254 * In TrAX/javax.xml.transform, this equates to creating a 255 * Templates object. 256 * 257 * Sets isStylesheetReady() to true if it succeeds. Users can 258 * then call transformWithStylesheet(xmlName, resultName) to 259 * actually perform a transformation with this pre-built 260 * stylesheet. 261 * 262 * @param xslName local path\filename of XSL stylesheet to use 263 * 264 * @return array of longs denoting timing of all parts of 265 * our operation: IDX_OVERALL, IDX_XSLREAD, IDX_XSLBUILD 266 * 267 * @throws Exception any underlying exceptions from the 268 * wrappered processor are simply allowed to propagate; throws 269 * a RuntimeException if any other problems prevent us from 270 * actually completing the operation 271 * 272 * @see #transformWithStylesheet(String xmlName, String resultName) 273 */ buildStylesheet(String xslName)274 public long[] buildStylesheet(String xslName) throws Exception 275 { 276 preventFootShooting(); 277 long startTime = 0; 278 long xslRead = 0; 279 long xslBuild = 0; 280 281 File xslFile = new File(xslName); 282 int xslLength = new Long(xslFile.length()).intValue(); //@todo warning: possible overflow 283 byte[] xslBytes = new byte[xslLength]; 284 FileInputStream xslStream = new FileInputStream(xslFile); 285 // Timed: read xsl into a byte array 286 startTime = System.currentTimeMillis(); 287 int xslRetVal = xslStream.read(xslBytes); 288 xslRead = System.currentTimeMillis() - startTime; 289 290 // Untimed: create StreamSource and setSystemId 291 StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes)); 292 // Note that systemIds must be a legal URI 293 xslSource.setSystemId(QetestUtils.filenameToURL(xslName)); 294 295 // Timed: build Transformer from StreamSource 296 startTime = System.currentTimeMillis(); 297 builtTemplates = factory.newTemplates(xslSource); 298 xslBuild = System.currentTimeMillis() - startTime; 299 300 // Set internal state that we have a templates ready 301 // Note: in theory, there's no need to check builtTemplates 302 // since the newTemplates should never return null 303 // (it might have thrown an exception, but we don't care) 304 m_stylesheetReady = true; 305 306 long[] times = getTimeArray(); 307 times[IDX_OVERALL] = xslBuild + xslRead; 308 times[IDX_XSLREAD] = xslRead; 309 times[IDX_XSLBUILD] = xslBuild; 310 return times; 311 } 312 313 314 /** 315 * Transform supplied xmlName file with a pre-built/pre-compiled 316 * stylesheet into a resultName file. 317 * 318 * User must have called buildStylesheet(xslName) beforehand, 319 * obviously. 320 * Names are assumed to be local path\filename references, and 321 * will be converted to URLs as needed. 322 * 323 * @param xmlName local path\filename of XML file to transform 324 * @param resultName local path\filename to put result in 325 * 326 * @return array of longs denoting timing of all parts of 327 * our operation: IDX_OVERALL, 328 * IDX_XMLREAD, IDX_TRANSFORM, IDX_RESULTWRITE 329 * 330 * @throws Exception any underlying exceptions from the 331 * wrappered processor are simply allowed to propagate; throws 332 * a RuntimeException if any other problems prevent us from 333 * actually completing the operation; throws an 334 * IllegalStateException if isStylesheetReady() == false. 335 * 336 * @see #buildStylesheet(String xslName) 337 */ transformWithStylesheet(String xmlName, String resultName)338 public long[] transformWithStylesheet(String xmlName, String resultName) 339 throws Exception 340 { 341 if (!isStylesheetReady()) 342 throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); 343 344 preventFootShooting(); 345 long getTransformer = 0; // This is timed in DataPower's xsltMark 346 long startTime = 0; 347 long xslRead = 0; 348 long xslBuild = 0; 349 long xmlRead = 0; 350 long transform = 0; 351 long resultWrite = 0; 352 353 // Timed: get Transformer from Templates 354 startTime = System.currentTimeMillis(); 355 Transformer transformer = builtTemplates.newTransformer(); 356 transformer.setErrorListener(new DefaultErrorHandler()); 357 getTransformer = System.currentTimeMillis() - startTime; 358 359 File xmlFile = new File(xmlName); 360 int xmlLength = new Long(xmlFile.length()).intValue(); //@todo warning: possible overflow 361 byte[] xmlBytes = new byte[xmlLength]; 362 FileInputStream xmlStream = new FileInputStream(xmlFile); 363 // Timed: read xml into a byte array 364 startTime = System.currentTimeMillis(); 365 int xmlRetVal = xmlStream.read(xmlBytes); 366 xmlRead = System.currentTimeMillis() - startTime; 367 368 // Untimed: create StreamSource and setSystemId 369 StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); 370 xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName)); 371 372 // Untimed: create StreamResult 373 ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); 374 StreamResult byteResult = new StreamResult(outBytes); 375 376 // Untimed: Set any of our options as Attributes on the transformer 377 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 378 379 // Untimed: Apply any parameters needed 380 applyParameters(transformer); 381 382 // Timed: build xml (so to speak) and transform 383 startTime = System.currentTimeMillis(); 384 transformer.transform(xmlSource, byteResult); 385 transform = System.currentTimeMillis() - startTime; 386 387 // Timed: writeResults from the byte array 388 startTime = System.currentTimeMillis(); 389 byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? 390 FileOutputStream writeStream = new FileOutputStream(resultName); 391 writeStream.write(writeBytes); 392 writeStream.close(); 393 resultWrite = System.currentTimeMillis() - startTime; 394 395 long[] times = getTimeArray(); 396 times[IDX_OVERALL] = getTransformer + xmlRead + transform + resultWrite; 397 times[IDX_XMLREAD] = xmlRead; 398 times[IDX_TRANSFORM] = getTransformer + xmlRead + transform; 399 times[IDX_RESULTWRITE] = resultWrite; 400 return times; 401 } 402 403 404 /** 405 * Transform supplied xmlName file with a stylesheet found in an 406 * xml-stylesheet PI into a resultName file. 407 * 408 * Names are assumed to be local path\filename references, and 409 * will be converted to URLs as needed. Implementations will 410 * use whatever facilities exist in their wrappered processor 411 * to fetch and build the stylesheet to use for the transform. 412 * 413 * @param xmlName local path\filename of XML file to transform 414 * @param resultName local path\filename to put result in 415 * 416 * @return array of longs denoting timing of only these parts of 417 * our operation: IDX_OVERALL, IDX_XSLREAD (time to find XSL 418 * reference from the xml-stylesheet PI), IDX_XSLBUILD, (time 419 * to then build the Transformer therefrom), IDX_TRANSFORM, 420 * IDX_XMLREAD, IDX_RESULTWRITE 421 * 422 * @throws Exception any underlying exceptions from the 423 * wrappered processor are simply allowed to propagate; throws 424 * a RuntimeException if any other problems prevent us from 425 * actually completing the operation 426 */ transformEmbedded(String xmlName, String resultName)427 public long[] transformEmbedded(String xmlName, String resultName) 428 throws Exception 429 { 430 preventFootShooting(); 431 long startTime = 0; 432 long xslRead = 0; 433 long xslBuild = 0; 434 long xmlRead = 0; 435 long transform = 0; 436 long resultWrite = 0; 437 438 439 File xmlFile = new File(xmlName); 440 int xmlLength = new Long(xmlFile.length()).intValue(); //@todo warning: possible overflow 441 byte[] xmlBytes = new byte[xmlLength]; 442 FileInputStream xmlStream = new FileInputStream(xmlFile); 443 // Timed: read xml into a byte array 444 startTime = System.currentTimeMillis(); 445 int xmlRetVal = xmlStream.read(xmlBytes); 446 xmlRead = System.currentTimeMillis() - startTime; 447 448 // Untimed: create StreamSource and setSystemId 449 StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); 450 xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName)); 451 452 // Timed: readxsl from the xml document 453 // Should this be timed as something? 454 startTime = System.currentTimeMillis(); 455 Source xslSource = factory.getAssociatedStylesheet(xmlSource, null, null, null); 456 xslRead = System.currentTimeMillis() - startTime; 457 458 // Timed: build xsl from a URL 459 startTime = System.currentTimeMillis(); 460 Transformer transformer = factory.newTransformer(xslSource); 461 transformer.setErrorListener(new DefaultErrorHandler()); 462 xslBuild = System.currentTimeMillis() - startTime; 463 464 // Re-read the XML file for use in transform; not timed 465 xmlFile = new File(xmlName); 466 xmlLength = new Long(xmlFile.length()).intValue(); //@todo warning: possible overflow 467 xmlBytes = new byte[xmlLength]; 468 xmlStream = new FileInputStream(xmlFile); 469 xmlRetVal = xmlStream.read(xmlBytes); 470 471 // Untimed: create StreamSource and setSystemId 472 xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); 473 xmlSource.setSystemId(QetestUtils.filenameToURL(xmlName)); 474 475 // Untimed: create StreamResult 476 ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); 477 StreamResult byteResult = new StreamResult(outBytes); 478 479 // Untimed: Set any of our options as Attributes on the transformer 480 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 481 482 // Untimed: Apply any parameters needed 483 applyParameters(transformer); 484 485 // Timed: build xml (so to speak) and transform 486 startTime = System.currentTimeMillis(); 487 transformer.transform(xmlSource, byteResult); 488 transform = System.currentTimeMillis() - startTime; 489 490 // Timed: writeResults from the byte array 491 startTime = System.currentTimeMillis(); 492 byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? 493 FileOutputStream writeStream = new FileOutputStream(resultName); 494 writeStream.write(writeBytes); 495 writeStream.close(); 496 resultWrite = System.currentTimeMillis() - startTime; 497 498 long[] times = getTimeArray(); 499 times[IDX_OVERALL] = xslRead + xslBuild + xmlRead 500 + transform + resultWrite; 501 times[IDX_XSLREAD] = xslRead; 502 times[IDX_XSLBUILD] = xslBuild; 503 times[IDX_XMLREAD] = xmlRead; 504 times[IDX_TRANSFORM] = transform; 505 times[IDX_RESULTWRITE] = resultWrite; 506 return times; 507 508 } 509 510 511 /** 512 * Reset our parameters and wrapper state, and optionally 513 * force creation of a new underlying processor implementation. 514 * 515 * This always clears our built stylesheet and any parameters 516 * that have been set. If newProcessor is true, also forces a 517 * re-creation of our underlying processor as if by calling 518 * newProcessor(). 519 * 520 * @param newProcessor if we should reset our underlying 521 * processor implementation as well 522 */ reset(boolean newProcessor)523 public void reset(boolean newProcessor) 524 { 525 super.reset(newProcessor); // clears indent and parameters 526 m_stylesheetReady = false; 527 builtTemplates = null; 528 if (newProcessor) 529 { 530 try 531 { 532 newProcessor(newProcessorOpts); 533 } 534 catch (Exception e) 535 { 536 //@todo Hmm: what should we do here? 537 } 538 } 539 } 540 541 542 /** 543 * Apply a single parameter to a Transformer. 544 * 545 * Overridden to take a Transformer and call setParameter(). 546 * 547 * @param passThru to be passed to each applyParameter() method 548 * call - for TrAX, you might pass a Transformer object. 549 * @param namespace for the parameter, may be null 550 * @param name for the parameter, should not be null 551 * @param value for the parameter, may be null 552 */ applyParameter(Object passThru, String namespace, String name, Object value)553 protected void applyParameter(Object passThru, String namespace, 554 String name, Object value) 555 { 556 try 557 { 558 Transformer t = (Transformer)passThru; 559 // Munge the namespace into the name per 560 // javax.xml.transform.Transformer.setParameter() 561 if (null != namespace) 562 { 563 name = "{" + namespace + "}" + name; 564 } 565 t.setParameter(name, value); 566 } 567 catch (Exception e) 568 { 569 throw new IllegalArgumentException("applyParameter threw: " + e.toString()); 570 } 571 } 572 573 574 /** 575 * Ensure newProcessor has been called when needed. 576 * 577 * Prevent users from shooting themselves in the foot by 578 * calling a transform* API before newProcessor(). 579 * 580 * (Sorry, I couldn't resist) 581 */ preventFootShooting()582 public void preventFootShooting() throws Exception 583 { 584 if (null == factory) 585 newProcessor(newProcessorOpts); 586 } 587 } 588