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 2005/06/08 20:07:33 UTC

svn commit: r189614 - in /cocoon/branches/BRANCH_2_1_X: ./ src/java/org/apache/cocoon/caching/ src/java/org/apache/cocoon/components/pipeline/impl/ src/webapp/

Author: cziegeler
Date: Wed Jun  8 11:07:32 2005
New Revision: 189614

URL: http://svn.apache.org/viewcvs?rev=189614&view=rev
Log:
Move expires pipeline implementation out of the scratchpad.

Added:
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/caching/IdentifierCacheKey.java   (with props)
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/pipeline/impl/ExpiresCachingProcessingPipeline.java   (with props)
Modified:
    cocoon/branches/BRANCH_2_1_X/src/webapp/sitemap.xmap
    cocoon/branches/BRANCH_2_1_X/status.xml

Added: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/caching/IdentifierCacheKey.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/caching/IdentifierCacheKey.java?rev=189614&view=auto
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/caching/IdentifierCacheKey.java (added)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/caching/IdentifierCacheKey.java Wed Jun  8 11:07:32 2005
@@ -0,0 +1,94 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed 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.caching;
+
+import java.io.Serializable;
+
+/**
+ * This is a "simple" cache key that does not consider the components used in the
+ * pipeline. It simply consists of a key (unique identifier for the request) and
+ * a boolean value that defines if the key is for a complete pipeline call or
+ * for an internal pipeline call.
+ *
+ * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
+ * @version CVS $Id$
+ * @since 2.1.1
+ */
+public class IdentifierCacheKey
+    implements Serializable {
+
+    /** The key */
+    final protected String key;
+
+    /** Is this an external pipeline call? */
+    final protected boolean external;
+
+    /** cache key */
+    final protected String cacheKey;
+    
+    /** cache toString() */
+    protected String toString;
+    
+    /**
+     * Constructor
+     */
+    public IdentifierCacheKey(String key, boolean external) {
+        this.key = key;
+        this.external = external;
+        final StringBuffer buf = new StringBuffer();
+        buf.append(this.external).append(':').append(this.key);
+        this.cacheKey = buf.toString();
+    }
+
+    /**
+     * Compare
+     */
+    public boolean equals(Object object) {
+        if (object instanceof IdentifierCacheKey) {
+            IdentifierCacheKey pck = (IdentifierCacheKey)object;
+            return this.cacheKey.equals( pck.cacheKey );
+        }
+        return false;
+    }
+
+    /**
+     * Generate a hash code
+     */
+    public int hashCode() {
+        return this.cacheKey.hashCode();
+    }
+
+    /**
+     * toString
+     * The FilesystemStore uses toString!
+     */
+    public String toString() {
+        if (this.toString == null) {
+            StringBuffer buffer = new StringBuffer();
+            buffer.append("IK:");
+            buffer.append(this.cacheKey);
+            this.toString = buffer.toString();
+        }
+        return toString;
+    }
+    
+    /**
+     * The cache key
+     */
+    public String getKey() {
+        return this.key;
+    }
+}

