| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cocoon.components.xslt; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.HashMap; |
| |
| import javax.xml.transform.Result; |
| import javax.xml.transform.Templates; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.URIResolver; |
| import javax.xml.transform.sax.SAXTransformerFactory; |
| import javax.xml.transform.sax.TemplatesHandler; |
| import javax.xml.transform.sax.TransformerHandler; |
| import javax.xml.transform.stream.StreamSource; |
| |
| import org.apache.avalon.framework.activity.Disposable; |
| import org.apache.avalon.framework.component.Component; |
| import org.apache.avalon.framework.component.ComponentException; |
| import org.apache.avalon.framework.component.ComponentManager; |
| import org.apache.avalon.framework.component.Composable; |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| import org.apache.avalon.framework.parameters.ParameterException; |
| import org.apache.avalon.framework.parameters.Parameterizable; |
| import org.apache.avalon.framework.parameters.Parameters; |
| import org.apache.cocoon.ProcessingException; |
| import org.apache.cocoon.util.ClassUtils; |
| import org.apache.cocoon.util.TraxErrorHandler; |
| import org.apache.excalibur.source.Source; |
| import org.apache.excalibur.source.SourceException; |
| import org.apache.excalibur.source.SourceResolver; |
| import org.apache.excalibur.store.Store; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.XMLFilter; |
| |
| /** |
| * This class defines the implementation of the {@link XSLTProcessor} |
| * component. |
| * |
| * To configure it, add the following lines in the |
| * <file>cocoon.xconf</file> file: |
| * |
| * <pre> |
| * <xslt-processor class="org.apache.cocoon.components.xslt.XSLTProcessorImpl"> |
| * <parameter name="use-store" value="true"/> |
| * <parameter name="transformer-factory" value="org.apache.xalan.processor.TransformerFactoryImpl"/> |
| * </xslt-processor> |
| * </pre> |
| * |
| * The <use-store> configuration forces the transformer to put the |
| * <code>Templates</code> generated from the XSLT stylesheet into the |
| * <code>Store</code>. This property is true by default. |
| * <p> |
| * The <transformer-factory> configuration tells the transformer to use a particular |
| * implementation of <code>javax.xml.transform.TransformerFactory</code>. This allows to force |
| * the use of a given TRAX implementation (e.g. xalan or saxon) if several are available in the |
| * classpath. If this property is not set, the transformer uses the standard TRAX mechanism |
| * (<code>TransformerFactory.newInstance()</code>). |
| * |
| * @deprecated Use the avalon excalibur xslt processor instead. |
| * @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a> |
| * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a> |
| * @version CVS $Id$ |
| * @version 1.0 |
| * @since July 11, 2001 |
| */ |
| public class XSLTProcessorImpl |
| extends AbstractLogEnabled |
| implements XSLTProcessor, |
| Composable, |
| Disposable, |
| Parameterizable, |
| URIResolver { |
| |
| protected ComponentManager manager; |
| |
| /** The store service instance */ |
| protected Store store; |
| |
| /** The trax TransformerFactory lookup table*/ |
| protected HashMap factories; |
| |
| /** The trax TransformerFactory this component uses */ |
| protected SAXTransformerFactory factory; |
| |
| /** Is the store turned on? (default is on) */ |
| protected boolean useStore = true; |
| |
| /** Is incremental processing turned on? (default for Xalan: no) */ |
| protected boolean incrementalProcessing = false; |
| |
| /** The source resolver used by this processor **/ |
| protected SourceResolver resolver; |
| |
| /** The error handler for the transformer */ |
| protected TraxErrorHandler errorHandler; |
| |
| /** |
| * Compose. Try to get the store |
| */ |
| public void compose(ComponentManager manager) |
| throws ComponentException { |
| this.manager = manager; |
| if (this.getLogger().isDebugEnabled()) |
| this.getLogger().debug("XSLTProcessorImpl component initialized."); |
| this.store = (Store) manager.lookup(Store.TRANSIENT_STORE); |
| this.errorHandler = new TraxErrorHandler( this.getLogger() ); |
| this.resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); |
| } |
| |
| /** |
| * Dispose |
| */ |
| public void dispose() { |
| if (this.manager != null) { |
| this.manager.release(this.store); |
| this.store = null; |
| this.manager.release((Component)this.resolver); |
| this.resolver = null; |
| } |
| this.errorHandler = null; |
| this.manager = null; |
| } |
| |
| /** |
| * Configure the component |
| */ |
| public void parameterize(Parameters params) |
| throws ParameterException { |
| this.useStore = params.getParameterAsBoolean("use-store", true); |
| this.incrementalProcessing = params.getParameterAsBoolean("incremental-processing", false); |
| this.factory = this.getTransformerFactory(params.getParameter("transformer-factory", DEFAULT_FACTORY)); |
| } |
| |
| /** |
| * Set the source resolver used by this component |
| * @deprecated The processor can now simply lookup the source resolver. |
| */ |
| public void setSourceResolver(org.apache.cocoon.environment.SourceResolver resolver) { |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("XSLTProcessor: the setSourceResolver() method is deprecated."); |
| } |
| } |
| |
| /** |
| * Set the transformer factory used by this component |
| */ |
| public void setTransformerFactory(String classname) { |
| this.factory = this.getTransformerFactory(classname); |
| } |
| |
| public TransformerHandler getTransformerHandler(org.apache.cocoon.environment.Source stylesheet) |
| throws ProcessingException { |
| return this.getTransformerHandler(stylesheet, null); |
| } |
| |
| public TransformerHandler getTransformerHandler(org.apache.cocoon.environment.Source stylesheet, |
| XMLFilter filter) |
| throws ProcessingException { |
| try { |
| final String id = stylesheet.getSystemId(); |
| Templates templates = this.getTemplates(stylesheet, id); |
| if (templates == null) { |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Creating new Templates for " + id); |
| } |
| |
| // Create a Templates ContentHandler to handle parsing of the |
| // stylesheet. |
| TemplatesHandler templatesHandler = this.factory.newTemplatesHandler(); |
| |
| // Set the system ID for the template handler since some |
| // TrAX implementations (XSLTC) rely on this in order to obtain |
| // a meaningful identifier for the Templates instances. |
| templatesHandler.setSystemId(id); |
| |
| if (filter != null) { |
| filter.setContentHandler(templatesHandler); |
| } |
| |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Source = " + stylesheet |
| + ", templatesHandler = " + templatesHandler); |
| } |
| |
| // Process the stylesheet. |
| stylesheet.toSAX(filter != null ? |
| (ContentHandler)filter : (ContentHandler)templatesHandler); |
| |
| // Get the Templates object (generated during the parsing of |
| // the stylesheet) from the TemplatesHandler. |
| templates = templatesHandler.getTemplates(); |
| this.putTemplates (templates, stylesheet, id); |
| } else { |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Reusing Templates for " + id); |
| } |
| } |
| |
| TransformerHandler handler = this.factory.newTransformerHandler(templates); |
| handler.getTransformer().setErrorListener(this.errorHandler); |
| handler.getTransformer().setURIResolver(this); |
| return handler; |
| } catch (ProcessingException e) { |
| throw e; |
| } catch (SAXException e) { |
| if (e.getException() == null) { |
| throw new ProcessingException("Exception in creating Transform Handler", e); |
| } else { |
| if (this.getLogger().isDebugEnabled()) |
| this.getLogger().debug("Got SAXException. Rethrowing cause exception.", e); |
| throw new ProcessingException("Exception in creating Transform Handler", e.getException()); |
| } |
| } catch (Exception e) { |
| throw new ProcessingException("Exception in creating Transform Handler", e); |
| } |
| } |
| |
| public void transform(org.apache.cocoon.environment.Source source, |
| org.apache.cocoon.environment.Source stylesheet, |
| Parameters params, |
| Result result) |
| throws ProcessingException { |
| try { |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("XSLTProcessorImpl: transform source = " + source |
| + ", stylesheet = " + stylesheet |
| + ", parameters = " + params |
| + ", result = " + result); |
| } |
| TransformerHandler handler = this.getTransformerHandler(stylesheet); |
| |
| Transformer transformer = handler.getTransformer(); |
| if (params != null) { |
| transformer.clearParameters(); |
| String[] names = params.getNames(); |
| for (int i = names.length -1 ; i >= 0; i--) { |
| transformer.setParameter(names[i], params.getParameter(names[i])); |
| } |
| } |
| |
| if (this.getLogger().isDebugEnabled()) |
| this.getLogger().debug("XSLTProcessorImpl: starting transform"); |
| // Is it possible to use Source's toSAX method? |
| handler.setResult(result); |
| source.toSAX(handler); |
| |
| if (this.getLogger().isDebugEnabled()) |
| this.getLogger().debug("XSLTProcessorImpl: transform done"); |
| } catch (Exception e) { |
| throw new ProcessingException("Error in running Transformation", e); |
| } |
| } |
| |
| /** |
| * Get the TransformerFactory associated with the given classname. If |
| * the class can't be found or the given class doesn't implement |
| * the required interface, the default factory is returned. |
| */ |
| private SAXTransformerFactory getTransformerFactory(String factoryName) { |
| SAXTransformerFactory _factory; |
| |
| if ((factoryName == null) || (factoryName == XSLTProcessor.DEFAULT_FACTORY)) { |
| _factory = (SAXTransformerFactory) TransformerFactory.newInstance(); |
| } else { |
| try { |
| _factory = (SAXTransformerFactory) ClassUtils.loadClass(factoryName).newInstance(); |
| } catch (ClassNotFoundException cnfe) { |
| if (this.getLogger().isErrorEnabled()) |
| this.getLogger().error("Cannot find the requested TrAX factory '" + factoryName |
| + "'. Using default TrAX Transformer Factory instead."); |
| if (this.factory != null) return this.factory; |
| _factory = (SAXTransformerFactory) TransformerFactory.newInstance(); |
| } catch (ClassCastException cce) { |
| if (this.getLogger().isErrorEnabled()) |
| this.getLogger().error("The indicated class '" + factoryName |
| + "' is not a TrAX Transformer Factory. Using default TrAX Transformer Factory instead."); |
| if (this.factory != null) return this.factory; |
| _factory = (SAXTransformerFactory) TransformerFactory.newInstance(); |
| } catch (Exception e) { |
| if (this.getLogger().isErrorEnabled()) |
| this.getLogger().error("Error found loading the requested TrAX Transformer Factory '" |
| + factoryName + "'. Using default TrAX Transformer Factory instead."); |
| if (this.factory != null) return this.factory; |
| _factory = (SAXTransformerFactory) TransformerFactory.newInstance(); |
| } |
| } |
| |
| _factory.setErrorListener(this.errorHandler); |
| _factory.setURIResolver(this); |
| |
| // implementation-specific parameter passing should be |
| // made more extensible. |
| if (_factory.getClass().getName().equals("org.apache.xalan.processor.TransformerFactoryImpl")) { |
| _factory.setAttribute("http://xml.apache.org/xalan/features/incremental", |
| new Boolean (this.incrementalProcessing)); |
| } |
| |
| return _factory; |
| } |
| |
| private Templates getTemplates(org.apache.cocoon.environment.Source stylesheet, |
| String id) |
| throws IOException, ProcessingException { |
| if (!this.useStore) { |
| return null; |
| } |
| |
| // we must augment the template ID with the factory classname since one |
| // transformer implementation cannot handle the instances of a |
| // template created by another one. |
| id += this.factory.getClass().getName(); |
| |
| Templates templates = null; |
| // only stylesheets with a last modification date are stored |
| if (stylesheet.getLastModified() != 0) { |
| // Stored is an array of the template and the caching time |
| if (this.store.containsKey(id)) { |
| Object[] templateAndTime = (Object[])this.store.get(id); |
| |
| if(templateAndTime != null && templateAndTime[1] != null) { |
| long storedTime = ((Long)templateAndTime[1]).longValue(); |
| |
| if (storedTime < stylesheet.getLastModified()) { |
| this.store.remove(id); |
| } else { |
| templates = (Templates)templateAndTime[0]; |
| } |
| } |
| } |
| } else if (this.store.containsKey(id)) { |
| // remove an old template if it exists |
| this.store.remove(id); |
| } |
| return templates; |
| } |
| |
| private void putTemplates (Templates templates, org.apache.cocoon.environment.Source stylesheet, |
| String id) |
| throws IOException, ProcessingException { |
| if (!this.useStore) { |
| return; |
| } |
| |
| // we must augment the template ID with the factory classname since one |
| // transformer implementation cannot handle the instances of a |
| // template created by another one. |
| id += this.factory.getClass().getName(); |
| |
| // only stylesheets with a last modification date are stored |
| if (stylesheet.getLastModified() != 0) { |
| |
| // Stored is an array of the template and the current time |
| Object[] templateAndTime = new Object[2]; |
| templateAndTime[0] = templates; |
| templateAndTime[1] = new Long(stylesheet.getLastModified()); |
| this.store.store(id, templateAndTime); |
| } |
| } |
| |
| /** |
| * Called by the processor when it encounters |
| * an xsl:include, xsl:import, or document() function. |
| * |
| * @param href An href attribute, which may be relative or absolute. |
| * @param base The base URI in effect when the href attribute |
| * was encountered. |
| * |
| * @return A Source object, or null if the href cannot be resolved, |
| * and the processor should try to resolve the URI itself. |
| * |
| * @throws TransformerException if an error occurs when trying to |
| * resolve the URI. |
| */ |
| public javax.xml.transform.Source resolve(String href, String base) |
| throws TransformerException { |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("resolve(href = " + href + |
| ", base = " + base + "); resolver = " + this.resolver); |
| } |
| |
| Source xslSource = null; |
| try { |
| if (href.indexOf(":") > 1) { |
| xslSource = this.resolver.resolveURI(href); |
| } else { |
| // patch for a null pointer passed as base |
| if (base == null) |
| throw new IllegalArgumentException("Null pointer passed as base"); |
| |
| // is the base a file or a real url |
| if (!base.startsWith("file:")) { |
| int lastPathElementPos = base.lastIndexOf('/'); |
| if (lastPathElementPos == -1) { |
| // this should never occur as the base should |
| // always be protocol:/.... |
| return null; // we can't resolve this |
| } else { |
| xslSource = this.resolver.resolveURI(new StringBuffer(base.substring(0, lastPathElementPos)) |
| .append("/").append(href).toString()); |
| } |
| } else { |
| File parent = new File(base.substring(5)); |
| File parent2 = new File(parent.getParentFile(), href); |
| xslSource = this.resolver.resolveURI(parent2.toURL().toExternalForm()); |
| } |
| } |
| |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("xslSource = " + xslSource |
| + ", system id = " + xslSource.getURI()); |
| } |
| |
| return new StreamSource(xslSource.getInputStream(), xslSource.getURI()); |
| |
| } catch (java.net.MalformedURLException mue) { |
| return null; |
| } catch (SourceException pe) { |
| throw new TransformerException(pe); |
| } catch (IOException ioe) { |
| return null; |
| } finally { |
| this.resolver.release( xslSource ); |
| } |
| } |
| } |