| /* |
| * 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(); |
| } |
| } |