001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: TransformerFactoryImpl.java 1225273 2011-12-28 18:46:51Z mrglavas $
020 */
021
022 package org.apache.xalan.xsltc.trax;
023
024 import java.io.File;
025 import java.io.FileInputStream;
026 import java.io.FileNotFoundException;
027 import java.io.FilenameFilter;
028 import java.io.IOException;
029 import java.io.InputStream;
030 import java.net.MalformedURLException;
031 import java.net.URL;
032 import java.util.ArrayList;
033 import java.util.Enumeration;
034 import java.util.Hashtable;
035 import java.util.List;
036 import java.util.Properties;
037 import java.util.Vector;
038 import java.util.zip.ZipEntry;
039 import java.util.zip.ZipFile;
040
041 import javax.xml.XMLConstants;
042 import javax.xml.parsers.SAXParser;
043 import javax.xml.parsers.SAXParserFactory;
044 import javax.xml.transform.ErrorListener;
045 import javax.xml.transform.Source;
046 import javax.xml.transform.Templates;
047 import javax.xml.transform.Transformer;
048 import javax.xml.transform.TransformerConfigurationException;
049 import javax.xml.transform.TransformerException;
050 import javax.xml.transform.URIResolver;
051 import javax.xml.transform.dom.DOMResult;
052 import javax.xml.transform.dom.DOMSource;
053 import javax.xml.transform.sax.SAXResult;
054 import javax.xml.transform.sax.SAXSource;
055 import javax.xml.transform.sax.SAXTransformerFactory;
056 import javax.xml.transform.sax.TemplatesHandler;
057 import javax.xml.transform.sax.TransformerHandler;
058 import javax.xml.transform.stream.StreamResult;
059 import javax.xml.transform.stream.StreamSource;
060
061 import org.apache.xalan.xsltc.compiler.SourceLoader;
062 import org.apache.xalan.xsltc.compiler.XSLTC;
063 import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
064 import org.apache.xalan.xsltc.dom.XSLTCDTMManager;
065 import org.apache.xml.utils.StopParseException;
066 import org.apache.xml.utils.StylesheetPIHandler;
067 import org.xml.sax.InputSource;
068 import org.xml.sax.XMLFilter;
069 import org.xml.sax.XMLReader;
070 import org.xml.sax.helpers.XMLReaderFactory;
071
072 /**
073 * Implementation of a JAXP1.1 TransformerFactory for Translets.
074 * @author G. Todd Miller
075 * @author Morten Jorgensen
076 * @author Santiago Pericas-Geertsen
077 */
078 public class TransformerFactoryImpl
079 extends SAXTransformerFactory implements SourceLoader, ErrorListener
080 {
081 // Public constants for attributes supported by the XSLTC TransformerFactory.
082 public final static String TRANSLET_NAME = "translet-name";
083 public final static String DESTINATION_DIRECTORY = "destination-directory";
084 public final static String PACKAGE_NAME = "package-name";
085 public final static String JAR_NAME = "jar-name";
086 public final static String GENERATE_TRANSLET = "generate-translet";
087 public final static String AUTO_TRANSLET = "auto-translet";
088 public final static String USE_CLASSPATH = "use-classpath";
089 public final static String DEBUG = "debug";
090 public final static String ENABLE_INLINING = "enable-inlining";
091 public final static String INDENT_NUMBER = "indent-number";
092
093 /**
094 * This error listener is used only for this factory and is not passed to
095 * the Templates or Transformer objects that we create.
096 */
097 private ErrorListener _errorListener = this;
098
099 /**
100 * This URIResolver is passed to all created Templates and Transformers
101 */
102 private URIResolver _uriResolver = null;
103
104 /**
105 * As Gregor Samsa awoke one morning from uneasy dreams he found himself
106 * transformed in his bed into a gigantic insect. He was lying on his hard,
107 * as it were armour plated, back, and if he lifted his head a little he
108 * could see his big, brown belly divided into stiff, arched segments, on
109 * top of which the bed quilt could hardly keep in position and was about
110 * to slide off completely. His numerous legs, which were pitifully thin
111 * compared to the rest of his bulk, waved helplessly before his eyes.
112 * "What has happened to me?", he thought. It was no dream....
113 */
114 protected final static String DEFAULT_TRANSLET_NAME = "GregorSamsa";
115
116 /**
117 * The class name of the translet
118 */
119 private String _transletName = DEFAULT_TRANSLET_NAME;
120
121 /**
122 * The destination directory for the translet
123 */
124 private String _destinationDirectory = null;
125
126 /**
127 * The package name prefix for all generated translet classes
128 */
129 private String _packageName = null;
130
131 /**
132 * The jar file name which the translet classes are packaged into
133 */
134 private String _jarFileName = null;
135
136 /**
137 * This Hashtable is used to store parameters for locating
138 * <?xml-stylesheet ...?> processing instructions in XML docs.
139 */
140 private Hashtable _piParams = null;
141
142 /**
143 * The above hashtable stores objects of this class.
144 */
145 private static class PIParamWrapper {
146 public String _media = null;
147 public String _title = null;
148 public String _charset = null;
149
150 public PIParamWrapper(String media, String title, String charset) {
151 _media = media;
152 _title = title;
153 _charset = charset;
154 }
155 }
156
157 /**
158 * Set to <code>true</code> when debugging is enabled.
159 */
160 private boolean _debug = false;
161
162 /**
163 * Set to <code>true</code> when templates are inlined.
164 */
165 private boolean _enableInlining = false;
166
167 /**
168 * Set to <code>true</code> when we want to generate
169 * translet classes from the stylesheet.
170 */
171 private boolean _generateTranslet = false;
172
173 /**
174 * If this is set to <code>true</code>, we attempt to use translet classes
175 * for transformation if possible without compiling the stylesheet. The
176 * translet class is only used if its timestamp is newer than the timestamp
177 * of the stylesheet.
178 */
179 private boolean _autoTranslet = false;
180
181 /**
182 * If this is set to <code>true</code>, we attempt to load the translet
183 * from the CLASSPATH.
184 */
185 private boolean _useClasspath = false;
186
187 /**
188 * Number of indent spaces when indentation is turned on.
189 */
190 private int _indentNumber = -1;
191
192 /**
193 * The provider of the XSLTC DTM Manager service. This is fixed for any
194 * instance of this class. In order to change service providers, a new
195 * XSLTC <code>TransformerFactory</code> must be instantiated.
196 * @see XSLTCDTMManager#getDTMManagerClass()
197 */
198 private Class m_DTMManagerClass;
199
200 /**
201 * <p>State of secure processing feature.</p>
202 */
203 private boolean _isSecureProcessing = false;
204
205 /**
206 * javax.xml.transform.sax.TransformerFactory implementation.
207 */
208 public TransformerFactoryImpl() {
209 m_DTMManagerClass = XSLTCDTMManager.getDTMManagerClass();
210 }
211
212 /**
213 * javax.xml.transform.sax.TransformerFactory implementation.
214 * Set the error event listener for the TransformerFactory, which is used
215 * for the processing of transformation instructions, and not for the
216 * transformation itself.
217 *
218 * @param listener The error listener to use with the TransformerFactory
219 * @throws IllegalArgumentException
220 */
221 public void setErrorListener(ErrorListener listener)
222 throws IllegalArgumentException
223 {
224 if (listener == null) {
225 ErrorMsg err = new ErrorMsg(ErrorMsg.ERROR_LISTENER_NULL_ERR,
226 "TransformerFactory");
227 throw new IllegalArgumentException(err.toString());
228 }
229 _errorListener = listener;
230 }
231
232 /**
233 * javax.xml.transform.sax.TransformerFactory implementation.
234 * Get the error event handler for the TransformerFactory.
235 *
236 * @return The error listener used with the TransformerFactory
237 */
238 public ErrorListener getErrorListener() {
239 return _errorListener;
240 }
241
242 /**
243 * javax.xml.transform.sax.TransformerFactory implementation.
244 * Returns the value set for a TransformerFactory attribute
245 *
246 * @param name The attribute name
247 * @return An object representing the attribute value
248 * @throws IllegalArgumentException
249 */
250 public Object getAttribute(String name)
251 throws IllegalArgumentException
252 {
253 // Return value for attribute 'translet-name'
254 if (name.equals(TRANSLET_NAME)) {
255 return _transletName;
256 }
257 else if (name.equals(GENERATE_TRANSLET)) {
258 return _generateTranslet ? Boolean.TRUE : Boolean.FALSE;
259 }
260 else if (name.equals(AUTO_TRANSLET)) {
261 return _autoTranslet ? Boolean.TRUE : Boolean.FALSE;
262 }
263 else if (name.equals(ENABLE_INLINING)) {
264 if (_enableInlining)
265 return Boolean.TRUE;
266 else
267 return Boolean.FALSE;
268 }
269
270 // Throw an exception for all other attributes
271 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_INVALID_ATTR_ERR, name);
272 throw new IllegalArgumentException(err.toString());
273 }
274
275 /**
276 * javax.xml.transform.sax.TransformerFactory implementation.
277 * Sets the value for a TransformerFactory attribute.
278 *
279 * @param name The attribute name
280 * @param value An object representing the attribute value
281 * @throws IllegalArgumentException
282 */
283 public void setAttribute(String name, Object value)
284 throws IllegalArgumentException
285 {
286 // Set the default translet name (ie. class name), which will be used
287 // for translets that cannot be given a name from their system-id.
288 if (name.equals(TRANSLET_NAME) && value instanceof String) {
289 _transletName = (String) value;
290 return;
291 }
292 else if (name.equals(DESTINATION_DIRECTORY) && value instanceof String) {
293 _destinationDirectory = (String) value;
294 return;
295 }
296 else if (name.equals(PACKAGE_NAME) && value instanceof String) {
297 _packageName = (String) value;
298 return;
299 }
300 else if (name.equals(JAR_NAME) && value instanceof String) {
301 _jarFileName = (String) value;
302 return;
303 }
304 else if (name.equals(GENERATE_TRANSLET)) {
305 if (value instanceof Boolean) {
306 _generateTranslet = ((Boolean) value).booleanValue();
307 return;
308 }
309 else if (value instanceof String) {
310 _generateTranslet = ((String) value).equalsIgnoreCase("true");
311 return;
312 }
313 }
314 else if (name.equals(AUTO_TRANSLET)) {
315 if (value instanceof Boolean) {
316 _autoTranslet = ((Boolean) value).booleanValue();
317 return;
318 }
319 else if (value instanceof String) {
320 _autoTranslet = ((String) value).equalsIgnoreCase("true");
321 return;
322 }
323 }
324 else if (name.equals(USE_CLASSPATH)) {
325 if (value instanceof Boolean) {
326 _useClasspath = ((Boolean) value).booleanValue();
327 return;
328 }
329 else if (value instanceof String) {
330 _useClasspath = ((String) value).equalsIgnoreCase("true");
331 return;
332 }
333 }
334 else if (name.equals(DEBUG)) {
335 if (value instanceof Boolean) {
336 _debug = ((Boolean) value).booleanValue();
337 return;
338 }
339 else if (value instanceof String) {
340 _debug = ((String) value).equalsIgnoreCase("true");
341 return;
342 }
343 }
344 else if (name.equals(ENABLE_INLINING)) {
345 if (value instanceof Boolean) {
346 _enableInlining = ((Boolean) value).booleanValue();
347 return;
348 }
349 else if (value instanceof String) {
350 _enableInlining = ((String) value).equalsIgnoreCase("true");
351 return;
352 }
353 }
354 else if (name.equals(INDENT_NUMBER)) {
355 if (value instanceof String) {
356 try {
357 _indentNumber = Integer.parseInt((String) value);
358 return;
359 }
360 catch (NumberFormatException e) {
361 // Falls through
362 }
363 }
364 else if (value instanceof Integer) {
365 _indentNumber = ((Integer) value).intValue();
366 return;
367 }
368 }
369
370 // Throw an exception for all other attributes
371 final ErrorMsg err
372 = new ErrorMsg(ErrorMsg.JAXP_INVALID_ATTR_ERR, name);
373 throw new IllegalArgumentException(err.toString());
374 }
375
376 /**
377 * <p>Set a feature for this <code>TransformerFactory</code> and <code>Transformer</code>s
378 * or <code>Template</code>s created by this factory.</p>
379 *
380 * <p>
381 * Feature names are fully qualified {@link java.net.URI}s.
382 * Implementations may define their own features.
383 * An {@link TransformerConfigurationException} is thrown if this <code>TransformerFactory</code> or the
384 * <code>Transformer</code>s or <code>Template</code>s it creates cannot support the feature.
385 * It is possible for an <code>TransformerFactory</code> to expose a feature value but be unable to change its state.
386 * </p>
387 *
388 * <p>See {@link javax.xml.transform.TransformerFactory} for full documentation of specific features.</p>
389 *
390 * @param name Feature name.
391 * @param value Is feature state <code>true</code> or <code>false</code>.
392 *
393 * @throws TransformerConfigurationException if this <code>TransformerFactory</code>
394 * or the <code>Transformer</code>s or <code>Template</code>s it creates cannot support this feature.
395 * @throws NullPointerException If the <code>name</code> parameter is null.
396 */
397 public void setFeature(String name, boolean value)
398 throws TransformerConfigurationException {
399
400 // feature name cannot be null
401 if (name == null) {
402 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_SET_FEATURE_NULL_NAME);
403 throw new NullPointerException(err.toString());
404 }
405 // secure processing?
406 else if (name.equals(XMLConstants.FEATURE_SECURE_PROCESSING)) {
407 _isSecureProcessing = value;
408 // all done processing feature
409 return;
410 }
411 else {
412 // unknown feature
413 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNSUPPORTED_FEATURE, name);
414 throw new TransformerConfigurationException(err.toString());
415 }
416 }
417
418 /**
419 * javax.xml.transform.sax.TransformerFactory implementation.
420 * Look up the value of a feature (to see if it is supported).
421 * This method must be updated as the various methods and features of this
422 * class are implemented.
423 *
424 * @param name The feature name
425 * @return 'true' if feature is supported, 'false' if not
426 */
427 public boolean getFeature(String name) {
428 // All supported features should be listed here
429 String[] features = {
430 DOMSource.FEATURE,
431 DOMResult.FEATURE,
432 SAXSource.FEATURE,
433 SAXResult.FEATURE,
434 StreamSource.FEATURE,
435 StreamResult.FEATURE,
436 SAXTransformerFactory.FEATURE,
437 SAXTransformerFactory.FEATURE_XMLFILTER
438 };
439
440 // feature name cannot be null
441 if (name == null) {
442 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_GET_FEATURE_NULL_NAME);
443 throw new NullPointerException(err.toString());
444 }
445
446 // Inefficient, but array is small
447 for (int i =0; i < features.length; i++) {
448 if (name.equals(features[i])) {
449 return true;
450 }
451 }
452 // secure processing?
453 if (name.equals(XMLConstants.FEATURE_SECURE_PROCESSING)) {
454 return _isSecureProcessing;
455 }
456
457 // Feature not supported
458 return false;
459 }
460
461 /**
462 * javax.xml.transform.sax.TransformerFactory implementation.
463 * Get the object that is used by default during the transformation to
464 * resolve URIs used in document(), xsl:import, or xsl:include.
465 *
466 * @return The URLResolver used for this TransformerFactory and all
467 * Templates and Transformer objects created using this factory
468 */
469 public URIResolver getURIResolver() {
470 return _uriResolver;
471 }
472
473 /**
474 * javax.xml.transform.sax.TransformerFactory implementation.
475 * Set the object that is used by default during the transformation to
476 * resolve URIs used in document(), xsl:import, or xsl:include. Note that
477 * this does not affect Templates and Transformers that are already
478 * created with this factory.
479 *
480 * @param resolver The URLResolver used for this TransformerFactory and all
481 * Templates and Transformer objects created using this factory
482 */
483 public void setURIResolver(URIResolver resolver) {
484 _uriResolver = resolver;
485 }
486
487 /**
488 * javax.xml.transform.sax.TransformerFactory implementation.
489 * Get the stylesheet specification(s) associated via the xml-stylesheet
490 * processing instruction (see http://www.w3.org/TR/xml-stylesheet/) with
491 * the document document specified in the source parameter, and that match
492 * the given criteria.
493 *
494 * @param source The XML source document.
495 * @param media The media attribute to be matched. May be null, in which
496 * case the prefered templates will be used (i.e. alternate = no).
497 * @param title The value of the title attribute to match. May be null.
498 * @param charset The value of the charset attribute to match. May be null.
499 * @return A Source object suitable for passing to the TransformerFactory.
500 * @throws TransformerConfigurationException
501 */
502 public Source getAssociatedStylesheet(Source source, String media,
503 String title, String charset)
504 throws TransformerConfigurationException {
505
506 String baseId;
507 XMLReader reader = null;
508 InputSource isource = null;
509
510
511 /**
512 * Fix for bugzilla bug 24187
513 */
514 StylesheetPIHandler _stylesheetPIHandler = new StylesheetPIHandler(null,media,title,charset);
515
516 try {
517
518 if (source instanceof DOMSource ) {
519 final DOMSource domsrc = (DOMSource) source;
520 baseId = domsrc.getSystemId();
521 final org.w3c.dom.Node node = domsrc.getNode();
522 final DOM2SAX dom2sax = new DOM2SAX(node);
523
524 _stylesheetPIHandler.setBaseId(baseId);
525
526 dom2sax.setContentHandler( _stylesheetPIHandler);
527 dom2sax.parse();
528 } else {
529 isource = SAXSource.sourceToInputSource(source);
530 baseId = isource.getSystemId();
531
532 SAXParserFactory factory = SAXParserFactory.newInstance();
533 factory.setNamespaceAware(true);
534
535 if (_isSecureProcessing) {
536 try {
537 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
538 }
539 catch (org.xml.sax.SAXException e) {}
540 }
541
542 SAXParser jaxpParser = factory.newSAXParser();
543
544 reader = jaxpParser.getXMLReader();
545 if (reader == null) {
546 reader = XMLReaderFactory.createXMLReader();
547 }
548
549 _stylesheetPIHandler.setBaseId(baseId);
550 reader.setContentHandler(_stylesheetPIHandler);
551 reader.parse(isource);
552
553 }
554
555 if (_uriResolver != null ) {
556 _stylesheetPIHandler.setURIResolver(_uriResolver);
557 }
558
559 } catch (StopParseException e ) {
560 // startElement encountered so do not parse further
561
562 } catch (javax.xml.parsers.ParserConfigurationException e) {
563
564 throw new TransformerConfigurationException(
565 "getAssociatedStylesheets failed", e);
566
567 } catch (org.xml.sax.SAXException se) {
568
569 throw new TransformerConfigurationException(
570 "getAssociatedStylesheets failed", se);
571
572
573 } catch (IOException ioe ) {
574 throw new TransformerConfigurationException(
575 "getAssociatedStylesheets failed", ioe);
576
577 }
578
579 return _stylesheetPIHandler.getAssociatedStylesheet();
580
581 }
582
583 /**
584 * javax.xml.transform.sax.TransformerFactory implementation.
585 * Create a Transformer object that copies the input document to the result.
586 *
587 * @return A Transformer object that simply copies the source to the result.
588 * @throws TransformerConfigurationException
589 */
590 public Transformer newTransformer()
591 throws TransformerConfigurationException
592 {
593 TransformerImpl result = new TransformerImpl(new Properties(),
594 _indentNumber, this);
595 if (_uriResolver != null) {
596 result.setURIResolver(_uriResolver);
597 }
598
599 if (_isSecureProcessing) {
600 result.setSecureProcessing(true);
601 }
602 return result;
603 }
604
605 /**
606 * javax.xml.transform.sax.TransformerFactory implementation.
607 * Process the Source into a Templates object, which is a a compiled
608 * representation of the source. Note that this method should not be
609 * used with XSLTC, as the time-consuming compilation is done for each
610 * and every transformation.
611 *
612 * @return A Templates object that can be used to create Transformers.
613 * @throws TransformerConfigurationException
614 */
615 public Transformer newTransformer(Source source) throws
616 TransformerConfigurationException
617 {
618 final Templates templates = newTemplates(source);
619 final Transformer transformer = templates.newTransformer();
620 if (_uriResolver != null) {
621 transformer.setURIResolver(_uriResolver);
622 }
623 return(transformer);
624 }
625
626 /**
627 * Pass warning messages from the compiler to the error listener
628 */
629 private void passWarningsToListener(Vector messages)
630 throws TransformerException
631 {
632 if (_errorListener == null || messages == null) {
633 return;
634 }
635 // Pass messages to listener, one by one
636 final int count = messages.size();
637 for (int pos = 0; pos < count; pos++) {
638 ErrorMsg msg = (ErrorMsg)messages.elementAt(pos);
639 // Workaround for the TCK failure ErrorListener.errorTests.error001.
640 if (msg.isWarningError())
641 _errorListener.error(
642 new TransformerConfigurationException(msg.toString()));
643 else
644 _errorListener.warning(
645 new TransformerConfigurationException(msg.toString()));
646 }
647 }
648
649 /**
650 * Pass error messages from the compiler to the error listener
651 */
652 private void passErrorsToListener(Vector messages) {
653 try {
654 if (_errorListener == null || messages == null) {
655 return;
656 }
657 // Pass messages to listener, one by one
658 final int count = messages.size();
659 for (int pos = 0; pos < count; pos++) {
660 String message = messages.elementAt(pos).toString();
661 _errorListener.error(new TransformerException(message));
662 }
663 }
664 catch (TransformerException e) {
665 // nada
666 }
667 }
668
669 /**
670 * javax.xml.transform.sax.TransformerFactory implementation.
671 * Process the Source into a Templates object, which is a a compiled
672 * representation of the source.
673 *
674 * @param source The input stylesheet - DOMSource not supported!!!
675 * @return A Templates object that can be used to create Transformers.
676 * @throws TransformerConfigurationException
677 */
678 public Templates newTemplates(Source source)
679 throws TransformerConfigurationException
680 {
681 // If the _useClasspath attribute is true, try to load the translet from
682 // the CLASSPATH and create a template object using the loaded
683 // translet.
684 if (_useClasspath) {
685 String transletName = getTransletBaseName(source);
686
687 if (_packageName != null)
688 transletName = _packageName + "." + transletName;
689
690 try {
691 final Class clazz = ObjectFactory.findProviderClass(
692 transletName, ObjectFactory.findClassLoader(), true);
693 resetTransientAttributes();
694
695 return new TemplatesImpl(new Class[]{clazz}, transletName, null, _indentNumber, this);
696 }
697 catch (ClassNotFoundException cnfe) {
698 ErrorMsg err = new ErrorMsg(ErrorMsg.CLASS_NOT_FOUND_ERR, transletName);
699 throw new TransformerConfigurationException(err.toString());
700 }
701 catch (Exception e) {
702 ErrorMsg err = new ErrorMsg(
703 new ErrorMsg(ErrorMsg.RUNTIME_ERROR_KEY)
704 + e.getMessage());
705 throw new TransformerConfigurationException(err.toString());
706 }
707 }
708
709 // If _autoTranslet is true, we will try to load the bytecodes
710 // from the translet classes without compiling the stylesheet.
711 if (_autoTranslet) {
712 byte[][] bytecodes = null;
713 String transletClassName = getTransletBaseName(source);
714
715 if (_packageName != null)
716 transletClassName = _packageName + "." + transletClassName;
717
718 if (_jarFileName != null)
719 bytecodes = getBytecodesFromJar(source, transletClassName);
720 else
721 bytecodes = getBytecodesFromClasses(source, transletClassName);
722
723 if (bytecodes != null) {
724 if (_debug) {
725 if (_jarFileName != null)
726 System.err.println(new ErrorMsg(
727 ErrorMsg.TRANSFORM_WITH_JAR_STR, transletClassName, _jarFileName));
728 else
729 System.err.println(new ErrorMsg(
730 ErrorMsg.TRANSFORM_WITH_TRANSLET_STR, transletClassName));
731 }
732
733 // Reset the per-session attributes to their default values
734 // after each newTemplates() call.
735 resetTransientAttributes();
736
737 return new TemplatesImpl(bytecodes, transletClassName, null, _indentNumber, this);
738 }
739 }
740
741 // Create and initialize a stylesheet compiler
742 final XSLTC xsltc = new XSLTC();
743 if (_debug) xsltc.setDebug(true);
744 if (_enableInlining)
745 xsltc.setTemplateInlining(true);
746 else
747 xsltc.setTemplateInlining(false);
748 if (_isSecureProcessing) xsltc.setSecureProcessing(true);
749 xsltc.init();
750
751 // Set a document loader (for xsl:include/import) if defined
752 if (_uriResolver != null) {
753 xsltc.setSourceLoader(this);
754 }
755
756 // Pass parameters to the Parser to make sure it locates the correct
757 // <?xml-stylesheet ...?> PI in an XML input document
758 if ((_piParams != null) && (_piParams.get(source) != null)) {
759 // Get the parameters for this Source object
760 PIParamWrapper p = (PIParamWrapper)_piParams.get(source);
761 // Pass them on to the compiler (which will pass then to the parser)
762 if (p != null) {
763 xsltc.setPIParameters(p._media, p._title, p._charset);
764 }
765 }
766
767 // Set the attributes for translet generation
768 int outputType = XSLTC.BYTEARRAY_OUTPUT;
769 if (_generateTranslet || _autoTranslet) {
770 // Set the translet name
771 xsltc.setClassName(getTransletBaseName(source));
772
773 if (_destinationDirectory != null)
774 xsltc.setDestDirectory(_destinationDirectory);
775 else {
776 String xslName = getStylesheetFileName(source);
777 if (xslName != null) {
778 File xslFile = new File(xslName);
779 String xslDir = xslFile.getParent();
780
781 if (xslDir != null)
782 xsltc.setDestDirectory(xslDir);
783 }
784 }
785
786 if (_packageName != null)
787 xsltc.setPackageName(_packageName);
788
789 if (_jarFileName != null) {
790 xsltc.setJarFileName(_jarFileName);
791 outputType = XSLTC.BYTEARRAY_AND_JAR_OUTPUT;
792 }
793 else
794 outputType = XSLTC.BYTEARRAY_AND_FILE_OUTPUT;
795 }
796
797 // Compile the stylesheet
798 final InputSource input = Util.getInputSource(xsltc, source);
799 byte[][] bytecodes = xsltc.compile(null, input, outputType);
800 final String transletName = xsltc.getClassName();
801
802 // Output to the jar file if the jar file name is set.
803 if ((_generateTranslet || _autoTranslet)
804 && bytecodes != null && _jarFileName != null) {
805 try {
806 xsltc.outputToJar();
807 }
808 catch (java.io.IOException e) { }
809 }
810
811 // Reset the per-session attributes to their default values
812 // after each newTemplates() call.
813 resetTransientAttributes();
814
815 // Pass compiler warnings to the error listener
816 if (_errorListener != this) {
817 try {
818 passWarningsToListener(xsltc.getWarnings());
819 }
820 catch (TransformerException e) {
821 throw new TransformerConfigurationException(e);
822 }
823 }
824 else {
825 xsltc.printWarnings();
826 }
827
828 // Check that the transformation went well before returning
829 if (bytecodes == null) {
830
831 ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_COMPILE_ERR);
832 TransformerConfigurationException exc = new TransformerConfigurationException(err.toString());
833
834 // Pass compiler errors to the error listener
835 if (_errorListener != null) {
836 passErrorsToListener(xsltc.getErrors());
837
838 // As required by TCK 1.2, send a fatalError to the
839 // error listener because compilation of the stylesheet
840 // failed and no further processing will be possible.
841 try {
842 _errorListener.fatalError(exc);
843 } catch (TransformerException te) {
844 // well, we tried.
845 }
846 }
847 else {
848 xsltc.printErrors();
849 }
850 throw exc;
851 }
852
853 return new TemplatesImpl(bytecodes, transletName,
854 xsltc.getOutputProperties(), _indentNumber, this);
855 }
856
857 /**
858 * javax.xml.transform.sax.SAXTransformerFactory implementation.
859 * Get a TemplatesHandler object that can process SAX ContentHandler
860 * events into a Templates object.
861 *
862 * @return A TemplatesHandler object that can handle SAX events
863 * @throws TransformerConfigurationException
864 */
865 public TemplatesHandler newTemplatesHandler()
866 throws TransformerConfigurationException
867 {
868 final TemplatesHandlerImpl handler =
869 new TemplatesHandlerImpl(_indentNumber, this);
870 if (_uriResolver != null) {
871 handler.setURIResolver(_uriResolver);
872 }
873 return handler;
874 }
875
876 /**
877 * javax.xml.transform.sax.SAXTransformerFactory implementation.
878 * Get a TransformerHandler object that can process SAX ContentHandler
879 * events into a Result. This method will return a pure copy transformer.
880 *
881 * @return A TransformerHandler object that can handle SAX events
882 * @throws TransformerConfigurationException
883 */
884 public TransformerHandler newTransformerHandler()
885 throws TransformerConfigurationException
886 {
887 final Transformer transformer = newTransformer();
888 if (_uriResolver != null) {
889 transformer.setURIResolver(_uriResolver);
890 }
891 return new TransformerHandlerImpl((TransformerImpl) transformer);
892 }
893
894 /**
895 * javax.xml.transform.sax.SAXTransformerFactory implementation.
896 * Get a TransformerHandler object that can process SAX ContentHandler
897 * events into a Result, based on the transformation instructions
898 * specified by the argument.
899 *
900 * @param src The source of the transformation instructions.
901 * @return A TransformerHandler object that can handle SAX events
902 * @throws TransformerConfigurationException
903 */
904 public TransformerHandler newTransformerHandler(Source src)
905 throws TransformerConfigurationException
906 {
907 final Transformer transformer = newTransformer(src);
908 if (_uriResolver != null) {
909 transformer.setURIResolver(_uriResolver);
910 }
911 return new TransformerHandlerImpl((TransformerImpl) transformer);
912 }
913
914 /**
915 * javax.xml.transform.sax.SAXTransformerFactory implementation.
916 * Get a TransformerHandler object that can process SAX ContentHandler
917 * events into a Result, based on the transformation instructions
918 * specified by the argument.
919 *
920 * @param templates Represents a pre-processed stylesheet
921 * @return A TransformerHandler object that can handle SAX events
922 * @throws TransformerConfigurationException
923 */
924 public TransformerHandler newTransformerHandler(Templates templates)
925 throws TransformerConfigurationException
926 {
927 final Transformer transformer = templates.newTransformer();
928 final TransformerImpl internal = (TransformerImpl)transformer;
929 return new TransformerHandlerImpl(internal);
930 }
931
932 /**
933 * javax.xml.transform.sax.SAXTransformerFactory implementation.
934 * Create an XMLFilter that uses the given source as the
935 * transformation instructions.
936 *
937 * @param src The source of the transformation instructions.
938 * @return An XMLFilter object, or null if this feature is not supported.
939 * @throws TransformerConfigurationException
940 */
941 public XMLFilter newXMLFilter(Source src)
942 throws TransformerConfigurationException
943 {
944 Templates templates = newTemplates(src);
945 if (templates == null) return null;
946 return newXMLFilter(templates);
947 }
948
949 /**
950 * javax.xml.transform.sax.SAXTransformerFactory implementation.
951 * Create an XMLFilter that uses the given source as the
952 * transformation instructions.
953 *
954 * @param templates The source of the transformation instructions.
955 * @return An XMLFilter object, or null if this feature is not supported.
956 * @throws TransformerConfigurationException
957 */
958 public XMLFilter newXMLFilter(Templates templates)
959 throws TransformerConfigurationException
960 {
961 try {
962 return new org.apache.xalan.xsltc.trax.TrAXFilter(templates);
963 }
964 catch (TransformerConfigurationException e1) {
965 if (_errorListener != null) {
966 try {
967 _errorListener.fatalError(e1);
968 return null;
969 }
970 catch (TransformerException e2) {
971 new TransformerConfigurationException(e2);
972 }
973 }
974 throw e1;
975 }
976 }
977
978 /**
979 * Receive notification of a recoverable error.
980 * The transformer must continue to provide normal parsing events after
981 * invoking this method. It should still be possible for the application
982 * to process the document through to the end.
983 *
984 * @param e The warning information encapsulated in a transformer
985 * exception.
986 * @throws TransformerException if the application chooses to discontinue
987 * the transformation (always does in our case).
988 */
989 public void error(TransformerException e)
990 throws TransformerException
991 {
992 Throwable wrapped = e.getException();
993 if (wrapped != null) {
994 System.err.println(new ErrorMsg(ErrorMsg.ERROR_PLUS_WRAPPED_MSG,
995 e.getMessageAndLocation(),
996 wrapped.getMessage()));
997 } else {
998 System.err.println(new ErrorMsg(ErrorMsg.ERROR_MSG,
999 e.getMessageAndLocation()));
1000 }
1001 throw e;
1002 }
1003
1004 /**
1005 * Receive notification of a non-recoverable error.
1006 * The application must assume that the transformation cannot continue
1007 * after the Transformer has invoked this method, and should continue
1008 * (if at all) only to collect addition error messages. In fact,
1009 * Transformers are free to stop reporting events once this method has
1010 * been invoked.
1011 *
1012 * @param e warning information encapsulated in a transformer
1013 * exception.
1014 * @throws TransformerException if the application chooses to discontinue
1015 * the transformation (always does in our case).
1016 */
1017 public void fatalError(TransformerException e)
1018 throws TransformerException
1019 {
1020 Throwable wrapped = e.getException();
1021 if (wrapped != null) {
1022 System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_PLUS_WRAPPED_MSG,
1023 e.getMessageAndLocation(),
1024 wrapped.getMessage()));
1025 } else {
1026 System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_MSG,
1027 e.getMessageAndLocation()));
1028 }
1029 throw e;
1030 }
1031
1032 /**
1033 * Receive notification of a warning.
1034 * Transformers can use this method to report conditions that are not
1035 * errors or fatal errors. The default behaviour is to take no action.
1036 * After invoking this method, the Transformer must continue with the
1037 * transformation. It should still be possible for the application to
1038 * process the document through to the end.
1039 *
1040 * @param e The warning information encapsulated in a transformer
1041 * exception.
1042 * @throws TransformerException if the application chooses to discontinue
1043 * the transformation (never does in our case).
1044 */
1045 public void warning(TransformerException e)
1046 throws TransformerException
1047 {
1048 Throwable wrapped = e.getException();
1049 if (wrapped != null) {
1050 System.err.println(new ErrorMsg(ErrorMsg.WARNING_PLUS_WRAPPED_MSG,
1051 e.getMessageAndLocation(),
1052 wrapped.getMessage()));
1053 } else {
1054 System.err.println(new ErrorMsg(ErrorMsg.WARNING_MSG,
1055 e.getMessageAndLocation()));
1056 }
1057 }
1058
1059 /**
1060 * This method implements XSLTC's SourceLoader interface. It is used to
1061 * glue a TrAX URIResolver to the XSLTC compiler's Input and Import classes.
1062 *
1063 * @param href The URI of the document to load
1064 * @param context The URI of the currently loaded document
1065 * @param xsltc The compiler that resuests the document
1066 * @return An InputSource with the loaded document
1067 */
1068 public InputSource loadSource(String href, String context, XSLTC xsltc) {
1069 try {
1070 if (_uriResolver != null) {
1071 final Source source = _uriResolver.resolve(href, context);
1072 if (source != null) {
1073 return Util.getInputSource(xsltc, source);
1074 }
1075 }
1076 }
1077 catch (TransformerException e) {
1078 // Falls through
1079 }
1080 return null;
1081 }
1082
1083 /**
1084 * Reset the per-session attributes to their default values
1085 */
1086 private void resetTransientAttributes() {
1087 _transletName = DEFAULT_TRANSLET_NAME;
1088 _destinationDirectory = null;
1089 _packageName = null;
1090 _jarFileName = null;
1091 }
1092
1093 /**
1094 * Load the translet classes from local .class files and return
1095 * the bytecode array.
1096 *
1097 * @param source The xsl source
1098 * @param fullClassName The full name of the translet
1099 * @return The bytecode array
1100 */
1101 private byte[][] getBytecodesFromClasses(Source source, String fullClassName)
1102 {
1103 if (fullClassName == null)
1104 return null;
1105
1106 String xslFileName = getStylesheetFileName(source);
1107 File xslFile = null;
1108 if (xslFileName != null)
1109 xslFile = new File(xslFileName);
1110
1111 // Find the base name of the translet
1112 final String transletName;
1113 int lastDotIndex = fullClassName.lastIndexOf('.');
1114 if (lastDotIndex > 0)
1115 transletName = fullClassName.substring(lastDotIndex+1);
1116 else
1117 transletName = fullClassName;
1118
1119 // Construct the path name for the translet class file
1120 String transletPath = fullClassName.replace('.', '/');
1121 if (_destinationDirectory != null) {
1122 transletPath = _destinationDirectory + "/" + transletPath + ".class";
1123 }
1124 else {
1125 if (xslFile != null && xslFile.getParent() != null)
1126 transletPath = xslFile.getParent() + "/" + transletPath + ".class";
1127 else
1128 transletPath = transletPath + ".class";
1129 }
1130
1131 // Return null if the translet class file does not exist.
1132 File transletFile = new File(transletPath);
1133 if (!transletFile.exists())
1134 return null;
1135
1136 // Compare the timestamps of the translet and the xsl file.
1137 // If the translet is older than the xsl file, return null
1138 // so that the xsl file is used for the transformation and
1139 // the translet is regenerated.
1140 if (xslFile != null && xslFile.exists()) {
1141 long xslTimestamp = xslFile.lastModified();
1142 long transletTimestamp = transletFile.lastModified();
1143 if (transletTimestamp < xslTimestamp)
1144 return null;
1145 }
1146
1147 // Load the translet into a bytecode array.
1148 List bytecodes = new ArrayList();
1149 int fileLength = (int)transletFile.length();
1150 if (fileLength > 0) {
1151 FileInputStream input = null;
1152 try {
1153 input = new FileInputStream(transletFile);
1154 }
1155 catch (FileNotFoundException e) {
1156 return null;
1157 }
1158
1159 byte[] bytes = new byte[fileLength];
1160 try {
1161 readFromInputStream(bytes, input, fileLength);
1162 input.close();
1163 }
1164 catch (IOException e) {
1165 return null;
1166 }
1167
1168 bytecodes.add(bytes);
1169 }
1170 else
1171 return null;
1172
1173 // Find the parent directory of the translet.
1174 String transletParentDir = transletFile.getParent();
1175 if (transletParentDir == null)
1176 transletParentDir = System.getProperty("user.dir");
1177
1178 File transletParentFile = new File(transletParentDir);
1179
1180 // Find all the auxiliary files which have a name pattern of "transletClass$nnn.class".
1181 final String transletAuxPrefix = transletName + "$";
1182 File[] auxfiles = transletParentFile.listFiles(new FilenameFilter() {
1183 public boolean accept(File dir, String name)
1184 {
1185 return (name.endsWith(".class") && name.startsWith(transletAuxPrefix));
1186 }
1187 });
1188
1189 // Load the auxiliary class files and add them to the bytecode array.
1190 for (int i = 0; i < auxfiles.length; i++)
1191 {
1192 File auxfile = auxfiles[i];
1193 int auxlength = (int)auxfile.length();
1194 if (auxlength > 0) {
1195 FileInputStream auxinput = null;
1196 try {
1197 auxinput = new FileInputStream(auxfile);
1198 }
1199 catch (FileNotFoundException e) {
1200 continue;
1201 }
1202
1203 byte[] bytes = new byte[auxlength];
1204
1205 try {
1206 readFromInputStream(bytes, auxinput, auxlength);
1207 auxinput.close();
1208 }
1209 catch (IOException e) {
1210 continue;
1211 }
1212
1213 bytecodes.add(bytes);
1214 }
1215 }
1216
1217 // Convert the Vector of byte[] to byte[][].
1218 final int count = bytecodes.size();
1219 if ( count > 0) {
1220 final byte[][] result = new byte[count][1];
1221 for (int i = 0; i < count; i++) {
1222 result[i] = (byte[])bytecodes.get(i);
1223 }
1224
1225 return result;
1226 }
1227 else
1228 return null;
1229 }
1230
1231 /**
1232 * Load the translet classes from the jar file and return the bytecode.
1233 *
1234 * @param source The xsl source
1235 * @param fullClassName The full name of the translet
1236 * @return The bytecode array
1237 */
1238 private byte[][] getBytecodesFromJar(Source source, String fullClassName)
1239 {
1240 String xslFileName = getStylesheetFileName(source);
1241 File xslFile = null;
1242 if (xslFileName != null)
1243 xslFile = new File(xslFileName);
1244
1245 // Construct the path for the jar file
1246 String jarPath = null;
1247 if (_destinationDirectory != null)
1248 jarPath = _destinationDirectory + "/" + _jarFileName;
1249 else {
1250 if (xslFile != null && xslFile.getParent() != null)
1251 jarPath = xslFile.getParent() + "/" + _jarFileName;
1252 else
1253 jarPath = _jarFileName;
1254 }
1255
1256 // Return null if the jar file does not exist.
1257 File file = new File(jarPath);
1258 if (!file.exists())
1259 return null;
1260
1261 // Compare the timestamps of the jar file and the xsl file. Return null
1262 // if the xsl file is newer than the jar file.
1263 if (xslFile != null && xslFile.exists()) {
1264 long xslTimestamp = xslFile.lastModified();
1265 long transletTimestamp = file.lastModified();
1266 if (transletTimestamp < xslTimestamp)
1267 return null;
1268 }
1269
1270 // Create a ZipFile object for the jar file
1271 ZipFile jarFile = null;
1272 try {
1273 jarFile = new ZipFile(file);
1274 }
1275 catch (IOException e) {
1276 return null;
1277 }
1278
1279 String transletPath = fullClassName.replace('.', '/');
1280 String transletAuxPrefix = transletPath + "$";
1281 String transletFullName = transletPath + ".class";
1282
1283 List bytecodes = new ArrayList();
1284
1285 // Iterate through all entries in the jar file to find the
1286 // translet and auxiliary classes.
1287 Enumeration entries = jarFile.entries();
1288 while (entries.hasMoreElements())
1289 {
1290 ZipEntry entry = (ZipEntry)entries.nextElement();
1291 String entryName = entry.getName();
1292 if (entry.getSize() > 0 &&
1293 (entryName.equals(transletFullName) ||
1294 (entryName.endsWith(".class") &&
1295 entryName.startsWith(transletAuxPrefix))))
1296 {
1297 try {
1298 InputStream input = jarFile.getInputStream(entry);
1299 int size = (int)entry.getSize();
1300 byte[] bytes = new byte[size];
1301 readFromInputStream(bytes, input, size);
1302 input.close();
1303 bytecodes.add(bytes);
1304 }
1305 catch (IOException e) {
1306 return null;
1307 }
1308 }
1309 }
1310
1311 // Convert the Vector of byte[] to byte[][].
1312 final int count = bytecodes.size();
1313 if (count > 0) {
1314 final byte[][] result = new byte[count][1];
1315 for (int i = 0; i < count; i++) {
1316 result[i] = (byte[])bytecodes.get(i);
1317 }
1318
1319 return result;
1320 }
1321 else
1322 return null;
1323 }
1324
1325 /**
1326 * Read a given number of bytes from the InputStream into a byte array.
1327 *
1328 * @param bytes The byte array to store the input content.
1329 * @param input The input stream.
1330 * @param size The number of bytes to read.
1331 */
1332 private void readFromInputStream(byte[] bytes, InputStream input, int size)
1333 throws IOException
1334 {
1335 int n = 0;
1336 int offset = 0;
1337 int length = size;
1338 while (length > 0 && (n = input.read(bytes, offset, length)) > 0) {
1339 offset = offset + n;
1340 length = length - n;
1341 }
1342 }
1343
1344 /**
1345 * Return the base class name of the translet.
1346 * The translet name is resolved using the following rules:
1347 * 1. if the _transletName attribute is set and its value is not "GregorSamsa",
1348 * then _transletName is returned.
1349 * 2. otherwise get the translet name from the base name of the system ID
1350 * 3. return "GregorSamsa" if the result from step 2 is null.
1351 *
1352 * @param source The input Source
1353 * @return The name of the translet class
1354 */
1355 private String getTransletBaseName(Source source)
1356 {
1357 String transletBaseName = null;
1358 if (!_transletName.equals(DEFAULT_TRANSLET_NAME))
1359 return _transletName;
1360 else {
1361 String systemId = source.getSystemId();
1362 if (systemId != null) {
1363 String baseName = Util.baseName(systemId);
1364 if (baseName != null) {
1365 baseName = Util.noExtName(baseName);
1366 transletBaseName = Util.toJavaName(baseName);
1367 }
1368 }
1369 }
1370
1371 return (transletBaseName != null) ? transletBaseName : DEFAULT_TRANSLET_NAME;
1372 }
1373
1374 /**
1375 * Return the local file name from the systemId of the Source object
1376 *
1377 * @param source The Source
1378 * @return The file name in the local filesystem, or null if the
1379 * systemId does not represent a local file.
1380 */
1381 private String getStylesheetFileName(Source source)
1382 {
1383 String systemId = source.getSystemId();
1384 if (systemId != null) {
1385 File file = new File(systemId);
1386 if (file.exists())
1387 return systemId;
1388 else {
1389 URL url = null;
1390 try {
1391 url = new URL(systemId);
1392 }
1393 catch (MalformedURLException e) {
1394 return null;
1395 }
1396
1397 if ("file".equals(url.getProtocol()))
1398 return url.getFile();
1399 else
1400 return null;
1401 }
1402 }
1403 else
1404 return null;
1405 }
1406
1407 /**
1408 * Returns the Class object the provides the XSLTC DTM Manager service.
1409 */
1410 protected Class getDTMManagerClass() {
1411 return m_DTMManagerClass;
1412 }
1413 }