/* * 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><map:select></tt> or * <tt><map:act></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><map:generator></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; } } }