blob: b5f23f8c72fceca292ef528e4b63a4d98418910d [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.components.store;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.cocoon.util.ClassUtils;
import org.apache.cocoon.util.MRUBucketMap;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Iterator;
import java.util.Map;
/**
* This class provides a cache algorithm for the requested documents.
* It combines a HashMap and a LinkedList to create a so called MRU
* (Most Recently Used) cache.
*
* <p>This implementation is based on MRUBucketMap - map with efficient
* O(1) implementation of MRU removal policy.
*
* <p>TODO: Port improvments to the Excalibur implementation
*
* @deprecated Use the Avalon Excalibur Store instead.
*
* @author <a href="mailto:g-froehlich@gmx.de">Gerhard Froehlich</a>
* @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
* @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
* @version CVS $Id$
*/
public final class MRUMemoryStore extends AbstractLogEnabled
implements Store, Parameterizable, Composable, Disposable, ThreadSafe {
private int maxobjects;
private boolean persistent;
protected MRUBucketMap cache;
private Store persistentStore;
private StoreJanitor storeJanitor;
private ComponentManager manager;
/**
* Get components of the ComponentManager
*
* @param manager The ComponentManager
*/
public void compose(ComponentManager manager) throws ComponentException {
this.manager = manager;
if (getLogger().isDebugEnabled()) {
getLogger().debug("Looking up " + Store.PERSISTENT_CACHE);
getLogger().debug("Looking up " + StoreJanitor.ROLE);
}
this.persistentStore = (Store)manager.lookup(Store.PERSISTENT_CACHE);
this.storeJanitor = (StoreJanitor)manager.lookup(StoreJanitor.ROLE);
}
/**
* Initialize the MRUMemoryStore.
* A few options can be used:
* <UL>
* <LI>maxobjects: Maximum number of objects stored in memory (Default: 100 objects)</LI>
* <LI>use-persistent-cache: Use persistent cache to keep objects persisted after
* container shutdown or not (Default: false)</LI>
* </UL>
*
* @param params Store parameters
* @exception ParameterException
*/
public void parameterize(Parameters params) throws ParameterException {
this.maxobjects = params.getParameterAsInteger("maxobjects", 100);
this.persistent = params.getParameterAsBoolean("use-persistent-cache", false);
if ((this.maxobjects < 1)) {
throw new ParameterException("MRUMemoryStore maxobjects must be at least 1!");
}
this.cache = new MRUBucketMap((int)(this.maxobjects * 1.2));
this.storeJanitor.register(this);
}
/**
* Dispose the component
*/
public void dispose() {
if (this.manager != null) {
getLogger().debug("Disposing component!");
if (this.storeJanitor != null)
this.storeJanitor.unregister(this);
this.manager.release(this.storeJanitor);
this.storeJanitor = null;
// save all cache entries to filesystem
if (this.persistent) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Final cache size: " + this.cache.size());
}
for (Iterator i = this.cache.keySet().iterator(); i.hasNext(); ) {
Object key = i.next();
try {
Object value = this.cache.remove(key);
if(checkSerializable(value)) {
persistentStore.store(getFileName(key.toString()),
value);
}
} catch (IOException ioe) {
getLogger().error("Error in dispose()", ioe);
}
}
}
this.manager.release(this.persistentStore);
this.persistentStore = null;
}
this.manager = null;
}
/**
* Store the given object in a persistent state. It is up to the
* caller to ensure that the key has a persistent state across
* different JVM executions.
*
* @param key The key for the object to store
* @param value The object to store
*/
public void store(Object key, Object value) {
this.hold(key,value);
}
/**
* This method holds the requested object in a HashMap combined
* with a LinkedList to create the MRU.
* It also stores objects onto the filesystem if configured.
*
* @param key The key of the object to be stored
* @param value The object to be stored
*/
public void hold(Object key, Object value) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Holding object in memory:");
getLogger().debug(" key: " + key);
getLogger().debug(" value: " + value);
}
/** ...first test if the max. objects in cache is reached... */
while (this.cache.size() >= this.maxobjects) {
/** ...ok, heapsize is reached, remove the last element... */
this.free();
}
/** ..put the new object in the cache, on the top of course ... */
this.cache.put(key, value);
}
/**
* Get the object associated to the given unique key.
*
* @param key The key of the requested object
* @return the requested object
*/
public Object get(Object key) {
Object value = this.cache.get(key);
if (value != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Found key: " + key.toString());
}
return value;
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("NOT Found key: " + key.toString());
}
/** try to fetch from filesystem */
if (this.persistent) {
value = this.persistentStore.get(getFileName(key.toString()));
if (value != null) {
try {
this.hold(key, value);
return value;
} catch (Exception e) {
getLogger().error("Error in hold()!", e);
return null;
}
}
}
return null;
}
/**
* Remove the object associated to the given key.
*
* @param key The key of to be removed object
*/
public void remove(Object key) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Removing object from store");
getLogger().debug(" key: " + key);
}
this.cache.remove(key);
if(this.persistent && key != null) {
this.persistentStore.remove(getFileName(key.toString()));
}
}
/**
* Indicates if the given key is associated to a contained object.
*
* @param key The key of the object
* @return true if the key exists
*/
public boolean containsKey(Object key) {
return cache.containsKey(key) || (persistent && persistentStore.containsKey(key));
}
/**
* Returns the list of used keys as an Enumeration.
*
* @return the enumeration of the cache
*/
public Enumeration keys() {
return new Enumeration() {
private Iterator i = cache.keySet().iterator();
public boolean hasMoreElements() {
return i.hasNext();
}
public Object nextElement() {
return i.next();
}
};
}
/**
* Returns count of the objects in the store, or -1 if could not be
* obtained.
*/
public int size() {
return this.cache.size();
}
/**
* Frees some of the fast memory used by this store.
* It removes the last element in the store.
*/
public void free() {
try {
if (this.cache.size() > 0) {
// This can throw NoSuchElementException
Map.Entry node = this.cache.removeLast();
if (getLogger().isDebugEnabled()) {
getLogger().debug("Freeing cache.");
getLogger().debug(" key: " + node.getKey());
getLogger().debug(" value: " + node.getValue());
}
if (this.persistent) {
// Swap object on fs.
if(checkSerializable(node.getValue())) {
try {
this.persistentStore.store(
getFileName(node.getKey().toString()), node.getValue());
} catch(Exception e) {
getLogger().error("Error storing object on fs", e);
}
}
}
}
} catch (NoSuchElementException e) {
getLogger().warn("Concurrency error in free()", e);
} catch (Exception e) {
getLogger().error("Error in free()", e);
}
}
/**
* This method checks if an object is seriazable.
*
* @param object The object to be checked
* @return true if the object is storeable
*/
private boolean checkSerializable(Object object) {
if (object == null) return false;
try {
String clazz = object.getClass().getName();
if((clazz.equals("org.apache.cocoon.caching.CachedEventObject"))
|| (clazz.equals("org.apache.cocoon.caching.CachedStreamObject"))
|| (ClassUtils.implementsInterface(clazz, "org.apache.cocoon.caching.CacheValidity"))) {
return true;
} else {
return false;
}
} catch (Exception e) {
getLogger().error("Error in checkSerializable()!", e);
return false;
}
}
/**
* This method puts together a filename for
* the object, which shall be stored on the
* filesystem.
*
* @param key The key of the object
* @return the filename of the key
*/
private String getFileName(String key) {
return URLEncoder.encode(key.toString());
}
}