001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.pipeline.impl;
018:
019: import org.apache.avalon.framework.component.ComponentException;
020: import org.apache.avalon.framework.parameters.ParameterException;
021: import org.apache.avalon.framework.parameters.Parameters;
022: import org.apache.cocoon.ProcessingException;
023: import org.apache.cocoon.caching.CachedResponse;
024: import org.apache.cocoon.caching.CachingOutputStream;
025: import org.apache.cocoon.caching.ComponentCacheKey;
026: import org.apache.cocoon.components.sax.XMLDeserializer;
027: import org.apache.cocoon.components.sax.XMLSerializer;
028: import org.apache.cocoon.components.sax.XMLTeePipe;
029: import org.apache.cocoon.environment.Environment;
030: import org.apache.cocoon.xml.XMLConsumer;
031: import org.apache.cocoon.xml.XMLProducer;
032: import org.apache.commons.lang.BooleanUtils;
033: import org.apache.excalibur.source.SourceValidity;
034:
035: import java.io.OutputStream;
036: import java.io.Serializable;
037: import java.util.ArrayList;
038: import java.util.Iterator;
039: import java.util.ListIterator;
040:
041: /**
042: * The caching-point pipeline implements an extended caching algorithm which is
043: * of particular benefit for use with those pipelines that utilise cocoon-views
044: * and/or provide drill-down functionality.
045: *
046: * @since 2.1
047: * @author <a href="mailto:Michael.Melhem@managesoft.com">Michael Melhem</a>
048: * @version CVS $Id: CachingPointProcessingPipeline.java 433543 2006-08-22 06:22:54Z crossley $
049: */
050: public class CachingPointProcessingPipeline extends
051: AbstractCachingProcessingPipeline {
052:
053: protected ArrayList isCachePoint = new ArrayList();
054: protected ArrayList xmlSerializerArray = new ArrayList();
055: protected boolean nextIsCachePoint = false;
056: protected String autoCachingPointSwitch;
057: protected boolean autoCachingPoint = true;
058:
059: /**
060: * The <code>CachingPointProcessingPipeline</code> is configurable.
061: *
062: * <p>The autoCachingPoint algorithm (if enabled) will automatically cache
063: * common elements of the pipeline currently being processed - as well as the
064: * entire cacheable pipeline according to the "longest cacheable key"
065: * algorithm. This feature is especially useful for pipelines that branch at
066: * some point (this is the case with <tt><map:select></tt> or
067: * <tt><map:act></tt>).
068: *
069: * <p>The option <tt>autoCachingPoint</tt> can be switched on/off in the
070: * sitemap.xmap (on by default). For linear pipelines, one can switch "Off"
071: * <tt>autoCachingPoint</tt> and use attribute
072: * <tt>pipeline-hints="caching-point"</tt> to manually indicate that certain
073: * pipeline components (eg on <tt><map:generator></tt>) should be
074: * considered as cache points. Both options (automatic at branch points and
075: * manual with pipeline hints) can coexist in the same pipeline.</p>
076: *
077: * <p>Works by requesting the pipeline processor to try shorter keys when
078: * looking for a cached content for the pipeline.</p>
079: */
080: public void parameterize(Parameters config)
081: throws ParameterException {
082: super .parameterize(config);
083:
084: this .autoCachingPointSwitch = config.getParameter(
085: "autoCachingPoint", null);
086:
087: if (this .getLogger().isDebugEnabled()) {
088: getLogger().debug(
089: "Auto caching-point is set to = '"
090: + this .autoCachingPointSwitch + "'");
091: }
092:
093: // Default is that auto caching-point is on
094: if (this .autoCachingPointSwitch == null) {
095: this .autoCachingPoint = true;
096: } else {
097: this .autoCachingPoint = BooleanUtils
098: .toBoolean(this .autoCachingPointSwitch);
099: }
100: }
101:
102: /**
103: * Set the generator.
104: */
105: public void setGenerator(String role, String source,
106: Parameters param, Parameters hintParam)
107: throws ProcessingException {
108: super .setGenerator(role, source, param, hintParam);
109:
110: // check the hint param for a "caching-point" hint
111: String pipelinehint = null;
112: try {
113: pipelinehint = hintParam
114: .getParameter("caching-point", null);
115:
116: if (this .getLogger().isDebugEnabled()) {
117: getLogger().debug(
118: "generator caching-point pipeline-hint is set to: "
119: + pipelinehint);
120: }
121: } catch (Exception ex) {
122: if (this .getLogger().isWarnEnabled()) {
123: getLogger().warn(
124: "caching-point hint Exception, pipeline-hint ignored: "
125: + ex);
126: }
127: }
128:
129: // if this generator is manually set to "caching-point" (via pipeline-hint)
130: // then ensure the next component is caching.
131: this .nextIsCachePoint = BooleanUtils.toBoolean(pipelinehint);
132: }
133:
134: /**
135: * Add a transformer.
136: */
137: public void addTransformer(String role, String source,
138: Parameters param, Parameters hintParam)
139: throws ProcessingException {
140: super .addTransformer(role, source, param, hintParam);
141:
142: // check the hint param for a "caching-point" hint
143: String pipelinehint = null;
144: try {
145: pipelinehint = hintParam
146: .getParameter("caching-point", null);
147:
148: if (this .getLogger().isDebugEnabled()) {
149: getLogger().debug(
150: "transformer caching-point pipeline-hint is set to: "
151: + pipelinehint);
152: }
153: } catch (Exception ex) {
154: if (this .getLogger().isWarnEnabled()) {
155: getLogger().warn(
156: "caching-point hint Exception, pipeline-hint ignored: "
157: + ex);
158: }
159: }
160:
161: // add caching point flag
162: // default value is false
163: this .isCachePoint.add(BooleanUtils
164: .toBooleanObject(this .nextIsCachePoint));
165:
166: // if this transformer is manually set to "caching-point" (via pipeline-hint)
167: // then ensure the next component is caching.
168: this .nextIsCachePoint = BooleanUtils.toBoolean(pipelinehint);
169: }
170:
171: /**
172: * Determine if the given branch-point is a caching-point. This is called
173: * by sitemap components when using cocoon views; it is also called by
174: * parent nodes (mainly selectors and actions).
175: *
176: * Please Note: this method is used by auto caching-point
177: * and is of no consequence when auto caching-point is switched off
178: *
179: * @see org.apache.cocoon.components.treeprocessor.SimpleParentProcessingNode
180: */
181: public void informBranchPoint() {
182: if (this .autoCachingPoint && this .generator != null) {
183: this .nextIsCachePoint = true;
184: if (this .getLogger().isDebugEnabled()) {
185: this .getLogger().debug(
186: "Informed Pipeline of branch point");
187: }
188: }
189: }
190:
191: /**
192: * Cache longest cacheable path plus cache points.
193: */
194: protected void cacheResults(Environment environment, OutputStream os)
195: throws Exception {
196:
197: if (this .toCacheKey != null) {
198: if (this .cacheCompleteResponse) {
199: if (this .getLogger().isDebugEnabled()) {
200: this .getLogger().debug(
201: "Cached: caching complete response; pSisze"
202: + this .toCacheKey.size() + " Key "
203: + this .toCacheKey);
204: }
205: CachedResponse response = new CachedResponse(
206: this .toCacheSourceValidities,
207: ((CachingOutputStream) os).getContent());
208: response.setContentType(environment.getContentType());
209: this .cache.store(this .toCacheKey.copy(), response);
210: //
211: // Scan back along the pipelineCacheKey for
212: // for any cachepoint(s)
213: //
214: this .toCacheKey.removeUntilCachePoint();
215:
216: //
217: // adjust the validities object
218: // to reflect the new length of the pipeline cache key.
219: //
220: // REVISIT: Is it enough to simply reduce the length of the validities array?
221: //
222: if (this .toCacheKey.size() > 0) {
223: SourceValidity[] copy = new SourceValidity[this .toCacheKey
224: .size()];
225: System.arraycopy(this .toCacheSourceValidities, 0,
226: copy, 0, copy.length);
227: this .toCacheSourceValidities = copy;
228: }
229: }
230:
231: if (this .toCacheKey.size() > 0) {
232: ListIterator itt = this .xmlSerializerArray
233: .listIterator(this .xmlSerializerArray.size());
234: while (itt.hasPrevious()) {
235: XMLSerializer serializer = (XMLSerializer) itt
236: .previous();
237: CachedResponse response = new CachedResponse(
238: this .toCacheSourceValidities,
239: (byte[]) serializer.getSAXFragment());
240: this .cache.store(this .toCacheKey.copy(), response);
241:
242: if (this .getLogger().isDebugEnabled()) {
243: this .getLogger().debug(
244: "Caching results for the following key: "
245: + this .toCacheKey);
246: }
247:
248: //
249: // Check for further cachepoints
250: //
251: toCacheKey.removeUntilCachePoint();
252: if (this .toCacheKey.size() == 0)
253: // no cachePoint found in key
254: break;
255:
256: //
257: // re-calculate validities array
258: //
259: SourceValidity[] copy = new SourceValidity[this .toCacheKey
260: .size()];
261: System.arraycopy(this .toCacheSourceValidities, 0,
262: copy, 0, copy.length);
263: this .toCacheSourceValidities = copy;
264: } //end serializer loop
265: }
266: }
267: }
268:
269: /**
270: * Create a new ComponentCachekey
271: * ComponentCacheKeys can be flagged as cachepoints
272: */
273: protected ComponentCacheKey newComponentCacheKey(int type,
274: String role, Serializable key) {
275: boolean cachePoint = false;
276:
277: if (type == ComponentCacheKey.ComponentType_Transformer) {
278: cachePoint = ((Boolean) this .isCachePoint
279: .get(this .firstNotCacheableTransformerIndex))
280: .booleanValue();
281: } else if (type == ComponentCacheKey.ComponentType_Serializer) {
282: cachePoint = this .nextIsCachePoint;
283: }
284: return new ComponentCacheKey(type, role, key, cachePoint);
285: }
286:
287: /**
288: * Connect the caching point pipeline.
289: */
290: protected void connectCachingPipeline(Environment environment)
291: throws ProcessingException {
292: try {
293: XMLSerializer localXMLSerializer = null;
294: XMLSerializer cachePointXMLSerializer = null;
295: if (!this .cacheCompleteResponse) {
296: this .xmlSerializer = (XMLSerializer) this .manager
297: .lookup(XMLSerializer.ROLE);
298: localXMLSerializer = this .xmlSerializer;
299: }
300:
301: if (this .cachedResponse == null) {
302: XMLProducer prev = super .generator;
303: XMLConsumer next;
304:
305: int cacheableTransformerCount = this .firstNotCacheableTransformerIndex;
306: int currentTransformerIndex = 0; //start with the first transformer
307:
308: Iterator itt = this .transformers.iterator();
309: while (itt.hasNext()) {
310: next = (XMLConsumer) itt.next();
311:
312: // if we have cacheable transformers,
313: // check the tranformers for cachepoints
314: if (cacheableTransformerCount > 0) {
315: if ((this .isCachePoint
316: .get(currentTransformerIndex) != null)
317: && ((Boolean) this .isCachePoint
318: .get(currentTransformerIndex))
319: .booleanValue()) {
320:
321: cachePointXMLSerializer = ((XMLSerializer) this .manager
322: .lookup(XMLSerializer.ROLE));
323: next = new XMLTeePipe(next,
324: cachePointXMLSerializer);
325: this .xmlSerializerArray
326: .add(cachePointXMLSerializer);
327: }
328: }
329:
330: // Serializer is not cacheable,
331: // but we have the longest cacheable key. Do default longest key caching
332: if (localXMLSerializer != null) {
333: if (cacheableTransformerCount == 0) {
334: next = new XMLTeePipe(next,
335: localXMLSerializer);
336: this .xmlSerializerArray
337: .add(localXMLSerializer);
338: localXMLSerializer = null;
339: } else {
340: cacheableTransformerCount--;
341: }
342: }
343: this .connect(environment, prev, next);
344: prev = (XMLProducer) next;
345:
346: currentTransformerIndex++;
347: }
348: next = super .lastConsumer;
349:
350: // if the serializer is not cacheable, but all the transformers are:
351: // (this is default longest key caching)
352: if (localXMLSerializer != null) {
353: next = new XMLTeePipe(next, localXMLSerializer);
354: this .xmlSerializerArray.add(localXMLSerializer);
355: localXMLSerializer = null;
356: }
357:
358: // else if the serializer is cacheable and has cocoon views
359: else if ((currentTransformerIndex == this .firstNotCacheableTransformerIndex)
360: && this .nextIsCachePoint) {
361: cachePointXMLSerializer = ((XMLSerializer) this .manager
362: .lookup(XMLSerializer.ROLE));
363: next = new XMLTeePipe(next, cachePointXMLSerializer);
364: this .xmlSerializerArray
365: .add(cachePointXMLSerializer);
366: }
367: this .connect(environment, prev, next);
368: } else {
369: // Here the first part of the pipeline has been retrived from cache
370: // we now check if any part of the rest of the pipeline can be cached
371: this .xmlDeserializer = (XMLDeserializer) this .manager
372: .lookup(XMLDeserializer.ROLE);
373: // connect the pipeline:
374: XMLProducer prev = xmlDeserializer;
375: XMLConsumer next;
376: int cacheableTransformerCount = 0;
377: Iterator itt = this .transformers.iterator();
378: while (itt.hasNext()) {
379: next = (XMLConsumer) itt.next();
380:
381: if (cacheableTransformerCount >= this .firstProcessedTransformerIndex) {
382:
383: // if we have cacheable transformers left,
384: // then check the tranformers for cachepoints
385: if (cacheableTransformerCount < this .firstNotCacheableTransformerIndex) {
386: if (!(prev instanceof XMLDeserializer)
387: && (this .isCachePoint
388: .get(cacheableTransformerCount) != null)
389: && ((Boolean) this .isCachePoint
390: .get(cacheableTransformerCount))
391: .booleanValue()) {
392: cachePointXMLSerializer = ((XMLSerializer) this .manager
393: .lookup(XMLSerializer.ROLE));
394: next = new XMLTeePipe(next,
395: cachePointXMLSerializer);
396: this .xmlSerializerArray
397: .add(cachePointXMLSerializer);
398: }
399: }
400:
401: // Serializer is not cacheable,
402: // but we have the longest cacheable key. Do default longest key caching
403: if (localXMLSerializer != null
404: && !(prev instanceof XMLDeserializer)
405: && cacheableTransformerCount == this .firstNotCacheableTransformerIndex) {
406: next = new XMLTeePipe(next,
407: localXMLSerializer);
408: this .xmlSerializerArray
409: .add(localXMLSerializer);
410: localXMLSerializer = null;
411: }
412: this .connect(environment, prev, next);
413: prev = (XMLProducer) next;
414: }
415: cacheableTransformerCount++;
416: }
417: next = super .lastConsumer;
418:
419: //*all* the transformers are cacheable, but the serializer is not!! this is longest key
420: if (localXMLSerializer != null
421: && !(prev instanceof XMLDeserializer)) {
422: next = new XMLTeePipe(next, localXMLSerializer);
423: this .xmlSerializerArray.add(localXMLSerializer);
424: localXMLSerializer = null;
425: }
426: // else the serializer is cacheable but has views
427: else if (this .nextIsCachePoint
428: && !(prev instanceof XMLDeserializer)
429: && cacheableTransformerCount == this .firstNotCacheableTransformerIndex) {
430: cachePointXMLSerializer = ((XMLSerializer) this .manager
431: .lookup(XMLSerializer.ROLE));
432: next = new XMLTeePipe(next, cachePointXMLSerializer);
433: this .xmlSerializerArray
434: .add(cachePointXMLSerializer);
435: }
436: this .connect(environment, prev, next);
437: }
438:
439: } catch (ComponentException e) {
440: throw new ProcessingException(
441: "Could not connect pipeline.", e);
442: }
443: }
444:
445: /**
446: * Recyclable Interface
447: */
448: public void recycle() {
449: super .recycle();
450:
451: Iterator itt = this .xmlSerializerArray.iterator();
452: while (itt.hasNext()) {
453: this .manager.release((XMLSerializer) itt.next());
454: }
455: this .isCachePoint.clear();
456: this .xmlSerializerArray.clear();
457: this .nextIsCachePoint = false;
458: this .autoCachingPointSwitch = null;
459: }
460:
461: boolean setupFromCacheKey() {
462: // try a shorter key
463: if (this .fromCacheKey.size() > 1) {
464: this .fromCacheKey.removeLastKey();
465: if (!this .completeResponseIsCached) {
466: this .firstProcessedTransformerIndex--;
467: }
468: return false;
469: } else {
470: this .fromCacheKey = null;
471: return true;
472: }
473: }
474: }
|