| /* |
| * 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.caching.impl; |
| |
| import java.io.Serializable; |
| import java.util.Iterator; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| |
| import org.apache.avalon.framework.activity.Initializable; |
| import org.apache.avalon.framework.activity.Startable; |
| import org.apache.avalon.framework.parameters.ParameterException; |
| 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.CachedResponse; |
| import org.apache.cocoon.caching.EventAware; |
| import org.apache.cocoon.caching.EventRegistry; |
| import org.apache.cocoon.caching.validity.Event; |
| import org.apache.cocoon.caching.validity.EventValidity; |
| import org.apache.cocoon.components.source.impl.SitemapSource; |
| import org.apache.excalibur.source.SourceValidity; |
| import org.apache.excalibur.source.impl.validity.AbstractAggregatedValidity; |
| |
| /** |
| * This implementation holds all mappings between Events and PipelineCacheKeys |
| * in two MultiValueMaps to facilitate efficient lookup by either as Key. |
| * |
| * @version $Id$ |
| */ |
| public class EventAwareCacheImpl |
| extends CacheImpl |
| implements Initializable, Startable, EventAware { |
| |
| private ServiceManager m_manager; |
| |
| private EventRegistry m_eventRegistry; |
| |
| // clean-up thread |
| private static final Timer timer = new Timer(true); |
| private TimerTask timerTask; |
| private long interval; |
| |
| public void parameterize(Parameters parameters) throws ParameterException { |
| |
| super.parameterize(parameters); |
| this.interval = parameters.getParameterAsInteger("cleanupthreadinterval",1000*60*60); // 1 hour |
| if(this.interval < 1) { |
| throw new ParameterException("EventAwareCacheImpl cleanupthreadinterval parameter has to be greater then 1"); |
| } |
| |
| String eventRegistryName = parameters.getParameter("registry", EventRegistry.ROLE); |
| try { |
| this.m_eventRegistry = (EventRegistry)m_manager.lookup(eventRegistryName); |
| } catch (ServiceException e) { |
| throw new ParameterException("Unable to lookup registry: " + eventRegistryName, e); |
| } |
| } |
| |
| /** |
| * Clears the entire Cache, including all registered event-pipeline key |
| * mappings.. |
| */ |
| public void clear() { |
| super.clear(); |
| m_eventRegistry.clear(); |
| } |
| |
| /** |
| * When a new Pipeline key is stored, it needs to have it's |
| * <code>SourceValidity</code> objects examined. For every |
| * <code>EventValidity</code> found, it's <code>Event</code> will be |
| * registered with this key in the <code>EventRegistry</code>. |
| * |
| * <code>AggregatedValidity</code> is handled recursively. |
| */ |
| public void store(Serializable key, |
| CachedResponse response) |
| throws ProcessingException { |
| SourceValidity[] validities = response.getValidityObjects(); |
| for (int i=0; i< validities.length;i++) { |
| SourceValidity val = validities[i]; |
| examineValidity(val, key); |
| } |
| super.store(key, response); |
| } |
| |
| /** |
| * Look up the EventRegistry |
| */ |
| public void service(ServiceManager manager) throws ServiceException { |
| this.m_manager = manager; |
| super.service(manager); |
| } |
| |
| /** |
| * Un-register this key in the EventRegistry in addition to |
| * removing it from the Store |
| */ |
| public void remove(Serializable key) { |
| super.remove(key); |
| m_eventRegistry.removeKey(key); |
| } |
| |
| /** |
| * Receive notification about the occurrence of an Event. |
| * If this event has registered pipeline keys, remove them |
| * from the Store and unregister them |
| * @param e The Event to be processed. |
| */ |
| public void processEvent(Event e) { |
| if (e == null) return; |
| Serializable[] keys = m_eventRegistry.keysForEvent(e); |
| if (keys == null) return; |
| for (int i=0;i<keys.length; i++) { |
| if (keys[i] != null) { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Processing cache event, found Pipeline key: " + keys[i].toString()); |
| } |
| /* every pck associated with this event needs to be |
| * removed -- regardless of event mapping. and every |
| * event mapped to those keys needs to be removed |
| * recursively. |
| */ |
| remove(keys[i]); |
| } |
| } |
| } |
| |
| /** |
| * Get the EventRegistry ready, and make sure it does not contain |
| * orphaned Event/PipelineKey mappings. |
| */ |
| public void initialize() throws Exception { |
| if (!m_eventRegistry.wasRecoverySuccessful()) { |
| super.clear(); |
| } else { |
| // Not sure if we want this overhead here, but where else? |
| verifyEventCache(); |
| } |
| } |
| |
| /** |
| * Ensure that all PipelineCacheKeys registered to events still |
| * point to valid cache entries. Having an isTotallyEmpty() on |
| * Store might make this less necessary, as the most likely time |
| * to discover orphaned entries is at startup. This is because |
| * stray events could hang around indefinitely if the cache is |
| * removed abnormally or is not configured with persistence. |
| */ |
| public void verifyEventCache() { |
| Serializable[] keys = m_eventRegistry.allKeys(); |
| if (keys == null) return; |
| for (int i=0; i<keys.length; i++) { |
| if (!this.containsKey(keys[i])) { |
| m_eventRegistry.removeKey(keys[i]); |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Cache key no longer valid: " + |
| keys[i]); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Release resources |
| */ |
| public void dispose() { |
| m_manager.release(m_eventRegistry); |
| super.dispose(); |
| m_manager = null; |
| m_eventRegistry = null; |
| } |
| |
| private void examineValidity(SourceValidity val, Serializable key) { |
| if (val instanceof AbstractAggregatedValidity) { |
| handleAggregatedValidity((AbstractAggregatedValidity)val, key); |
| } else if (val instanceof EventValidity) { |
| handleEventValidity((EventValidity)val, key); |
| } else if (val instanceof SitemapSource.SitemapSourceValidity) { |
| examineValidity(((SitemapSource.SitemapSourceValidity) val).getNestedValidity(), key); |
| } |
| } |
| |
| private void handleAggregatedValidity( |
| AbstractAggregatedValidity val, |
| Serializable key) { |
| // AggregatedValidity must be investigated further. |
| Iterator it = val.getValidities().iterator(); |
| while (it.hasNext()) { |
| SourceValidity thisVal = (SourceValidity)it.next(); |
| // Allow recursion |
| examineValidity(thisVal, key); |
| } |
| } |
| |
| private void handleEventValidity(EventValidity val, Serializable key) { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Found EventValidity: " + val.toString()); |
| } |
| m_eventRegistry.register(val.getEvent(),key); |
| } |
| |
| /** |
| * starts cache-cleaner timer task scheduling |
| */ |
| public void start() throws Exception { |
| getLogger().debug("Intializing event-cache checker thread"); |
| timerTask = new TimerTask() { public void run() {verifyEventCache();}; }; |
| timer.schedule(timerTask, interval, interval); |
| } |
| |
| /** |
| * stops cache-cleaner timer task scheduling |
| */ |
| public void stop() { |
| timerTask.cancel(); |
| } |
| } |