Propchange: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/caching/IdentifierCacheKey.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/caching/IdentifierCacheKey.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/pipeline/impl/ExpiresCachingProcessingPipeline.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/pipeline/impl/ExpiresCachingProcessingPipeline.java?rev=189614&view=auto
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/pipeline/impl/ExpiresCachingProcessingPipeline.java (added)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/pipeline/impl/ExpiresCachingProcessingPipeline.java Wed Jun  8 11:07:32 2005
@@ -0,0 +1,358 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.pipeline.impl;
+
+import org.apache.avalon.framework.component.ComponentException;
+import org.apache.avalon.framework.parameters.ParameterException;
+import org.apache.avalon.framework.parameters.Parameters;
+
+import org.apache.cocoon.ProcessingException;
+import org.apache.cocoon.caching.CachedResponse;
+import org.apache.cocoon.caching.CachingOutputStream;
+import org.apache.cocoon.caching.IdentifierCacheKey;
+import org.apache.cocoon.components.sax.XMLDeserializer;
+import org.apache.cocoon.components.sax.XMLSerializer;
+import org.apache.cocoon.components.sax.XMLTeePipe;
+import org.apache.cocoon.environment.Environment;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Response;
+import org.apache.cocoon.xml.XMLConsumer;
+
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.source.impl.validity.ExpiresValidity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.Map;
+
+/**
+ * This pipeline implementation caches the complete content for a defined
+ * period of time (expires).
+ *
+ * <map:pipe name="expires" src="org.apache.cocoon.components.pipeline.impl.ExpiresCachingProcessingPipeline">
+ *   <parameter name="cache-expires" value="180"/> <!-- Expires in secondes -->
+ * </map:pipe>
+ *
+ * @since 2.1
+ * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
+ * @version CVS $Id$
+ */
+public class ExpiresCachingProcessingPipeline
+    extends BaseCachingProcessingPipeline {
+
+    /** This key can be used to put a key in the object model */
+    public static final String CACHE_KEY_KEY = ExpiresCachingProcessingPipeline.class.getName() + "/CacheKey";
+
+    /** This key can be used to put an expires information in the object model */
+    public static final String CACHE_EXPIRES_KEY = ExpiresCachingProcessingPipeline.class.getName() + "/Expires";
+
+    /** The source validity */
+    protected SourceValidity cacheValidity;
+
+    /** The key used for caching */
+    protected IdentifierCacheKey cacheKey;
+
+    /** The expires information */
+    protected long cacheExpires;
+
+    /** Default value for expiration */
+    protected long defaultCacheExpires = 3600; // 1 hour
+
+    /** The cached response */
+    protected CachedResponse cachedResponse;
+
+    public void parameterize(Parameters params)
+    throws ParameterException {
+        super.parameterize(params);
+
+        this.defaultCacheExpires = params.getParameterAsLong("cache-expires", this.defaultCacheExpires);
+    }
+
+    /**
+     * Process the given <code>Environment</code>, producing the output.
+     */
+    protected boolean processXMLPipeline(Environment environment)
+    throws ProcessingException {
+        try {
+            if (this.cachedResponse != null) {
+                byte[] content = cachedResponse.getResponse();
+
+                if ( this.serializer == this.lastConsumer ) {
+                    if ( cachedResponse.getContentType() != null ) {
+                        environment.setContentType(cachedResponse.getContentType());
+                    } else {
+                        this.setMimeTypeForSerializer(environment);
+                    }
+                    final OutputStream outputStream = environment.getOutputStream(0);
+                    if (content.length > 0) {
+                        environment.setContentLength(content.length);
+                        outputStream.write(content);
+                    }
+                } else {
+                    this.setMimeTypeForSerializer(environment);
+                    this.xmlDeserializer.setConsumer( this.lastConsumer );
+                    this.xmlDeserializer.deserialize( content );
+                }
+
+            } else {
+
+                // generate new response
+
+                if ( this.cacheExpires == 0 ) {
+                    return super.processXMLPipeline( environment );
+                }
+
+                this.setMimeTypeForSerializer(environment);
+                byte[] cachedData;
+                if ( this.serializer == this.lastConsumer ) {
+
+                    if (this.serializer.shouldSetContentLength()) {
+                        OutputStream os = environment.getOutputStream(this.outputBufferSize);
+
+                        // set the output stream
+                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                        this.serializer.setOutputStream(baos);
+
+                        this.generator.generate();
+
+                        cachedData = baos.toByteArray();
+                        environment.setContentLength(cachedData.length);
+                        os.write(cachedData);
+                    } else {
+                        CachingOutputStream os = new CachingOutputStream( environment.getOutputStream(this.outputBufferSize) );
+                        // set the output stream
+                        this.serializer.setOutputStream( os );
+                        this.generator.generate();
+
+                        cachedData = os.getContent();
+                    }
+
+                } else {
+                    this.generator.generate();
+                    cachedData = (byte[])this.xmlSerializer.getSAXFragment();
+                }
+
+                //
+                // Now that we have processed the pipeline,
+                // we do the actual caching
+                //
+                if (this.cacheValidity != null) {
+                    cachedResponse = new CachedResponse(this.cacheValidity,
+                                                        cachedData);
+                    cachedResponse.setContentType(environment.getContentType());
+                    this.cache.store(this.cacheKey, cachedResponse);
+                }
+            }
+        } catch (Exception e) {
+            handleException(e);
+        }
+
+        return true;
+    }
+
+    /**
+     * Connect the XML pipeline.
+     */
+    protected void connectPipeline(Environment environment)
+    throws ProcessingException {
+        if ( this.lastConsumer != this.serializer ) {
+            // internal
+            if ( this.cachedResponse == null) {
+                // if we cache, we need an xml serializer
+                if ( this.cacheExpires > 0) {
+                    try {
+                        final XMLConsumer old = this.lastConsumer;
+                        this.xmlSerializer = (XMLSerializer)this.manager.lookup( XMLSerializer.ROLE );
+                        this.lastConsumer = new XMLTeePipe(this.lastConsumer, this.xmlSerializer);
+
+                        super.connectPipeline( environment );
+
+                        this.lastConsumer = old;
+                    } catch ( ComponentException e ) {
+                        throw new ProcessingException("Could not connect pipeline.", e);
+                    }
+                } else {
+                    super.connectPipeline( environment );
+                }
+            } else {
+                // we use the cache, so we need an xml deserializer
+                try {
+                    this.xmlDeserializer = (XMLDeserializer)this.manager.lookup(XMLDeserializer.ROLE);
+                } catch ( ComponentException e ) {
+                    throw new ProcessingException("Could not connect pipeline.", e);
+                }
+            }
+        } else {
+            // external: we only need to connect if we don't use a cached response
+            if ( this.cachedResponse == null) {
+                super.connectPipeline( environment );
+            }
+        }
+    }
+
+    /**
+     * Prepare the pipeline
+     */
+    protected void preparePipeline(Environment environment)
+    throws ProcessingException {
+        // get the key and the expires info
+        // we must do this before we call super.preparePipeline,
+        // otherwise internal pipelines are instantiated and
+        // get a copy of the object model with our info!
+        final Map objectModel = environment.getObjectModel();
+        String key = (String)objectModel.get(CACHE_KEY_KEY);
+        if ( key == null ) {
+            key = this.parameters.getParameter("cache-key", null);
+            if ( key == null ) {
+                key = environment.getURIPrefix()+environment.getURI();
+            }
+        } else {
+            objectModel.remove(CACHE_KEY_KEY);
+        }
+        String expiresValue = (String)objectModel.get(CACHE_EXPIRES_KEY);
+        if ( expiresValue == null ) {
+            this.cacheExpires = this.parameters.getParameterAsLong("cache-expires", this.defaultCacheExpires);
+        } else {
+            this.cacheExpires = Long.valueOf(expiresValue).longValue();
+            objectModel.remove(CACHE_EXPIRES_KEY);
+        }
+
+        // prepare the pipeline
+        super.preparePipeline( environment );
+
+        // and now prepare the caching information
+        this.cacheKey = new IdentifierCacheKey(key,
+                                           this.serializer == this.lastConsumer);
+        if ( this.cacheExpires > 0) {
+            this.cacheValidity = new ExpiresValidity(this.cacheExpires*1000);
+        }
+        final boolean purge = this.parameters.getParameterAsBoolean("purge-cache", false);
+
+        this.cachedResponse = this.cache.get(this.cacheKey);
+        if ( this.cachedResponse != null ) {
+            final SourceValidity sv = cachedResponse.getValidityObjects()[0];
+            if ( purge
+                 || (this.cacheExpires != -1 && sv.isValid() != SourceValidity.VALID) ) {
+                this.cache.remove( this.cacheKey );
+                this.cachedResponse = null;
+            }
+        }
+        if ( this.cacheExpires > 0
+             && (this.reader != null || this.lastConsumer == this.serializer )) {
+            Response res = ObjectModelHelper.getResponse(environment.getObjectModel());
+            res.setDateHeader("Expires", System.currentTimeMillis() + (this.cacheExpires*1000));
+            res.setHeader("Cache-Control", "max-age=" + this.cacheExpires + ", public");
+        }
+    }
+
+    /**
+     * Return valid validity objects for the event pipeline
+     * If the "event pipeline" (= the complete pipeline without the
+     * serializer) is cacheable and valid, return all validity objects.
+     * Otherwise return <code>null</code>
+     */
+    public SourceValidity getValidityForEventPipeline() {
+        return this.cacheValidity;
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.cocoon.components.pipeline.ProcessingPipeline#getKeyForEventPipeline()
+     */
+    public String getKeyForEventPipeline() {
+        if (this.cacheKey != null && this.cacheValidity != null) {
+            return this.cacheKey.toString();
+        }
+        return null;
+    }
+
+    /**
+     * Recyclable Interface
+     */
+    public void recycle() {
+        this.cacheKey = null;
+        this.cacheExpires = 0;
+        this.cachedResponse = null;
+        super.recycle();
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.cocoon.components.pipeline.AbstractProcessingPipeline#processReader(org.apache.cocoon.environment.Environment)
+     */
+    protected boolean processReader(Environment environment)
+    throws ProcessingException {
+        try {
+            if (this.cachedResponse != null) {
+                if ( cachedResponse.getContentType() != null ) {
+                    environment.setContentType(cachedResponse.getContentType());
+                } else {
+                    this.setMimeTypeForReader(environment);
+                }
+
+                final byte[] content = cachedResponse.getResponse();
+                environment.setContentLength(content.length);
+
+                final OutputStream os = environment.getOutputStream(0);
+                os.write(content);
+
+            } else {
+                // generate new response
+
+                if ( this.cacheExpires == 0 ) {
+                    return super.processReader( environment );
+                }
+
+                byte[] cachedData;
+
+                this.setMimeTypeForReader(environment);
+                if (this.reader.shouldSetContentLength()) {
+                    final OutputStream os = environment.getOutputStream(this.outputBufferSize);
+
+                    // set the output stream
+                    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    this.reader.setOutputStream(baos);
+
+                    this.reader.generate();
+
+                    cachedData = baos.toByteArray();
+                    environment.setContentLength(cachedData.length);
+                    os.write(cachedData);
+                } else {
+                    final CachingOutputStream os = new CachingOutputStream( environment.getOutputStream(this.outputBufferSize) );
+                    // set the output stream
+                    this.reader.setOutputStream( os );
+                    this.reader.generate();
+
+                    cachedData = os.getContent();
+                }
+
+                //
+                // Now that we have processed the pipeline,
+                // we do the actual caching
+                //
+                if (this.cacheValidity != null) {
+                    cachedResponse = new CachedResponse(this.cacheValidity,
+                                                        cachedData);
+                    cachedResponse.setContentType(environment.getContentType());
+                    this.cache.store(this.cacheKey, cachedResponse);
+                }
+            }
+        } catch (Exception e) {
+            handleException(e);
+        }
+
+        return true;
+    }
+}

Propchange: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/pipeline/impl/ExpiresCachingProcessingPipeline.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/pipeline/impl/ExpiresCachingProcessingPipeline.java
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: cocoon/branches/BRANCH_2_1_X/src/webapp/sitemap.xmap
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/webapp/sitemap.xmap?rev=189614&r1=189613&r2=189614&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/sitemap.xmap (original)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/sitemap.xmap Wed Jun  8 11:07:32 2005
@@ -425,6 +425,13 @@
      <map:pipe name="noncaching" src="org.apache.cocoon.components.pipeline.impl.NonCachingProcessingPipeline">
        <!-- parameter name="outputBufferSize" value="8192"/ -->
      </map:pipe>
+     <!--+
+         | This pipeline implementation caches the complete content for a defined
+         | period of time (expires). The cache key is the current uri.
+         +-->
+     <map:pipe name="expires" src="org.apache.cocoon.components.pipeline.impl.ExpiresCachingProcessingPipeline">
+       <parameter name="cache-expires" value="180"/> <!-- Expires in secondes -->
+     </map:pipe>
    </map:pipes>
 
  </map:components>

Modified: cocoon/branches/BRANCH_2_1_X/status.xml
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/status.xml?rev=189614&r1=189613&r2=189614&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/status.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/status.xml Wed Jun  8 11:07:32 2005
@@ -196,6 +196,9 @@
 
   <changes>
   <release version="@version@" date="@date@">
+    <action dev="CZ" type="update">
+      Move expires pipeline implementation out of the scratchpad.
+    </action>
     <action dev="CZ" type="add">
       Add setter action to set values in the object model, request or session.
     </action>