Package org.apache.cocoon.components.pipeline.impl

Source Code of org.apache.cocoon.components.pipeline.impl.DeferredPipelineValidity

/*

============================================================================
                   The Apache Software License, Version 1.1
============================================================================

Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:

1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
(INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software  consists of voluntary contributions made  by many individuals
on  behalf of the Apache Software  Foundation and was  originally created by
Stefano Mazzocchi  <stefano@apache.org>. For more  information on the Apache
Software Foundation, please see <http://www.apache.org/>.

*/
package org.apache.cocoon.components.pipeline.impl;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Date;

import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ConnectionResetException;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.*;
import org.apache.cocoon.components.pipeline.AbstractProcessingPipeline;
import org.apache.cocoon.components.sax.XMLDeserializer;
import org.apache.cocoon.components.sax.XMLSerializer;
import org.apache.cocoon.environment.Environment;
import org.apache.cocoon.transformation.Transformer;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.AggregatedValidity;
import org.apache.excalibur.source.impl.validity.DeferredValidity;

/**
* This is the base class for all caching pipeline implementations.
*
*
* @since 2.1
* @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
* @author <a href="mailto:Michael.Melhem@managesoft.com">Michael Melhem</a>
* @version CVS $Id: AbstractCachingProcessingPipeline.java,v 1.5 2003/05/03 18:34:41 gianugo Exp $
*/
public abstract class AbstractCachingProcessingPipeline
            extends AbstractProcessingPipeline
    implements Disposable {

    /** This is the Cache holding cached responses */
    protected Cache  cache;

    /** The role name of the generator */
    protected String generatorRole;

    /** The role names of the transfomrers */
    protected ArrayList transformerRoles = new ArrayList();

    /** The role name of the serializer */
    protected String serializerRole;

    /** The role name of the reader */
    protected String readerRole;

    /** The deserializer */
    protected XMLDeserializer xmlDeserializer;
    /** The serializer */
    protected XMLSerializer xmlSerializer;

    /** The cached byte stream */
    protected byte[]           cachedResponse;
    /** The timestamp of the cached byte stream */
    protected long             cachedLastModified;

    /** The index indicating the first transformer getting input from the cache */
    protected int firstProcessedTransformerIndex;
    /** Complete response is cached */
    protected boolean completeResponseIsCached;


    /** This key indicates the response that is fetched from the cache */
    protected PipelineCacheKey fromCacheKey;
    /** This key indicates the response that will get into the cache */
    protected PipelineCacheKey toCacheKey;
    /** The source validities used for caching */
    protected SourceValidity[] toCacheSourceValidities;
   
    /** The index indicating to the first transformer which is not cacheable */
    protected int firstNotCacheableTransformerIndex;
    /** Cache complete response */
    protected boolean cacheCompleteResponse;

    protected boolean   generatorIsCacheableProcessingComponent;
    protected boolean   serializerIsCacheableProcessingComponent;
    protected boolean[] transformerIsCacheableProcessingComponent;

    /** Smart caching ? */
    protected boolean doSmartCaching;
    /** Default setting for smart caching */
    protected boolean configuredDoSmartCaching;
   
    /**
     * Abstract methods defined in subclasses
     */
    protected abstract void cacheResults(Environment environment, OutputStream osthrows Exception;
    protected abstract ComponentCacheKey newComponentCacheKey(int type, String role,Serializable key);
    protected abstract void connectCachingPipeline(Environment   environment) throws ProcessingException;

    /**
     * Composable Interface
     */
    public void compose (ComponentManager manager)
    throws ComponentException {
        super.compose(manager);
        this.cache = (Cache)this.manager.lookup(Cache.ROLE);
    }

    /**
     * Parameterizable Interface - Configuration
     */
    public void parameterize(Parameters params) {
        super.parameterize(params);
        this.configuredDoSmartCaching = params.getParameterAsBoolean("smart-caching", true);
    }
   
    /**
     * Setup this component
     */
    public void setup(Parameters params) {
        super.setup(params);
        this.doSmartCaching = params.getParameterAsBoolean("smart-caching",
                                                           this.configuredDoSmartCaching);
    }

    /**
     * Set the generator.
     */
    public void setGenerator (String role, String source, Parameters param, Parameters hintParam)
    throws ProcessingException {
        super.setGenerator(role, source, param, hintParam);
        this.generatorRole = role;
    }

    /**
     * Add a transformer.
     */
    public void addTransformer (String role, String source, Parameters param, Parameters hintParam)
    throws ProcessingException {
        super.addTransformer(role, source, param, hintParam);
        this.transformerRoles.add(role);
    }


    /**
     * Set the serializer.
     */
    public void setSerializer (String role, String source, Parameters param, Parameters hintParam, String mimeType)
    throws ProcessingException {
        super.setSerializer(role, source, param, hintParam, mimeType);
        this.serializerRole = role;
    }

    /**
     * Set the Reader.
     */
    public void setReader (String role, String source, Parameters param, String mimeType)
    throws ProcessingException {
        super.setReader(role, source, param, mimeType);
        this.readerRole = role;
    }

    /**
     * Process the given <code>Environment</code>, producing the output.
     */
    protected boolean processXMLPipeline(Environment environment)
    throws ProcessingException {
        if (this.toCacheKey == null && this.cachedResponse == null) {
            return super.processXMLPipeline( environment );
        } else if (this.cachedResponse != null && this.completeResponseIsCached) {

            // Allow for 304 (not modified) responses in dynamic content
            if (super.checkIfModified( environment, this.cachedLastModified )) {
                return true;
            }

            try {
                final OutputStream outputStream = environment.getOutputStream(0);
                if (this.cachedResponse.length > 0) {
                    environment.setContentLength(this.cachedResponse.length);
                    outputStream.write(this.cachedResponse);
                }
            } catch ( SocketException se ) {
                if (se.getMessage().indexOf("reset") > 0
                        || se.getMessage().indexOf("aborted") > 0) {
                    throw new ConnectionResetException("Connection reset by peer", se);
                } else {
                    throw new ProcessingException("Failed to execute reader pipeline.", se);
                }
            } catch ( Exception e ) {
                if (e instanceof ProcessingException)
                    throw (ProcessingException)e;
                throw new ProcessingException("Error executing reader pipeline.",e);
            }
        } else {

            if (this.getLogger().isDebugEnabled() && this.toCacheKey != null) {
                this.getLogger().debug("processXMLPipeline: caching content for further requests of '" + environment.getURI() + "' using key " + this.toCacheKey);
            }
            try {
                OutputStream os = null;
               
                if ( this.cacheCompleteResponse && this.toCacheKey != null) {
                    os = new CachingOutputStream( environment.getOutputStream(this.outputBufferSize) );
                }
                if ( super.serializer != super.lastConsumer ) {
                    if (os == null) {
                        os = environment.getOutputStream(this.outputBufferSize);
                    }
                    // internal processing
                    if ( this.xmlDeserializer != null ) {
                        this.xmlDeserializer.deserialize(this.cachedResponse);
                    } else {
                        this.generator.generate();
                    }
                } else {
                    if (this.serializer.shouldSetContentLength()) {
                        if (os == null) {
                            os = environment.getOutputStream(0);
                        }
                        // set the output stream
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        this.serializer.setOutputStream(baos);
   
                        // execute the pipeline:
                        if ( this.xmlDeserializer != null ) {
                            this.xmlDeserializer.deserialize(this.cachedResponse);
                        } else {
                            this.generator.generate();
                        }
                        byte[] data = baos.toByteArray();
                        environment.setContentLength(data.length);
                        os.write(data);
                    } else {
                        if (os == null) {
                            os = environment.getOutputStream(this.outputBufferSize);
                        }
                        // set the output stream
                        this.serializer.setOutputStream( os );
                        // execute the pipeline:
                        if ( this.xmlDeserializer != null ) {
                            this.xmlDeserializer.deserialize(this.cachedResponse);
                        } else {
                            this.generator.generate();
                        }
                    }
                }
                //
                // Now that we have processed the pipeline,
                // we do the actual caching
                //
                this.cacheResults(environment,os);

            } catch ( SocketException se ) {
                if (se.getMessage().indexOf("reset") > 0
                        || se.getMessage().indexOf("aborted") > 0) {
                    throw new ConnectionResetException("Connection reset by peer", se);
                } else {
                    throw new ProcessingException("Failed to execute reader pipeline.", se);
                }
            } catch ( ProcessingException e ) {
                throw e;
            } catch ( Exception e ) {
                throw new ProcessingException("Failed to execute pipeline.", e);
            }
            return true;
        }
        return true;
    }

    /**
     * The components of the pipeline are checked if they are Cacheable.
     */
    protected void generateCachingKey(Environment environment)
    throws ProcessingException {

        this.toCacheKey = null;

        Serializable key = null;
        this.generatorIsCacheableProcessingComponent = false;
        this.serializerIsCacheableProcessingComponent = false;
        this.transformerIsCacheableProcessingComponent = new boolean[this.transformers.size()];

        this.firstNotCacheableTransformerIndex = 0;
        this.cacheCompleteResponse = false;

        // first step is to generate the key:
        // All pipeline components starting with the generator
        // are tested if they are either a CacheableProcessingComponent
        // or Cacheable (deprecated). The returned keys are chained together
        // to build a unique key of the request

        // is the generator cacheable?
        if (super.generator instanceof CacheableProcessingComponent) {
            key = ((CacheableProcessingComponent)super.generator).getKey();
            this.generatorIsCacheableProcessingComponent = true;
        } else if (super.generator instanceof Cacheable) {
            key = new Long(((Cacheable)super.generator).generateKey());
        }

        if (key != null) {
            this.toCacheKey = new PipelineCacheKey();
            this.toCacheKey.addKey(this.newComponentCacheKey(ComponentCacheKey.ComponentType_Generator,
                                       this.generatorRole,
                                       key)
                              );

            // now testing transformers
            final int transformerSize = super.transformers.size();
            boolean continueTest = true;

            while (this.firstNotCacheableTransformerIndex < transformerSize
                    && continueTest) {
                final Transformer trans =
                    (Transformer)super.transformers.get(this.firstNotCacheableTransformerIndex);
                key = null;
                if (trans instanceof CacheableProcessingComponent) {
                    key = ((CacheableProcessingComponent)trans).getKey();
                    this.transformerIsCacheableProcessingComponent[this.firstNotCacheableTransformerIndex] = true;
                } else if (trans instanceof Cacheable) {
                    key = new Long(((Cacheable)trans).generateKey());
                }
                if (key != null) {
                    this.toCacheKey.addKey(this.newComponentCacheKey(ComponentCacheKey.ComponentType_Transformer,
                                                 (String)this.transformerRoles.get(this.firstNotCacheableTransformerIndex),
                                                 key));

                    this.firstNotCacheableTransformerIndex++;
                } else {
                    continueTest = false;
                }
            }
            // all transformers are cacheable => pipeline is cacheable
            // test serializer if this is not an internal request
            if (this.firstNotCacheableTransformerIndex == transformerSize
                && super.serializer == this.lastConsumer) {

                key = null;
                if (super.serializer instanceof CacheableProcessingComponent) {
                    key = ((CacheableProcessingComponent)this.serializer).getKey();
                    this.serializerIsCacheableProcessingComponent = true;
                } else if (this.serializer instanceof Cacheable) {
                    key = new Long(((Cacheable)this.serializer).generateKey());
                }
                if (key != null) {
                    this.toCacheKey.addKey(this.newComponentCacheKey(ComponentCacheKey.ComponentType_Serializer,
                                                 (String)this.serializerRole,
                                                 key)
                                                );
                    this.cacheCompleteResponse = true;
                }
            }
        }
    }

    /**
     * Generate validity objects for the new response
     */
    protected void setupValidities()
    throws ProcessingException {

        if (this.toCacheKey != null) {
            // only update validity objects if we cannot use
            // a cached response or when the cached response does
            // cache less than now is cacheable
            if (this.fromCacheKey == null
                || this.fromCacheKey.size() < this.toCacheKey.size()) {

                this.toCacheSourceValidities = new SourceValidity[this.toCacheKey.size()];
                int len = this.toCacheSourceValidities.length;
                int i = 0;
                while (i < len) {
                    final SourceValidity validity = this.getValidityForInternalPipeline(i);

                    if (validity == null) {
                        if (i > 0
                            && (this.fromCacheKey == null || i > this.fromCacheKey.size())) {
                            // shorten key
                            for(int m=i; m < this.toCacheSourceValidities.length; m++) {
                                this.toCacheKey.removeLastKey();
                                if (!this.cacheCompleteResponse) {
                                    this.firstNotCacheableTransformerIndex--;
                                }
                                this.cacheCompleteResponse = false;
                            }
                            SourceValidity[] copy = new SourceValidity[i];
                            System.arraycopy(this.toCacheSourceValidities, 0,
                                             copy, 0, copy.length);
                            this.toCacheSourceValidities = copy;
                            len = this.toCacheSourceValidities.length;
                        } else {
                            // caching is not possible!
                            this.toCacheKey = null;
                            this.toCacheSourceValidities = null;
                            this.cacheCompleteResponse = false;
                            len = 0;
                        }
                    } else {
                        this.toCacheSourceValidities[i] = validity;
                    }
                    i++;
                }
            } else {
                // we don't have to cache
                this.toCacheKey = null;
                this.cacheCompleteResponse = false;
            }
        }
    }

    /**
     * Calculate the key that can be used to get something from the cache, and
     * handle expires properly.
     *
     */
    protected void validatePipeline(Environment environment)
    throws ProcessingException {
        this.completeResponseIsCached = this.cacheCompleteResponse;
        this.fromCacheKey = this.toCacheKey.copy();       
        this.firstProcessedTransformerIndex = this.firstNotCacheableTransformerIndex;
        this.cachedLastModified = 0L;

        boolean finished = false;
       
        while (this.fromCacheKey != null && !finished) {
           
            finished = true;
            final CachedResponse response = this.cache.get( this.fromCacheKey );

            // now test validity
            if (response != null) {
                if (this.getLogger().isDebugEnabled()) {
                    this.getLogger().debug(
                        "Found cached response for '" + environment.getURI() +
                        "' using key: " + this.fromCacheKey
                    );
                }

                boolean responseIsValid = true;
                boolean responseIsUsable = true;

                // See if we have an explicit "expires" setting. If so,
                // and if it's still fresh, we're done.
                Long responseExpires = (Long) response.getExpires();

                if (responseExpires != null) {
                    if (this.getLogger().isDebugEnabled()) {
                       this.getLogger().debug(
                       "Expires time found for " +
                       environment.getURI());
                    }
                    if ( responseExpires.longValue() > System.currentTimeMillis()) {
                        if (this.getLogger().isDebugEnabled()) {
                            this.getLogger().debug(
                                "Expires time still fresh for " +
                                environment.getURI() +
                                ", ignoring all other cache settings. This entry expires on "+
                                new Date(responseExpires.longValue()));
                        }
                        this.cachedResponse = response.getResponse();
                        this.cachedLastModified = response.lastModified;
                        return;
                    } else {
                        if (this.getLogger().isDebugEnabled()) {
                            this.getLogger().debug(
                                "Expires time has expired for " +
                                environment.getURI() +
                                " regenerating content.");
                        }
                       
                        // If an expires parameter was provided, use it. If this parameter is not available
                        // it means that the sitemap was modified, and the old expires value is not valid
                        // anymore.
                        if (expires != 0) {
                    
                            if (this.getLogger().isDebugEnabled())
                                this.getLogger().debug("Refreshing expires informations");
                    
                            response.setExpires(new Long(expires + System.currentTimeMillis()));   
                    
                        } else {
                    
                            if (this.getLogger().isDebugEnabled())
                                this.getLogger().debug("No expires defined anymore for this object, setting it to no expires");
                    
                            response.setExpires(null);
                        }                                  
                    }
                } else {
                    // The response had no expires informations. See if it needs to be set (i.e. because the configuration has changed)
                    if (expires != 0) {
                        if (this.getLogger().isDebugEnabled())
                            this.getLogger().debug("Setting a new expires object for this resource");
                        response.setExpires(new Long(expires + System.currentTimeMillis()));                               
                    }       
                }

                SourceValidity[] fromCacheValidityObjects = response.getValidityObjects();

                int i = 0;
                while (responseIsValid && i < fromCacheValidityObjects.length) {
                    boolean isValid = false;
                    // BH check if validities[i] is null, may happen
                    // if exception was thrown due to malformed content
                    SourceValidity validity = fromCacheValidityObjects[i];
                    int valid = validity != null ? validity.isValid() : -1;
                    if ( valid == 0) { // don't know if valid, make second test
                      
                        validity = this.getValidityForInternalPipeline(i);

                        if (validity != null) {
                            valid = fromCacheValidityObjects[i].isValid( validity );
                            if (valid == 0) {
                                validity = null;
                            } else {
                                isValid = (valid == 1);
                            }
                        }
                    } else {
                        isValid = (valid == 1);
                    }
                    if ( !isValid ) {
                        responseIsValid = false;
                        // update validity
                        if (validity == null) {
                            responseIsUsable = false;
                            if (this.getLogger().isDebugEnabled()) {
                                this.getLogger().debug("validatePipeline: responseIsUsable is false, valid==" + valid + " at index " + i);
                            }
                        } else {
                            if (this.getLogger().isDebugEnabled()) {
                                this.getLogger().debug("validatePipeline: responseIsValid is false due to " + validity);
                            }
                        }
                    } else {
                        i++;
                    }
                }
                if ( responseIsValid ) {
                    if (this.getLogger().isDebugEnabled()) {
                        this.getLogger().debug("validatePipeline: using valid cached content for '" + environment.getURI() + "'.");
                    }
                    // we are valid, ok that's it
                    this.cachedResponse = response.getResponse();
                    this.cachedLastModified = response.lastModified;
                } else {
                    if (this.getLogger().isDebugEnabled()) {
                        this.getLogger().debug("validatePipeline: cached content is invalid for '" + environment.getURI() + "'.");
                    }
                    // we are not valid!

                    if (!responseIsUsable) {
                        // we could not compare, because we got no
                        // validity object, so shorten pipeline key
                        if (i > 0) {
                            int deleteCount = fromCacheValidityObjects.length - i;
                            if (i > 0 && i <= firstNotCacheableTransformerIndex + 1) {
                                this.firstNotCacheableTransformerIndex = i-1;
                            }
                            for(int x=0; x < deleteCount; x++) {
                                this.toCacheKey.removeLastKey();
                            }
                            finished = false;
                        } else {
                            this.toCacheKey = null;
                        }
                        this.cacheCompleteResponse = false;
                    } else {
                        // the entry is invalid, remove it
                        this.cache.remove( this.fromCacheKey );
                    }

                    // try a shorter key
                    if (i > 0) {
                        this.fromCacheKey.removeLastKey();
                        if (!this.completeResponseIsCached) {
                            this.firstProcessedTransformerIndex--;
                        }
                    } else {
                        this.fromCacheKey = null;
                    }
                    finished = false;
                    this.completeResponseIsCached = false;
                }
            } else {
               
                // no cached response found
                if (this.getLogger().isDebugEnabled()) {
                    this.getLogger().debug(
                        "Cached response not found for '" + environment.getURI() +
                        "' using key: " this.fromCacheKey
                    );
                }
              
                if (!this.doSmartCaching) {
                    // try a shorter key
                    if (this.fromCacheKey.size() > 1) {
                        this.fromCacheKey.removeLastKey();
                        if (!this.completeResponseIsCached) {
                            this.firstProcessedTransformerIndex--;
                        }
                        finished = false;
                    } else {
                        this.fromCacheKey = null;
                    }
                } else {
                    // stop on longest key for smart caching
                    this.fromCacheKey = null;
                }
                this.completeResponseIsCached = false;
            }
        }

    }

    /**
     * Setup the evenet pipeline.
     * The components of the pipeline are checked if they are
     * Cacheable.
     */
    protected void setupPipeline(Environment environment)
    throws ProcessingException {
        super.setupPipeline( environment );

        // generate the key to fill the cache
        this.generateCachingKey(environment);

        // test the cache for a valid response
        if (this.toCacheKey != null) {
            this.validatePipeline(environment);
        }

        this.setupValidities();       
    }

    /**
     * Connect the pipeline.
     */
    protected void connectPipeline(Environment   environment)
    throws ProcessingException {
        if ( this.toCacheKey == null && this.cachedResponse == null) {
            super.connectPipeline( environment );
            return;
        } else if (this.completeResponseIsCached) {
            // do nothing
            return;
        } else {
            this.connectCachingPipeline(environment);
        }
    }

    /** Process the pipeline using a reader.
     * @throws ProcessingException if an error occurs
     */
    protected boolean processReader(Environment  environment)
    throws ProcessingException {
        try {
            boolean usedCache = false;
            OutputStream outputStream = null;
            SourceValidity readerValidity = null;
            PipelineCacheKey pcKey = null;

            // test if reader is cacheable
            Serializable readerKey = null;
            boolean isCacheableProcessingComponent = false;
            if (super.reader instanceof CacheableProcessingComponent) {
                readerKey = ((CacheableProcessingComponent)super.reader).getKey();
                isCacheableProcessingComponent = true;
            } else if (super.reader instanceof Cacheable) {
                readerKey = new Long(((Cacheable)super.reader).generateKey());
            }

            if ( readerKey != null) {
                // response is cacheable, build the key
                pcKey = new PipelineCacheKey();
                pcKey.addKey(new ComponentCacheKey(ComponentCacheKey.ComponentType_Reader,
                                                   this.readerRole,
                                                   readerKey)
                            );

                // now we have the key to get the cached object
                CachedResponse cachedObject = (CachedResponse)this.cache.get( pcKey );

                if (cachedObject != null) {
                    if (this.getLogger().isDebugEnabled()) {
                        this.getLogger().debug(
                            "Found cached response for '" + environment.getURI() +
                            "' using key: " + pcKey
                        );
                    }
                    SourceValidity[] validities = cachedObject.getValidityObjects();
                    if (validities == null || validities.length != 1) {
                        throw new ProcessingException("Cached response is not correct.");
                    }
                    SourceValidity cachedValidity = validities[0];
                    int result = cachedValidity.isValid();
                    boolean valid = false;
                    if ( result == 0 ) {
                        // get reader validity and compare
                        if (isCacheableProcessingComponent) {
                            readerValidity = ((CacheableProcessingComponent)super.reader).getValidity();
                        } else {
                            CacheValidity cv = ((Cacheable)super.reader).generateValidity();
                            if ( cv != null ) {
                                readerValidity = CacheValidityToSourceValidity.createValidity( cv );
                            }
                        }
                        if (readerValidity != null) {
                            result = cachedValidity.isValid(readerValidity);
                            if ( result == 0 ) {
                                readerValidity = null;
                            } else {
                                valid = (result == 1);
                            }
                        }
                    } else {
                        valid = (result > 0);
                    }

                    if (valid) {
                        if (this.getLogger().isDebugEnabled()) {
                            this.getLogger().debug("processReader: using valid cached content for '" + environment.getURI() + "'.");
                        }
                        byte[] response = cachedObject.getResponse();
                        if (response.length > 0) {
                            usedCache = true;
                            outputStream = environment.getOutputStream(0);
                            environment.setContentLength(response.length);
                            outputStream.write(response);
                        }
                    } else {
                        if (this.getLogger().isDebugEnabled()) {
                            this.getLogger().debug("processReader: cached content is invalid for '" + environment.getURI() + "'.");
                        }
                        // remove invalid cached object
                        this.cache.remove(pcKey);
                    }
                }
            }

            if (!usedCache) {

                if ( pcKey != null ) {
                    if (this.getLogger().isDebugEnabled()) {
                        this.getLogger().debug("processReader: caching content for further requests of '" + environment.getURI() + "'.");
                    }
                    if (readerValidity == null) {
                        if (isCacheableProcessingComponent) {
                            readerValidity = ((CacheableProcessingComponent)super.reader).getValidity();
                        } else {
                            CacheValidity cv = ((Cacheable)super.reader).generateValidity();
                            if ( cv != null ) {
                                readerValidity = CacheValidityToSourceValidity.createValidity( cv );
                            }
                        }
                    }
                    if (readerValidity != null) {
                        outputStream = environment.getOutputStream(this.outputBufferSize);
                        outputStream = new CachingOutputStream(outputStream);
                    } else {
                        pcKey = null;
                    }
                }


                if (this.reader.shouldSetContentLength()) {
                    ByteArrayOutputStream os = new ByteArrayOutputStream();
                    this.reader.setOutputStream(os);
                    this.reader.generate();
                    byte[] data = os.toByteArray();
                    environment.setContentLength(data.length);
                    if (outputStream == null) {
                        outputStream = environment.getOutputStream(0);
                    }
                    environment.getOutputStream(0).write(data);
                } else {
                    if (outputStream == null) {
                        outputStream = environment.getOutputStream(this.outputBufferSize);
                    }
                    this.reader.setOutputStream(outputStream);
                    this.reader.generate();
                }

                // store the response
                if (pcKey != null) {
                    this.cache.store(
                        environment.getObjectModel(),
                        pcKey,
                        new CachedResponse( new SourceValidity[] {readerValidity},
                                            ((CachingOutputStream)outputStream).getContent())
                    );
                }
            }
        } catch ( SocketException se ) {
            if (se.getMessage().indexOf("reset") > 0
                    || se.getMessage().indexOf("aborted") > 0) {
                throw new ConnectionResetException("Connection reset by peer", se);
            } else {
                throw new ProcessingException("Failed to execute pipeline.", se);
            }
        } catch ( ProcessingException e ) {
            throw e;
        } catch ( Exception e ) {
            throw new ProcessingException("Failed to execute pipeline.", e);
        }

        return true;
    }


    /**
     * 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() {
        if ( null != this.toCacheKey
             && !this.completeResponseIsCached
             && this.firstNotCacheableTransformerIndex == super.transformers.size()) {
            AggregatedValidity validity = new AggregatedValidity();
            for(int i=0; i < this.toCacheKey.size(); i++) {
                validity.add(this.getValidityForInternalPipeline(i));
                //validity.add(new DeferredPipelineValidity(this, i));
            }
            return validity;
        }
        return null;
    }

    /**
     *
     * @see org.apache.cocoon.components.pipeline.ProcessingPipeline#getValidityForInternalPipeline(int)
     */
    SourceValidity getValidityForInternalPipeline(int index) {
        final SourceValidity validity;

        // if debugging try to tell why something is not cacheable
        final boolean debug = this.getLogger().isDebugEnabled();
        String msg = null;
        if(debug) msg = "getValidityForInternalPipeline(" + index + "): ";

        if (index == 0) {
            // test generator
            if (this.generatorIsCacheableProcessingComponent) {
                validity = ((CacheableProcessingComponent)super.generator).getValidity();
                if(debug) msg += "generator: using getValidity";
            } else {
                validity = CacheValidityToSourceValidity.createValidity(((Cacheable)super.generator).generateValidity());
                if(debug) msg += "generator: using generateValidity";
            }
        } else if (index <= firstNotCacheableTransformerIndex) {
            // test transformer
            final Transformer trans = (Transformer)super.transformers.get(index-1);
            if (this.transformerIsCacheableProcessingComponent[index-1]) {
                validity = ((CacheableProcessingComponent)trans).getValidity();
                if(debug) msg += "transformer: using getValidity";
            } else {
                validity = CacheValidityToSourceValidity.createValidity(((Cacheable)trans).generateValidity());
                if(debug) msg += "transformer: using generateValidity";
            }
        } else {
            // test serializer
            if (this.serializerIsCacheableProcessingComponent) {
                validity = ((CacheableProcessingComponent)super.serializer).getValidity();
                if(debug) msg += "serializer: using getValidity";
            } else {
                validity = CacheValidityToSourceValidity.createValidity(((Cacheable)super.serializer).generateValidity());
                if(debug) msg += "serializer: using generateValidity";
            }
        }

        if(debug) {
            msg += ", validity==" + validity;
            this.getLogger().debug(msg);
        }
        return validity;
    }
   
    /**
     * Recyclable Interface
     */
    public void recycle() {
        super.recycle();

        this.manager.release( this.xmlDeserializer );
        this.xmlDeserializer = null;

        this.manager.release( this.xmlSerializer );
        this.xmlSerializer = null;

        this.generatorRole = null;
        this.transformerRoles.clear();
        this.serializerRole = null;
        this.readerRole = null;

        this.fromCacheKey = null;
        this.cachedResponse = null;
       
        this.transformerIsCacheableProcessingComponent = null;
        this.toCacheKey = null;
        this.toCacheSourceValidities = null;
    }

    /**
     * Disposable Interface
     */
    public void dispose() {
        if ( null != this.manager ) {
            this.manager.release(this.cache);
        }
        this.cache = null;
        this.manager = null;
    }
}

final class DeferredPipelineValidity implements DeferredValidity {

    private final AbstractCachingProcessingPipeline pipeline;
    private final int index;
   
    public DeferredPipelineValidity(AbstractCachingProcessingPipeline pipeline, int index) {
        this.pipeline = pipeline;
        this.index = index;
    }
   
    /**
     * @see org.apache.excalibur.source.impl.validity.DeferredValidity#getValidity()
     */
    public SourceValidity getValidity() {
        return pipeline.getValidityForInternalPipeline(this.index);
    }
}
TOP

Related Classes of org.apache.cocoon.components.pipeline.impl.DeferredPipelineValidity

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.