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