blob: 9c06b68c651c07c6dfd3e75530e5fd7a07fba261 [file] [log] [blame]
/*
* 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);
}
}