Author: mircea.markus
Date: 2007-09-26 06:15:08 -0400 (Wed, 26 Sep 2007)
New Revision: 4507
Added:
core/trunk/src/main/java/org/jboss/cache/MetadataNodeWrapper.java
core/trunk/src/test/java/org/jboss/cache/api/ResidentNodesTest.java
Modified:
core/trunk/src/main/java/org/jboss/cache/AbstractNode.java
core/trunk/src/main/java/org/jboss/cache/CacheImpl.java
core/trunk/src/main/java/org/jboss/cache/Node.java
core/trunk/src/main/java/org/jboss/cache/NodeSPI.java
core/trunk/src/main/java/org/jboss/cache/Region.java
core/trunk/src/main/java/org/jboss/cache/RegionImpl.java
core/trunk/src/main/java/org/jboss/cache/RegionManager.java
core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java
core/trunk/src/main/java/org/jboss/cache/interceptors/EvictionInterceptor.java
core/trunk/src/main/java/org/jboss/cache/marshall/MethodCall.java
core/trunk/src/main/resources/META-INF/local-lru-eviction-service.xml
Log:
JBCACHE-1154
Modified: core/trunk/src/main/java/org/jboss/cache/AbstractNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/AbstractNode.java 2007-09-25 20:55:20 UTC
(rev 4506)
+++ core/trunk/src/main/java/org/jboss/cache/AbstractNode.java 2007-09-26 10:15:08 UTC
(rev 4507)
@@ -15,7 +15,13 @@
protected boolean deleted;
protected Map<Object, Node<K, V>> children;
protected Fqn fqn;
+ protected boolean resident;
+ /**
+ * Under this key the {@link #resident} attribute will be keept within the data map.
+ */
+ protected static final String INTERNAL_IS_RESIDENT =
"_jbosscache.internal.resident";
+
public boolean isDeleted()
{
return deleted;
@@ -41,6 +47,26 @@
}
}
+ public void setResident(boolean resident)
+ {
+ if (!resident)
+ {
+ remove((K)INTERNAL_IS_RESIDENT);
+ } else
+ {
+ put((K)INTERNAL_IS_RESIDENT, (V)"True");
+ }
+ }
+
+
+ public boolean isResident()
+ {
+ //hack - see setResident internals for details
+ return "True".equals(get((K)INTERNAL_IS_RESIDENT));
+ }
+
+
+
public boolean equals(Object another)
{
if (another instanceof AbstractNode)
@@ -51,6 +77,7 @@
return false;
}
+
public int hashCode()
{
return fqn.hashCode();
Modified: core/trunk/src/main/java/org/jboss/cache/CacheImpl.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/CacheImpl.java 2007-09-25 20:55:20 UTC (rev
4506)
+++ core/trunk/src/main/java/org/jboss/cache/CacheImpl.java 2007-09-26 10:15:08 UTC (rev
4507)
@@ -281,7 +281,7 @@
*/
public NodeSPI<K, V> getRoot()
{
- return root;
+ return MetadataNodeWrapper.wrapNodeData(root);
}
/**
@@ -4438,12 +4438,17 @@
public void evict(Fqn<?> fqn, boolean recursive)
{
+ //this method should be called by eviction thread only, so no transaction -
expected (sec param is false)
+ Node<K, V> node = peek(fqn, false);
+ if (node != null && node.isResident())
+ {
+ return;
+ }
if (recursive)
{
- Node<K, V> n = get(fqn);
- if (n != null)
+ if (node != null)
{
- evictChildren((NodeSPI<K, V>) n);
+ evictChildren((NodeSPI<K, V>) node);
}
}
else
Added: core/trunk/src/main/java/org/jboss/cache/MetadataNodeWrapper.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/MetadataNodeWrapper.java
(rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/MetadataNodeWrapper.java 2007-09-26 10:15:08
UTC (rev 4507)
@@ -0,0 +1,420 @@
+package org.jboss.cache;
+
+import org.jboss.cache.Node;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.optimistic.DataVersion;
+import org.jboss.cache.lock.NodeLock;
+import org.jboss.cache.transaction.GlobalTransaction;
+
+import java.util.*;
+
+/**
+ * Metadata information from class <tt>Node</tt> (metadata = all
configuration information except attribute map,
+ * e.g. Node.isResident) is internally also held in the internal attribute map. The role
of this class is not the
+ * expose those internal map information to the end user - so it wraps such a node and
expose it to the the user
+ * removing wrapped info.
+ *
+ * @author <a href="mailto:mircea.markus@jboss.com">Mircea
Markus</a>
+ * @since 2.1.0
+ */
+public class MetadataNodeWrapper implements NodeSPI
+{
+
+ public static final String JBOSSCACHE_INTERNAL_RESIDENT =
"_jbosscache.internal.resident";
+
+ private Node wrappedNode;
+
+ public MetadataNodeWrapper(Node wrappedNode)
+ {
+ this.wrappedNode = wrappedNode;
+ }
+
+ public NodeSPI getParent()
+ {
+ return wrapNodeData(wrappedNode);
+ }
+
+ public Set getChildren()
+ {
+ Iterator it = wrappedNode.getChildren().iterator();
+ Set result = new HashSet();
+ while (it.hasNext())
+ {
+ result.add(wrapNodeData((Node) it.next()));
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ public Set getChildrenNames()
+ {
+ return wrappedNode.getChildrenNames();
+ }
+
+ public Map getData()
+ {
+ Map realData = wrappedNode.getData();
+ if (!realData.containsKey(JBOSSCACHE_INTERNAL_RESIDENT))
+ {
+ return realData;
+ } else
+ {
+ Map clearedData = new HashMap(realData);
+ clearedData.remove(JBOSSCACHE_INTERNAL_RESIDENT);
+ return Collections.unmodifiableMap(clearedData);
+ }
+ }
+
+ public Set getKeys()
+ {
+ Set realSet = wrappedNode.getKeys();
+ if (!realSet.contains(JBOSSCACHE_INTERNAL_RESIDENT))
+ {
+ return realSet;
+ } else
+ {
+ Set clearedData = new HashSet(realSet);
+ clearedData.remove(JBOSSCACHE_INTERNAL_RESIDENT);
+ return Collections.unmodifiableSet(clearedData);
+ }
+ }
+
+ public Fqn getFqn()
+ {
+ return wrappedNode.getFqn();
+ }
+
+ public Node addChild(Fqn f)
+ {
+ return wrapNodeData(wrappedNode.addChild(f));
+ }
+
+ public boolean removeChild(Fqn f)
+ {
+ return wrappedNode.removeChild(f);
+ }
+
+ public boolean removeChild(Object childName)
+ {
+ return wrappedNode.removeChild(childName);
+ }
+
+ public Node getChild(Fqn f)
+ {
+ return wrapNodeData(wrappedNode.getChild(f));
+ }
+
+ public Node getChild(Object name)
+ {
+ return wrapNodeData(wrappedNode.getChild(name));
+ }
+
+ public Object put(Object key, Object value)
+ {
+ return wrappedNode.put(key, value);
+ }
+
+ public Object putIfAbsent(Object key, Object value)
+ {
+ return wrappedNode.putIfAbsent(key, value);
+ }
+
+ public Object replace(Object key, Object value)
+ {
+ return wrappedNode.replace(key, value);
+ }
+
+ public boolean replace(Object key, Object oldValue, Object newValue)
+ {
+ return wrappedNode.replace(key, oldValue, newValue);
+ }
+
+ public void putAll(Map map)
+ {
+ wrappedNode.putAll(map);
+ }
+
+ public void replaceAll(Map map)
+ {
+ wrappedNode.replaceAll(map);
+ }
+
+ public Object get(Object key)
+ {
+ return wrappedNode.get(key);
+ }
+
+ public Object remove(Object key)
+ {
+ if (JBOSSCACHE_INTERNAL_RESIDENT.equals(key))
+ {
+ throw new IllegalArgumentException("The '" +
JBOSSCACHE_INTERNAL_RESIDENT + "' is an reserved key, please " +
+ "refrain using it!");
+ }
+ return wrappedNode.remove(key);
+ }
+
+ public void clearData()
+ {
+ if (wrappedNode.get(JBOSSCACHE_INTERNAL_RESIDENT) == null)
+ {
+ wrappedNode.clearData();
+ } else
+ {
+ Iterator it = wrappedNode.getKeys().iterator();
+ while (it.hasNext())
+ {
+ Object key = it.next();
+ if (!JBOSSCACHE_INTERNAL_RESIDENT.equals(key))
+ {
+ wrappedNode.remove(key);
+ }
+ }
+ }
+ }
+
+ public int dataSize()
+ {
+ if (wrappedNode.getKeys().contains(JBOSSCACHE_INTERNAL_RESIDENT))
+ {
+ return wrappedNode.dataSize() - 1;
+ }
+ return wrappedNode.dataSize();
+ }
+
+ public boolean hasChild(Fqn f)
+ {
+ return wrappedNode.hasChild(f);
+ }
+
+ public boolean hasChild(Object o)
+ {
+ return wrappedNode.hasChild(o);
+ }
+
+ public boolean isValid()
+ {
+ return wrappedNode.isValid();
+ }
+
+ public boolean isResident()
+ {
+ return wrappedNode.isResident();
+ }
+
+ public void setResident(boolean resident)
+ {
+ wrappedNode.setResident(resident);
+ }
+
+ public static MetadataNodeWrapper wrapNodeData(Node node)
+ {
+ if (node == null)
+ {
+ return null;
+ }
+ if (node instanceof MetadataNodeWrapper)
+ {
+ return (MetadataNodeWrapper) node;
+ } else
+ {
+ return new MetadataNodeWrapper(node);
+ }
+ }
+
+
+ public boolean isChildrenLoaded()
+ {
+ return ((NodeSPI) wrappedNode).isChildrenLoaded();
+ }
+
+ public void setChildrenLoaded(boolean loaded)
+ {
+ ((NodeSPI) wrappedNode).setChildrenLoaded(loaded);
+ }
+
+ public boolean isDataLoaded()
+ {
+ return ((NodeSPI) wrappedNode).isDataLoaded();
+ }
+
+ public void setDataLoaded(boolean dataLoaded)
+ {
+ ((NodeSPI) wrappedNode).setDataLoaded(dataLoaded);
+ }
+
+ public Map getChildrenMapDirect()
+ {
+ return ((NodeSPI) wrappedNode).getChildrenMapDirect();
+ }
+
+ public void setChildrenMapDirect(Map children)
+ {
+ ((NodeSPI) wrappedNode).setChildrenMapDirect(children);
+ }
+
+ public NodeSPI getOrCreateChild(Object name, GlobalTransaction tx)
+ {
+ return ((NodeSPI) wrappedNode).getOrCreateChild(name, tx);
+ }
+
+ public NodeLock getLock()
+ {
+ return ((NodeSPI) wrappedNode).getLock();
+ }
+
+ public void setFqn(Fqn f)
+ {
+ ((NodeSPI) wrappedNode).setFqn(f);
+ }
+
+ public boolean isDeleted()
+ {
+ return ((NodeSPI) wrappedNode).isDeleted();
+ }
+
+ public void markAsDeleted(boolean marker)
+ {
+ ((NodeSPI) wrappedNode).markAsDeleted(marker);
+ }
+
+ public void markAsDeleted(boolean marker, boolean recursive)
+ {
+ ((NodeSPI) wrappedNode).markAsDeleted(marker, recursive);
+ }
+
+ public void addChild(Object nodeName, Node nodeToAdd)
+ {
+ ((NodeSPI) wrappedNode).addChild(nodeName, nodeToAdd);
+ }
+
+ public void printDetails(StringBuffer sb, int indent)
+ {
+ ((NodeSPI) wrappedNode).printDetails(sb, indent);
+ }
+
+ public void print(StringBuffer sb, int indent)
+ {
+ ((NodeSPI) wrappedNode).print(sb, indent);
+ }
+
+ public void setVersion(DataVersion version)
+ {
+ ((NodeSPI) wrappedNode).setVersion(version);
+ }
+
+ public DataVersion getVersion()
+ {
+ return ((NodeSPI) wrappedNode).getVersion();
+ }
+
+ public Set getChildrenDirect()
+ {
+ return ((NodeSPI) wrappedNode).getChildrenDirect();
+ }
+
+ public void removeChildrenDirect()
+ {
+ ((NodeSPI) wrappedNode).removeChildrenDirect();
+ }
+
+ public Set getChildrenDirect(boolean includeMarkedAsDeleted)
+ {
+ return ((NodeSPI) wrappedNode).getChildrenDirect(includeMarkedAsDeleted);
+ }
+
+ public NodeSPI getChildDirect(Object childName)
+ {
+ return ((NodeSPI) wrappedNode).getChildDirect(childName);
+ }
+
+ public NodeSPI addChildDirect(Fqn childName)
+ {
+ return ((NodeSPI) wrappedNode).addChildDirect(childName);
+ }
+
+ public void addChildDirect(NodeSPI child)
+ {
+ ((NodeSPI) wrappedNode).addChildDirect(child);
+ }
+
+ public NodeSPI getChildDirect(Fqn childName)
+ {
+ return ((NodeSPI) wrappedNode).getChildDirect(childName);
+ }
+
+ public boolean removeChildDirect(Fqn fqn)
+ {
+ return ((NodeSPI) wrappedNode).removeChildDirect(fqn);
+ }
+
+ public boolean removeChildDirect(Object childName)
+ {
+ return ((NodeSPI) wrappedNode).removeChildDirect(childName);
+ }
+
+ public Object removeDirect(Object key)
+ {
+ return ((NodeSPI) wrappedNode).removeDirect(key);
+ }
+
+ public Object putDirect(Object key, Object value)
+ {
+ return ((NodeSPI) wrappedNode).putDirect(key, value);
+ }
+
+ public void putAllDirect(Map data)
+ {
+ ((NodeSPI) wrappedNode).putAllDirect(data);
+ }
+
+ public Map getDataDirect()
+ {
+ return ((NodeSPI) wrappedNode).getDataDirect();
+ }
+
+ public Object getDirect(Object key)
+ {
+ return ((NodeSPI) wrappedNode).getDirect(key);
+ }
+
+ public void clearDataDirect()
+ {
+ ((NodeSPI) wrappedNode).clearDataDirect();
+ }
+
+ public Set getKeysDirect()
+ {
+ return ((NodeSPI) wrappedNode).getKeysDirect();
+ }
+
+ public Set getChildrenNamesDirect()
+ {
+ return ((NodeSPI) wrappedNode).getChildrenNamesDirect();
+ }
+
+ public CacheSPI getCache()
+ {
+ return ((NodeSPI) wrappedNode).getCache();
+ }
+
+ public boolean hasChildrenDirect()
+ {
+ return ((NodeSPI) wrappedNode).hasChildrenDirect();
+ }
+
+ public boolean isResidentDirect()
+ {
+ return ((NodeSPI) wrappedNode).isResidentDirect();
+ }
+
+
+ public boolean isLockForChildInsertRemove()
+ {
+ return wrappedNode.isLockForChildInsertRemove();
+ }
+
+ public void setLockForChildInsertRemove(boolean lockForChildInsertRemove)
+ {
+ wrappedNode.setLockForChildInsertRemove(lockForChildInsertRemove);
+ }
+}
Modified: core/trunk/src/main/java/org/jboss/cache/Node.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/Node.java 2007-09-25 20:55:20 UTC (rev 4506)
+++ core/trunk/src/main/java/org/jboss/cache/Node.java 2007-09-26 10:15:08 UTC (rev 4507)
@@ -277,7 +277,21 @@
*/
boolean isValid();
+ /**
+ * Nodes marked resident would be ignored by the eviction algorithms. E.g. if the
algorithm is
+ * "keep LRU 10 nodes" - the resident nodes won't be counted within
those 10 nodes,
+ * and also won't be evicted when the threshold is reached.
+ * N.B. calling this method won't have any effect on node's eviction, e.g. we
won't consider this node as being
+ * 'used' in a LRU scenario
+ */
+ boolean isResident();
+
/**
+ * @see #isResident()
+ */
+ void setResident(boolean resident);
+
+ /**
* Tests whether this node is configured to be exclusively locked when inserting or
removing children.
* <p />
* The default
@@ -297,5 +311,4 @@
* @since 2.1.0
*/
void setLockForChildInsertRemove(boolean lockForChildInsertRemove);
-
}
Modified: core/trunk/src/main/java/org/jboss/cache/NodeSPI.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/NodeSPI.java 2007-09-25 20:55:20 UTC (rev
4506)
+++ core/trunk/src/main/java/org/jboss/cache/NodeSPI.java 2007-09-26 10:15:08 UTC (rev
4507)
@@ -437,4 +437,10 @@
* @return true if the node has one or more child nodes; false otherwise.
*/
boolean hasChildrenDirect();
+
+
+ /**
+ * Same as {@link #isResident()} but it bypasses the interceptors chain.
+ */
+ boolean isResidentDirect();
}
Modified: core/trunk/src/main/java/org/jboss/cache/Region.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/Region.java 2007-09-25 20:55:20 UTC (rev
4506)
+++ core/trunk/src/main/java/org/jboss/cache/Region.java 2007-09-26 10:15:08 UTC (rev
4507)
@@ -168,6 +168,8 @@
*
* @param fqn Fqn of the node.
* @see #unmarkNodeCurrentlyInUse(Fqn)
+ * @deprecated you are now able to specify Node.setResident which has (almost ->
i.e. you cannot specify a timeout)
+ * the same behavior. That metod also replicates the change clusterwise
*/
void markNodeCurrentlyInUse(Fqn fqn, long timeout);
@@ -176,6 +178,8 @@
*
* @param fqn Fqn of the node.
* @see #markNodeCurrentlyInUse(Fqn,long)
+ * @deprecated you are now able to specify Node.isResident which has (almost) the same
behavior. That metod also
+ * replicates the change clusterwise
*/
void unmarkNodeCurrentlyInUse(Fqn fqn);
Modified: core/trunk/src/main/java/org/jboss/cache/RegionImpl.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/RegionImpl.java 2007-09-25 20:55:20 UTC (rev
4506)
+++ core/trunk/src/main/java/org/jboss/cache/RegionImpl.java 2007-09-26 10:15:08 UTC (rev
4507)
@@ -149,7 +149,10 @@
" You will need to reduce the wakeUpIntervalSeconds
parameter.");
}
- nodeEventQueue.put(event);
+ if (!regionManager.isNodeResident(event.getFqn()))
+ {
+ nodeEventQueue.put(event);
+ }
}
catch (InterruptedException e)
{
Modified: core/trunk/src/main/java/org/jboss/cache/RegionManager.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/RegionManager.java 2007-09-25 20:55:20 UTC
(rev 4506)
+++ core/trunk/src/main/java/org/jboss/cache/RegionManager.java 2007-09-26 10:15:08 UTC
(rev 4507)
@@ -813,4 +813,19 @@
return this.cache;
}
+ /**
+ * Will check to see if the given node is resident or not.
+ * N.B. This method MUST not raise any eviction events (i.e. do not call any
intercepted methods on the cache, but
+ * rather use direct calls). Otherwise an endless loop of events being
generated/consumed will take place, as this
+ * is called in the context of consuming events.
+ */
+ public boolean isNodeResident(Fqn fqn)
+ {
+ if (cache == null)
+ {
+ return false;
+ }
+ NodeSPI theNode = cache.getRoot().getChildDirect(fqn);
+ return theNode == null ? false : theNode.isResidentDirect();
+ }
}
Modified: core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java 2007-09-25 20:55:20 UTC
(rev 4506)
+++ core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java 2007-09-26 10:15:08 UTC
(rev 4507)
@@ -681,6 +681,11 @@
return children != null && children.size() != 0;
}
+ public boolean isResidentDirect()
+ {
+ return getDataDirect().containsKey(AbstractNode.INTERNAL_IS_RESIDENT);
+ }
+
public Set<NodeSPI<K, V>> getChildrenDirect(boolean
includeMarkedForRemoval)
{
if (includeMarkedForRemoval)
Modified: core/trunk/src/main/java/org/jboss/cache/interceptors/EvictionInterceptor.java
===================================================================
---
core/trunk/src/main/java/org/jboss/cache/interceptors/EvictionInterceptor.java 2007-09-25
20:55:20 UTC (rev 4506)
+++
core/trunk/src/main/java/org/jboss/cache/interceptors/EvictionInterceptor.java 2007-09-26
10:15:08 UTC (rev 4507)
@@ -9,11 +9,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.jboss.cache.CacheSPI;
-import org.jboss.cache.Fqn;
-import org.jboss.cache.InvocationContext;
-import org.jboss.cache.Region;
-import org.jboss.cache.RegionManager;
+import org.jboss.cache.*;
import org.jboss.cache.eviction.EvictedEventNode;
import org.jboss.cache.eviction.NodeEventType;
import org.jboss.cache.marshall.MethodCall;
@@ -98,6 +94,8 @@
// is thrown, this interceptor is terminated. there is no need for explicit
rollback logic.
this.updateNode(m, ret);
+
+
if (log.isTraceEnabled())
{
log.trace("Finished invoking EvictionInterceptor");
@@ -200,6 +198,10 @@
Object args[] = mc.getArgs();
Fqn fqn = (Fqn) args[0];
Object key = args[1];
+ /*see hack comment inside PutKeyEvictionMethodHandler.extractEvictedEventNode
*/
+ if (MetadataNodeWrapper.JBOSSCACHE_INTERNAL_RESIDENT.equals(key)) {
+ return null;
+ }
if (fqn != null && key != null
&& !EvictionInterceptor.this.canIgnoreEvent(fqn,
NodeEventType.VISIT_NODE_EVENT))
{
@@ -328,11 +330,21 @@
protected class PutKeyEvictionMethodHandler implements EvictionMethodHandler
{
+
public EvictedEventNode extractEvictedEventNode(MethodCall mc, Object retVal)
{
Object[] args = mc.getArgs();
Fqn fqn = (Fqn) args[1];
Object key = args[2];
+ //hack - for seting an node as resident we won't create a an eviction
event.
+ //this is an implementation restriction, as this method(i.e. isEvited) is
intensively called internally
+ //by the eviction code, which would result in events being produced -> that
when consumed would produce
+ // other events (due to call to Node.isResident). The isResidentDirect call
cannot be used here
+ //the resident information is stored in the node attributes map directly
+ if (MetadataNodeWrapper.JBOSSCACHE_INTERNAL_RESIDENT.equals(key))
+ {
+ return null;
+ }
if (fqn != null && key != null
&& !EvictionInterceptor.this.canIgnoreEvent(fqn,
NodeEventType.ADD_ELEMENT_EVENT))
{
Modified: core/trunk/src/main/java/org/jboss/cache/marshall/MethodCall.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/marshall/MethodCall.java 2007-09-25 20:55:20
UTC (rev 4506)
+++ core/trunk/src/main/java/org/jboss/cache/marshall/MethodCall.java 2007-09-26 10:15:08
UTC (rev 4507)
@@ -92,4 +92,16 @@
return ret.toString();
}
+
+
+ public Object invoke(Object object) throws Throwable
+ {
+ return super.invoke(object); //To change body of overridden methods use File |
Settings | File Templates.
+ }
+
+
+ public Object invoke(Object object, Object[] objects) throws Throwable
+ {
+ return super.invoke(object, objects); //To change body of overridden methods use
File | Settings | File Templates.
+ }
}
Modified: core/trunk/src/main/resources/META-INF/local-lru-eviction-service.xml
===================================================================
--- core/trunk/src/main/resources/META-INF/local-lru-eviction-service.xml 2007-09-25
20:55:20 UTC (rev 4506)
+++ core/trunk/src/main/resources/META-INF/local-lru-eviction-service.xml 2007-09-26
10:15:08 UTC (rev 4507)
@@ -106,6 +106,11 @@
<attribute name="timeToLiveSeconds">8</attribute>
<attribute name="maxAgeSeconds">10</attribute>
</region>
+ <region name="/residentNodesTest">
+ <attribute name="maxNodes">3</attribute>
+ <attribute
name="timeToLiveSeconds">800000</attribute>
+ <attribute name="maxAgeSeconds">100000</attribute>
+ </region>
</config>
</attribute>
</mbean>
Added: core/trunk/src/test/java/org/jboss/cache/api/ResidentNodesTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/ResidentNodesTest.java
(rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/ResidentNodesTest.java 2007-09-26
10:15:08 UTC (rev 4507)
@@ -0,0 +1,219 @@
+package org.jboss.cache.api;
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.AfterMethod;
+import static org.testng.Assert.*;
+import static org.testng.Assert.assertNotNull;
+import org.jboss.cache.*;
+import org.jboss.cache.misc.TestingUtil;
+import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
+import org.jboss.cache.config.CacheLoaderConfig;
+import org.jboss.cache.config.Configuration;
+import org.jboss.cache.loader.DummyInMemoryCacheLoader;
+import org.jboss.cache.loader.DummyCacheLoader;
+import org.jboss.cache.lock.IsolationLevel;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Tester class for Node.isResident functionality.
+ *
+ * @author <a href="mailto:mircea.markus@jboss.com">Mircea
Markus</a>
+ * @since 2.1.0
+ */
+@Test(groups = {"functional"})
+public class ResidentNodesTest
+{
+
+ private CacheImpl<Object, Object> cache;
+
+ private final String TEST_NODES_ROOT = "residentNodesTest";
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp()
+ {
+ cache = (CacheImpl<Object, Object>)
DefaultCacheFactory.getInstance().createCache("META-INF/local-lru-eviction-service.xml",
false);
+ cache.getConfiguration().getEvictionConfig().setWakeupIntervalSeconds(1);
+
cache.getConfiguration().setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
+ CacheLoaderConfig.IndividualCacheLoaderConfig indCLConfig = new
CacheLoaderConfig.IndividualCacheLoaderConfig();
+ indCLConfig.setClassName(DummyInMemoryCacheLoader.class.getName());
+ List<CacheLoaderConfig.IndividualCacheLoaderConfig> indCLConfigs = new
ArrayList<CacheLoaderConfig.IndividualCacheLoaderConfig>();
+ indCLConfigs.add(indCLConfig);
+ CacheLoaderConfig cacheLoaderConfig = new CacheLoaderConfig();
+ cacheLoaderConfig.setIndividualCacheLoaderConfigs(indCLConfigs);
+ cache.getConfiguration().setCacheLoaderConfig(cacheLoaderConfig);
+ cache.start();
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() throws Exception
+ {
+ cache.stop();
+ }
+
+ /**
+ * Mark some nodes as resident and show that they won't get evicted,
+ * even if normally scenario they would
+ */
+ public void testHappyFlow() throws InterruptedException
+ {
+ cache.put(getSubFqn("/a"), "k_a", "v_a");
+ cache.get(getSubFqn("/a")).setResident(true);
+ cache.put(getSubFqn("/b"), "k_b", "v_b");
+ cache.get(getSubFqn("/b")).setResident(true);
+ cache.put(getSubFqn("/c"), "k_c", "v_c");
+ cache.put(getSubFqn("/d"), "k_d", "v_d");
+ cache.put(getSubFqn("/e"), "k_e", "v_e");
+ cache.put(getSubFqn("/f"), "k_f", "v_f");
+ cache.put(getSubFqn("/g"), "k_g", "v_g");
+ cache.put(getSubFqn("/h"), "k_h", "v_h");
+ cache.put(getSubFqn("/i"), "k_i", "v_i");
+
+ Thread.sleep(3000);//so that eviction is activated
+
+ assertTrue(cache.exists(getSubFqn("/a")));
+ assertTrue(cache.exists(getSubFqn("/b")));
+ assertFalse(cache.exists(getSubFqn("/c")));
+ assertFalse(cache.exists(getSubFqn("/d")));
+ assertFalse(cache.exists(getSubFqn("/e")));
+ assertFalse(cache.exists(getSubFqn("/f")));
+
+ //only last three used are not evicted
+ assertTrue(cache.exists(getSubFqn("/g")));
+ assertTrue(cache.exists(getSubFqn("/h")));
+ assertTrue(cache.exists(getSubFqn("/i")));
+
+ }
+
+ /**
+ * The 'resident' attribute of the node is kept within node's attribute
map.
+ * This information won't be visible to the ouside clients if they are not using
the
+ * resident attribute. This would ensure a 100% backward comatibility. New clients
would need to be aware of this
+ * extra attribute, though.
+ */
+ public void testInternalStateNotVisibleOutside()
+ {
+ cache.put(getSubFqn("/a"), "k_a", "v_a");
+ cache.get(getSubFqn("/a")).setResident(true);
+
assertEquals(cache.getRoot().getChild(getSubFqn("/a")).getData().keySet().size(),
1);
+ assertEquals(cache.getRoot().getChild(getSubFqn("/a")).getKeys().size(),
1);
+ assertTrue(cache.getRoot().getChild(getSubFqn("/a")).isResident());
+
+ cache.get(getSubFqn("/a")).setResident(false);
+ assertFalse(cache.getRoot().getChild(getSubFqn("/a")).isResident());
+
assertEquals(cache.getRoot().getChild(TEST_NODES_ROOT).getChild("a").getData().keySet().size(),
1);
+
assertEquals(cache.getRoot().getChild(TEST_NODES_ROOT).getChild("a").getKeys().size(),
1);
+
+ cache.remove(getSubFqn("/a"), "k_a");
+ assertEquals(cache.get(getSubFqn("/a")).getData().keySet().size(), 0);
+
assertFalse(cache.getRoot().getChild(TEST_NODES_ROOT).getChild("a").isResident());
+
+ cache.get(getSubFqn("/a")).setResident(true);
+
assertEquals(cache.getRoot().getChild(TEST_NODES_ROOT).getChild("a").getData().keySet().size(),
0);
+
assertEquals(cache.getRoot().getChild(TEST_NODES_ROOT).getChild("a").getKeys().size(),
0);
+
assertTrue(cache.getRoot().getChild(TEST_NODES_ROOT).getChild("a").isResident());
+ }
+
+ /**
+ * When replication is on, we want to make sure that the operation will subscribe to
the global
+ * replication strategy.
+ */
+ public void testNodeResidencyInformationIsReplicated()
+ {
+ Cache first =
DefaultCacheFactory.getInstance().createCache(UnitTestCacheConfigurationFactory.createConfiguration(Configuration.CacheMode.REPL_SYNC));
+ Cache second =
DefaultCacheFactory.getInstance().createCache(UnitTestCacheConfigurationFactory.createConfiguration(Configuration.CacheMode.REPL_SYNC));
+ Cache[] caches = {first, second};
+ TestingUtil.blockUntilViewsReceived(caches, 5000);
+
+ System.out.println("Caches started!");
+
+ Fqn fqn = Fqn.fromString("/a/b");
+ first.put(fqn, "key", "value");
+ assertNotNull(second.get(fqn, "key"));
+
+ first.getRoot().getChild(fqn).setResident(true);
+ assertTrue(second.getRoot().getChild(fqn).isResident());
+
+ second.getRoot().getChild(fqn).setResident(false);
+ assertFalse(first.getRoot().getChild(fqn).isResident());
+
+ for (Cache c : caches)
+ {
+ c.stop();
+ }
+ }
+
+ /**
+ * If a node is marked as resident, and a get is made on that given node then an
VISITED event would normally be
+ * added to the eviction queue. In a LRU scenario, this will cause another node to be
evicted given that the size of
+ * the eviction queue is bounded. This test makes sure that this scenario will not
hapen.
+ */
+ public void testNoEvictionEventsForResidentNodes() throws InterruptedException
+ {
+ cache.put(getSubFqn("/a"), "k_a", "v_a");
+ cache.put(getSubFqn("/b"), "k_b", "v_b");
+
+ //cache node reference here as getting a node will trigger an read event
+ cache.get(getSubFqn("/a")).setResident(true);
+ cache.get(getSubFqn("/b")).setResident(true);
+
+ cache.put(getSubFqn("/c"), "k_c", "v_c");
+ cache.put(getSubFqn("/d"), "k_d", "v_d");
+ cache.put(getSubFqn("/e"), "k_e", "v_e");
+ cache.put(getSubFqn("/f"), "k_f", "v_f");
+ cache.put(getSubFqn("/g"), "k_g", "v_g");
+ cache.put(getSubFqn("/h"), "k_h", "v_h");
+
+ //at this point the oldest nodes are /a and /b so. There are eviction events in the
queue corresponding
+ // to those nodes
+ cache.get(getSubFqn("/a"));
+ cache.get(getSubFqn("/b"));
+
+ Thread.sleep(3000);//so that eviction is activated
+
+ //a and b should exist as those were marked resident. Also they shouldn't be
caunted as nodes in the eviction
+ // queue
+ assertTrue(cache.exists(getSubFqn("/a")));
+ assertTrue(cache.exists(getSubFqn("/b")));
+
+ // c, d and e were the first accessed, they should be evicted
+ assertFalse(cache.exists(getSubFqn("/c")));
+ assertFalse(cache.exists(getSubFqn("/d")));
+ assertFalse(cache.exists(getSubFqn("/e")));
+
+ //all of them should be there - even if we re-retrieved a and b at a prev step
(cache.get(getSubFqn("/a"))) this
+ //call shouldn't create an eviction event.
+ assertTrue(cache.exists(getSubFqn("/f")));
+ assertTrue(cache.exists(getSubFqn("/g")));
+ assertTrue(cache.exists(getSubFqn("/h")));
+ }
+
+ /**
+ * Underlying metadata information is held as Strings. As Node is parametrized class
with keys and values, and there
+ * is an explicit downcast to String in code, this is just a check to make sure no
cast exceptions are being thrown.
+ * Note: this is rather a paranoia check as type info is lost during type erasure and
underlying map supports
+ * objects - superclass of Strings
+ */
+ public void testNoStringCache()
+ {
+ CacheFactory<Integer, Float> cacheFactory =
DefaultCacheFactory.getInstance();
+ Cache<Integer, Float> cache =
cacheFactory.createCache("META-INF/local-lru-eviction-service.xml", true);
+ cache.put(getSubFqn("/a"), 1, 5.3f);
+ cache.put(getSubFqn("/b"), 1, 2.3f);
+ cache.put(getSubFqn("/c"), 1, 7.3f);
+ cache.getRoot().getChild(getSubFqn("/a")).setResident(true);
+ cache.getRoot().getChild(getSubFqn("/b")).setResident(false);
+ cache.getRoot().getChild(getSubFqn("/c")).setResident(true);
+ assertTrue(cache.getRoot().getChild(getSubFqn("/a")).isResident());
+ assertFalse(cache.getRoot().getChild(getSubFqn("/b")).isResident());
+ assertTrue(cache.getRoot().getChild(getSubFqn("/c")).isResident());
+ cache.stop();
+ }
+
+ private Fqn getSubFqn(String str)
+ {
+ return Fqn.fromString("/" + TEST_NODES_ROOT + str);
+ }
+}