/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.components.pipeline.impl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; 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.CacheValidity; import org.apache.cocoon.caching.CacheValidityToSourceValidity; import org.apache.cocoon.caching.Cacheable; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.cocoon.caching.CachedResponse; import org.apache.cocoon.caching.CachingOutputStream; import org.apache.cocoon.caching.ComponentCacheKey; import org.apache.cocoon.caching.PipelineCacheKey; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.commandline.AbstractCommandLineEnvironment; import org.apache.cocoon.environment.http.HttpEnvironment; import org.apache.cocoon.transformation.Transformer; import org.apache.cocoon.util.HashUtil; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.source.impl.validity.AggregatedValidity; import org.apache.excalibur.source.impl.validity.DeferredValidity; import org.apache.excalibur.source.impl.validity.NOPValidity; import org.apache.excalibur.store.Store; /** * This is the base class for all caching pipeline implementations * that check different pipeline components. * * @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 $Id$ */ public abstract class AbstractCachingProcessingPipeline extends BaseCachingProcessingPipeline { public static final String PIPELOCK_PREFIX = "PIPELOCK:"; /** 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 cached response */ protected CachedResponse cachedResponse; /** 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; /** Store for pipeline locks (optional) */ protected Store transientStore; /** Maximum wait time on a pipeline lock */ protected long lockTimeout; /** Abstract method defined in subclasses */ protected abstract void cacheResults(Environment environment, OutputStream os) throws Exception; /** Abstract method defined in subclasses */ protected abstract ComponentCacheKey newComponentCacheKey(int type, String role, Serializable key); /** Abstract method defined in subclasses */ protected abstract void connectCachingPipeline(Environment environment) throws ProcessingException; /** * Parameterizable Interface - Configuration */ public void parameterize(Parameters params) throws ParameterException { super.parameterize(params); // Is pipeline locking enabled? if (params.getParameterAsBoolean("locking", true)) { lockTimeout = params.getParameterAsLong("locking-timeout", 7000); final String storeRole = params.getParameter("store-role", Store.TRANSIENT_STORE); try { transientStore = (Store) manager.lookup(storeRole); } catch (ComponentException e) { if (getLogger().isDebugEnabled()) { getLogger().debug("Transient store '" + storeRole + "' not available. Pipeline locking will not work.", e); } } } } /** * 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; } protected boolean waitForLock(Environment env, Object key) { if (transientStore != null) { final String lockKey = PIPELOCK_PREFIX + key; // Get a lock object from the store Object lock; synchronized (transientStore) { lock = transientStore.get(lockKey); } // Avoid deadlock with self (see JIRA COCOON-1985). Object current = env.getObjectModel().get(HttpEnvironment.HTTP_REQUEST_OBJECT); if (current==null){ current = env.getObjectModel().get(AbstractCommandLineEnvironment.CLI_REQUEST_ID); } if (lock != null && lock != current) { if (getLogger().isDebugEnabled()) { getLogger().debug("Waiting on Lock '" + lockKey + "'"); } try { synchronized (lock) { // Close synchronization gap between retrieving the lock and // waiting on it. If lock is not in store anymore, don't wait // on it. synchronized (transientStore) { if (!transientStore.containsKey(lockKey)) { return false; } } lock.wait(lockTimeout); } if (getLogger().isDebugEnabled()) { getLogger().debug("Notified on Lock '" + lockKey + "'"); } } catch (InterruptedException e) { /* ignored */ } return false; } } return true; } /** * makes the lock (instantiates a new object and puts it into the store) */ protected void generateLock(Environment env, Object key) { if (transientStore != null && key != null) { final String lockKey = PIPELOCK_PREFIX + key; if (getLogger().isDebugEnabled()) { getLogger().debug("Adding Lock '" + lockKey + "'"); } synchronized (transientStore) { if (transientStore.containsKey(lockKey)) { if (getLogger().isDebugEnabled()) { getLogger().debug("Lock EXISTS: '" + lockKey + "'"); } } else { Object lock = env.getObjectModel().get(HttpEnvironment.HTTP_REQUEST_OBJECT); if (lock == null){ lock = env.getObjectModel().get(AbstractCommandLineEnvironment.CLI_REQUEST_ID); } try { transientStore.store(lockKey, lock); } catch (IOException e) { /* should not happen */ } } } } } /** * releases the lock (notifies it and removes it from the store) */ protected void releaseLock(Object key) { if (transientStore != null && key != null) { final String lockKey = PIPELOCK_PREFIX + key; if (getLogger().isDebugEnabled()) { getLogger().debug("Releasing Lock '" + lockKey + "'"); } Object lock = null; synchronized (transientStore) { if (!transientStore.containsKey(lockKey)) { if (getLogger().isDebugEnabled()) { getLogger().debug("Lock MISSING: '" + lockKey + "'"); } } else { lock = transientStore.get(lockKey); transientStore.remove(lockKey); } } if (lock != null) { // Notify everybody who's waiting synchronized (lock) { lock.notifyAll(); } } } } /** * 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); } if (this.cachedResponse != null && this.completeResponseIsCached) { // Allow for 304 (not modified) responses in dynamic content if (checkIfModified(environment, this.cachedResponse.getLastModified())) { return true; } // Set mime-type if (this.cachedResponse.getContentType() != null) { environment.setContentType(this.cachedResponse.getContentType()); } else { setMimeTypeForSerializer(environment); } // Write response out try { final OutputStream outputStream = environment.getOutputStream(0); final byte[] content = this.cachedResponse.getResponse(); if (content.length > 0) { environment.setContentLength(content.length); outputStream.write(content); } } catch (Exception e) { handleException(e); } } else { setMimeTypeForSerializer(environment); if (getLogger().isDebugEnabled() && this.toCacheKey != null) { getLogger().debug("processXMLPipeline: caching content for further" + " requests of '" + environment.getURI() + "' using key " + this.toCacheKey); } generateLock(environment, 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.getResponse()); } 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.getResponse()); } else { this.generator.generate(); } environment.setContentLength(baos.size()); baos.writeTo(os); } 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.getResponse()); } else { this.generator.generate(); } } } // // Now that we have processed the pipeline, // we do the actual caching // cacheResults(environment,os); } catch (Exception e) { handleException(e); } finally { releaseLock(this.toCacheKey); } 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; 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? Serializable key = getGeneratorKey(); 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 = getTransformerKey(trans); 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 = getSerializerKey(); if (key != null) { this.toCacheKey.addKey( this.newComponentCacheKey( ComponentCacheKey.ComponentType_Serializer, 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 = 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; 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 (getLogger().isDebugEnabled()) { 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 = response.getExpires(); if (responseExpires != null) { if (getLogger().isDebugEnabled()) { getLogger().debug("Expires time found for " + environment.getURI()); } if (responseExpires.longValue() > System.currentTimeMillis()) { if (getLogger().isDebugEnabled()) { 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; return; } else { if (getLogger().isDebugEnabled()) { 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 ? SourceValidity.INVALID : validity.isValid(); if (valid == SourceValidity.UNKNOWN) { // Don't know if valid, make second test validity = getValidityForInternalPipeline(i); if (validity != null) { valid = fromCacheValidityObjects[i].isValid(validity); if (valid == SourceValidity.UNKNOWN) { validity = null; } else { isValid = (valid == SourceValidity.VALID); } } } else { isValid = (valid == SourceValidity.VALID); } if (!isValid) { responseIsValid = false; // update validity if (validity == null) { responseIsUsable = false; if (getLogger().isDebugEnabled()) { getLogger().debug("validatePipeline: responseIsUsable is false, valid=" + valid + " at index " + i); } } else { if (getLogger().isDebugEnabled()) { getLogger().debug("validatePipeline: responseIsValid is false due to " + validity); } } } else { i++; } } if (responseIsValid) { if (getLogger().isDebugEnabled()) { getLogger().debug("validatePipeline: using valid cached content for '" + environment.getURI() + "'."); } // we are valid, ok that's it this.cachedResponse = response; this.toCacheSourceValidities = fromCacheValidityObjects; } else { if (getLogger().isDebugEnabled()) { 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 { // check if there might be one being generated if (!waitForLock(environment, this.fromCacheKey)) { finished = false; continue; } // no cached response found if (this.getLogger().isDebugEnabled()) { this.getLogger().debug( "Cached response not found for '" + environment.getURI() + "' using key: " + this.fromCacheKey ); } finished = setupFromCacheKey(); this.completeResponseIsCached = false; } } } boolean setupFromCacheKey() { // stop on longest key for smart caching this.fromCacheKey = null; return true; } /** * 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 generateCachingKey(environment); // Test the cache for a valid response if (this.toCacheKey != null) { validatePipeline(environment); } 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()); } boolean finished = false; if (readerKey != null) { // response is cacheable, build the key pcKey = new PipelineCacheKey(); pcKey.addKey(new ComponentCacheKey(ComponentCacheKey.ComponentType_Reader, this.readerRole, readerKey) ); while(!finished) { finished = true; // now we have the key to get the cached object CachedResponse cachedObject = this.cache.get(pcKey); if (cachedObject != null) { if (getLogger().isDebugEnabled()) { getLogger().debug("Found cached response for '" + environment.getURI() + "' using key: " + pcKey); } SourceValidity[] validities = cachedObject.getValidityObjects(); if (validities == null || validities.length != 1) { // to avoid getting here again and again, we delete it this.cache.remove(pcKey); if (getLogger().isDebugEnabled()) { getLogger().debug("Cached response for '" + environment.getURI() + "' using key: " + pcKey + " is invalid."); } this.cachedResponse = null; } else { SourceValidity cachedValidity = validities[0]; boolean isValid = false; int valid = cachedValidity.isValid(); if (valid == SourceValidity.UNKNOWN) { // 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) { valid = cachedValidity.isValid(readerValidity); if (valid == SourceValidity.UNKNOWN) { readerValidity = null; } else { isValid = (valid == SourceValidity.VALID); } } } else { isValid = (valid == SourceValidity.VALID); } if (isValid) { if (getLogger().isDebugEnabled()) { getLogger().debug("processReader: using valid cached content for '" + environment.getURI() + "'."); } byte[] response = cachedObject.getResponse(); if (response.length > 0) { usedCache = true; if (cachedObject.getContentType() != null) { environment.setContentType(cachedObject.getContentType()); } else { setMimeTypeForReader(environment); } outputStream = environment.getOutputStream(0); environment.setContentLength(response.length); outputStream.write(response); } } else { if (getLogger().isDebugEnabled()) { getLogger().debug("processReader: cached content is invalid for '" + environment.getURI() + "'."); } // remove invalid cached object this.cache.remove(pcKey); } } } else { // check if something is being generated right now if (!waitForLock(environment, pcKey)) { finished = false; continue; } } } } if (!usedCache) { // make sure lock will be released try { if (pcKey != null) { if (getLogger().isDebugEnabled()) { getLogger().debug("processReader: caching content for further requests of '" + environment.getURI() + "'."); } generateLock(environment, pcKey); 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); } } setMimeTypeForReader(environment); if (this.reader.shouldSetContentLength()) { ByteArrayOutputStream os = new ByteArrayOutputStream(); this.reader.setOutputStream(os); this.reader.generate(); environment.setContentLength(os.size()); if (outputStream == null) { outputStream = environment.getOutputStream(0); } os.writeTo(outputStream); } else { if (outputStream == null) { outputStream = environment.getOutputStream(this.outputBufferSize); } this.reader.setOutputStream(outputStream); this.reader.generate(); } // store the response if (pcKey != null && readerValidity != null) { final CachedResponse res = new CachedResponse(new SourceValidity[] {readerValidity}, ((CachingOutputStream)outputStream).getContent()); res.setContentType(environment.getContentType()); this.cache.store(pcKey, res); } } finally { if (pcKey != null) { releaseLock(pcKey); } } } } catch (Exception e) { handleException(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 (isInternalError()) { return null; } if (this.cachedResponse != null) { if (!this.cacheCompleteResponse && this.firstNotCacheableTransformerIndex < super.transformers.size()) { // Cache contains only partial pipeline. return null; } if (this.toCacheSourceValidities != null) { // This means that the pipeline is valid based on the validities // of the individual components final AggregatedValidity validity = new AggregatedValidity(); for (int i=0; i < this.toCacheSourceValidities.length; i++) { validity.add(this.toCacheSourceValidities[i]); } return validity; } // This means that the pipeline is valid because it has not yet expired return NOPValidity.SHARED_INSTANCE; } else { int vals = 0; if (null != this.toCacheKey && !this.cacheCompleteResponse && this.firstNotCacheableTransformerIndex == super.transformers.size()) { vals = this.toCacheKey.size(); } else if (null != this.fromCacheKey && !this.completeResponseIsCached && this.firstProcessedTransformerIndex == super.transformers.size()) { vals = this.fromCacheKey.size(); } if (vals > 0) { final AggregatedValidity validity = new AggregatedValidity(); for (int i = 0; i < vals; i++) { validity.add(getValidityForInternalPipeline(i)); } return validity; } return null; } } /** * Get generator cache key (null if not cacheable) */ private Serializable getGeneratorKey() { Serializable key = null; 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()); } return key; } /** * Get transformer cache key (null if not cacheable) */ private Serializable getTransformerKey(final Transformer transformer) { Serializable key = null; if (transformer instanceof CacheableProcessingComponent) { key = ((CacheableProcessingComponent)transformer).getKey(); this.transformerIsCacheableProcessingComponent[this.firstNotCacheableTransformerIndex] = true; } else if (transformer instanceof Cacheable) { key = new Long(((Cacheable)transformer).generateKey()); } return key; } /** * Get serializer cache key (null if not cacheable) */ private Serializable getSerializerKey() { Serializable 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()); } return key; } /* (non-Javadoc) * @see org.apache.cocoon.components.pipeline.ProcessingPipeline#getKeyForEventPipeline() */ public String getKeyForEventPipeline() { if (isInternalError()) { return null; } if (null != this.toCacheKey && !this.cacheCompleteResponse && this.firstNotCacheableTransformerIndex == super.transformers.size()) { return String.valueOf(HashUtil.hash(this.toCacheKey.toString())); } if (null != this.fromCacheKey && !this.completeResponseIsCached && this.firstProcessedTransformerIndex == super.transformers.size()) { return String.valueOf(HashUtil.hash(this.fromCacheKey.toString())); } return null; } 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() { 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; super.recycle(); } } 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); } }