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.transform.Source; 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.stream.StreamResult; 31 import javax.xml.transform.stream.StreamSource; 32 33 import org.apache.qetest.QetestUtils; 34 import org.apache.xml.utils.DefaultErrorHandler; 35 36 /** 37 * Implementation of TransformWrapper that uses the TrAX API and 38 * uses systemId URL's for it's sources. 39 * 40 * This is the most common usage: 41 * transformer = factory.newTransformer(new StreamSource(xslURL)); 42 * transformer.transform(new StreamSource(xmlURL), new StreamResult(resultFileName)); 43 * 44 * <b>Important!</b> The underlying System property of 45 * javax.xml.transform.TransformerFactory will determine the actual 46 * TrAX implementation used. This value will be reported out in 47 * our getProcessorInfo() method. 48 * 49 * @author Shane Curcuru 50 * @version $Id$ 51 */ 52 public class TraxSystemIdWrapper extends TransformWrapperHelper 53 { 54 55 /** 56 * TransformerFactory to use; constructed in newProcessor(). 57 */ 58 protected TransformerFactory factory = null; 59 60 61 /** 62 * Templates to use for buildStylesheet(). 63 */ 64 protected Templates builtTemplates = null; 65 66 67 /** 68 * Cached copy of newProcessor() Hashtable. 69 */ 70 protected Hashtable newProcessorOpts = null; 71 72 /** 73 * Get a general description of this wrapper itself. 74 * 75 * @return Uses TrAX to perform transforms from StreamSource(systemId) 76 */ getDescription()77 public String getDescription() 78 { 79 return "Uses TrAX to perform transforms from StreamSource(systemId)"; 80 } 81 82 83 /** 84 * Get a specific description of the wrappered processor. 85 * 86 * @return specific description of the underlying processor or 87 * transformer implementation: this should include both the 88 * general product name, as well as specific version info. If 89 * possible, should be implemented without actively creating 90 * an underlying processor. 91 */ getProcessorInfo()92 public Properties getProcessorInfo() 93 { 94 Properties p = TraxWrapperUtils.getTraxInfo(); 95 p.put("traxwrapper.method", "systemId"); 96 p.put("traxwrapper.desc", getDescription()); 97 return p; 98 } 99 100 101 /** 102 * Actually create/initialize an underlying processor or factory. 103 * 104 * For TrAX/javax.xml.transform implementations, this creates 105 * a new TransformerFactory. For Xalan-J 1.x this creates an 106 * XSLTProcessor. Other implmentations may or may not actually 107 * do any work in this method. 108 * 109 * @param options Hashtable of options, unused. 110 * 111 * @return (Object)getProcessor() as a side-effect, this will 112 * be null if there was any problem creating the processor OR 113 * if the underlying implementation doesn't use this 114 * 115 * @throws Exception covers any underlying exceptions thrown 116 * by the actual implementation 117 */ newProcessor(Hashtable options)118 public Object newProcessor(Hashtable options) throws Exception 119 { 120 newProcessorOpts = options; 121 //@todo do we need to do any other cleanup? 122 reset(false); 123 factory = TransformerFactory.newInstance(); 124 factory.setErrorListener(new DefaultErrorHandler()); 125 // Verify the factory supports Streams! 126 if (!(factory.getFeature(StreamSource.FEATURE) 127 && factory.getFeature(StreamResult.FEATURE))) 128 { 129 throw new TransformerConfigurationException("TraxSystemIdWrapper.newProcessor: factory does not support Streams!"); 130 } 131 // Set any of our options as Attributes on the factory 132 TraxWrapperUtils.setAttributes(factory, newProcessorOpts); 133 return (Object)factory; 134 } 135 136 137 /** 138 * Transform supplied xmlName file with the stylesheet in the 139 * xslName file into a resultName file. 140 * 141 * Names are assumed to be local path\filename references, and 142 * will be converted to URLs as needed for any underlying 143 * processor implementation. 144 * 145 * @param xmlName local path\filename of XML file to transform 146 * @param xslName local path\filename of XSL stylesheet to use 147 * @param resultName local path\filename to put result in 148 * 149 * @return array of longs denoting timing of only these parts of 150 * our operation: IDX_OVERALL, IDX_XSLBUILD, IDX_TRANSFORM 151 * 152 * @throws Exception any underlying exceptions from the 153 * wrappered processor are simply allowed to propagate; throws 154 * a RuntimeException if any other problems prevent us from 155 * actually completing the operation 156 */ transform(String xmlName, String xslName, String resultName)157 public long[] transform(String xmlName, String xslName, String resultName) 158 throws Exception 159 { 160 preventFootShooting(); 161 long startTime = 0; 162 long xslBuild = 0; 163 long transform = 0; 164 165 // Timed: read/build xsl from a URL 166 startTime = System.currentTimeMillis(); 167 168 Transformer transformer = factory.newTransformer( 169 new StreamSource(QetestUtils.filenameToURL(xslName))); 170 transformer.setErrorListener(new DefaultErrorHandler()); 171 172 xslBuild = System.currentTimeMillis() - startTime; 173 174 // Untimed: Set any of our options as Attributes on the transformer 175 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 176 177 // Untimed: Apply any parameters needed 178 applyParameters(transformer); 179 180 // Timed: read/build xml, transform, and write results 181 startTime = System.currentTimeMillis(); 182 transformer.transform(new StreamSource(QetestUtils.filenameToURL(xmlName)), 183 new StreamResult(resultName)); 184 transform = System.currentTimeMillis() - startTime; 185 186 long[] times = getTimeArray(); 187 times[IDX_OVERALL] = xslBuild + transform; 188 times[IDX_XSLBUILD] = xslBuild; 189 times[IDX_TRANSFORM] = transform; 190 return times; 191 } 192 193 194 /** 195 * Pre-build/pre-compile a stylesheet. 196 * 197 * Although the actual mechanics are implementation-dependent, 198 * most processors have some method of pre-setting up the data 199 * needed by the stylesheet itself for later use in transforms. 200 * In TrAX/javax.xml.transform, this equates to creating a 201 * Templates object. 202 * 203 * Sets isStylesheetReady() to true if it succeeds. Users can 204 * then call transformWithStylesheet(xmlName, resultName) to 205 * actually perform a transformation with this pre-built 206 * stylesheet. 207 * 208 * @param xslName local path\filename of XSL stylesheet to use 209 * 210 * @return array of longs denoting timing of only these parts of 211 * our operation: IDX_OVERALL, IDX_XSLBUILD 212 * 213 * @throws Exception any underlying exceptions from the 214 * wrappered processor are simply allowed to propagate; throws 215 * a RuntimeException if any other problems prevent us from 216 * actually completing the operation 217 * 218 * @see #transformWithStylesheet(String xmlName, String resultName) 219 */ buildStylesheet(String xslName)220 public long[] buildStylesheet(String xslName) throws Exception 221 { 222 preventFootShooting(); 223 long startTime = 0; 224 long xslBuild = 0; 225 226 // Timed: read/build xsl from a URL 227 startTime = System.currentTimeMillis(); 228 builtTemplates = factory.newTemplates( 229 new StreamSource(QetestUtils.filenameToURL(xslName))); 230 xslBuild = System.currentTimeMillis() - startTime; 231 m_stylesheetReady = true; 232 233 long[] times = getTimeArray(); 234 times[IDX_OVERALL] = xslBuild; 235 times[IDX_XSLBUILD] = xslBuild; 236 return times; 237 } 238 239 240 /** 241 * Transform supplied xmlName file with a pre-built/pre-compiled 242 * stylesheet into a resultName file. 243 * 244 * User must have called buildStylesheet(xslName) beforehand, 245 * obviously. 246 * Names are assumed to be local path\filename references, and 247 * will be converted to URLs as needed. 248 * 249 * @param xmlName local path\filename of XML file to transform 250 * @param resultName local path\filename to put result in 251 * 252 * @return array of longs denoting timing of only these parts of 253 * our operation: IDX_OVERALL, IDX_XSLBUILD, IDX_TRANSFORM 254 * 255 * @throws Exception any underlying exceptions from the 256 * wrappered processor are simply allowed to propagate; throws 257 * a RuntimeException if any other problems prevent us from 258 * actually completing the operation; throws an 259 * IllegalStateException if isStylesheetReady() == false. 260 * 261 * @see #buildStylesheet(String xslName) 262 */ transformWithStylesheet(String xmlName, String resultName)263 public long[] transformWithStylesheet(String xmlName, String resultName) 264 throws Exception 265 { 266 if (!isStylesheetReady()) 267 throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); 268 269 preventFootShooting(); 270 long startTime = 0; 271 long transform = 0; 272 273 // UNTimed: get Transformer from Templates 274 Transformer transformer = builtTemplates.newTransformer(); 275 transformer.setErrorListener(new DefaultErrorHandler()); 276 277 // Untimed: Set any of our options as Attributes on the transformer 278 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 279 280 // Untimed: Apply any parameters needed 281 applyParameters(transformer); 282 283 // Timed: read/build xml, transform, and write results 284 startTime = System.currentTimeMillis(); 285 transformer.transform(new StreamSource(QetestUtils.filenameToURL(xmlName)), 286 new StreamResult(resultName)); 287 transform = System.currentTimeMillis() - startTime; 288 289 long[] times = getTimeArray(); 290 times[IDX_OVERALL] = transform; 291 times[IDX_TRANSFORM] = transform; 292 return times; 293 } 294 295 296 /** 297 * Transform supplied xmlName file with a stylesheet found in an 298 * xml-stylesheet PI into a resultName file. 299 * 300 * Names are assumed to be local path\filename references, and 301 * will be converted to URLs as needed. Implementations will 302 * use whatever facilities exist in their wrappered processor 303 * to fetch and build the stylesheet to use for the transform. 304 * 305 * @param xmlName local path\filename of XML file to transform 306 * @param resultName local path\filename to put result in 307 * 308 * @return array of longs denoting timing of only these parts of 309 * our operation: IDX_OVERALL, IDX_XSLREAD (time to find XSL 310 * reference from the xml-stylesheet PI), IDX_XSLBUILD, (time 311 * to then build the Transformer therefrom), IDX_TRANSFORM 312 * 313 * @throws Exception any underlying exceptions from the 314 * wrappered processor are simply allowed to propagate; throws 315 * a RuntimeException if any other problems prevent us from 316 * actually completing the operation 317 */ transformEmbedded(String xmlName, String resultName)318 public long[] transformEmbedded(String xmlName, String resultName) 319 throws Exception 320 { 321 preventFootShooting(); 322 long startTime = 0; 323 long xslRead = 0; 324 long xslBuild = 0; 325 long transform = 0; 326 327 // Timed: readxsl from the xml document 328 startTime = System.currentTimeMillis(); 329 Source xslSource = factory.getAssociatedStylesheet(new StreamSource(QetestUtils.filenameToURL(xmlName)), 330 null, null, null); 331 xslRead = System.currentTimeMillis() - startTime; 332 333 // Timed: build xsl from a URL 334 startTime = System.currentTimeMillis(); 335 Transformer transformer = factory.newTransformer(xslSource); 336 transformer.setErrorListener(new DefaultErrorHandler()); 337 xslBuild = System.currentTimeMillis() - startTime; 338 339 // Untimed: Set any of our options as Attributes on the transformer 340 TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); 341 342 // Untimed: Apply any parameters needed 343 applyParameters(transformer); 344 345 // Timed: read/build xml, transform, and write results 346 startTime = System.currentTimeMillis(); 347 transformer.transform(new StreamSource(QetestUtils.filenameToURL(xmlName)), 348 new StreamResult(resultName)); 349 transform = System.currentTimeMillis() - startTime; 350 351 long[] times = getTimeArray(); 352 times[IDX_OVERALL] = xslRead + xslBuild + transform; 353 times[IDX_XSLREAD] = xslRead; 354 times[IDX_XSLBUILD] = xslBuild; 355 times[IDX_TRANSFORM] = transform; 356 return times; 357 } 358 359 360 /** 361 * Reset our parameters and wrapper state, and optionally 362 * force creation of a new underlying processor implementation. 363 * 364 * This always clears our built stylesheet and any parameters 365 * that have been set. If newProcessor is true, also forces a 366 * re-creation of our underlying processor as if by calling 367 * newProcessor(). 368 * 369 * @param newProcessor if we should reset our underlying 370 * processor implementation as well 371 */ reset(boolean newProcessor)372 public void reset(boolean newProcessor) 373 { 374 super.reset(newProcessor); // clears indent and parameters 375 m_stylesheetReady = false; 376 builtTemplates = null; 377 if (newProcessor) 378 { 379 try 380 { 381 newProcessor(newProcessorOpts); 382 } 383 catch (Exception e) 384 { 385 //@todo Hmm: what should we do here? 386 } 387 } 388 } 389 390 391 /** 392 * Apply a single parameter to a Transformer. 393 * 394 * Overridden to take a Transformer and call setParameter(). 395 * 396 * @param passThru to be passed to each applyParameter() method 397 * call - for TrAX, you might pass a Transformer object. 398 * @param namespace for the parameter, may be null 399 * @param name for the parameter, should not be null 400 * @param value for the parameter, may be null 401 */ applyParameter(Object passThru, String namespace, String name, Object value)402 protected void applyParameter(Object passThru, String namespace, 403 String name, Object value) 404 { 405 try 406 { 407 Transformer t = (Transformer)passThru; 408 // Munge the namespace into the name per 409 // javax.xml.transform.Transformer.setParameter() 410 if (null != namespace) 411 { 412 name = "{" + namespace + "}" + name; 413 } 414 t.setParameter(name, value); 415 } 416 catch (Exception e) 417 { 418 throw new IllegalArgumentException("applyParameter threw: " + e.toString()); 419 } 420 } 421 422 423 /** 424 * Ensure newProcessor has been called when needed. 425 * 426 * Prevent users from shooting themselves in the foot by 427 * calling a transform* API before newProcessor(). 428 * 429 * (Sorry, I couldn't resist) 430 */ preventFootShooting()431 public void preventFootShooting() throws Exception 432 { 433 if (null == factory) 434 newProcessor(newProcessorOpts); 435 } 436 } 437