• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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