diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index d4171bc4..77a66d2a 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -274,7 +274,9 @@ public final class Application implements IPathElement, Runnable { * Get the application ready to run, initializing the evaluators and type manager. */ public synchronized void init() - throws DatabaseException, ScriptingException, MalformedURLException { + throws DatabaseException, MalformedURLException, + IllegalAccessException, InstantiationException, + ClassNotFoundException { // create and init type mananger typemgr = new TypeManager(this); diff --git a/src/helma/objectmodel/ObjectCache.java b/src/helma/objectmodel/ObjectCache.java new file mode 100644 index 00000000..d19a2205 --- /dev/null +++ b/src/helma/objectmodel/ObjectCache.java @@ -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 keys 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(); + +} diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index 46e4a9a7..d4306b46 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -542,6 +542,15 @@ public final class Node implements INode, Serializable { return dbmap; } + /** + * + * + * @param nmgr + */ + public void setWrappedNodeManager(WrappedNodeManager nmgr) { + this.nmgr = nmgr; + } + /** * * diff --git a/src/helma/objectmodel/db/NodeManager.java b/src/helma/objectmodel/db/NodeManager.java index 2006e85b..3c4ee872 100644 --- a/src/helma/objectmodel/db/NodeManager.java +++ b/src/helma/objectmodel/db/NodeManager.java @@ -35,7 +35,7 @@ import java.util.*; public final class NodeManager { protected Application app; - private CacheMap cache; + private ObjectCache cache; private Replicator replicator; protected IDatabase db; protected IDGenerator idgen; @@ -51,22 +51,17 @@ public final class NodeManager { * created in dbHome if one doesn't already exist. */ public NodeManager(Application app, String dbHome, Properties props) - throws DatabaseException { + throws DatabaseException, ClassNotFoundException, + IllegalAccessException, InstantiationException { 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 = new CacheMap ((int) Math.ceil (cacheSize/0.75f), 0.75f); - cache = new CacheMap(cacheSize, 0.75f); - cache.setApplication(app); - if (cacheSize != 1000) { - app.logEvent("Setting cache size for "+app.getName()+" to " + cacheSize); - } + cache = (ObjectCache) Class.forName(cacheImpl).newInstance(); + cache.init(app); safe = new WrappedNodeManager(this); - // nullNode = new Node (); logSql = "true".equalsIgnoreCase(props.getProperty("logsql")); logReplication = "true".equalsIgnoreCase(props.getProperty("logReplication")); @@ -102,9 +97,8 @@ public final class NodeManager { * app.properties file has been updated. Reread some settings. */ public void updateProperties(Properties props) { - int cacheSize = Integer.parseInt(props.getProperty("cachesize", "1000")); - - cache.setCapacity(cacheSize); + // notify the cache about the properties update + cache.updateProperties(props); logSql = "true".equalsIgnoreCase(props.getProperty("logsql")); 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 */ public Object[] getCacheEntries() { - return cache.getEntryArray(); + return cache.getCachedObjects(); } /** diff --git a/src/helma/objectmodel/db/Transactor.java b/src/helma/objectmodel/db/Transactor.java index f0789944..2d67f14f 100644 --- a/src/helma/objectmodel/db/Transactor.java +++ b/src/helma/objectmodel/db/Transactor.java @@ -241,11 +241,13 @@ public class Transactor extends Thread { int nstate = node.getState(); if (nstate == Node.NEW) { - nmgr.registerNode(node); // register node with nodemanager cache nmgr.insertNode(nmgr.db, txn, node); dirtyDbMappings.add(node.getDbMapping()); node.setState(Node.CLEAN); + // register node with nodemanager cache + nmgr.registerNode(node); + if (replicator != null) { replicator.addNewNode(node); } @@ -260,6 +262,9 @@ public class Transactor extends Thread { } node.setState(Node.CLEAN); + // update node with nodemanager cache + nmgr.registerNode(node); + if (replicator != null) { replicator.addModifiedNode(node); } @@ -270,6 +275,8 @@ public class Transactor extends Thread { } else if (nstate == Node.DELETED) { nmgr.deleteNode(nmgr.db, txn, node); dirtyDbMappings.add(node.getDbMapping()); + + // remove node from nodemanager cache nmgr.evictNode(node); if (replicator != null) { diff --git a/src/helma/util/CacheMap.java b/src/helma/util/CacheMap.java index 8513ebe4..322f1abf 100644 --- a/src/helma/util/CacheMap.java +++ b/src/helma/util/CacheMap.java @@ -30,7 +30,10 @@ package helma.util; import java.util.HashMap; +import java.util.Properties; + import helma.framework.core.Application; +import helma.objectmodel.ObjectCache; /// A Hashtable that expires least-recently-used objects. @@ -44,7 +47,7 @@ import helma.framework.core.Application; //

// @see java.util.Hashtable -public class CacheMap { +public class CacheMap implements ObjectCache { // Load factor. private float loadFactor; @@ -62,6 +65,14 @@ public class CacheMap { // the application to output messages to 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 // capacity and the specified load factor. // Unlike a plain Hashtable, an LruHashtable will never grow or @@ -73,25 +84,25 @@ public class CacheMap { // is less than or equal to zero. // @exception IllegalArgumentException If the load factor is // less than or equal to zero. - public CacheMap (int initialCapacity, float loadFactor) { - // 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 - // is for type conformance. So, make a parent hash table of minimum - // size and then ignore it. - if ( initialCapacity <= 0 || loadFactor <= 0.0 ) - throw new IllegalArgumentException(); - this.loadFactor = loadFactor; - // table rotation threshold: we allow each table to gain - // initialCapacity/2 entries. - threshold = initialCapacity / 2; - // We deliberately choose the initial capacity of tables large - // enough that it can hold threshold entries without being rehashed, - // in other words, make sure our threshold for table rotation is lower - // than that of the underlying HashMap for table rehashing. - eachCapacity = (int) (threshold / loadFactor) + 2; - // create tables - oldTable = new HashMap (); - newTable = new HashMap (eachCapacity, loadFactor); + public CacheMap(int initialCapacity, float loadFactor) { + // 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 + // is for type conformance. So, make a parent hash table of minimum + // size and then ignore it. + if (initialCapacity <= 0 || loadFactor <= 0.0) + throw new IllegalArgumentException(); + this.loadFactor = loadFactor; + // table rotation threshold: we allow each table to gain + // initialCapacity/2 entries. + threshold = initialCapacity / 2; + // We deliberately choose the initial capacity of tables large + // enough that it can hold threshold entries without being rehashed, + // in other words, make sure our threshold for table rotation is lower + // than that of the underlying HashMap for table rehashing. + eachCapacity = (int) (threshold / loadFactor) + 2; + // create tables + oldTable = new HashMap(); + newTable = new HashMap(eachCapacity, loadFactor); } /// 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 // shrink from this initial capacity. // @param initialCapacity the initial number of buckets - public CacheMap (int initialCapacity) { - this (initialCapacity, 0.75F); + public CacheMap(int initialCapacity) { + this(initialCapacity, 0.75F); } /// Returns the number of elements contained in the hashtable. public int size() { - return newTable.size() + oldTable.size(); + return newTable.size() + oldTable.size(); } /// Returns true if the hashtable contains no elements. public boolean isEmpty() { - return size() == 0; + return size() == 0; } /// Set the capacity of the CacheMap public void setCapacity(int capacity) { - // table rotation threshold: we allow each table to gain - // initialCapacity/2 entries. - int newThreshold = capacity / 2; - if (newThreshold != threshold) { - if (app != null) - app.logEvent ("Setting cache capacity to "+capacity); - updateThreshold (newThreshold); - } + // table rotation threshold: we allow each table to gain + // initialCapacity/2 entries. + int newThreshold = capacity / 2; + if (newThreshold != threshold) { + if (app != null) + app.logEvent("Setting cache capacity to " + capacity); + updateThreshold(newThreshold); + } } - private synchronized void updateThreshold (int newThreshold) { - threshold = newThreshold; - eachCapacity = (int) (threshold / loadFactor) + 2; - // if newtable is larger than threshold, rotate. - if (newTable.size() > threshold) { - oldTable = newTable; - newTable = new HashMap (eachCapacity, loadFactor); - } + private synchronized void updateThreshold(int newThreshold) { + threshold = newThreshold; + eachCapacity = (int) (threshold / loadFactor) + 2; + // if newtable is larger than threshold, rotate. + if (newTable.size() > threshold) { + oldTable = newTable; + newTable = new HashMap(eachCapacity, loadFactor); + } } /// 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 // for is equal to null. // @see LruHashtable#containsKey - public synchronized boolean containsValue (Object value) { - if (newTable.containsValue (value)) - return true; - if (oldTable.containsValue (value)) { - // 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 - // 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 - // one. Yuck. For now we just punt. Anyway, contains() is - // probably not a commonly-used operation. - return true; - } - return false; + public synchronized boolean containsValue(Object value) { + if (newTable.containsValue(value)) + return true; + if (oldTable.containsValue(value)) { + // 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 + // 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 + // one. Yuck. For now we just punt. Anyway, contains() is + // probably not a commonly-used operation. + return true; + } + return false; } /// Returns true if the collection contains an element for the key. // @param key the key that we are looking for // @see LruHashtable#contains - public synchronized boolean containsKey (Object key) { - if (newTable.containsKey(key)) - return true; - if (oldTable.containsKey (key)) { - // Move object from old table to new table. - Object value = oldTable.get (key); - newTable.put (key, value); - oldTable.remove (key); - return true; - } - return false; + public synchronized boolean containsKey(Object key) { + if (newTable.containsKey(key)) + return true; + if (oldTable.containsKey(key)) { + // Move object from old table to new table. + Object value = oldTable.get(key); + newTable.put(key, value); + oldTable.remove(key); + return true; + } + return false; } /// Returns the number of keys in object array keys that @@ -178,21 +189,21 @@ public class CacheMap { // 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 LruHashtable#contains - public synchronized int containsKeys (Object[] keys) { - int notfound = 0; - for (int i=0; i= threshold) { - // Rotate the tables. - if (app != null) - app.logEvent ("Rotating Cache tables at "+newTable.size()+ - "/"+oldTable.size()+" (new/old)"); - oldTable = newTable; - newTable = new HashMap (eachCapacity, loadFactor); - } - return oldValue; + Object oldValue = newTable.put(key, value); + if (oldValue != null) + return oldValue; + oldValue = oldTable.get(key); + if (oldValue != null) + oldTable.remove(key); + // we put a key into newtable that wasn't there before. check if it + // grew beyond the threshold + if (newTable.size() >= threshold) { + // Rotate the tables. + if (app != null) + app.logEvent("Rotating Cache tables at " + newTable.size() + + "/" + oldTable.size() + " (new/old)"); + oldTable = newTable; + newTable = new HashMap(eachCapacity, loadFactor); + } + return oldValue; } /// 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. - public synchronized Object remove (Object key) { - Object oldValue = newTable.remove (key); - if (oldValue == null) - oldValue = oldTable.remove (key); - return oldValue; + public synchronized Object remove(Object key) { + Object oldValue = newTable.remove(key); + if (oldValue == null) + oldValue = oldTable.remove(key); + return oldValue; } /// Clears the hash table so that it has no more elements in it. - public synchronized void clear() { - newTable.clear (); - oldTable.clear (); + public synchronized boolean clear() { + newTable.clear(); + oldTable.clear(); + return true; } /// Set the application to use for debug and profiling output - public void setApplication (Application app) { - this.app = app; + public void init(Application app) { + this.app = app; + int cacheSize = Integer.parseInt(app.getProperty("cachesize", "1000")); + setCapacity(cacheSize); } - public synchronized Object[] getEntryArray () { - 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; + /// The app properties have been modified, reload settings + public void updateProperties(Properties props) { + int cacheSize = Integer.parseInt(props.getProperty("cachesize", "1000")); + setCapacity(cacheSize); } - public String toString () { - return newTable.toString () + oldTable.toString () + hashCode (); + public synchronized Object[] getCachedObjects() { + 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(); } }