blob: 0c2089132d4093699027068ed64e780480e07a18 [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.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();
}
}