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.util.HashMap;
022:
023: import javax.xml.transform.Result;
024: import javax.xml.transform.Templates;
025: import javax.xml.transform.Transformer;
026: import javax.xml.transform.TransformerException;
027: import javax.xml.transform.TransformerFactory;
028: import javax.xml.transform.URIResolver;
029: import javax.xml.transform.sax.SAXTransformerFactory;
030: import javax.xml.transform.sax.TemplatesHandler;
031: import javax.xml.transform.sax.TransformerHandler;
032: import javax.xml.transform.stream.StreamSource;
033:
034: import org.apache.avalon.framework.activity.Disposable;
035: import org.apache.avalon.framework.component.Component;
036: import org.apache.avalon.framework.component.ComponentException;
037: import org.apache.avalon.framework.component.ComponentManager;
038: import org.apache.avalon.framework.component.Composable;
039: import org.apache.avalon.framework.logger.AbstractLogEnabled;
040: import org.apache.avalon.framework.parameters.ParameterException;
041: import org.apache.avalon.framework.parameters.Parameterizable;
042: import org.apache.avalon.framework.parameters.Parameters;
043: import org.apache.cocoon.ProcessingException;
044: import org.apache.cocoon.util.ClassUtils;
045: import org.apache.cocoon.util.TraxErrorHandler;
046: import org.apache.excalibur.source.Source;
047: import org.apache.excalibur.source.SourceException;
048: import org.apache.excalibur.source.SourceResolver;
049: import org.apache.excalibur.store.Store;
050: import org.xml.sax.ContentHandler;
051: import org.xml.sax.SAXException;
052: import org.xml.sax.XMLFilter;
053:
054: /**
055: * This class defines the implementation of the {@link XSLTProcessor}
056: * component.
057: *
058: * To configure it, add the following lines in the
059: * <file>cocoon.xconf</file> file:
060: *
061: * <pre>
062: * <xslt-processor class="org.apache.cocoon.components.xslt.XSLTProcessorImpl">
063: * <parameter name="use-store" value="true"/>
064: * <parameter name="transformer-factory" value="org.apache.xalan.processor.TransformerFactoryImpl"/>
065: * </xslt-processor>
066: * </pre>
067: *
068: * The <use-store> configuration forces the transformer to put the
069: * <code>Templates</code> generated from the XSLT stylesheet into the
070: * <code>Store</code>. This property is true by default.
071: * <p>
072: * The <transformer-factory> configuration tells the transformer to use a particular
073: * implementation of <code>javax.xml.transform.TransformerFactory</code>. This allows to force
074: * the use of a given TRAX implementation (e.g. xalan or saxon) if several are available in the
075: * classpath. If this property is not set, the transformer uses the standard TRAX mechanism
076: * (<code>TransformerFactory.newInstance()</code>).
077: *
078: * @deprecated Use the avalon excalibur xslt processor instead.
079: * @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
080: * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
081: * @version CVS $Id: XSLTProcessorImpl.java 540711 2007-05-22 19:36:07Z cziegeler $
082: * @version 1.0
083: * @since July 11, 2001
084: */
085: public class XSLTProcessorImpl extends AbstractLogEnabled implements
086: XSLTProcessor, Composable, Disposable, Parameterizable,
087: URIResolver {
088:
089: protected ComponentManager manager;
090:
091: /** The store service instance */
092: protected Store store;
093:
094: /** The trax TransformerFactory lookup table*/
095: protected HashMap factories;
096:
097: /** The trax TransformerFactory this component uses */
098: protected SAXTransformerFactory factory;
099:
100: /** Is the store turned on? (default is on) */
101: protected boolean useStore = true;
102:
103: /** Is incremental processing turned on? (default for Xalan: no) */
104: protected boolean incrementalProcessing = false;
105:
106: /** The source resolver used by this processor **/
107: protected SourceResolver resolver;
108:
109: /** The error handler for the transformer */
110: protected TraxErrorHandler errorHandler;
111:
112: /**
113: * Compose. Try to get the store
114: */
115: public void compose(ComponentManager manager)
116: throws ComponentException {
117: this .manager = manager;
118: if (this .getLogger().isDebugEnabled())
119: this .getLogger().debug(
120: "XSLTProcessorImpl component initialized.");
121: this .store = (Store) manager.lookup(Store.TRANSIENT_STORE);
122: this .errorHandler = new TraxErrorHandler(this .getLogger());
123: this .resolver = (SourceResolver) manager
124: .lookup(SourceResolver.ROLE);
125: }
126:
127: /**
128: * Dispose
129: */
130: public void dispose() {
131: if (this .manager != null) {
132: this .manager.release(this .store);
133: this .store = null;
134: this .manager.release((Component) this .resolver);
135: this .resolver = null;
136: }
137: this .errorHandler = null;
138: this .manager = null;
139: }
140:
141: /**
142: * Configure the component
143: */
144: public void parameterize(Parameters params)
145: throws ParameterException {
146: this .useStore = params.getParameterAsBoolean("use-store", true);
147: this .incrementalProcessing = params.getParameterAsBoolean(
148: "incremental-processing", false);
149: this .factory = this .getTransformerFactory(params.getParameter(
150: "transformer-factory", DEFAULT_FACTORY));
151: }
152:
153: /**
154: * Set the source resolver used by this component
155: * @deprecated The processor can now simply lookup the source resolver.
156: */
157: public void setSourceResolver(
158: org.apache.cocoon.environment.SourceResolver resolver) {
159: if (this .getLogger().isDebugEnabled()) {
160: this
161: .getLogger()
162: .debug(
163: "XSLTProcessor: the setSourceResolver() method is deprecated.");
164: }
165: }
166:
167: /**
168: * Set the transformer factory used by this component
169: */
170: public void setTransformerFactory(String classname) {
171: this .factory = this .getTransformerFactory(classname);
172: }
173:
174: public TransformerHandler getTransformerHandler(
175: org.apache.cocoon.environment.Source stylesheet)
176: throws ProcessingException {
177: return this .getTransformerHandler(stylesheet, null);
178: }
179:
180: public TransformerHandler getTransformerHandler(
181: org.apache.cocoon.environment.Source stylesheet,
182: XMLFilter filter) throws ProcessingException {
183: try {
184: final String id = stylesheet.getSystemId();
185: Templates templates = this .getTemplates(stylesheet, id);
186: if (templates == null) {
187: if (this .getLogger().isDebugEnabled()) {
188: this .getLogger().debug(
189: "Creating new Templates for " + id);
190: }
191:
192: // Create a Templates ContentHandler to handle parsing of the
193: // stylesheet.
194: TemplatesHandler templatesHandler = this .factory
195: .newTemplatesHandler();
196:
197: // Set the system ID for the template handler since some
198: // TrAX implementations (XSLTC) rely on this in order to obtain
199: // a meaningful identifier for the Templates instances.
200: templatesHandler.setSystemId(id);
201:
202: if (filter != null) {
203: filter.setContentHandler(templatesHandler);
204: }
205:
206: if (this .getLogger().isDebugEnabled()) {
207: this .getLogger().debug(
208: "Source = " + stylesheet
209: + ", templatesHandler = "
210: + templatesHandler);
211: }
212:
213: // Process the stylesheet.
214: stylesheet
215: .toSAX(filter != null ? (ContentHandler) filter
216: : (ContentHandler) templatesHandler);
217:
218: // Get the Templates object (generated during the parsing of
219: // the stylesheet) from the TemplatesHandler.
220: templates = templatesHandler.getTemplates();
221: this .putTemplates(templates, stylesheet, id);
222: } else {
223: if (this .getLogger().isDebugEnabled()) {
224: this .getLogger().debug(
225: "Reusing Templates for " + id);
226: }
227: }
228:
229: TransformerHandler handler = this .factory
230: .newTransformerHandler(templates);
231: handler.getTransformer()
232: .setErrorListener(this .errorHandler);
233: handler.getTransformer().setURIResolver(this );
234: return handler;
235: } catch (ProcessingException e) {
236: throw e;
237: } catch (SAXException e) {
238: if (e.getException() == null) {
239: throw new ProcessingException(
240: "Exception in creating Transform Handler", e);
241: } else {
242: if (this .getLogger().isDebugEnabled())
243: this
244: .getLogger()
245: .debug(
246: "Got SAXException. Rethrowing cause exception.",
247: e);
248: throw new ProcessingException(
249: "Exception in creating Transform Handler", e
250: .getException());
251: }
252: } catch (Exception e) {
253: throw new ProcessingException(
254: "Exception in creating Transform Handler", e);
255: }
256: }
257:
258: public void transform(org.apache.cocoon.environment.Source source,
259: org.apache.cocoon.environment.Source stylesheet,
260: Parameters params, Result result)
261: throws ProcessingException {
262: try {
263: if (this .getLogger().isDebugEnabled()) {
264: this .getLogger().debug(
265: "XSLTProcessorImpl: transform source = "
266: + source + ", stylesheet = "
267: + stylesheet + ", parameters = "
268: + params + ", result = " + result);
269: }
270: TransformerHandler handler = this
271: .getTransformerHandler(stylesheet);
272:
273: Transformer transformer = handler.getTransformer();
274: if (params != null) {
275: transformer.clearParameters();
276: String[] names = params.getNames();
277: for (int i = names.length - 1; i >= 0; i--) {
278: transformer.setParameter(names[i], params
279: .getParameter(names[i]));
280: }
281: }
282:
283: if (this .getLogger().isDebugEnabled())
284: this .getLogger().debug(
285: "XSLTProcessorImpl: starting transform");
286: // Is it possible to use Source's toSAX method?
287: handler.setResult(result);
288: source.toSAX(handler);
289:
290: if (this .getLogger().isDebugEnabled())
291: this .getLogger().debug(
292: "XSLTProcessorImpl: transform done");
293: } catch (Exception e) {
294: throw new ProcessingException(
295: "Error in running Transformation", e);
296: }
297: }
298:
299: /**
300: * Get the TransformerFactory associated with the given classname. If
301: * the class can't be found or the given class doesn't implement
302: * the required interface, the default factory is returned.
303: */
304: private SAXTransformerFactory getTransformerFactory(
305: String factoryName) {
306: SAXTransformerFactory _factory;
307:
308: if ((factoryName == null)
309: || (factoryName == XSLTProcessor.DEFAULT_FACTORY)) {
310: _factory = (SAXTransformerFactory) TransformerFactory
311: .newInstance();
312: } else {
313: try {
314: _factory = (SAXTransformerFactory) ClassUtils
315: .loadClass(factoryName).newInstance();
316: } catch (ClassNotFoundException cnfe) {
317: if (this .getLogger().isErrorEnabled())
318: this
319: .getLogger()
320: .error(
321: "Cannot find the requested TrAX factory '"
322: + factoryName
323: + "'. Using default TrAX Transformer Factory instead.");
324: if (this .factory != null)
325: return this .factory;
326: _factory = (SAXTransformerFactory) TransformerFactory
327: .newInstance();
328: } catch (ClassCastException cce) {
329: if (this .getLogger().isErrorEnabled())
330: this
331: .getLogger()
332: .error(
333: "The indicated class '"
334: + factoryName
335: + "' is not a TrAX Transformer Factory. Using default TrAX Transformer Factory instead.");
336: if (this .factory != null)
337: return this .factory;
338: _factory = (SAXTransformerFactory) TransformerFactory
339: .newInstance();
340: } catch (Exception e) {
341: if (this .getLogger().isErrorEnabled())
342: this
343: .getLogger()
344: .error(
345: "Error found loading the requested TrAX Transformer Factory '"
346: + factoryName
347: + "'. Using default TrAX Transformer Factory instead.");
348: if (this .factory != null)
349: return this .factory;
350: _factory = (SAXTransformerFactory) TransformerFactory
351: .newInstance();
352: }
353: }
354:
355: _factory.setErrorListener(this .errorHandler);
356: _factory.setURIResolver(this );
357:
358: // implementation-specific parameter passing should be
359: // made more extensible.
360: if (_factory.getClass().getName().equals(
361: "org.apache.xalan.processor.TransformerFactoryImpl")) {
362: _factory.setAttribute(
363: "http://xml.apache.org/xalan/features/incremental",
364: new Boolean(this .incrementalProcessing));
365: }
366:
367: return _factory;
368: }
369:
370: private Templates getTemplates(
371: org.apache.cocoon.environment.Source stylesheet, String id)
372: throws IOException, ProcessingException {
373: if (!this .useStore) {
374: return null;
375: }
376:
377: // we must augment the template ID with the factory classname since one
378: // transformer implementation cannot handle the instances of a
379: // template created by another one.
380: id += this .factory.getClass().getName();
381:
382: Templates templates = null;
383: // only stylesheets with a last modification date are stored
384: if (stylesheet.getLastModified() != 0) {
385: // Stored is an array of the template and the caching time
386: if (this .store.containsKey(id)) {
387: Object[] templateAndTime = (Object[]) this .store
388: .get(id);
389:
390: if (templateAndTime != null
391: && templateAndTime[1] != null) {
392: long storedTime = ((Long) templateAndTime[1])
393: .longValue();
394:
395: if (storedTime < stylesheet.getLastModified()) {
396: this .store.remove(id);
397: } else {
398: templates = (Templates) templateAndTime[0];
399: }
400: }
401: }
402: } else if (this .store.containsKey(id)) {
403: // remove an old template if it exists
404: this .store.remove(id);
405: }
406: return templates;
407: }
408:
409: private void putTemplates(Templates templates,
410: org.apache.cocoon.environment.Source stylesheet, String id)
411: throws IOException, ProcessingException {
412: if (!this .useStore) {
413: return;
414: }
415:
416: // we must augment the template ID with the factory classname since one
417: // transformer implementation cannot handle the instances of a
418: // template created by another one.
419: id += this .factory.getClass().getName();
420:
421: // only stylesheets with a last modification date are stored
422: if (stylesheet.getLastModified() != 0) {
423:
424: // Stored is an array of the template and the current time
425: Object[] templateAndTime = new Object[2];
426: templateAndTime[0] = templates;
427: templateAndTime[1] = new Long(stylesheet.getLastModified());
428: this .store.store(id, templateAndTime);
429: }
430: }
431:
432: /**
433: * Called by the processor when it encounters
434: * an xsl:include, xsl:import, or document() function.
435: *
436: * @param href An href attribute, which may be relative or absolute.
437: * @param base The base URI in effect when the href attribute
438: * was encountered.
439: *
440: * @return A Source object, or null if the href cannot be resolved,
441: * and the processor should try to resolve the URI itself.
442: *
443: * @throws TransformerException if an error occurs when trying to
444: * resolve the URI.
445: */
446: public javax.xml.transform.Source resolve(String href, String base)
447: throws TransformerException {
448: if (this .getLogger().isDebugEnabled()) {
449: this .getLogger().debug(
450: "resolve(href = " + href + ", base = " + base
451: + "); resolver = " + this .resolver);
452: }
453:
454: Source xslSource = null;
455: try {
456: if (href.indexOf(":") > 1) {
457: xslSource = this .resolver.resolveURI(href);
458: } else {
459: // patch for a null pointer passed as base
460: if (base == null)
461: throw new IllegalArgumentException(
462: "Null pointer passed as base");
463:
464: // is the base a file or a real url
465: if (!base.startsWith("file:")) {
466: int lastPathElementPos = base.lastIndexOf('/');
467: if (lastPathElementPos == -1) {
468: // this should never occur as the base should
469: // always be protocol:/....
470: return null; // we can't resolve this
471: } else {
472: xslSource = this .resolver
473: .resolveURI(new StringBuffer(base
474: .substring(0,
475: lastPathElementPos))
476: .append("/").append(href)
477: .toString());
478: }
479: } else {
480: File parent = new File(base.substring(5));
481: File parent2 = new File(parent.getParentFile(),
482: href);
483: xslSource = this .resolver.resolveURI(parent2
484: .toURL().toExternalForm());
485: }
486: }
487:
488: if (this .getLogger().isDebugEnabled()) {
489: this .getLogger().debug(
490: "xslSource = " + xslSource + ", system id = "
491: + xslSource.getURI());
492: }
493:
494: return new StreamSource(xslSource.getInputStream(),
495: xslSource.getURI());
496:
497: } catch (java.net.MalformedURLException mue) {
498: return null;
499: } catch (SourceException pe) {
500: throw new TransformerException(pe);
501: } catch (IOException ioe) {
502: return null;
503: } finally {
504: this.resolver.release(xslSource);
505: }
506: }
507: }
|