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.io.ByteArrayOutputStream; 23 import java.io.FileOutputStream; 24 import java.util.Properties; 25 import javax.xml.parsers.SAXParserFactory; 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.sax.SAXResult; 31 import javax.xml.transform.sax.SAXSource; 32 import javax.xml.transform.sax.SAXTransformerFactory; 33 import javax.xml.transform.sax.TemplatesHandler; 34 import javax.xml.transform.sax.TransformerHandler; 35 import javax.xml.transform.stream.StreamResult; 36 import static jaxp.library.JAXPTestUtilities.filenameToURL; 37 import org.xml.sax.SAXException; 38 import org.xml.sax.XMLReader; 39 40 /** 41 * Implementation of TransformWrapper that uses the TrAX API and uses 42 * SAXSource/SAXResult whenever possible. 43 * This implementation uses SAX to build the stylesheet and to perform the 44 * transformation. 45 * <p><b>Important!</b> The underlying System property of 46 * javax.xml.transform.TransformerFactory will determine the actual TrAX 47 * implementation used. This value will be reported out in our 48 * getProcessorInfo() method.</p> 49 * 50 */ 51 public class TraxSAXWrapper extends TransformWrapperHelper { 52 53 /** 54 * TransformerFactory to use; constructed in newProcessor(). 55 */ 56 private TransformerFactory factory; 57 58 /** 59 * SAXTransformerFactory we actually use; constructed in newProcessor(). 60 */ 61 private SAXTransformerFactory saxFactory; 62 63 /** 64 * Templates to use for buildStylesheet(). 65 */ 66 private Templates builtTemplates; 67 68 /** 69 * Cached copy of newProcessor() properties. 70 */ 71 protected Properties newProcessorOpts = null; 72 73 /** 74 * Get a general description of this wrapper itself. 75 * 76 * @return Uses TrAX to perform transforms from SAXSource(systemId) 77 */ 78 public String getDescription() { 79 return "Uses TrAX to perform transforms from SAXSource(stream)"; 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", "sax"); 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. 103 * 104 * @param options Hashtable of options, unused. 105 * 106 * @return (Object)getProcessor() as a side-effect, this will be null if 107 * there was any problem creating the processor OR if the underlying 108 * implementation doesn't use this 109 * @throws javax.xml.transform.TransformerConfigurationException when 110 * actual implementation doesn't support StreamSource or StreamResult. 111 */ 112 @Override 113 public TransformerFactory newProcessor(Properties options) throws TransformerConfigurationException { 114 newProcessorOpts = options; 115 reset(false); 116 factory = TransformerFactory.newInstance(); 117 // Verify the factory supports SAX! 118 if (!(factory.getFeature(SAXSource.FEATURE) 119 && factory.getFeature(SAXResult.FEATURE))) { 120 throw new TransformerConfigurationException("TraxSAXWrapper.newProcessor: factory does not support SAX!"); 121 } 122 // Set any of our options as Attributes on the factory 123 TraxWrapperUtils.setAttributes(factory, options); 124 saxFactory = (SAXTransformerFactory) factory; 125 return saxFactory; 126 } 127 128 /** 129 * Transform supplied xmlName file with the stylesheet in the xslName file 130 * into a resultName file using SAX. 131 * 132 * Pseudocode: <code> 133 * // Read/build stylesheet 134 * xslReader.setContentHandler(templatesHandler); 135 * xslReader.parse(xslName); 136 * 137 * xslOutputProps = templates.getOutputProperties(); 138 * // Set features and tie in DTD, lexical, etc. handling 139 * 140 * serializingHandler.getTransformer().setOutputProperties(xslOutputProps); 141 * serializingHandler.setResult(new StreamResult(outBytes)); 142 * stylesheetHandler.setResult(new SAXResult(serializingHandler)); 143 * xmlReader.setContentHandler(stylesheetHandler); 144 * // Perform Transform 145 * xmlReader.parse(xmlName); 146 * // Separately: write bytes to disk 147 * </code> 148 * 149 * @param xmlName local path\filename of XML file to transform 150 * @param xslName local path\filename of XSL stylesheet to use 151 * @param resultName local path\filename to put result in 152 * 153 * @throws Exception any underlying exceptions from the wrappered processor 154 * are simply allowed to propagate; throws a RuntimeException if any other 155 * problems prevent us from actually completing the operation 156 */ 157 @Override 158 public void transform(String xmlName, String xslName, String resultName) 159 throws Exception { 160 preventFootShooting(); 161 162 // Create a ContentHandler to handle parsing of the xsl 163 TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler(); 164 165 // Create an XMLReader and set its ContentHandler. 166 // Be sure to use the JAXP methods only! 167 XMLReader xslReader = getJAXPXMLReader(); 168 xslReader.setContentHandler(templatesHandler); 169 170 // read/build Templates from StreamSource 171 xslReader.parse(filenameToURL(xslName)); 172 173 // Get the Templates object from the ContentHandler. 174 Templates templates = templatesHandler.getTemplates(); 175 // Get the outputProperties from the stylesheet, so 176 // we can later use them for the serialization 177 Properties xslOutputProps = templates.getOutputProperties(); 178 179 // Create a ContentHandler to handle parsing of the XML 180 TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(templates); 181 // Also set systemId to the stylesheet 182 stylesheetHandler.setSystemId(filenameToURL(xslName)); 183 184 // Untimed: Set any of our options as Attributes on the transformer 185 TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts); 186 187 // Apply any parameters needed 188 applyParameters(stylesheetHandler.getTransformer()); 189 190 // Use a new XMLReader to parse the XML document 191 XMLReader xmlReader = getJAXPXMLReader(); 192 xmlReader.setContentHandler(stylesheetHandler); 193 194 // Set the ContentHandler to also function as LexicalHandler, 195 // includes "lexical" events (e.g., comments and CDATA). 196 xmlReader.setProperty( 197 "http://xml.org/sax/properties/lexical-handler", 198 stylesheetHandler); 199 200 // Also attempt to set as a DeclHandler, which Xalan-J 201 // supports even though it is not required by JAXP 202 // Ignore exceptions for other processors since this 203 // is not a required setting 204 try { 205 xmlReader.setProperty( 206 "http://xml.org/sax/properties/declaration-handler", 207 stylesheetHandler); 208 } catch (SAXException se) { /* no-op - ignore */ } 209 210 // added by sb. Tie together DTD and other handling 211 xmlReader.setDTDHandler(stylesheetHandler); 212 try { 213 xmlReader.setFeature( 214 "http://xml.org/sax/features/namespace-prefixes", 215 true); 216 } catch (SAXException se) { /* no-op - ignore */ } 217 try { 218 xmlReader.setFeature( 219 "http://apache.org/xml/features/validation/dynamic", 220 true); 221 } catch (SAXException se) { /* no-op - ignore */ } 222 223 // Create a 'pipe'-like identity transformer, so we can 224 // easily get our results serialized to disk 225 TransformerHandler serializingHandler = saxFactory.newTransformerHandler(); 226 // Set the stylesheet's output properties into the output 227 // via it's Transformer 228 serializingHandler.getTransformer().setOutputProperties(xslOutputProps); 229 230 // Create StreamResult to byte stream in memory 231 ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); 232 StreamResult byteResult = new StreamResult(outBytes); 233 serializingHandler.setResult(byteResult); 234 235 // Create a SAXResult dumping into our 'pipe' serializer 236 // and tie in lexical handling (is any other handling needed?) 237 SAXResult saxResult = new SAXResult(serializingHandler); 238 saxResult.setLexicalHandler(serializingHandler); 239 240 // Set the original stylesheet to dump into our result 241 stylesheetHandler.setResult(saxResult); 242 243 // Parse the XML input document and do transform 244 xmlReader.parse(filenameToURL(xmlName)); 245 246 // writeResults from the byte array 247 byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? 248 try (FileOutputStream writeStream = new FileOutputStream(resultName)) { 249 writeStream.write(writeBytes); 250 } 251 } 252 253 /** 254 * Pre-build/pre-compile a stylesheet. 255 * 256 * Although the actual mechanics are implementation-dependent, most 257 * processors have some method of pre-setting up the data needed by the 258 * stylesheet itself for later use in transforms. In 259 * TrAX/javax.xml.transform, this equates to creating a Templates object. 260 * 261 * Sets isStylesheetReady() to true if it succeeds. Users can then call 262 * transformWithStylesheet(xmlName, resultName) to actually perform a 263 * transformation with this pre-built stylesheet. 264 * 265 * @param xslName local path\filename of XSL stylesheet to use 266 * 267 * @throws Exception any underlying exceptions from the wrapped processor 268 * are simply allowed to propagate; throws a RuntimeException if any other 269 * problems prevent us from actually completing the operation 270 * 271 * @see #transformWithStylesheet(String xmlName, String resultName) 272 */ 273 @Override 274 public void buildStylesheet(String xslName) throws Exception { 275 preventFootShooting(); 276 277 // Create a ContentHandler to handle parsing of the xsl 278 TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler(); 279 280 // Create an XMLReader and set its ContentHandler. 281 XMLReader xslReader = getJAXPXMLReader(); 282 xslReader.setContentHandler(templatesHandler); 283 284 // read/build Templates from StreamSource 285 xslReader.parse(filenameToURL(xslName)); 286 287 // Also set systemId to the stylesheet 288 templatesHandler.setSystemId(filenameToURL(xslName)); 289 290 // Get the Templates object from the ContentHandler. 291 builtTemplates = templatesHandler.getTemplates(); 292 m_stylesheetReady = true; 293 } 294 295 /** 296 * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet 297 * into a resultName file. 298 * 299 * User must have called buildStylesheet(xslName) beforehand, obviously. 300 * Names are assumed to be local path\filename references, and will be 301 * converted to URLs as needed. 302 * 303 * @param xmlName local path\filename of XML file to transform 304 * @param resultName local path\filename to put result in 305 * 306 * @throws Exception any underlying exceptions from the wrappered processor 307 * are simply allowed to propagate; throws a RuntimeException if any other 308 * problems prevent us from actually completing the operation; throws an 309 * IllegalStateException if isStylesheetReady() == false. 310 * 311 * @see #buildStylesheet(String xslName) 312 */ 313 @Override 314 public void transformWithStylesheet(String xmlName, String resultName) 315 throws Exception { 316 if (!isStylesheetReady()) { 317 throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); 318 } 319 preventFootShooting(); 320 321 // Get the outputProperties from the stylesheet, so 322 // we can later use them for the serialization 323 Properties xslOutputProps = builtTemplates.getOutputProperties(); 324 325 // Create a ContentHandler to handle parsing of the XML 326 TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(builtTemplates); 327 328 // Untimed: Set any of our options as Attributes on the transformer 329 TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts); 330 331 // Apply any parameters needed 332 applyParameters(stylesheetHandler.getTransformer()); 333 334 // Use a new XMLReader to parse the XML document 335 XMLReader xmlReader = getJAXPXMLReader(); 336 xmlReader.setContentHandler(stylesheetHandler); 337 338 // Set the ContentHandler to also function as LexicalHandler, 339 // includes "lexical" events (e.g., comments and CDATA). 340 xmlReader.setProperty( 341 "http://xml.org/sax/properties/lexical-handler", 342 stylesheetHandler); 343 xmlReader.setProperty( 344 "http://xml.org/sax/properties/declaration-handler", 345 stylesheetHandler); 346 347 // added by sb. Tie together DTD and other handling 348 xmlReader.setDTDHandler(stylesheetHandler); 349 try { 350 xmlReader.setFeature( 351 "http://xml.org/sax/features/namespace-prefixes", 352 true); 353 } catch (SAXException se) { /* no-op - ignore */ } 354 try { 355 xmlReader.setFeature( 356 "http://apache.org/xml/features/validation/dynamic", 357 true); 358 } catch (SAXException se) { /* no-op - ignore */ } 359 360 // Create a 'pipe'-like identity transformer, so we can 361 // easily get our results serialized to disk 362 TransformerHandler serializingHandler = saxFactory.newTransformerHandler(); 363 // Set the stylesheet's output properties into the output 364 // via it's Transformer 365 serializingHandler.getTransformer().setOutputProperties(xslOutputProps); 366 367 // Create StreamResult to byte stream in memory 368 ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); 369 StreamResult byteResult = new StreamResult(outBytes); 370 serializingHandler.setResult(byteResult); 371 372 // Create a SAXResult dumping into our 'pipe' serializer 373 // and tie in lexical handling (is any other handling needed?) 374 SAXResult saxResult = new SAXResult(serializingHandler); 375 saxResult.setLexicalHandler(serializingHandler); 376 377 // Set the original stylesheet to dump into our result 378 stylesheetHandler.setResult(saxResult); 379 380 // Timed: Parse the XML input document and do transform 381 xmlReader.parse(filenameToURL(xmlName)); 382 383 // Timed: writeResults from the byte array 384 byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? 385 try (FileOutputStream writeStream = new FileOutputStream(resultName)) { 386 writeStream.write(writeBytes); 387 } 388 } 389 390 /** 391 * Transform supplied xmlName file with a stylesheet found in an 392 * xml-stylesheet PI into a resultName file. 393 * 394 * Names are assumed to be local path\filename references, and will be 395 * converted to URLs as needed. Implementations will use whatever facilities 396 * exist in their wrappered processor to fetch and build the stylesheet to 397 * use for the transform. 398 * 399 * @param xmlName local path\filename of XML file to transform 400 * @param resultName local path\filename to put result in 401 * 402 * @throws Exception any underlying exceptions from the wrappered processor 403 * are simply allowed to propagate; throws a RuntimeException if any other 404 * problems prevent us from actually completing the operation 405 */ 406 @Override 407 public void transformEmbedded(String xmlName, String resultName) 408 throws Exception { 409 throw new RuntimeException("TraxSAXWrapper.transformEmbedded not implemented yet!"); 410 } 411 412 /** 413 * Reset our parameters and wrapper state, and optionally force creation of 414 * a new underlying processor implementation. 415 * 416 * This always clears our built stylesheet and any parameters that have been 417 * set. If newProcessor is true, also forces a re-creation of our underlying 418 * processor as if by calling newProcessor(). 419 * 420 * @param newProcessor if we should reset our underlying processor 421 * implementation as well 422 */ 423 @Override 424 public void reset(boolean newProcessor) { 425 super.reset(newProcessor); // clears indent and parameters 426 m_stylesheetReady = false; 427 builtTemplates = null; 428 if (newProcessor) { 429 try { 430 newProcessor(newProcessorOpts); 431 } catch (TransformerConfigurationException ignore) {} 432 } 433 } 434 435 /** 436 * Apply a single parameter to a Transformer. 437 * 438 * Overridden to take a Transformer and call setParameter(). 439 * 440 * @param transformer a Transformer object. 441 * @param namespace for the parameter, may be null 442 * @param name for the parameter, should not be null 443 * @param value for the parameter, may be null 444 */ 445 @Override 446 protected void applyParameter(Transformer transformer, String namespace, 447 String name, String value) { 448 try { 449 // Munge the namespace into the name per 450 // javax.xml.transform.Transformer.setParameter() 451 if (null != namespace) { 452 name = "{" + namespace + "}" + name; 453 } 454 transformer.setParameter(name, value); 455 } catch (Exception e) { 456 throw new IllegalArgumentException("applyParameter threw: " + e.toString()); 457 } 458 } 459 460 /** 461 * Ensure newProcessor has been called when needed. 462 * 463 * Prevent users from shooting themselves in the foot by calling a 464 * transform* API before newProcessor(). 465 * 466 * (Sorry, I couldn't resist) 467 * @throws java.lang.Exception 468 */ 469 public void preventFootShooting() throws Exception { 470 if (null == factory) { 471 newProcessor(newProcessorOpts); 472 } 473 } 474 475 /** 476 * Worker method to get an XMLReader. 477 * 478 * Not the most efficient of methods, but makes the code simpler. 479 * 480 * @return a new XMLReader for use, with setNamespaceAware(true) 481 * @throws java.lang.Exception 482 */ 483 protected XMLReader getJAXPXMLReader() throws Exception { 484 // Be sure to use the JAXP methods only! 485 SAXParserFactory sfactory = SAXParserFactory.newInstance(); 486 sfactory.setNamespaceAware(true); 487 return sfactory.newSAXParser().getXMLReader(); 488 } 489 }