You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by cz...@apache.org on 2007/02/26 14:50:41 UTC
svn commit: r511804 -
/cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java
Author: cziegeler
Date: Mon Feb 26 05:50:40 2007
New Revision: 511804
URL: http://svn.apache.org/viewvc?view=rev&rev=511804
Log:
New xslt transformer using the xslt processor bean
Added:
cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java (with props)
Added: cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java?view=auto&rev=511804
==============================================================================
--- cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java (added)
+++ cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java Mon Feb 26 05:50:40 2007
@@ -0,0 +1,594 @@
+/*
+ * 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.transformation;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.logger.LogEnabled;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.cocoon.ProcessingException;
+import org.apache.cocoon.caching.CacheableProcessingComponent;
+import org.apache.cocoon.components.source.util.SourceUtil;
+import org.apache.cocoon.components.xslt.TraxErrorListener;
+import org.apache.cocoon.components.xslt.XSLTProcessor;
+import org.apache.cocoon.components.xslt.XSLTProcessorException;
+import org.apache.cocoon.environment.Cookie;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.Session;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.xml.XMLConsumer;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.exception.NestableRuntimeException;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceValidity;
+import org.xml.sax.SAXException;
+
+/**
+ * @cocoon.sitemap.component.documentation
+ * The stylesheet processor
+ *
+ * @cocoon.sitemap.component.name xslt
+ * @cocoon.sitemap.component.logger sitemap.transformer.xslt
+ * @cocoon.sitemap.component.documentation.caching
+ * Uses the last modification date of the xslt document for validation
+ *
+ * @cocoon.sitemap.component.pooling.max 32
+ * <p>
+ * This Transformer is used to transform the incoming SAX stream using
+ * a TrAXProcessor. Use the following sitemap declarations to define, configure
+ * and parameterize it:
+ * </p>
+ * <b>In the map:sitemap/map:components/map:transformers:</b><br>
+ * <pre>
+ * <map:transformer name="xslt" src="org.apache.cocoon.transformation.TraxTransformer"><br>
+ * <use-request-parameters>false</use-request-parameters>
+ * <use-browser-capabilities-db>false</use-browser-capabilities-db>
+ * <use-session-info>false</use-session-info>
+ * <xslt-processor-role>xslt</xslt-processor-role>
+ * <check-includes>true</check-includes>
+ * </map:transformer>
+ * </pre>
+ *
+ * The <use-request-parameter> configuration forces the transformer to make all
+ * request parameters available in the XSLT stylesheet. Note that this has
+ * implications for caching of the generated output of this transformer.<br>
+ * This property is false by default.
+ * <p>
+ * The <use-cookies> configuration forces the transformer to make all
+ * cookies from the request available in the XSLT stylesheets.
+ * Note that this has implications for caching of the generated output of this
+ * transformer.<br>
+ * This property is false by default.
+ * <p>
+ * The <use-session-info> configuration forces the transformer to make all
+ * of the session information available in the XSLT stylesheetas.<br>
+ * These infos are (boolean values are "true" or "false" strings: session-is-new,
+ * session-id-from-cookie, session-id-from-url, session-valid, session-id.<br>
+ * This property is false by default.
+ *
+ * <p>Note that this has implications for caching of the generated output of
+ * this transformer.<br>
+ *
+ *
+ * The <xslt-processor-role> configuration allows to specify the TrAX processor (defined in
+ * the cocoon.xconf) that will be used to obtain the XSLT processor. This allows to have
+ * several XSLT processors in the configuration (e.g. Xalan, XSLTC, Saxon, ...) and choose
+ * one or the other depending on the needs of stylesheet specificities.<br>
+ * If no processor is specified, this transformer will use the XSLT implementation
+ * that Cocoon uses internally.
+ *
+ * The <check-includes> configuration specifies if the included stylesheets are
+ * also checked for changes during caching. If this is set to true (default), the
+ * included stylesheets are also checked for changes; if this is set to false, only
+ * the main stylesheet is checked. Setting this to false improves the performance,
+ * and should be used whenever no includes are in the stylesheet. However, if
+ * you have includes, you have to be careful when changing included stylesheets
+ * as the changes might not take effect immediately. You should touch the main
+ * stylesheet as well.
+ *
+ * <p>
+ * <b>In a map:sitemap/map:pipelines/map:pipeline:</b><br>
+ * <pre>
+ * <map:transform type="xslt" src="stylesheets/yours.xsl"><br>
+ * <parameter name="myparam" value="myvalue"/>
+ * </map:transform>
+ * </pre>
+ * All <parameter> declarations will be made available in the XSLT stylesheet as
+ * xsl:variables.
+ *
+ * @version SVN $Id$
+ */
+public class XSLTTransformer extends AbstractTransformer
+implements Serviceable, Configurable, CacheableProcessingComponent, Disposable {
+
+ /** The service manager instance (protected because used by subclasses) */
+ protected ServiceManager manager;
+
+ /** The object model (protected because used by subclasses) */
+ protected Map objectModel;
+
+ /** Logicsheet parameters (protected because used by subclasses) */
+ protected Map logicSheetParameters;
+
+ /** Should we make the request parameters available in the stylesheet? (default is off) */
+ private boolean useParameters = false;
+ private boolean _useParameters = false;
+
+ /** Should we make the cookies available in the stylesheet? (default is off) */
+ private boolean useCookies = false;
+ private boolean _useCookies = false;
+
+ /** Should we info about the session available in the stylesheet? (default is off) */
+ private boolean useSessionInfo = false;
+ private boolean _useSessionInfo = false;
+
+ /** Do we check included stylesheets for changes? */
+ private boolean checkIncludes = true;
+
+ /** The trax TransformerHandler */
+ protected TransformerHandler transformerHandler;
+
+ /** The validity of the Transformer */
+ protected SourceValidity transformerValidity;
+
+ /** The Source */
+ private Source inputSource;
+ /** The parameters */
+ private Parameters par;
+ /** The source resolver */
+ private SourceResolver resolver;
+
+ /** Default source, used to create specialized transformers by configuration */
+ private String defaultSrc;
+
+ /** The XSLTProcessor */
+ private XSLTProcessor xsltProcessor;
+
+ /** Did we finish the processing (is endDocument() called) */
+ private boolean finishedDocument = false;
+
+ /** Xalan's DTMManager.getIncremental() method. See recycle() method to see what we need this for. */
+ private Method xalanDtmManagerGetIncrementalMethod;
+
+ /** Exception that might occur during setConsumer */
+ private SAXException exceptionDuringSetConsumer;
+
+ /** The error listener used by the stylesheet */
+ private TraxErrorListener errorListener;
+
+ /**
+ * Configure this transformer.
+ */
+ public void configure(Configuration conf)
+ throws ConfigurationException {
+ Configuration child;
+
+ child = conf.getChild("use-request-parameters");
+ this.useParameters = child.getValueAsBoolean(false);
+ this._useParameters = this.useParameters;
+
+ child = conf.getChild("use-cookies");
+ this.useCookies = child.getValueAsBoolean(false);
+ this._useCookies = this.useCookies;
+
+ child = conf.getChild("use-session-info");
+ this.useSessionInfo = child.getValueAsBoolean(false);
+ this._useSessionInfo = this.useSessionInfo;
+
+ child = conf.getChild("xslt-processor-role");
+ String xsltProcessorRole = child.getValue(XSLTProcessor.class.getName());
+ if (!xsltProcessorRole.startsWith(XSLTProcessor.class.getName())) {
+ xsltProcessorRole = XSLTProcessor.class.getName() + '/' + xsltProcessorRole;
+ }
+
+ child = conf.getChild("check-includes");
+ this.checkIncludes = child.getValueAsBoolean(this.checkIncludes);
+
+ child = conf.getChild("default-src",false);
+ if(child!=null) {
+ this.defaultSrc = child.getValue();
+ }
+
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("Use parameters is " + this.useParameters);
+ getLogger().debug("Use cookies is " + this.useCookies);
+ getLogger().debug("Use session info is " + this.useSessionInfo);
+ getLogger().debug("Use TrAX Processor " + xsltProcessorRole);
+ getLogger().debug("Check for included stylesheets is " + this.checkIncludes);
+ getLogger().debug("Default source = " + this.defaultSrc);
+ }
+
+ try {
+ this.xsltProcessor = (XSLTProcessor) this.manager.lookup(xsltProcessorRole);
+ } catch (ServiceException e) {
+ throw new ConfigurationException("Cannot load XSLT processor", e);
+ }
+
+ try {
+ // see the recyle() method to see what we need this for
+ Class dtmManagerClass = Class.forName("org.apache.xml.dtm.DTMManager");
+ xalanDtmManagerGetIncrementalMethod = dtmManagerClass.getMethod("getIncremental", null);
+ } catch (ClassNotFoundException e) {
+ // do nothing -- user does not use xalan, so we don't need the dtm manager
+ } catch (NoSuchMethodException e) {
+ throw new ConfigurationException("Was not able to get getIncremental method from Xalan's DTMManager.", e);
+ }
+ }
+
+ /**
+ * Set the current <code>ServiceManager</code> instance used by this
+ * <code>Serviceable</code>.
+ */
+ public void service(ServiceManager manager) throws ServiceException {
+ this.manager = manager;
+ }
+
+ /**
+ * Set the <code>SourceResolver</code>, the <code>Map</code> with
+ * the object model, the source and sitemap
+ * <code>Parameters</code> used to process the request.
+ */
+ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
+ throws SAXException, ProcessingException, IOException {
+
+ if(src==null && defaultSrc!=null) {
+ if(getLogger().isDebugEnabled()) {
+ getLogger().debug("src is null, using default source " + defaultSrc);
+ }
+ src = defaultSrc;
+ }
+
+ if (src == null) {
+ throw new ProcessingException("Stylesheet URI can't be null");
+ }
+
+ this.par = par;
+ this.objectModel = objectModel;
+ this.resolver = resolver;
+ try {
+ this.inputSource = resolver.resolveURI(src);
+ } catch (SourceException se) {
+ throw SourceUtil.handle("Unable to resolve " + src, se);
+ }
+ _useParameters = par.getParameterAsBoolean("use-request-parameters", this.useParameters);
+ _useCookies = par.getParameterAsBoolean("use-cookies", this.useCookies);
+ _useSessionInfo = par.getParameterAsBoolean("use-session-info", this.useSessionInfo);
+ final boolean _checkIncludes = par.getParameterAsBoolean("check-includes", this.checkIncludes);
+
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("Using stylesheet: '" + this.inputSource.getURI() + "' in " + this);
+ getLogger().debug("Use parameters is " + this._useParameters);
+ getLogger().debug("Use cookies is " + this._useCookies);
+ getLogger().debug("Use session info is " + this._useSessionInfo);
+ getLogger().debug("Check for included stylesheets is " + _checkIncludes);
+ }
+
+ // Get a Transformer Handler if we check for includes
+ // If we don't check the handler is get during setConsumer()
+ try {
+ if ( _checkIncludes ) {
+ XSLTProcessor.TransformerHandlerAndValidity handlerAndValidity =
+ this.xsltProcessor.getTransformerHandlerAndValidity(this.inputSource, null);
+ this.transformerHandler = handlerAndValidity.getTransfomerHandler();
+ this.transformerValidity = handlerAndValidity.getTransfomerValidity();
+ } else {
+ this.transformerValidity = this.inputSource.getValidity();
+ }
+ } catch (XSLTProcessorException se) {
+ throw new ProcessingException("Unable to get transformer handler for " + this.inputSource.getURI(), se);
+ }
+ }
+
+ /**
+ * Generate the unique key.
+ * This key must be unique inside the space of this component.
+ *
+ * @return The generated key hashes the src
+ */
+ public Serializable getKey() {
+ Map map = getLogicSheetParameters();
+ if (map == null) {
+ return this.inputSource.getURI();
+ }
+
+ StringBuffer sb = new StringBuffer();
+ sb.append(this.inputSource.getURI());
+ Set entries = map.entrySet();
+ for(Iterator i=entries.iterator(); i.hasNext();){
+ sb.append(';');
+ Map.Entry entry = (Map.Entry)i.next();
+ sb.append(entry.getKey());
+ sb.append('=');
+ sb.append(entry.getValue());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Generate the validity object.
+ *
+ * @return The generated validity object or <code>null</code> if the
+ * component is currently not cacheable.
+ */
+ public SourceValidity getValidity() {
+ //
+ // VG: Key is generated using parameter/value pairs,
+ // so this information does not need to be verified again
+ // (if parameter added/removed or value changed, key should
+ // change also), only stylesheet's validity is included.
+ //
+ return this.transformerValidity;
+ }
+
+ /**
+ * Set the <code>XMLConsumer</code> that will receive XML data.
+ */
+ public void setConsumer(XMLConsumer consumer) {
+
+ if ( this.transformerHandler == null ) {
+ try {
+ this.transformerHandler = this.xsltProcessor.getTransformerHandler(this.inputSource);
+ } catch (XSLTProcessorException se) {
+ // the exception will be thrown during startDocument()
+ this.exceptionDuringSetConsumer =
+ new SAXException("Unable to get transformer handler for " + this.inputSource.getURI(), se);
+ return;
+ }
+ }
+ final Map map = getLogicSheetParameters();
+ if (map != null) {
+ final javax.xml.transform.Transformer transformer = this.transformerHandler.getTransformer();
+ final Iterator iterator = map.entrySet().iterator();
+ while (iterator.hasNext()) {
+ final Map.Entry entry = (Entry) iterator.next();
+ transformer.setParameter((String)entry.getKey(), entry.getValue());
+ }
+ }
+
+ super.setContentHandler(this.transformerHandler);
+ super.setLexicalHandler(this.transformerHandler);
+
+ if (this.transformerHandler instanceof LogEnabled) {
+ ((LogEnabled)this.transformerHandler).enableLogging(getLogger());
+ }
+ // According to TrAX specs, all TransformerHandlers are LexicalHandlers
+ final SAXResult result = new SAXResult(consumer);
+ result.setLexicalHandler(consumer);
+ this.transformerHandler.setResult(result);
+
+ this.errorListener = new TraxErrorListener(getLogger(), this.inputSource.getURI());
+ this.transformerHandler.getTransformer().setErrorListener(this.errorListener);
+ }
+
+ /**
+ * Get the parameters for the logicsheet
+ */
+ protected Map getLogicSheetParameters() {
+ if (this.logicSheetParameters != null) {
+ return this.logicSheetParameters;
+ }
+ HashMap map = null;
+ if (par != null) {
+ String[] params = par.getNames();
+ if (params != null) {
+ for(int i = 0; i < params.length; i++) {
+ String name = params[i];
+ if (isValidXSLTParameterName(name)) {
+ String value = par.getParameter(name,null);
+ if (value != null) {
+ if (map == null) {
+ map = new HashMap(params.length);
+ }
+ map.put(name,value);
+ }
+ }
+ }
+ }
+ }
+
+ if (this._useParameters) {
+ Request request = ObjectModelHelper.getRequest(objectModel);
+
+ Enumeration parameters = request.getParameterNames();
+ if (parameters != null) {
+ while (parameters.hasMoreElements()) {
+ String name = (String) parameters.nextElement();
+ if (isValidXSLTParameterName(name)) {
+ String value = request.getParameter(name);
+ if (map == null) {
+ map = new HashMap();
+ }
+ map.put(name,value);
+ }
+ }
+ }
+ }
+
+ if (this._useSessionInfo) {
+ final Request request = ObjectModelHelper.getRequest(objectModel);
+ if (map == null) {
+ map = new HashMap(6);
+ }
+
+ final Session session = request.getSession(false);
+ if (session != null) {
+ map.put("session-available", "true");
+ map.put("session-is-new", BooleanUtils.toStringTrueFalse(session.isNew()));
+ map.put("session-id-from-cookie", BooleanUtils.toStringTrueFalse(request.isRequestedSessionIdFromCookie()));
+ map.put("session-id-from-url", BooleanUtils.toStringTrueFalse(request.isRequestedSessionIdFromURL()));
+ map.put("session-valid", BooleanUtils.toStringTrueFalse(request.isRequestedSessionIdValid()));
+ map.put("session-id", session.getId());
+ } else {
+ map.put("session-available", "false");
+ }
+ }
+
+ if (this._useCookies) {
+ Request request = ObjectModelHelper.getRequest(objectModel);
+ Cookie cookies[] = request.getCookies();
+ if (cookies != null) {
+ for (int i = 0; i < cookies.length; i++) {
+ String name = cookies[i].getName();
+ if (isValidXSLTParameterName(name)) {
+ String value = cookies[i].getValue();
+ if (map == null) {
+ map = new HashMap(cookies.length);
+ }
+ map.put(name,value);
+ }
+ }
+ }
+ }
+ this.logicSheetParameters = map;
+ return this.logicSheetParameters;
+ }
+
+ /**
+ * Test if the name is a valid parameter name for XSLT
+ */
+ static boolean isValidXSLTParameterName(String name) {
+ if (name.length() == 0) {
+ return false;
+ }
+
+ char c = name.charAt(0);
+ if (!(Character.isLetter(c) || c == '_')) {
+ return false;
+ }
+
+ for (int i = name.length()-1; i > 1; i--) {
+ c = name.charAt(i);
+ if (!(Character.isLetterOrDigit(c) ||
+ c == '-' ||
+ c == '_' ||
+ c == '.')) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Disposable
+ */
+ public void dispose() {
+ if ( this.manager != null ) {
+ this.manager.release(this.xsltProcessor);
+ this.xsltProcessor = null;
+ this.manager = null;
+ }
+ }
+
+ /**
+ * Recyclable
+ */
+ public void recycle() {
+ this.objectModel = null;
+ if (this.inputSource != null) {
+ this.resolver.release(this.inputSource);
+ this.inputSource = null;
+ }
+ this.resolver = null;
+ this.par = null;
+ if (!this.finishedDocument && transformerHandler != null) {
+ // This situation will only occur if an exception occured during pipeline execution.
+ // If Xalan is used in incremental mode, it is important that endDocument is called, otherwise
+ // the thread on which it runs the transformation will keep waiting.
+ // However, calling endDocument will cause the pipeline to continue executing, and thus the
+ // serializer will write output to the outputstream after what's already there (the error page),
+ // see also bug 13186.
+ if (xalanDtmManagerGetIncrementalMethod != null
+ && transformerHandler.getClass().getName().equals("org.apache.xalan.transformer.TransformerHandlerImpl")) {
+ try {
+ final boolean incremental = ((Boolean)xalanDtmManagerGetIncrementalMethod.invoke(null, null)).booleanValue();
+ if (incremental) {
+ super.endDocument();
+ }
+ } catch (Exception ignore) {}
+ }
+ }
+ this.finishedDocument = true;
+ this.logicSheetParameters = null;
+ this.transformerHandler = null;
+ this.transformerValidity = null;
+ this.exceptionDuringSetConsumer = null;
+ this.errorListener = null;
+ super.recycle();
+ }
+
+ /**
+ * Fix for stopping hanging threads of Xalan
+ */
+ public void endDocument()
+ throws SAXException {
+ try {
+ super.endDocument();
+ } catch(Exception e) {
+
+ Throwable realEx = this.errorListener.getThrowable();
+ if (realEx == null) realEx = e;
+
+ if (realEx instanceof RuntimeException) {
+ throw (RuntimeException)realEx;
+ }
+
+ if (realEx instanceof SAXException) {
+ throw (SAXException)realEx;
+ }
+
+ if (realEx instanceof Error) {
+ throw (Error)realEx;
+ }
+
+ throw new NestableRuntimeException(realEx);
+ }
+ this.finishedDocument = true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.ContentHandler#startDocument()
+ */
+ public void startDocument() throws SAXException {
+ // did an exception occur during setConsumer?
+ // if so, throw it here
+ if ( this.exceptionDuringSetConsumer != null ) {
+ throw this.exceptionDuringSetConsumer;
+ }
+ this.finishedDocument = false;
+ super.startDocument();
+ }
+}
Propchange: cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java
------------------------------------------------------------------------------
svn:keywords = Id