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.portal.coplet.adapter.impl;
018:
019: import java.io.UnsupportedEncodingException;
020: import java.util.ArrayList;
021: import java.util.Collections;
022: import java.util.Iterator;
023: import java.util.List;
024:
025: import org.apache.avalon.framework.parameters.Parameterizable;
026: import org.apache.avalon.framework.parameters.Parameters;
027: import org.apache.avalon.framework.service.ServiceException;
028: import org.apache.avalon.framework.service.ServiceManager;
029: import org.apache.cocoon.ProcessingException;
030: import org.apache.cocoon.caching.Cache;
031: import org.apache.cocoon.caching.CachedResponse;
032: import org.apache.cocoon.components.sax.XMLByteStreamCompiler;
033: import org.apache.cocoon.components.sax.XMLByteStreamInterpreter;
034: import org.apache.cocoon.portal.PortalService;
035: import org.apache.cocoon.portal.coplet.CopletInstanceData;
036: import org.apache.cocoon.portal.event.CopletInstanceEvent;
037: import org.apache.cocoon.portal.event.impl.ChangeCopletInstanceAspectDataEvent;
038: import org.apache.cocoon.util.Deprecation;
039: import org.apache.cocoon.util.NetUtils;
040: import org.apache.excalibur.source.SourceValidity;
041: import org.xml.sax.ContentHandler;
042: import org.xml.sax.SAXException;
043: import org.xml.sax.ext.LexicalHandler;
044:
045: /**
046: * This adapter extends the {@link org.apache.cocoon.portal.coplet.adapter.impl.URICopletAdapter}
047: * by a caching mechanism. The result of the called uri/pipeline is cached until a
048: * {@link org.apache.cocoon.portal.event.CopletInstanceEvent} for that coplet instance
049: * is received.
050: * The content can eiter be cached in the user session or globally. The default is
051: * the user session.
052: *
053: * @author <a href="mailto:gerald.kahrer@rizit.at">Gerald Kahrer</a>
054: * @author <a href="mailto:cziegeler.at.apache.dot.org">Carsten Ziegeler</a>
055: * @version $Id: CachingURICopletAdapter.java 433543 2006-08-22 06:22:54Z crossley $
056: */
057: public class CachingURICopletAdapter extends URICopletAdapter implements
058: Parameterizable {
059:
060: /** The configuration name for enabling/disabling the cache. */
061: public static final String CONFIGURATION_ENABLE_CACHING = "cache-enabled";
062:
063: /** The configuration name for using the global cache. */
064: public static final String CONFIGURATION_CACHE_GLOBAL = "cache-global";
065:
066: /** The configuration name for querying instance attributes to generate the key
067: * for the global cache. */
068: public static final String CONFIGURATION_CACHE_GLOBAL_USE_ATTRIBUTES = "cache-global-use-attributes";
069:
070: /** The configuration name for ignoring sizing events to clear the cache. */
071: public static final String CONFIGURATION_IGNORE_SIZING_EVENTS = "ignore-sizing-events";
072:
073: /** The temporary attribute name for the storing the cached coplet content. */
074: public static final String CACHE = "cacheData";
075:
076: /** This temporary attribute can be set on the instance to not cache the current response. */
077: public static final String DO_NOT_CACHE = "doNotCache";
078:
079: /**
080: * Caching can be basically disabled with this boolean parameter.
081: * @deprecated Use coplet base data configuration.
082: */
083: public static final String PARAMETER_DISABLE_CACHING = "disable_caching";
084:
085: /** Is caching enabled? */
086: protected Boolean enableCaching = Boolean.TRUE;
087:
088: /** The cache to use for global caching. */
089: protected Cache cache;
090:
091: /**
092: * @see org.apache.avalon.framework.parameters.Parameterizable#parameterize(org.apache.avalon.framework.parameters.Parameters)
093: */
094: public void parameterize(Parameters parameters) {
095: if (parameters.getParameter(PARAMETER_DISABLE_CACHING, null) != null) {
096: Deprecation.logger
097: .info("The 'disable_caching' parameter on the caching uri coplet adapter is deprecated. "
098: + "Use the configuration of the base coplet data instead.");
099: }
100: boolean disableCaching = parameters.getParameterAsBoolean(
101: PARAMETER_DISABLE_CACHING, !this .enableCaching
102: .booleanValue());
103: this .enableCaching = new Boolean(!disableCaching);
104: if (this .getLogger().isInfoEnabled()) {
105: this .getLogger().info(
106: this .getClass().getName() + ": enable-caching="
107: + this .enableCaching);
108: }
109: }
110:
111: /**
112: * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
113: */
114: public void service(ServiceManager manager) throws ServiceException {
115: super .service(manager);
116: this .cache = (Cache) this .manager.lookup(Cache.ROLE);
117: }
118:
119: /**
120: * @see org.apache.avalon.framework.activity.Disposable#dispose()
121: */
122: public void dispose() {
123: if (this .manager != null) {
124: this .manager.release(this .cache);
125: this .cache = null;
126: }
127: super .dispose();
128: }
129:
130: /**
131: * @see org.apache.cocoon.portal.coplet.adapter.impl.AbstractCopletAdapter#streamContent(org.apache.cocoon.portal.coplet.CopletInstanceData, org.xml.sax.ContentHandler)
132: */
133: public void streamContent(CopletInstanceData coplet,
134: ContentHandler contentHandler) throws SAXException {
135: this .streamContent(coplet, (String) coplet.getCopletData()
136: .getAttribute("uri"), contentHandler);
137: }
138:
139: /**
140: * @see org.apache.cocoon.portal.coplet.adapter.impl.URICopletAdapter#streamContent(org.apache.cocoon.portal.coplet.CopletInstanceData, java.lang.String, org.xml.sax.ContentHandler)
141: */
142: public void streamContent(final CopletInstanceData coplet,
143: final String uri, final ContentHandler contentHandler)
144: throws SAXException {
145: // Is caching enabled?
146: boolean cachingEnabled = ((Boolean) this .getConfiguration(
147: coplet, CONFIGURATION_ENABLE_CACHING,
148: this .enableCaching)).booleanValue();
149: // do we cache globally?
150: boolean cacheGlobal = ((Boolean) this .getConfiguration(coplet,
151: CONFIGURATION_CACHE_GLOBAL, Boolean.FALSE))
152: .booleanValue();
153:
154: Object data = null;
155: // If caching is enabed and the cache is still valid, then use the cache
156: if (cachingEnabled) {
157: if (cacheGlobal) {
158: final String key = this .getCacheKey(coplet, uri);
159: CachedResponse response = this .cache.get(key);
160: if (response != null) {
161: data = response.getResponse();
162: }
163: } else {
164: data = coplet.getTemporaryAttribute(CACHE);
165: }
166: }
167: if (data == null) {
168: // if caching is permanently or temporary disabled, flush the cache and invoke coplet
169: if (!cachingEnabled
170: || coplet.getTemporaryAttribute(DO_NOT_CACHE) != null) {
171: coplet.removeTemporaryAttribute(DO_NOT_CACHE);
172: if (cacheGlobal) {
173: final String key = this .getCacheKey(coplet, uri);
174: this .cache.remove(key);
175: } else {
176: coplet.removeTemporaryAttribute(CACHE);
177: }
178: super .streamContent(coplet, uri, contentHandler);
179: } else {
180:
181: XMLByteStreamCompiler bc = new XMLByteStreamCompiler();
182:
183: super .streamContent(coplet, uri, bc);
184: data = bc.getSAXFragment();
185: if (coplet.removeTemporaryAttribute(DO_NOT_CACHE) == null) {
186: if (cacheGlobal) {
187: CachedResponse response = new CachedResponse(
188: (SourceValidity[]) null, (byte[]) data);
189: try {
190: final String key = this .getCacheKey(coplet,
191: uri);
192: this .cache.store(key, response);
193: } catch (ProcessingException pe) {
194: // we ignore this
195: this
196: .getLogger()
197: .warn(
198: "Exception during storing response into cache.",
199: pe);
200: }
201: } else {
202: coplet.setTemporaryAttribute(CACHE, data);
203: }
204: }
205: }
206: }
207: // and now stream the data
208: if (data != null) {
209: XMLByteStreamInterpreter bi = new XMLByteStreamInterpreter();
210: bi.setContentHandler(contentHandler);
211: if (contentHandler instanceof LexicalHandler) {
212: bi.setLexicalHandler((LexicalHandler) contentHandler);
213: }
214: bi.deserialize(data);
215: }
216: }
217:
218: /**
219: * @see org.apache.cocoon.portal.event.Receiver
220: */
221: public void inform(CopletInstanceEvent e, PortalService service) {
222: if (this .getLogger().isInfoEnabled()) {
223: this .getLogger().info(
224: "CopletInstanceEvent " + e
225: + " caught by CachingURICopletAdapter");
226: }
227: this .handleCopletInstanceEvent(e);
228: super .inform(e, service);
229: }
230:
231: /**
232: * This adapter listens for CopletInstanceEvents. Each event sets the cache invalid,
233: * except for global caching using attributes, as all attributes are part of the cache key.
234: */
235: public void handleCopletInstanceEvent(CopletInstanceEvent event) {
236: final CopletInstanceData coplet = (CopletInstanceData) event
237: .getTarget();
238:
239: // do we ignore SizingEvents
240: boolean ignoreSizing = ((Boolean) this .getConfiguration(coplet,
241: CONFIGURATION_IGNORE_SIZING_EVENTS, Boolean.TRUE))
242: .booleanValue();
243:
244: if (!ignoreSizing || !isSizingEvent(event)) {
245: // do we cache globally?
246: boolean cacheGlobal = ((Boolean) this .getConfiguration(
247: coplet, CONFIGURATION_CACHE_GLOBAL, Boolean.FALSE))
248: .booleanValue();
249: boolean cacheGlobalUseAttributes = ((Boolean) this
250: .getConfiguration(coplet,
251: CONFIGURATION_CACHE_GLOBAL_USE_ATTRIBUTES,
252: Boolean.FALSE)).booleanValue();
253: if (cacheGlobal) {
254: if (!cacheGlobalUseAttributes) {
255: final String key = this .getCacheKey(coplet,
256: (String) coplet.getCopletData()
257: .getAttribute("uri"));
258: this .cache.remove(key);
259: }
260: } else {
261: coplet.removeTemporaryAttribute(CACHE);
262: }
263: }
264: }
265:
266: /**
267: * Tests if the event is a sizing event for the coplet.
268: */
269: protected boolean isSizingEvent(CopletInstanceEvent event) {
270: if (event instanceof ChangeCopletInstanceAspectDataEvent) {
271: if (((ChangeCopletInstanceAspectDataEvent) event)
272: .getAspectName().equals("size")) {
273: return true;
274: }
275: }
276: return false;
277: }
278:
279: /**
280: * Build the key for the global cache.
281: */
282: protected String getCacheKey(CopletInstanceData coplet, String uri) {
283: final Boolean useAttributes = (Boolean) this .getConfiguration(
284: coplet, CONFIGURATION_CACHE_GLOBAL_USE_ATTRIBUTES,
285: Boolean.FALSE);
286: if (!useAttributes.booleanValue()) {
287: return "coplet:" + coplet.getCopletData().getId() + '/'
288: + uri;
289: }
290: final StringBuffer buffer = new StringBuffer("coplet:");
291: buffer.append(coplet.getCopletData().getId());
292: buffer.append('/');
293: buffer.append(uri);
294: boolean hasParams = false;
295: // first add attributes:
296: // sort the keys
297: List keyList = new ArrayList(coplet.getAttributes().keySet());
298: Collections.sort(keyList);
299: Iterator i = keyList.iterator();
300: while (i.hasNext()) {
301: final Object name = i.next();
302: final Object value = coplet.getAttribute(name.toString());
303: if (hasParams) {
304: buffer.append('&');
305: } else {
306: buffer.append('?');
307: hasParams = true;
308: }
309: buffer.append(name.toString());
310: buffer.append('=');
311: if (value != null) {
312: try {
313: buffer.append(NetUtils.encode(value.toString(),
314: "utf-8"));
315: } catch (UnsupportedEncodingException ignore) {
316: // we ignore this
317: }
318: }
319: }
320: // second add temporary attributes
321: keyList = new ArrayList(coplet.getTemporaryAttributes()
322: .keySet());
323: Collections.sort(keyList);
324: i = keyList.iterator();
325: while (i.hasNext()) {
326: final Object name = i.next();
327: final Object value = coplet.getTemporaryAttribute(name
328: .toString());
329: if (hasParams) {
330: buffer.append('&');
331: } else {
332: buffer.append('?');
333: hasParams = true;
334: }
335: buffer.append(name.toString());
336: buffer.append('=');
337: if (value != null) {
338: try {
339: buffer.append(NetUtils.encode(value.toString(),
340: "utf-8"));
341: } catch (UnsupportedEncodingException ignore) {
342: // we ignore this
343: }
344: }
345: }
346: return buffer.toString();
347: }
348: }
|