diff --git a/config/alfresco/avm-services-context.xml b/config/alfresco/avm-services-context.xml
index 7fe5c6b781..cbe5dc6368 100644
--- a/config/alfresco/avm-services-context.xml
+++ b/config/alfresco/avm-services-context.xml
@@ -127,6 +127,9 @@
+
+ 200
+
diff --git a/source/java/org/alfresco/repo/avm/LookupCache.java b/source/java/org/alfresco/repo/avm/LookupCache.java
index 9231f84542..538eee1180 100644
--- a/source/java/org/alfresco/repo/avm/LookupCache.java
+++ b/source/java/org/alfresco/repo/avm/LookupCache.java
@@ -3,6 +3,13 @@
*/
package org.alfresco.repo.avm;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
import org.alfresco.repo.avm.util.SimplePath;
import org.apache.log4j.Logger;
@@ -14,6 +21,34 @@ public class LookupCache
{
private static Logger fgLogger = Logger.getLogger(LookupCache.class);
+ /**
+ * The Map of of keys to lookups.
+ */
+ private Map fCache;
+
+ /**
+ * The Map of time stamps to keys.
+ */
+ private SortedMap fTimeStamps;
+
+ /**
+ * The inverse map of keys to timestamps.
+ */
+ private Map fInverseTimeStamps;
+
+ /**
+ * The timestamp to next issue.
+ */
+ private long fTimeStamp;
+
+ /**
+ * The maximum number of lines to have in the cache.
+ */
+ private int fMaxSize;
+
+ /**
+ * Reference to the Node DAO.
+ */
private AVMNodeDAO fAVMNodeDAO;
/**
@@ -21,6 +56,11 @@ public class LookupCache
*/
public LookupCache()
{
+ fCache = new HashMap();
+ fTimeStamps = new TreeMap();
+ fInverseTimeStamps = new HashMap();
+ fTimeStamp = 0L;
+ fMaxSize = 100;
}
/**
@@ -32,9 +72,33 @@ public class LookupCache
fAVMNodeDAO = dao;
}
+ /**
+ * Set the maximum cache size.
+ * @param maxSize
+ */
+ public void setMaxSize(int maxSize)
+ {
+ fMaxSize = maxSize;
+ }
+
+ /**
+ * Lookup a path. Try to fulfill the request from the cache.
+ * @param store The AVMStore.
+ * @param version The versions.
+ * @param path The path we are looking up.
+ * @param write Whether this is a write lookup.
+ * @param includeDeleted
+ * @return
+ */
public Lookup lookup(AVMStore store, int version, SimplePath path,
boolean write, boolean includeDeleted)
{
+ LookupKey key = new LookupKey(version, path, store.getName(), write, includeDeleted);
+ Lookup found = findInCache(key);
+ if (found != null)
+ {
+ return found;
+ }
// Make up a Lookup to hold the results.
if (path.size() == 0)
{
@@ -61,6 +125,7 @@ public class LookupCache
dir = (DirectoryNode)result.getCurrentNode();
if (path.size() == 1 && path.get(0).equals(""))
{
+ updateCache(key, result);
return result;
}
// Now look up each path element in sequence up to one
@@ -89,6 +154,127 @@ public class LookupCache
return null;
}
result.add(child, path.get(path.size() - 1), write);
+ updateCache(key, result);
return result;
}
+
+ /**
+ * Try to find a match in the cache.
+ * @param key The lookup key.
+ * @return A valid for this session Lookup or null if not found.
+ */
+ private synchronized Lookup findInCache(LookupKey key)
+ {
+ return null;
+ }
+
+ /**
+ * Add or update an entry in the cache.
+ * @param key
+ * @param lookup
+ */
+ private synchronized void updateCache(LookupKey key, Lookup lookup)
+ {
+ if (fCache.containsKey(key))
+ {
+ fCache.remove(key);
+ Long oldTime = fInverseTimeStamps.get(key);
+ fInverseTimeStamps.remove(key);
+ fTimeStamps.remove(oldTime);
+ }
+ long timeStamp = fTimeStamp++;
+ fTimeStamps.put(timeStamp, key);
+ fInverseTimeStamps.put(key, timeStamp);
+ fCache.put(key, lookup);
+ if (fCache.size() > fMaxSize)
+ {
+ // Get rid of the oldest entry.
+ Long oldTime = fTimeStamps.firstKey();
+ LookupKey old = fTimeStamps.remove(oldTime);
+ fInverseTimeStamps.remove(old);
+ fCache.remove(old);
+ }
+ }
+
+ /**
+ * Remove a List of entries.
+ * @param keys The List of entries.
+ */
+ private void purgeEntries(List keys)
+ {
+ for (LookupKey key : keys)
+ {
+ fCache.remove(key);
+ Long time = fInverseTimeStamps.remove(key);
+ fTimeStamps.remove(time);
+ }
+ }
+
+ // Following are the cache invalidation calls.
+
+ /**
+ * Called when a simple write operation occurs. This
+ * invalidates all read lookups and all layered lookups.
+ */
+ public synchronized void onWrite(String storeName)
+ {
+ List toDelete = new ArrayList();
+ for (Map.Entry entry : fCache.entrySet())
+ {
+ if ((entry.getKey().getStoreName().equals(storeName) &&
+ !entry.getKey().isWrite()) || entry.getValue().isLayered())
+ {
+ toDelete.add(entry.getKey());
+ }
+ }
+ purgeEntries(toDelete);
+ }
+
+ /**
+ * Called when a delete has occurred in a store. This invalidates both
+ * reads and write lookups in that store.
+ */
+ public synchronized void onDelete(String storeName)
+ {
+ List toDelete = new ArrayList();
+ for (Map.Entry entry : fCache.entrySet())
+ {
+ if (entry.getKey().getStoreName().equals(storeName) ||
+ entry.getValue().isLayered())
+ {
+ toDelete.add(entry.getKey());
+ }
+ }
+ purgeEntries(toDelete);
+ }
+
+ /**
+ * Called when a snapshot occurs in a store. This invalidates write
+ * lookups. Read lookups stay untouched.
+ */
+ public synchronized void onSnapshot(String storeName)
+ {
+ List toDelete = new ArrayList();
+ for (Map.Entry entry : fCache.entrySet())
+ {
+ if ((entry.getKey().getStoreName().equals(storeName) &&
+ entry.getKey().isWrite()) ||
+ entry.getValue().isLayered())
+ {
+ toDelete.add(entry.getKey());
+ }
+ }
+ purgeEntries(toDelete);
+ }
+
+ /**
+ * Called when a rollback has occurred. This invalidates the entire
+ * cache. Heavy handed but quick.
+ */
+ public synchronized void onRollback()
+ {
+ fCache.clear();
+ fTimeStamps.clear();
+ fInverseTimeStamps.clear();
+ }
}
diff --git a/source/java/org/alfresco/repo/avm/LookupKey.java b/source/java/org/alfresco/repo/avm/LookupKey.java
new file mode 100644
index 0000000000..0819e052cf
--- /dev/null
+++ b/source/java/org/alfresco/repo/avm/LookupKey.java
@@ -0,0 +1,130 @@
+/**
+ *
+ */
+package org.alfresco.repo.avm;
+
+import org.alfresco.repo.avm.util.SimplePath;
+
+/**
+ * This is the key by which Lookup's are retrieved from the cache.
+ * @author britt
+ */
+public class LookupKey
+{
+ /**
+ * The name of the store.
+ */
+ private String fStoreName;
+
+ /**
+ * The path being looked up.
+ */
+ private SimplePath fPath;
+
+ /**
+ * The version being looked up.
+ */
+ private int fVersion;
+
+ /**
+ * Whether the lookup is a write lookup.
+ */
+ private boolean fWrite;
+
+ /**
+ * Whether the lookup includes deleted nodes.
+ */
+ private boolean fIncludeDeleted;
+
+ /**
+ * Create one from whole cloth.
+ * @param version The version we're looking under.
+ * @param path The path.
+ * @param storeName The name of the store.
+ * @param write Whether this is a write lookup.
+ * @param includeDeleted Whether this lookup should include deleted items.
+ */
+ public LookupKey(int version,
+ SimplePath path,
+ String storeName,
+ boolean write,
+ boolean includeDeleted)
+ {
+ fVersion = version;
+ fPath = path;
+ fStoreName = storeName;
+ fWrite = write;
+ fIncludeDeleted = includeDeleted;
+ }
+
+ /**
+ * Set the writeness of this key.
+ */
+ public void setWrite(boolean write)
+ {
+ fWrite = write;
+ }
+
+ /**
+ * Get the store name for this key.
+ * @return The store name.
+ */
+ public String getStoreName()
+ {
+ return fStoreName;
+ }
+
+ /**
+ * Is this a write lookup.
+ * @return Whether this is a write lookup.
+ */
+ public boolean isWrite()
+ {
+ return fWrite;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (!(obj instanceof LookupKey))
+ {
+ return false;
+ }
+ LookupKey o = (LookupKey)obj;
+ return fStoreName.equals(o.fStoreName) &&
+ fVersion == o.fVersion &&
+ fPath.equals(o.fPath) &&
+ fWrite == o.fWrite &&
+ fIncludeDeleted == o.fIncludeDeleted;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ int hash = fStoreName.hashCode();
+ hash += fPath.hashCode();
+ hash += fVersion;
+ hash += fWrite ? 1 : 0;
+ hash += fIncludeDeleted ? 1 : 0;
+ return hash;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString()
+ {
+ return fStoreName + ":" + fPath;
+ }
+}