Introduce helma.objectmodel.ObjectCache interface for switchable cache implementations.

This commit is contained in:
hns 2004-09-17 15:53:35 +00:00
parent f2180bfa81
commit 59bec76b45
6 changed files with 286 additions and 146 deletions

View file

@ -274,7 +274,9 @@ public final class Application implements IPathElement, Runnable {
* Get the application ready to run, initializing the evaluators and type manager. * Get the application ready to run, initializing the evaluators and type manager.
*/ */
public synchronized void init() public synchronized void init()
throws DatabaseException, ScriptingException, MalformedURLException { throws DatabaseException, MalformedURLException,
IllegalAccessException, InstantiationException,
ClassNotFoundException {
// create and init type mananger // create and init type mananger
typemgr = new TypeManager(this); typemgr = new TypeManager(this);

View file

@ -0,0 +1,108 @@
/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.objectmodel;
import helma.framework.core.Application;
import java.util.Properties;
/**
* Interface Helma object cache classes need to implement.
*
*/
public interface ObjectCache {
/**
* Set the {@link helma.framework.core.Application Application} instance
* for the cache.
* @param app the app instance
*/
void init(Application app);
/**
* Called when the application's properties have been updated to let
* the cache implementation update its settings.
* @param props
*/
void updateProperties(Properties props);
/**
* Returns true if the collection contains an element for the key.
*
* @param key the key that we are looking for
*/
boolean containsKey(Object key);
/**
* Returns the number of keys in object array <code>keys</code> that
* were not found in the Map.
* Those keys that are contained in the Map are nulled out in the array.
* @param keys an array of key objects we are looking for
* @see ObjectCache#containsKey
*/
int containsKeys(Object[] keys);
/**
* Gets the object associated with the specified key in the
* hashtable.
* @param key the specified key
* @return the element for the key or null if the key
* is not defined in the hash table.
* @see ObjectCache#put
*/
Object get(Object key);
/**
* Puts the specified element into the hashtable, using the specified
* key. The element may be retrieved by doing a get() with the same key.
* The key and the element cannot be null.
* @param key the specified key in the hashtable
* @param value the specified element
* @exception NullPointerException If the value of the element
* is equal to null.
* @see ObjectCache#get
* @return the old value of the key, or null if it did not have one.
*/
Object put(Object key, Object value);
/**
* Removes the element corresponding to the key. Does nothing if the
* key is not present.
* @param key the key that needs to be removed
* @return the value of key, or null if the key was not found.
*/
Object remove(Object key);
/**
* Removes all items currently stored in the cache.
*
* @return true if the operation succeeded
*/
boolean clear();
/**
* Return the number of objects currently stored in the cache.
* @return the number of cached items
*/
int size();
/**
* Return an array with all objects currently contained in the cache.
*/
Object[] getCachedObjects();
}

View file

@ -542,6 +542,15 @@ public final class Node implements INode, Serializable {
return dbmap; return dbmap;
} }
/**
*
*
* @param nmgr
*/
public void setWrappedNodeManager(WrappedNodeManager nmgr) {
this.nmgr = nmgr;
}
/** /**
* *
* *

View file

@ -35,7 +35,7 @@ import java.util.*;
public final class NodeManager { public final class NodeManager {
protected Application app; protected Application app;
private CacheMap cache; private ObjectCache cache;
private Replicator replicator; private Replicator replicator;
protected IDatabase db; protected IDatabase db;
protected IDGenerator idgen; protected IDGenerator idgen;
@ -51,22 +51,17 @@ public final class NodeManager {
* created in dbHome if one doesn't already exist. * created in dbHome if one doesn't already exist.
*/ */
public NodeManager(Application app, String dbHome, Properties props) public NodeManager(Application app, String dbHome, Properties props)
throws DatabaseException { throws DatabaseException, ClassNotFoundException,
IllegalAccessException, InstantiationException {
this.app = app; this.app = app;
int cacheSize = Integer.parseInt(props.getProperty("cachesize", "1000")); String cacheImpl = props.getProperty("cacheimpl", "helma.util.CacheMap");
// Make actual cache size bigger, since we use it only up to the threshold cache = (ObjectCache) Class.forName(cacheImpl).newInstance();
// cache = new CacheMap ((int) Math.ceil (cacheSize/0.75f), 0.75f); cache.init(app);
cache = new CacheMap(cacheSize, 0.75f);
cache.setApplication(app);
if (cacheSize != 1000) {
app.logEvent("Setting cache size for "+app.getName()+" to " + cacheSize);
}
safe = new WrappedNodeManager(this); safe = new WrappedNodeManager(this);
// nullNode = new Node ();
logSql = "true".equalsIgnoreCase(props.getProperty("logsql")); logSql = "true".equalsIgnoreCase(props.getProperty("logsql"));
logReplication = "true".equalsIgnoreCase(props.getProperty("logReplication")); logReplication = "true".equalsIgnoreCase(props.getProperty("logReplication"));
@ -102,9 +97,8 @@ public final class NodeManager {
* app.properties file has been updated. Reread some settings. * app.properties file has been updated. Reread some settings.
*/ */
public void updateProperties(Properties props) { public void updateProperties(Properties props) {
int cacheSize = Integer.parseInt(props.getProperty("cachesize", "1000")); // notify the cache about the properties update
cache.updateProperties(props);
cache.setCapacity(cacheSize);
logSql = "true".equalsIgnoreCase(props.getProperty("logsql")); logSql = "true".equalsIgnoreCase(props.getProperty("logsql"));
logReplication = "true".equalsIgnoreCase(props.getProperty("logReplication")); logReplication = "true".equalsIgnoreCase(props.getProperty("logReplication"));
} }
@ -1788,7 +1782,7 @@ public final class NodeManager {
* Get an array of the the keys currently held in the object cache * Get an array of the the keys currently held in the object cache
*/ */
public Object[] getCacheEntries() { public Object[] getCacheEntries() {
return cache.getEntryArray(); return cache.getCachedObjects();
} }
/** /**

View file

@ -241,11 +241,13 @@ public class Transactor extends Thread {
int nstate = node.getState(); int nstate = node.getState();
if (nstate == Node.NEW) { if (nstate == Node.NEW) {
nmgr.registerNode(node); // register node with nodemanager cache
nmgr.insertNode(nmgr.db, txn, node); nmgr.insertNode(nmgr.db, txn, node);
dirtyDbMappings.add(node.getDbMapping()); dirtyDbMappings.add(node.getDbMapping());
node.setState(Node.CLEAN); node.setState(Node.CLEAN);
// register node with nodemanager cache
nmgr.registerNode(node);
if (replicator != null) { if (replicator != null) {
replicator.addNewNode(node); replicator.addNewNode(node);
} }
@ -260,6 +262,9 @@ public class Transactor extends Thread {
} }
node.setState(Node.CLEAN); node.setState(Node.CLEAN);
// update node with nodemanager cache
nmgr.registerNode(node);
if (replicator != null) { if (replicator != null) {
replicator.addModifiedNode(node); replicator.addModifiedNode(node);
} }
@ -270,6 +275,8 @@ public class Transactor extends Thread {
} else if (nstate == Node.DELETED) { } else if (nstate == Node.DELETED) {
nmgr.deleteNode(nmgr.db, txn, node); nmgr.deleteNode(nmgr.db, txn, node);
dirtyDbMappings.add(node.getDbMapping()); dirtyDbMappings.add(node.getDbMapping());
// remove node from nodemanager cache
nmgr.evictNode(node); nmgr.evictNode(node);
if (replicator != null) { if (replicator != null) {

View file

@ -30,7 +30,10 @@
package helma.util; package helma.util;
import java.util.HashMap; import java.util.HashMap;
import java.util.Properties;
import helma.framework.core.Application; import helma.framework.core.Application;
import helma.objectmodel.ObjectCache;
/// A Hashtable that expires least-recently-used objects. /// A Hashtable that expires least-recently-used objects.
@ -44,7 +47,7 @@ import helma.framework.core.Application;
// <P> // <P>
// @see java.util.Hashtable // @see java.util.Hashtable
public class CacheMap { public class CacheMap implements ObjectCache {
// Load factor. // Load factor.
private float loadFactor; private float loadFactor;
@ -62,6 +65,14 @@ public class CacheMap {
// the application to output messages to // the application to output messages to
private Application app = null; private Application app = null;
/**
* Zero argument constructor. Creates a CacheMap with capacity of 1000
* and load factor 0.75
*/
public CacheMap() {
this(1000, 0.75f);
}
/// Constructs a new, empty hashtable with the specified initial /// Constructs a new, empty hashtable with the specified initial
// capacity and the specified load factor. // capacity and the specified load factor.
// Unlike a plain Hashtable, an LruHashtable will never grow or // Unlike a plain Hashtable, an LruHashtable will never grow or
@ -73,25 +84,25 @@ public class CacheMap {
// is less than or equal to zero. // is less than or equal to zero.
// @exception IllegalArgumentException If the load factor is // @exception IllegalArgumentException If the load factor is
// less than or equal to zero. // less than or equal to zero.
public CacheMap (int initialCapacity, float loadFactor) { public CacheMap(int initialCapacity, float loadFactor) {
// We have to call a superclass constructor, but we're not actually // We have to call a superclass constructor, but we're not actually
// going to use it at all. The only reason we want to extend Hashtable // going to use it at all. The only reason we want to extend Hashtable
// is for type conformance. So, make a parent hash table of minimum // is for type conformance. So, make a parent hash table of minimum
// size and then ignore it. // size and then ignore it.
if ( initialCapacity <= 0 || loadFactor <= 0.0 ) if (initialCapacity <= 0 || loadFactor <= 0.0)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
this.loadFactor = loadFactor; this.loadFactor = loadFactor;
// table rotation threshold: we allow each table to gain // table rotation threshold: we allow each table to gain
// initialCapacity/2 entries. // initialCapacity/2 entries.
threshold = initialCapacity / 2; threshold = initialCapacity / 2;
// We deliberately choose the initial capacity of tables large // We deliberately choose the initial capacity of tables large
// enough that it can hold threshold entries without being rehashed, // enough that it can hold threshold entries without being rehashed,
// in other words, make sure our threshold for table rotation is lower // in other words, make sure our threshold for table rotation is lower
// than that of the underlying HashMap for table rehashing. // than that of the underlying HashMap for table rehashing.
eachCapacity = (int) (threshold / loadFactor) + 2; eachCapacity = (int) (threshold / loadFactor) + 2;
// create tables // create tables
oldTable = new HashMap (); oldTable = new HashMap();
newTable = new HashMap (eachCapacity, loadFactor); newTable = new HashMap(eachCapacity, loadFactor);
} }
/// Constructs a new, empty hashtable with the specified initial /// Constructs a new, empty hashtable with the specified initial
@ -99,41 +110,41 @@ public class CacheMap {
// Unlike a plain Hashtable, an LruHashtable will never grow or // Unlike a plain Hashtable, an LruHashtable will never grow or
// shrink from this initial capacity. // shrink from this initial capacity.
// @param initialCapacity the initial number of buckets // @param initialCapacity the initial number of buckets
public CacheMap (int initialCapacity) { public CacheMap(int initialCapacity) {
this (initialCapacity, 0.75F); this(initialCapacity, 0.75F);
} }
/// Returns the number of elements contained in the hashtable. /// Returns the number of elements contained in the hashtable.
public int size() { public int size() {
return newTable.size() + oldTable.size(); return newTable.size() + oldTable.size();
} }
/// Returns true if the hashtable contains no elements. /// Returns true if the hashtable contains no elements.
public boolean isEmpty() { public boolean isEmpty() {
return size() == 0; return size() == 0;
} }
/// Set the capacity of the CacheMap /// Set the capacity of the CacheMap
public void setCapacity(int capacity) { public void setCapacity(int capacity) {
// table rotation threshold: we allow each table to gain // table rotation threshold: we allow each table to gain
// initialCapacity/2 entries. // initialCapacity/2 entries.
int newThreshold = capacity / 2; int newThreshold = capacity / 2;
if (newThreshold != threshold) { if (newThreshold != threshold) {
if (app != null) if (app != null)
app.logEvent ("Setting cache capacity to "+capacity); app.logEvent("Setting cache capacity to " + capacity);
updateThreshold (newThreshold); updateThreshold(newThreshold);
} }
} }
private synchronized void updateThreshold (int newThreshold) { private synchronized void updateThreshold(int newThreshold) {
threshold = newThreshold; threshold = newThreshold;
eachCapacity = (int) (threshold / loadFactor) + 2; eachCapacity = (int) (threshold / loadFactor) + 2;
// if newtable is larger than threshold, rotate. // if newtable is larger than threshold, rotate.
if (newTable.size() > threshold) { if (newTable.size() > threshold) {
oldTable = newTable; oldTable = newTable;
newTable = new HashMap (eachCapacity, loadFactor); newTable = new HashMap(eachCapacity, loadFactor);
} }
} }
/// Returns true if the specified object is an element of the hashtable. /// Returns true if the specified object is an element of the hashtable.
@ -142,35 +153,35 @@ public class CacheMap {
// @exception NullPointerException If the value being searched // @exception NullPointerException If the value being searched
// for is equal to null. // for is equal to null.
// @see LruHashtable#containsKey // @see LruHashtable#containsKey
public synchronized boolean containsValue (Object value) { public synchronized boolean containsValue(Object value) {
if (newTable.containsValue (value)) if (newTable.containsValue(value))
return true; return true;
if (oldTable.containsValue (value)) { if (oldTable.containsValue(value)) {
// We would like to move the object from the old table to the // We would like to move the object from the old table to the
// new table. However, we need keys to re-add the objects, and // new table. However, we need keys to re-add the objects, and
// there's no good way to find all the keys for the given object. // there's no good way to find all the keys for the given object.
// We'd have to enumerate through all the keys and check each // We'd have to enumerate through all the keys and check each
// one. Yuck. For now we just punt. Anyway, contains() is // one. Yuck. For now we just punt. Anyway, contains() is
// probably not a commonly-used operation. // probably not a commonly-used operation.
return true; return true;
} }
return false; return false;
} }
/// Returns true if the collection contains an element for the key. /// Returns true if the collection contains an element for the key.
// @param key the key that we are looking for // @param key the key that we are looking for
// @see LruHashtable#contains // @see LruHashtable#contains
public synchronized boolean containsKey (Object key) { public synchronized boolean containsKey(Object key) {
if (newTable.containsKey(key)) if (newTable.containsKey(key))
return true; return true;
if (oldTable.containsKey (key)) { if (oldTable.containsKey(key)) {
// Move object from old table to new table. // Move object from old table to new table.
Object value = oldTable.get (key); Object value = oldTable.get(key);
newTable.put (key, value); newTable.put(key, value);
oldTable.remove (key); oldTable.remove(key);
return true; return true;
} }
return false; return false;
} }
/// Returns the number of keys in object array <code>keys</code> that /// Returns the number of keys in object array <code>keys</code> that
@ -178,21 +189,21 @@ public class CacheMap {
// Those keys that are contained in the Map are nulled out in the array. // Those keys that are contained in the Map are nulled out in the array.
// @param keys an array of key objects we are looking for // @param keys an array of key objects we are looking for
// @see LruHashtable#contains // @see LruHashtable#contains
public synchronized int containsKeys (Object[] keys) { public synchronized int containsKeys(Object[] keys) {
int notfound = 0; int notfound = 0;
for (int i=0; i<keys.length; i++) { for (int i = 0; i < keys.length; i++) {
if (newTable.containsKey(keys[i])) if (newTable.containsKey(keys[i]))
keys[i] = null; keys[i] = null;
else if (oldTable.containsKey (keys[i])) { else if (oldTable.containsKey(keys[i])) {
// Move object from old table to new table. // Move object from old table to new table.
Object value = oldTable.get (keys[i]); Object value = oldTable.get(keys[i]);
newTable.put (keys[i], value); newTable.put(keys[i], value);
oldTable.remove (keys[i]); oldTable.remove(keys[i]);
keys[i] = null; keys[i] = null;
} else } else
notfound++; notfound++;
} }
return notfound; return notfound;
} }
/// Gets the object associated with the specified key in the /// Gets the object associated with the specified key in the
@ -201,19 +212,19 @@ public class CacheMap {
// @returns the element for the key or null if the key // @returns the element for the key or null if the key
// is not defined in the hash table. // is not defined in the hash table.
// @see LruHashtable#put // @see LruHashtable#put
public synchronized Object get (Object key) { public synchronized Object get(Object key) {
Object value; Object value;
value = newTable.get (key); value = newTable.get(key);
if (value != null) if (value != null)
return value; return value;
value = oldTable.get (key); value = oldTable.get(key);
if (value != null) { if (value != null) {
// Move object from old table to new table. // Move object from old table to new table.
newTable.put (key, value); newTable.put(key, value);
oldTable.remove (key); oldTable.remove(key);
return value; return value;
} }
return null; return null;
} }
/// Puts the specified element into the hashtable, using the specified /// Puts the specified element into the hashtable, using the specified
@ -225,60 +236,69 @@ public class CacheMap {
// is equal to null. // is equal to null.
// @see LruHashtable#get // @see LruHashtable#get
// @return the old value of the key, or null if it did not have one. // @return the old value of the key, or null if it did not have one.
public synchronized Object put (Object key, Object value) { public synchronized Object put(Object key, Object value) {
Object oldValue = newTable.put (key, value); Object oldValue = newTable.put(key, value);
if (oldValue != null) if (oldValue != null)
return oldValue; return oldValue;
oldValue = oldTable.get (key); oldValue = oldTable.get(key);
if (oldValue != null) if (oldValue != null)
oldTable.remove( key ); oldTable.remove(key);
// we put a key into newtable that wasn't there before. check if it // we put a key into newtable that wasn't there before. check if it
// grew beyond the threshold // grew beyond the threshold
if (newTable.size() >= threshold) { if (newTable.size() >= threshold) {
// Rotate the tables. // Rotate the tables.
if (app != null) if (app != null)
app.logEvent ("Rotating Cache tables at "+newTable.size()+ app.logEvent("Rotating Cache tables at " + newTable.size() +
"/"+oldTable.size()+" (new/old)"); "/" + oldTable.size() + " (new/old)");
oldTable = newTable; oldTable = newTable;
newTable = new HashMap (eachCapacity, loadFactor); newTable = new HashMap(eachCapacity, loadFactor);
} }
return oldValue; return oldValue;
} }
/// Removes the element corresponding to the key. Does nothing if the /// Removes the element corresponding to the key. Does nothing if the
// key is not present. // key is not present.
// @param key the key that needs to be removed // @param key the key that needs to be removed
// @return the value of key, or null if the key was not found. // @return the value of key, or null if the key was not found.
public synchronized Object remove (Object key) { public synchronized Object remove(Object key) {
Object oldValue = newTable.remove (key); Object oldValue = newTable.remove(key);
if (oldValue == null) if (oldValue == null)
oldValue = oldTable.remove (key); oldValue = oldTable.remove(key);
return oldValue; return oldValue;
} }
/// Clears the hash table so that it has no more elements in it. /// Clears the hash table so that it has no more elements in it.
public synchronized void clear() { public synchronized boolean clear() {
newTable.clear (); newTable.clear();
oldTable.clear (); oldTable.clear();
return true;
} }
/// Set the application to use for debug and profiling output /// Set the application to use for debug and profiling output
public void setApplication (Application app) { public void init(Application app) {
this.app = app; this.app = app;
int cacheSize = Integer.parseInt(app.getProperty("cachesize", "1000"));
setCapacity(cacheSize);
} }
public synchronized Object[] getEntryArray () { /// The app properties have been modified, reload settings
Object[] k1 = newTable.keySet().toArray(); public void updateProperties(Properties props) {
Object[] k2 = oldTable.keySet().toArray(); int cacheSize = Integer.parseInt(props.getProperty("cachesize", "1000"));
Object[] k = new Object[k1.length+k2.length]; setCapacity(cacheSize);
System.arraycopy (k1, 0, k, 0, k1.length);
System.arraycopy (k2, 0, k, k1.length, k2.length);
return k;
} }
public String toString () { public synchronized Object[] getCachedObjects() {
return newTable.toString () + oldTable.toString () + hashCode (); Object[] k1 = newTable.keySet().toArray();
Object[] k2 = oldTable.keySet().toArray();
Object[] k = new Object[k1.length + k2.length];
System.arraycopy(k1, 0, k, 0, k1.length);
System.arraycopy(k2, 0, k, k1.length, k2.length);
return k;
}
public String toString() {
return newTable.toString() + oldTable.toString() + hashCode();
} }
} }