blob: 7cae31e7dd8a9580efdd640a858c6b3f003228ec [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.portal.coplet.adapter.impl;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.Cache;
import org.apache.cocoon.caching.CachedResponse;
import org.apache.cocoon.components.sax.XMLByteStreamCompiler;
import org.apache.cocoon.components.sax.XMLByteStreamInterpreter;
import org.apache.cocoon.portal.PortalService;
import org.apache.cocoon.portal.coplet.CopletInstanceData;
import org.apache.cocoon.portal.event.CopletInstanceEvent;
import org.apache.cocoon.portal.event.impl.ChangeCopletInstanceAspectDataEvent;
import org.apache.cocoon.util.Deprecation;
import org.apache.cocoon.util.NetUtils;
import org.apache.excalibur.source.SourceValidity;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
/**
* This adapter extends the {@link org.apache.cocoon.portal.coplet.adapter.impl.URICopletAdapter}
* by a caching mechanism. The result of the called uri/pipeline is cached until a
* {@link org.apache.cocoon.portal.event.CopletInstanceEvent} for that coplet instance
* is received.
* The content can eiter be cached in the user session or globally. The default is
* the user session.
*
* @author <a href="mailto:gerald.kahrer@rizit.at">Gerald Kahrer</a>
* @author <a href="mailto:cziegeler.at.apache.dot.org">Carsten Ziegeler</a>
* @version $Id$
*/
public class CachingURICopletAdapter
extends URICopletAdapter
implements Parameterizable {
/** The configuration name for enabling/disabling the cache. */
public static final String CONFIGURATION_ENABLE_CACHING = "cache-enabled";
/** The configuration name for using the global cache. */
public static final String CONFIGURATION_CACHE_GLOBAL= "cache-global";
/** The configuration name for querying instance attributes to generate the key
* for the global cache. */
public static final String CONFIGURATION_CACHE_GLOBAL_USE_ATTRIBUTES= "cache-global-use-attributes";
/** The configuration name for ignoring sizing events to clear the cache. */
public static final String CONFIGURATION_IGNORE_SIZING_EVENTS = "ignore-sizing-events";
/** The temporary attribute name for the storing the cached coplet content. */
public static final String CACHE = "cacheData";
/** This temporary attribute can be set on the instance to not cache the current response. */
public static final String DO_NOT_CACHE = "doNotCache";
/**
* Caching can be basically disabled with this boolean parameter.
* @deprecated Use coplet base data configuration.
*/
public static final String PARAMETER_DISABLE_CACHING = "disable_caching";
/** Is caching enabled? */
protected Boolean enableCaching = Boolean.TRUE;
/** The cache to use for global caching. */
protected Cache cache;
/**
* @see org.apache.avalon.framework.parameters.Parameterizable#parameterize(org.apache.avalon.framework.parameters.Parameters)
*/
public void parameterize(Parameters parameters) {
if ( parameters.getParameter(PARAMETER_DISABLE_CACHING, null) != null ) {
Deprecation.logger.info("The 'disable_caching' parameter on the caching uri coplet adapter is deprecated. "
+"Use the configuration of the base coplet data instead.");
}
boolean disableCaching = parameters.getParameterAsBoolean(PARAMETER_DISABLE_CACHING,
!this.enableCaching.booleanValue());
this.enableCaching = new Boolean(!disableCaching);
if ( this.getLogger().isInfoEnabled() ) {
this.getLogger().info(this.getClass().getName() + ": enable-caching=" + this.enableCaching);
}
}
/**
* @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
*/
public void service(ServiceManager manager) throws ServiceException {
super.service(manager);
this.cache = (Cache)this.manager.lookup(Cache.ROLE);
}
/**
* @see org.apache.avalon.framework.activity.Disposable#dispose()
*/
public void dispose() {
if ( this.manager != null ) {
this.manager.release(this.cache);
this.cache = null;
}
super.dispose();
}
/**
* @see org.apache.cocoon.portal.coplet.adapter.impl.AbstractCopletAdapter#streamContent(org.apache.cocoon.portal.coplet.CopletInstanceData, org.xml.sax.ContentHandler)
*/
public void streamContent(CopletInstanceData coplet, ContentHandler contentHandler)
throws SAXException {
this.streamContent( coplet,
(String) coplet.getCopletData().getAttribute("uri"),
contentHandler);
}
/**
* @see org.apache.cocoon.portal.coplet.adapter.impl.URICopletAdapter#streamContent(org.apache.cocoon.portal.coplet.CopletInstanceData, java.lang.String, org.xml.sax.ContentHandler)
*/
public void streamContent( final CopletInstanceData coplet,
final String uri,
final ContentHandler contentHandler)
throws SAXException {
// Is caching enabled?
boolean cachingEnabled = ((Boolean)this.getConfiguration(coplet, CONFIGURATION_ENABLE_CACHING, this.enableCaching)).booleanValue();
// do we cache globally?
boolean cacheGlobal = ((Boolean)this.getConfiguration(coplet, CONFIGURATION_CACHE_GLOBAL, Boolean.FALSE)).booleanValue();
Object data = null;
// If caching is enabed and the cache is still valid, then use the cache
if (cachingEnabled) {
if ( cacheGlobal ) {
final String key = this.getCacheKey(coplet, uri);
CachedResponse response = this.cache.get(key);
if (response != null ) {
data = response.getResponse();
}
} else {
data = coplet.getTemporaryAttribute(CACHE);
}
}
if (data == null) {
// if caching is permanently or temporary disabled, flush the cache and invoke coplet
if ( !cachingEnabled || coplet.getTemporaryAttribute(DO_NOT_CACHE) != null ) {
coplet.removeTemporaryAttribute(DO_NOT_CACHE);
if ( cacheGlobal ) {
final String key = this.getCacheKey(coplet, uri);
this.cache.remove(key);
} else {
coplet.removeTemporaryAttribute(CACHE);
}
super.streamContent(coplet, uri, contentHandler);
} else {
XMLByteStreamCompiler bc = new XMLByteStreamCompiler();
super.streamContent(coplet, uri, bc);
data = bc.getSAXFragment();
if (coplet.removeTemporaryAttribute(DO_NOT_CACHE) == null) {
if ( cacheGlobal ) {
CachedResponse response = new CachedResponse((SourceValidity[])null, (byte[])data);
try {
final String key = this.getCacheKey(coplet, uri);
this.cache.store(key, response);
} catch (ProcessingException pe) {
// we ignore this
this.getLogger().warn("Exception during storing response into cache.", pe);
}
} else {
coplet.setTemporaryAttribute(CACHE, data);
}
}
}
}
// and now stream the data
if ( data != null ) {
XMLByteStreamInterpreter bi = new XMLByteStreamInterpreter();
bi.setContentHandler(contentHandler);
if ( contentHandler instanceof LexicalHandler ) {
bi.setLexicalHandler((LexicalHandler)contentHandler);
}
bi.deserialize(data);
}
}
/**
* @see org.apache.cocoon.portal.event.Receiver
*/
public void inform(CopletInstanceEvent e, PortalService service) {
if ( this.getLogger().isInfoEnabled() ) {
this.getLogger().info("CopletInstanceEvent " + e + " caught by CachingURICopletAdapter");
}
this.handleCopletInstanceEvent(e);
super.inform(e, service);
}
/**
* This adapter listens for CopletInstanceEvents. Each event sets the cache invalid,
* except for global caching using attributes, as all attributes are part of the cache key.
*/
public void handleCopletInstanceEvent(CopletInstanceEvent event) {
final CopletInstanceData coplet = (CopletInstanceData) event.getTarget();
// do we ignore SizingEvents
boolean ignoreSizing = ((Boolean)this.getConfiguration(coplet, CONFIGURATION_IGNORE_SIZING_EVENTS, Boolean.TRUE)).booleanValue();
if ( !ignoreSizing || !isSizingEvent(event)) {
// do we cache globally?
boolean cacheGlobal = ((Boolean)this.getConfiguration(coplet, CONFIGURATION_CACHE_GLOBAL, Boolean.FALSE)).booleanValue();
boolean cacheGlobalUseAttributes = ((Boolean)this.getConfiguration(coplet, CONFIGURATION_CACHE_GLOBAL_USE_ATTRIBUTES, Boolean.FALSE)).booleanValue();
if ( cacheGlobal ) {
if ( !cacheGlobalUseAttributes ) {
final String key = this.getCacheKey(coplet,
(String) coplet.getCopletData().getAttribute("uri"));
this.cache.remove(key);
}
} else {
coplet.removeTemporaryAttribute(CACHE);
}
}
}
/**
* Tests if the event is a sizing event for the coplet.
*/
protected boolean isSizingEvent(CopletInstanceEvent event) {
if ( event instanceof ChangeCopletInstanceAspectDataEvent ) {
if (((ChangeCopletInstanceAspectDataEvent)event).getAspectName().equals("size")) {
return true;
}
}
return false;
}
/**
* Build the key for the global cache.
*/
protected String getCacheKey(CopletInstanceData coplet, String uri) {
final Boolean useAttributes = (Boolean)this.getConfiguration(coplet,
CONFIGURATION_CACHE_GLOBAL_USE_ATTRIBUTES,
Boolean.FALSE);
if ( !useAttributes.booleanValue() ) {
return "coplet:" + coplet.getCopletData().getId() + '/' + uri;
}
final StringBuffer buffer = new StringBuffer("coplet:");
buffer.append(coplet.getCopletData().getId());
buffer.append('/');
buffer.append(uri);
boolean hasParams = false;
// first add attributes:
// sort the keys
List keyList = new ArrayList(coplet.getAttributes().keySet());
Collections.sort(keyList);
Iterator i = keyList.iterator();
while ( i.hasNext() ) {
final Object name = i.next();
final Object value = coplet.getAttribute(name.toString());
if ( hasParams ) {
buffer.append('&');
} else {
buffer.append('?');
hasParams = true;
}
buffer.append(name.toString());
buffer.append('=');
if ( value != null ) {
try {
buffer.append(NetUtils.encode(value.toString(), "utf-8"));
} catch (UnsupportedEncodingException ignore) {
// we ignore this
}
}
}
// second add temporary attributes
keyList = new ArrayList(coplet.getTemporaryAttributes().keySet());
Collections.sort(keyList);
i = keyList.iterator();
while ( i.hasNext() ) {
final Object name = i.next();
final Object value = coplet.getTemporaryAttribute(name.toString());
if ( hasParams ) {
buffer.append('&');
} else {
buffer.append('?');
hasParams = true;
}
buffer.append(name.toString());
buffer.append('=');
if ( value != null ) {
try {
buffer.append(NetUtils.encode(value.toString(), "utf-8"));
} catch (UnsupportedEncodingException ignore) {
// we ignore this
}
}
}
return buffer.toString();
}
}