Return-Path: Delivered-To: apmail-cocoon-cvs-archive@www.apache.org Received: (qmail 2354 invoked from network); 26 Feb 2007 13:51:14 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 26 Feb 2007 13:51:14 -0000 Received: (qmail 32951 invoked by uid 500); 26 Feb 2007 13:51:12 -0000 Delivered-To: apmail-cocoon-cvs-archive@cocoon.apache.org Received: (qmail 32916 invoked by uid 500); 26 Feb 2007 13:51:11 -0000 Mailing-List: contact cvs-help@cocoon.apache.org; run by ezmlm Precedence: bulk Reply-To: dev@cocoon.apache.org list-help: list-unsubscribe: List-Post: List-Id: Delivered-To: mailing list cvs@cocoon.apache.org Received: (qmail 32894 invoked by uid 99); 26 Feb 2007 13:51:11 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 26 Feb 2007 05:51:11 -0800 X-ASF-Spam-Status: No, hits=-99.5 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 26 Feb 2007 05:51:01 -0800 Received: by eris.apache.org (Postfix, from userid 65534) id 4D3021A981A; Mon, 26 Feb 2007 05:50:41 -0800 (PST) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r511804 - /cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-components/src/main/java/org/apache/cocoon/transformation/XSLTTransformer.java Date: Mon, 26 Feb 2007 13:50:41 -0000 To: cvs@cocoon.apache.org From: cziegeler@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20070226135041.4D3021A981A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 + *

+ * This Transformer is used to transform the incoming SAX stream using + * a TrAXProcessor. Use the following sitemap declarations to define, configure + * and parameterize it: + *

+ * In the map:sitemap/map:components/map:transformers:
+ *
+ * <map:transformer name="xslt" src="org.apache.cocoon.transformation.TraxTransformer">
+ * <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> + *
+ * + * 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.
+ * This property is false by default. + *

+ * 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.
+ * This property is false by default. + *

+ * The <use-session-info> configuration forces the transformer to make all + * of the session information available in the XSLT stylesheetas.
+ * 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.
+ * This property is false by default. + * + *

Note that this has implications for caching of the generated output of + * this transformer.
+ * + * + * 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.
+ * 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. + * + *

+ * In a map:sitemap/map:pipelines/map:pipeline:
+ *

+ * <map:transform type="xslt" src="stylesheets/yours.xsl">
+ * <parameter name="myparam" value="myvalue"/> + * </map:transform> + *
+ * 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 ServiceManager instance used by this + * Serviceable. + */ + public void service(ServiceManager manager) throws ServiceException { + this.manager = manager; + } + + /** + * Set the SourceResolver, the Map with + * the object model, the source and sitemap + * Parameters 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 null 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 XMLConsumer 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