blob: 3c854c94f2ba56e9a1046ede951aab7491af9292 [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 org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CachedResponse;
import org.apache.cocoon.caching.CachingOutputStream;
import org.apache.cocoon.caching.ComponentCacheKey;
import org.apache.cocoon.components.sax.XMLDeserializer;
import org.apache.cocoon.components.sax.XMLSerializer;
import org.apache.cocoon.components.sax.XMLTeePipe;
import org.apache.cocoon.environment.Environment;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLProducer;
import org.apache.commons.lang.BooleanUtils;
import org.apache.excalibur.source.SourceValidity;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
/**
* The caching-point pipeline implements an extended caching algorithm which is
* of particular benefit for use with those pipelines that utilise cocoon-views
* and/or provide drill-down functionality.
*
* @since 2.1
* @author <a href="mailto:Michael.Melhem@managesoft.com">Michael Melhem</a>
* @version CVS $Id$
*/
public class CachingPointProcessingPipeline
extends AbstractCachingProcessingPipeline {
protected ArrayList isCachePoint = new ArrayList();
protected ArrayList xmlSerializerArray = new ArrayList();
protected boolean nextIsCachePoint = false;
protected String autoCachingPointSwitch;
protected boolean autoCachingPoint = true;
/**
* The <code>CachingPointProcessingPipeline</code> is configurable.
*
* <p>The autoCachingPoint algorithm (if enabled) will automatically cache
* common elements of the pipeline currently being processed - as well as the
* entire cacheable pipeline according to the "longest cacheable key"
* algorithm. This feature is especially useful for pipelines that branch at
* some point (this is the case with <tt>&lt;map:select&gt;</tt> or
* <tt>&lt;map:act&gt;</tt>).
*
* <p>The option <tt>autoCachingPoint</tt> can be switched on/off in the
* sitemap.xmap (on by default). For linear pipelines, one can switch "Off"
* <tt>autoCachingPoint</tt> and use attribute
* <tt>pipeline-hints="caching-point"</tt> to manually indicate that certain
* pipeline components (eg on <tt>&lt;map:generator&gt;</tt>) should be
* considered as cache points. Both options (automatic at branch points and
* manual with pipeline hints) can coexist in the same pipeline.</p>
*
* <p>Works by requesting the pipeline processor to try shorter keys when
* looking for a cached content for the pipeline.</p>
*/
public void parameterize(Parameters config) throws ParameterException {
super.parameterize(config);
this.autoCachingPointSwitch = config.getParameter("autoCachingPoint", null);
if (this.getLogger().isDebugEnabled()) {
getLogger().debug("Auto caching-point is set to = '" + this.autoCachingPointSwitch + "'");
}
// Default is that auto caching-point is on
if (this.autoCachingPointSwitch == null) {
this.autoCachingPoint=true;
} else {
this.autoCachingPoint = BooleanUtils.toBoolean(this.autoCachingPointSwitch);
}
}
/**
* Set the generator.
*/
public void setGenerator (String role, String source, Parameters param, Parameters hintParam)
throws ProcessingException {
super.setGenerator(role, source, param, hintParam);
// check the hint param for a "caching-point" hint
String pipelinehint = null;
try {
pipelinehint = hintParam.getParameter("caching-point", null);
if (this.getLogger().isDebugEnabled()) {
getLogger().debug("generator caching-point pipeline-hint is set to: " + pipelinehint);
}
} catch (Exception ex) {
if (this.getLogger().isWarnEnabled()) {
getLogger().warn("caching-point hint Exception, pipeline-hint ignored: " + ex);
}
}
// if this generator is manually set to "caching-point" (via pipeline-hint)
// then ensure the next component is caching.
this.nextIsCachePoint = BooleanUtils.toBoolean(pipelinehint);
}
/**
* Add a transformer.
*/
public void addTransformer (String role, String source, Parameters param, Parameters hintParam)
throws ProcessingException {
super.addTransformer(role, source, param, hintParam);
// check the hint param for a "caching-point" hint
String pipelinehint = null;
try {
pipelinehint = hintParam.getParameter("caching-point", null);
if (this.getLogger().isDebugEnabled()) {
getLogger().debug("transformer caching-point pipeline-hint is set to: " + pipelinehint);
}
} catch (Exception ex) {
if (this.getLogger().isWarnEnabled()) {
getLogger().warn("caching-point hint Exception, pipeline-hint ignored: " + ex);
}
}
// add caching point flag
// default value is false
this.isCachePoint.add(BooleanUtils.toBooleanObject(this.nextIsCachePoint));
// if this transformer is manually set to "caching-point" (via pipeline-hint)
// then ensure the next component is caching.
this.nextIsCachePoint = BooleanUtils.toBoolean(pipelinehint);
}
/**
* Determine if the given branch-point is a caching-point. This is called
* by sitemap components when using cocoon views; it is also called by
* parent nodes (mainly selectors and actions).
*
* Please Note: this method is used by auto caching-point
* and is of no consequence when auto caching-point is switched off
*
* @see org.apache.cocoon.components.treeprocessor.SimpleParentProcessingNode
*/
public void informBranchPoint() {
if (this.autoCachingPoint && this.generator != null) {
this.nextIsCachePoint = true;
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug("Informed Pipeline of branch point");
}
}
}
/**
* Cache longest cacheable path plus cache points.
*/
protected void cacheResults(Environment environment, OutputStream os) throws Exception {
if (this.toCacheKey != null) {
if (this.cacheCompleteResponse) {
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug("Cached: caching complete response; pSisze"
+ this.toCacheKey.size() + " Key " + this.toCacheKey);
}
CachedResponse response = new CachedResponse(this.toCacheSourceValidities,
((CachingOutputStream)os).getContent());
response.setContentType(environment.getContentType());
this.cache.store(this.toCacheKey.copy(), response);
//
// Scan back along the pipelineCacheKey for
// for any cachepoint(s)
//
this.toCacheKey.removeUntilCachePoint();
//
// adjust the validities object
// to reflect the new length of the pipeline cache key.
//
// REVISIT: Is it enough to simply reduce the length of the validities array?
//
if (this.toCacheKey.size() > 0) {
SourceValidity[] copy = new SourceValidity[this.toCacheKey.size()];
System.arraycopy(this.toCacheSourceValidities, 0, copy, 0, copy.length);
this.toCacheSourceValidities = copy;
}
}
if (this.toCacheKey.size() > 0) {
ListIterator itt = this.xmlSerializerArray.listIterator(this.xmlSerializerArray.size());
while (itt.hasPrevious()) {
XMLSerializer serializer = (XMLSerializer) itt.previous();
CachedResponse response = new CachedResponse(this.toCacheSourceValidities,
(byte[])serializer.getSAXFragment());
this.cache.store(this.toCacheKey.copy(), response);
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug("Caching results for the following key: "
+ this.toCacheKey);
}
//
// Check for further cachepoints
//
toCacheKey.removeUntilCachePoint();
if (this.toCacheKey.size()==0)
// no cachePoint found in key
break;
//
// re-calculate validities array
//
SourceValidity[] copy = new SourceValidity[this.toCacheKey.size()];
System.arraycopy(this.toCacheSourceValidities, 0, copy, 0, copy.length);
this.toCacheSourceValidities = copy;
} //end serializer loop
}
}
}
/**
* Create a new ComponentCachekey
* ComponentCacheKeys can be flagged as cachepoints
*/
protected ComponentCacheKey newComponentCacheKey(int type, String role,Serializable key) {
boolean cachePoint = false;
if (type == ComponentCacheKey.ComponentType_Transformer) {
cachePoint =
((Boolean)this.isCachePoint.get(this.firstNotCacheableTransformerIndex)).booleanValue();
} else if (type == ComponentCacheKey.ComponentType_Serializer) {
cachePoint = this.nextIsCachePoint;
}
return new ComponentCacheKey(type, role, key, cachePoint);
}
/**
* Connect the caching point pipeline.
*/
protected void connectCachingPipeline(Environment environment)
throws ProcessingException {
try {
XMLSerializer localXMLSerializer = null;
XMLSerializer cachePointXMLSerializer = null;
if (!this.cacheCompleteResponse) {
this.xmlSerializer = (XMLSerializer)this.manager.lookup( XMLSerializer.ROLE );
localXMLSerializer = this.xmlSerializer;
}
if (this.cachedResponse == null) {
XMLProducer prev = super.generator;
XMLConsumer next;
int cacheableTransformerCount = this.firstNotCacheableTransformerIndex;
int currentTransformerIndex = 0; //start with the first transformer
Iterator itt = this.transformers.iterator();
while (itt.hasNext()) {
next = (XMLConsumer) itt.next();
// if we have cacheable transformers,
// check the tranformers for cachepoints
if (cacheableTransformerCount > 0) {
if ((this.isCachePoint.get(currentTransformerIndex) != null) &&
((Boolean)this.isCachePoint.get(currentTransformerIndex)).booleanValue()) {
cachePointXMLSerializer = ((XMLSerializer)
this.manager.lookup( XMLSerializer.ROLE ));
next = new XMLTeePipe(next, cachePointXMLSerializer);
this.xmlSerializerArray.add(cachePointXMLSerializer);
}
}
// Serializer is not cacheable,
// but we have the longest cacheable key. Do default longest key caching
if (localXMLSerializer != null) {
if (cacheableTransformerCount == 0) {
next = new XMLTeePipe(next, localXMLSerializer);
this.xmlSerializerArray.add(localXMLSerializer);
localXMLSerializer = null;
} else {
cacheableTransformerCount--;
}
}
this.connect(environment, prev, next);
prev = (XMLProducer) next;
currentTransformerIndex++;
}
next = super.lastConsumer;
// if the serializer is not cacheable, but all the transformers are:
// (this is default longest key caching)
if (localXMLSerializer != null) {
next = new XMLTeePipe(next, localXMLSerializer);
this.xmlSerializerArray.add(localXMLSerializer);
localXMLSerializer = null;
}
// else if the serializer is cacheable and has cocoon views
else if ((currentTransformerIndex == this.firstNotCacheableTransformerIndex) &&
this.nextIsCachePoint) {
cachePointXMLSerializer = ((XMLSerializer)this.manager.lookup( XMLSerializer.ROLE ));
next = new XMLTeePipe(next, cachePointXMLSerializer);
this.xmlSerializerArray.add(cachePointXMLSerializer);
}
this.connect(environment, prev, next);
} else {
// Here the first part of the pipeline has been retrived from cache
// we now check if any part of the rest of the pipeline can be cached
this.xmlDeserializer = (XMLDeserializer)this.manager.lookup(XMLDeserializer.ROLE);
// connect the pipeline:
XMLProducer prev = xmlDeserializer;
XMLConsumer next;
int cacheableTransformerCount = 0;
Iterator itt = this.transformers.iterator();
while (itt.hasNext()) {
next = (XMLConsumer) itt.next();
if (cacheableTransformerCount >= this.firstProcessedTransformerIndex) {
// if we have cacheable transformers left,
// then check the tranformers for cachepoints
if (cacheableTransformerCount < this.firstNotCacheableTransformerIndex) {
if (!(prev instanceof XMLDeserializer) &&
(this.isCachePoint.get(cacheableTransformerCount) != null) &&
((Boolean)this.isCachePoint.get(cacheableTransformerCount)).booleanValue()) {
cachePointXMLSerializer = ((XMLSerializer)this.manager.lookup( XMLSerializer.ROLE ));
next = new XMLTeePipe(next, cachePointXMLSerializer);
this.xmlSerializerArray.add(cachePointXMLSerializer);
}
}
// Serializer is not cacheable,
// but we have the longest cacheable key. Do default longest key caching
if (localXMLSerializer != null && !(prev instanceof XMLDeserializer)
&& cacheableTransformerCount == this.firstNotCacheableTransformerIndex) {
next = new XMLTeePipe(next, localXMLSerializer);
this.xmlSerializerArray.add(localXMLSerializer);
localXMLSerializer = null;
}
this.connect(environment, prev, next);
prev = (XMLProducer)next;
}
cacheableTransformerCount++;
}
next = super.lastConsumer;
//*all* the transformers are cacheable, but the serializer is not!! this is longest key
if (localXMLSerializer != null && !(prev instanceof XMLDeserializer)) {
next = new XMLTeePipe(next, localXMLSerializer);
this.xmlSerializerArray.add(localXMLSerializer);
localXMLSerializer = null;
}
// else the serializer is cacheable but has views
else if (this.nextIsCachePoint && !(prev instanceof XMLDeserializer) &&
cacheableTransformerCount == this.firstNotCacheableTransformerIndex) {
cachePointXMLSerializer = ((XMLSerializer)this.manager.lookup( XMLSerializer.ROLE ));
next = new XMLTeePipe(next, cachePointXMLSerializer);
this.xmlSerializerArray.add(cachePointXMLSerializer);
}
this.connect(environment, prev, next);
}
} catch (ComponentException e) {
throw new ProcessingException("Could not connect pipeline.", e);
}
}
/**
* Recyclable Interface
*/
public void recycle() {
super.recycle();
Iterator itt = this.xmlSerializerArray.iterator();
while (itt.hasNext()) {
this.manager.release((XMLSerializer) itt.next());
}
this.isCachePoint.clear();
this.xmlSerializerArray.clear();
this.nextIsCachePoint = false;
this.autoCachingPointSwitch=null;
}
boolean setupFromCacheKey() {
// try a shorter key
if (this.fromCacheKey.size() > 1) {
this.fromCacheKey.removeLastKey();
if (!this.completeResponseIsCached) {
this.firstProcessedTransformerIndex--;
}
return false;
} else {
this.fromCacheKey = null;
return true;
}
}
}