001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.xslt;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.lang.reflect.Method;
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.List;
026: import java.util.Map;
027:
028: import javax.xml.transform.Result;
029: import javax.xml.transform.Templates;
030: import javax.xml.transform.Transformer;
031: import javax.xml.transform.TransformerException;
032: import javax.xml.transform.TransformerFactory;
033: import javax.xml.transform.URIResolver;
034: import javax.xml.transform.sax.SAXTransformerFactory;
035: import javax.xml.transform.sax.TemplatesHandler;
036: import javax.xml.transform.sax.TransformerHandler;
037: import javax.xml.transform.stream.StreamSource;
038:
039: import org.apache.avalon.excalibur.pool.Recyclable;
040: import org.apache.avalon.framework.activity.Disposable;
041: import org.apache.avalon.framework.activity.Initializable;
042: import org.apache.avalon.framework.logger.AbstractLogEnabled;
043: import org.apache.avalon.framework.parameters.ParameterException;
044: import org.apache.avalon.framework.parameters.Parameterizable;
045: import org.apache.avalon.framework.parameters.Parameters;
046: import org.apache.avalon.framework.service.ServiceException;
047: import org.apache.avalon.framework.service.ServiceManager;
048: import org.apache.avalon.framework.service.Serviceable;
049: import org.apache.commons.lang.BooleanUtils;
050: import org.apache.excalibur.source.Source;
051: import org.apache.excalibur.source.SourceException;
052: import org.apache.excalibur.source.SourceResolver;
053: import org.apache.excalibur.source.SourceValidity;
054: import org.apache.excalibur.source.impl.validity.AggregatedValidity;
055: import org.apache.excalibur.store.Store;
056: import org.apache.excalibur.xml.sax.XMLizable;
057: import org.apache.excalibur.xml.xslt.XSLTProcessor;
058: import org.apache.excalibur.xml.xslt.XSLTProcessorException;
059: import org.apache.excalibur.xmlizer.XMLizer;
060: import org.xml.sax.ContentHandler;
061: import org.xml.sax.InputSource;
062: import org.xml.sax.SAXException;
063: import org.xml.sax.XMLFilter;
064:
065: /**
066: * Adaptation of Excalibur's XSLTProcessor implementation to allow for better
067: * error reporting.
068: *
069: * @version $Id: TraxProcessor.java 440004 2006-09-04 10:03:19Z anathaniel $
070: * @since 2.1.8
071: */
072:
073: public class TraxProcessor extends AbstractLogEnabled implements
074: XSLTProcessor, Serviceable, Initializable, Disposable,
075: Parameterizable, Recyclable, URIResolver {
076: /** The store service instance */
077: protected Store m_store;
078:
079: /** The configured transformer factory to use */
080: protected String m_transformerFactory;
081:
082: /** The trax TransformerFactory this component uses */
083: protected SAXTransformerFactory m_factory;
084:
085: /** The default TransformerFactory used by this component */
086: protected SAXTransformerFactory m_defaultFactory;
087:
088: /** Is the store turned on? (default is off) */
089: protected boolean m_useStore;
090:
091: /** Is incremental processing turned on? (default for Xalan: no) */
092: protected boolean m_incrementalProcessing;
093:
094: /** Resolver used to resolve XSLT document() calls, imports and includes */
095: protected SourceResolver m_resolver;
096:
097: /** Check included stylesheets */
098: protected boolean m_checkIncludes;
099:
100: /** Map of pairs of System ID's / validities of the included stylesheets */
101: protected Map m_includesMap = new HashMap();
102:
103: protected XMLizer m_xmlizer;
104:
105: /** The ServiceManager */
106: protected ServiceManager m_manager;
107:
108: /**
109: * Compose. Try to get the store
110: *
111: * @avalon.service interface="XMLizer"
112: * @avalon.service interface="SourceResolver"
113: * @avalon.service interface="Store/TransientStore" optional="true"
114: */
115: public void service(final ServiceManager manager)
116: throws ServiceException {
117: m_manager = manager;
118: m_xmlizer = (XMLizer) m_manager.lookup(XMLizer.ROLE);
119: m_resolver = (SourceResolver) m_manager
120: .lookup(SourceResolver.ROLE);
121:
122: if (m_manager.hasService(Store.TRANSIENT_STORE)) {
123: m_store = (Store) m_manager.lookup(Store.TRANSIENT_STORE);
124: }
125: }
126:
127: /**
128: * Initialize
129: */
130: public void initialize() throws Exception {
131: m_factory = getTransformerFactory(m_transformerFactory);
132: m_defaultFactory = m_factory;
133: }
134:
135: /**
136: * Disposable
137: */
138: public void dispose() {
139: if (null != m_manager) {
140: m_manager.release(m_store);
141: m_manager.release(m_resolver);
142: m_manager.release(m_xmlizer);
143: m_manager = null;
144: }
145: m_xmlizer = null;
146: m_store = null;
147: m_resolver = null;
148: }
149:
150: /**
151: * Configure the component
152: */
153: public void parameterize(final Parameters params)
154: throws ParameterException {
155: m_useStore = params.getParameterAsBoolean("use-store",
156: this .m_useStore);
157: m_incrementalProcessing = params.getParameterAsBoolean(
158: "incremental-processing", this .m_incrementalProcessing);
159: m_transformerFactory = params.getParameter(
160: "transformer-factory", null);
161: m_checkIncludes = params.getParameterAsBoolean(
162: "check-includes", true);
163: if (!m_useStore) {
164: // release the store, if we don't need it anymore
165: m_manager.release(m_store);
166: m_store = null;
167: } else if (null == m_store) {
168: final String message = "XSLTProcessor: use-store is set to true, "
169: + "but unable to aquire the Store.";
170: throw new ParameterException(message);
171: }
172: }
173:
174: /**
175: * Set the transformer factory used by this component
176: */
177: public void setTransformerFactory(final String classname) {
178: m_factory = getTransformerFactory(classname);
179: }
180:
181: /**
182: * @see org.apache.excalibur.xml.xslt.XSLTProcessor#getTransformerHandler(org.apache.excalibur.source.Source)
183: */
184: public TransformerHandler getTransformerHandler(
185: final Source stylesheet) throws XSLTProcessorException {
186: return getTransformerHandler(stylesheet, null);
187: }
188:
189: /**
190: * @see org.apache.excalibur.xml.xslt.XSLTProcessor#getTransformerHandler(org.apache.excalibur.source.Source,
191: * org.xml.sax.XMLFilter)
192: */
193: public TransformerHandler getTransformerHandler(
194: final Source stylesheet, final XMLFilter filter)
195: throws XSLTProcessorException {
196: final XSLTProcessor.TransformerHandlerAndValidity validity = getTransformerHandlerAndValidity(
197: stylesheet, filter);
198: return validity.getTransfomerHandler();
199: }
200:
201: public TransformerHandlerAndValidity getTransformerHandlerAndValidity(
202: final Source stylesheet) throws XSLTProcessorException {
203: return getTransformerHandlerAndValidity(stylesheet, null);
204: }
205:
206: public TransformerHandlerAndValidity getTransformerHandlerAndValidity(
207: Source stylesheet, XMLFilter filter)
208: throws XSLTProcessorException {
209:
210: final String id = stylesheet.getURI();
211: TransformerHandlerAndValidity handlerAndValidity;
212:
213: try {
214: handlerAndValidity = getTemplates(stylesheet, id);
215: if (handlerAndValidity != null) {
216: if (getLogger().isDebugEnabled()) {
217: getLogger().debug("Reusing Templates for " + id);
218: }
219: return handlerAndValidity;
220: }
221: } catch (Exception e) {
222: throw new XSLTProcessorException(
223: "Error retrieving template", e);
224: }
225:
226: TraxErrorListener errorListener = new TraxErrorListener(
227: getLogger(), stylesheet.getURI());
228: try {
229: if (getLogger().isDebugEnabled()) {
230: getLogger().debug("Creating new Templates for " + id);
231: }
232:
233: m_factory.setErrorListener(errorListener);
234:
235: // Create a Templates ContentHandler to handle parsing of the
236: // stylesheet.
237: TemplatesHandler templatesHandler = m_factory
238: .newTemplatesHandler();
239:
240: // Set the system ID for the template handler since some
241: // TrAX implementations (XSLTC) rely on this in order to obtain
242: // a meaningful identifier for the Templates instances.
243: templatesHandler.setSystemId(id);
244: if (filter != null) {
245: filter.setContentHandler(templatesHandler);
246: }
247:
248: if (getLogger().isDebugEnabled()) {
249: getLogger().debug(
250: "Source = " + stylesheet
251: + ", templatesHandler = "
252: + templatesHandler);
253: }
254:
255: // Initialize List for included validities
256: SourceValidity validity = stylesheet.getValidity();
257: if (validity != null && m_checkIncludes) {
258: m_includesMap.put(id, new ArrayList());
259: }
260:
261: try {
262: // Process the stylesheet.
263: sourceToSAX(stylesheet,
264: filter != null ? (ContentHandler) filter
265: : (ContentHandler) templatesHandler);
266:
267: // Get the Templates object (generated during the parsing of
268: // the stylesheet) from the TemplatesHandler.
269: final Templates template = templatesHandler
270: .getTemplates();
271:
272: if (null == template) {
273: throw new XSLTProcessorException(
274: "Unable to create templates for stylesheet: "
275: + stylesheet.getURI());
276: }
277:
278: // Must set base for Xalan stylesheet.
279: // Otherwise document('') in logicsheet causes NPE.
280: Class clazz = template.getClass();
281: if (clazz.getName().equals(
282: "org.apache.xalan.templates.StylesheetRoot")) {
283: Method method = clazz.getMethod("setHref",
284: new Class[] { String.class });
285: method.invoke(template, new Object[] { id });
286: }
287:
288: putTemplates(template, stylesheet, id);
289:
290: // Create transformer handler
291: final TransformerHandler handler = m_factory
292: .newTransformerHandler(template);
293: handler.getTransformer().setErrorListener(
294: new TraxErrorListener(getLogger(), stylesheet
295: .getURI()));
296: handler.getTransformer().setURIResolver(this );
297:
298: // Create aggregated validity
299: AggregatedValidity aggregated = null;
300: if (validity != null && m_checkIncludes) {
301: List includes = (List) m_includesMap.get(id);
302: if (includes != null) {
303: aggregated = new AggregatedValidity();
304: aggregated.add(validity);
305: for (int i = includes.size() - 1; i >= 0; i--) {
306: aggregated
307: .add((SourceValidity) ((Object[]) includes
308: .get(i))[1]);
309: }
310: validity = aggregated;
311: }
312: }
313:
314: // Create result
315: handlerAndValidity = new MyTransformerHandlerAndValidity(
316: handler, validity);
317: } finally {
318: if (m_checkIncludes)
319: m_includesMap.remove(id);
320: }
321:
322: return handlerAndValidity;
323: } catch (Exception e) {
324: Throwable realEx = errorListener.getThrowable();
325: if (realEx == null)
326: realEx = e;
327:
328: if (realEx instanceof RuntimeException) {
329: throw (RuntimeException) realEx;
330: }
331:
332: if (realEx instanceof XSLTProcessorException) {
333: throw (XSLTProcessorException) realEx;
334: }
335:
336: throw new XSLTProcessorException(
337: "Exception when creating Transformer from "
338: + stylesheet.getURI(), realEx);
339: }
340: }
341:
342: protected void sourceToSAX(Source source, ContentHandler handler)
343: throws SAXException, IOException, SourceException {
344: if (source instanceof XMLizable) {
345: ((XMLizable) source).toSAX(handler);
346: } else {
347: final InputStream inputStream = source.getInputStream();
348: final String mimeType = source.getMimeType();
349: final String systemId = source.getURI();
350: m_xmlizer.toSAX(inputStream, mimeType, systemId, handler);
351: }
352: }
353:
354: public void transform(final Source source, final Source stylesheet,
355: final Parameters params, final Result result)
356: throws XSLTProcessorException {
357: try {
358: if (getLogger().isDebugEnabled()) {
359: getLogger().debug(
360: "Transform source = " + source
361: + ", stylesheet = " + stylesheet
362: + ", parameters = " + params
363: + ", result = " + result);
364: }
365: final TransformerHandler handler = getTransformerHandler(stylesheet);
366: if (params != null) {
367: final Transformer transformer = handler
368: .getTransformer();
369: transformer.clearParameters();
370: String[] names = params.getNames();
371: for (int i = names.length - 1; i >= 0; i--) {
372: transformer.setParameter(names[i], params
373: .getParameter(names[i]));
374: }
375: }
376:
377: handler.setResult(result);
378: sourceToSAX(source, handler);
379: if (getLogger().isDebugEnabled()) {
380: getLogger().debug("Transform done");
381: }
382: } catch (SAXException e) {
383: // Unwrapping the exception will "remove" the real cause with
384: // never Xalan versions and makes the exception message unusable
385: final String message = "Error in running Transformation";
386: throw new XSLTProcessorException(message, e);
387: /*
388: * if( e.getException() == null ) { final String message = "Error in
389: * running Transformation"; throw new XSLTProcessorException(
390: * message, e ); } else { final String message = "Got SAXException.
391: * Rethrowing cause exception."; getLogger().debug( message, e );
392: * throw new XSLTProcessorException( "Error in running
393: * Transformation", e.getException() ); }
394: */
395: } catch (Exception e) {
396: final String message = "Error in running Transformation";
397: throw new XSLTProcessorException(message, e);
398: }
399: }
400:
401: /**
402: * Get the TransformerFactory associated with the given classname. If the
403: * class can't be found or the given class doesn't implement the required
404: * interface, the default factory is returned.
405: */
406: private SAXTransformerFactory getTransformerFactory(
407: String factoryName) {
408: SAXTransformerFactory _factory;
409:
410: if (null == factoryName) {
411: _factory = (SAXTransformerFactory) TransformerFactory
412: .newInstance();
413: } else {
414: try {
415: ClassLoader loader = Thread.currentThread()
416: .getContextClassLoader();
417: if (loader == null) {
418: loader = getClass().getClassLoader();
419: }
420: _factory = (SAXTransformerFactory) loader.loadClass(
421: factoryName).newInstance();
422: } catch (ClassNotFoundException cnfe) {
423: getLogger()
424: .error(
425: "Cannot find the requested TrAX factory '"
426: + factoryName
427: + "'. Using default TrAX Transformer Factory instead.");
428: if (m_factory != null)
429: return m_factory;
430: _factory = (SAXTransformerFactory) TransformerFactory
431: .newInstance();
432: } catch (ClassCastException cce) {
433: getLogger()
434: .error(
435: "The indicated class '"
436: + factoryName
437: + "' is not a TrAX Transformer Factory. Using default TrAX Transformer Factory instead.");
438: if (m_factory != null)
439: return m_factory;
440: _factory = (SAXTransformerFactory) TransformerFactory
441: .newInstance();
442: } catch (Exception e) {
443: getLogger()
444: .error(
445: "Error found loading the requested TrAX Transformer Factory '"
446: + factoryName
447: + "'. Using default TrAX Transformer Factory instead.");
448: if (m_factory != null)
449: return m_factory;
450: _factory = (SAXTransformerFactory) TransformerFactory
451: .newInstance();
452: }
453: }
454:
455: _factory.setErrorListener(new TraxErrorListener(getLogger(),
456: null));
457: _factory.setURIResolver(this );
458:
459: // FIXME (SM): implementation-specific parameter passing should be
460: // made more extensible.
461: if (_factory.getClass().getName().equals(
462: "org.apache.xalan.processor.TransformerFactoryImpl")) {
463: _factory.setAttribute(
464: "http://xml.apache.org/xalan/features/incremental",
465: BooleanUtils
466: .toBooleanObject(m_incrementalProcessing));
467: }
468: // SAXON 8 will not report errors unless version warning is set to false.
469: if (_factory.getClass().getName().equals(
470: "net.sf.saxon.TransformerFactoryImpl")) {
471: _factory.setAttribute(
472: "http://saxon.sf.net/feature/version-warning",
473: Boolean.FALSE);
474: }
475:
476: return _factory;
477: }
478:
479: private TransformerHandlerAndValidity getTemplates(
480: Source stylesheet, String id) throws IOException,
481: TransformerException {
482: if (!m_useStore) {
483: return null;
484: }
485:
486: // we must augment the template ID with the factory classname since one
487: // transformer implementation cannot handle the instances of a
488: // template created by another one.
489: String key = "XSLTTemplate: " + id + '('
490: + m_factory.getClass().getName() + ')';
491:
492: if (getLogger().isDebugEnabled()) {
493: getLogger().debug("getTemplates: stylesheet " + id);
494: }
495:
496: SourceValidity newValidity = stylesheet.getValidity();
497:
498: // Only stylesheets with validity are stored
499: if (newValidity == null) {
500: // Remove an old template
501: m_store.remove(key);
502: return null;
503: }
504:
505: // Stored is an array of the templates and the caching time and list of
506: // includes
507: Object[] templateAndValidityAndIncludes = (Object[]) m_store
508: .get(key);
509: if (templateAndValidityAndIncludes == null) {
510: // Templates not found in cache
511: return null;
512: }
513:
514: // Check template modification time
515: SourceValidity storedValidity = (SourceValidity) templateAndValidityAndIncludes[1];
516: int valid = storedValidity.isValid();
517: boolean isValid;
518: if (valid == 0) {
519: valid = storedValidity.isValid(newValidity);
520: isValid = (valid == 1);
521: } else {
522: isValid = (valid == 1);
523: }
524: if (!isValid) {
525: m_store.remove(key);
526: return null;
527: }
528:
529: // Check includes
530: if (m_checkIncludes) {
531: AggregatedValidity aggregated = null;
532: List includes = (List) templateAndValidityAndIncludes[2];
533: if (includes != null) {
534: aggregated = new AggregatedValidity();
535: aggregated.add(storedValidity);
536:
537: for (int i = includes.size() - 1; i >= 0; i--) {
538: // Every include stored as pair of source ID and validity
539: Object[] pair = (Object[]) includes.get(i);
540: storedValidity = (SourceValidity) pair[1];
541: aggregated.add(storedValidity);
542:
543: valid = storedValidity.isValid();
544: isValid = false;
545: if (valid == 0) {
546: Source includedSource = null;
547: try {
548: includedSource = m_resolver
549: .resolveURI((String) pair[0]);
550: SourceValidity included = includedSource
551: .getValidity();
552: if (included != null) {
553: valid = storedValidity
554: .isValid(included);
555: isValid = (valid == 1);
556: }
557: } finally {
558: m_resolver.release(includedSource);
559: }
560: } else {
561: isValid = (valid == 1);
562: }
563: if (!isValid) {
564: m_store.remove(key);
565: return null;
566: }
567: }
568: storedValidity = aggregated;
569: }
570: }
571:
572: TransformerHandler handler = m_factory
573: .newTransformerHandler((Templates) templateAndValidityAndIncludes[0]);
574: handler.getTransformer()
575: .setErrorListener(
576: new TraxErrorListener(getLogger(), stylesheet
577: .getURI()));
578: handler.getTransformer().setURIResolver(this );
579: return new MyTransformerHandlerAndValidity(handler,
580: storedValidity);
581: }
582:
583: private void putTemplates(Templates templates, Source stylesheet,
584: String id) throws IOException {
585: if (!m_useStore)
586: return;
587:
588: // we must augment the template ID with the factory classname since one
589: // transformer implementation cannot handle the instances of a
590: // template created by another one.
591: String key = "XSLTTemplate: " + id + '('
592: + m_factory.getClass().getName() + ')';
593:
594: // only stylesheets with a last modification date are stored
595: SourceValidity validity = stylesheet.getValidity();
596: if (null != validity) {
597: // Stored is an array of the template and the current time
598: Object[] templateAndValidityAndIncludes = new Object[3];
599: templateAndValidityAndIncludes[0] = templates;
600: templateAndValidityAndIncludes[1] = validity;
601: if (m_checkIncludes) {
602: templateAndValidityAndIncludes[2] = m_includesMap
603: .get(id);
604: }
605: m_store.store(key, templateAndValidityAndIncludes);
606: }
607: }
608:
609: /**
610: * Called by the processor when it encounters an xsl:include, xsl:import, or
611: * document() function.
612: *
613: * @param href
614: * An href attribute, which may be relative or absolute.
615: * @param base
616: * The base URI in effect when the href attribute was
617: * encountered.
618: *
619: * @return A Source object, or null if the href cannot be resolved, and the
620: * processor should try to resolve the URI itself.
621: *
622: * @throws TransformerException
623: * if an error occurs when trying to resolve the URI.
624: */
625: public javax.xml.transform.Source resolve(String href, String base)
626: throws TransformerException {
627: if (getLogger().isDebugEnabled()) {
628: getLogger().debug(
629: "resolve(href = " + href + ", base = " + base
630: + "); resolver = " + m_resolver);
631: }
632:
633: Source xslSource = null;
634: try {
635: if (base == null || href.indexOf(":") > 1) {
636: // Null base - href must be an absolute URL
637: xslSource = m_resolver.resolveURI(href);
638: } else if (href.length() == 0) {
639: // Empty href resolves to base
640: xslSource = m_resolver.resolveURI(base);
641: } else {
642: // is the base a file or a real m_url
643: if (!base.startsWith("file:")) {
644: int lastPathElementPos = base.lastIndexOf('/');
645: if (lastPathElementPos == -1) {
646: // this should never occur as the base should
647: // always be protocol:/....
648: return null; // we can't resolve this
649: } else {
650: xslSource = m_resolver.resolveURI(base
651: .substring(0, lastPathElementPos)
652: + "/" + href);
653: }
654: } else {
655: File parent = new File(base.substring(5));
656: File parent2 = new File(parent.getParentFile(),
657: href);
658: xslSource = m_resolver.resolveURI(parent2.toURL()
659: .toExternalForm());
660: }
661: }
662:
663: InputSource is = getInputSource(xslSource);
664:
665: if (getLogger().isDebugEnabled()) {
666: getLogger().debug(
667: "xslSource = " + xslSource + ", system id = "
668: + xslSource.getURI());
669: }
670:
671: if (m_checkIncludes) {
672: // Populate included validities
673: List includes = (List) m_includesMap.get(base);
674: if (includes != null) {
675: SourceValidity included = xslSource.getValidity();
676: if (included != null) {
677: includes.add(new Object[] { xslSource.getURI(),
678: xslSource.getValidity() });
679: } else {
680: // One of the included stylesheets is not cacheable
681: m_includesMap.remove(base);
682: }
683: }
684: }
685:
686: return new StreamSource(is.getByteStream(), is
687: .getSystemId());
688: } catch (SourceException e) {
689: if (getLogger().isDebugEnabled()) {
690: getLogger().debug(
691: "Failed to resolve " + href + "(base = " + base
692: + "), return null", e);
693: }
694:
695: // CZ: To obtain the same behaviour as when the resource is
696: // transformed by the XSLT Transformer we should return null here.
697: return null;
698: } catch (java.net.MalformedURLException mue) {
699: if (getLogger().isDebugEnabled()) {
700: getLogger().debug(
701: "Failed to resolve " + href + "(base = " + base
702: + "), return null", mue);
703: }
704:
705: return null;
706: } catch (IOException ioe) {
707: if (getLogger().isDebugEnabled()) {
708: getLogger().debug(
709: "Failed to resolve " + href + "(base = " + base
710: + "), return null", ioe);
711: }
712:
713: return null;
714: } finally {
715: m_resolver.release(xslSource);
716: }
717: }
718:
719: /**
720: * Return a new <code>InputSource</code> object that uses the
721: * <code>InputStream</code> and the system ID of the <code>Source</code>
722: * object.
723: *
724: * @throws IOException
725: * if I/O error occured.
726: */
727: protected InputSource getInputSource(final Source source)
728: throws IOException, SourceException {
729: final InputSource newObject = new InputSource(source
730: .getInputStream());
731: newObject.setSystemId(source.getURI());
732: return newObject;
733: }
734:
735: /**
736: * Recycle the component
737: */
738: public void recycle() {
739: m_includesMap.clear();
740: // restore default factory
741: if (m_factory != m_defaultFactory) {
742: m_factory = m_defaultFactory;
743: }
744: }
745:
746: /**
747: * Subclass to allow for instanciation, as for some unknown reason the
748: * constructor is protected....
749: */
750: public static class MyTransformerHandlerAndValidity extends
751: TransformerHandlerAndValidity {
752:
753: protected MyTransformerHandlerAndValidity(
754: TransformerHandler handler, SourceValidity validity) {
755: super(handler, validity);
756: }
757: }
758: }
|