/*
* JBoss, Home of Professional Open Source.
* Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.cache.invocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DataContainer;
import org.jboss.cache.Fqn;
import org.jboss.cache.InternalNode;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.Node;
import org.jboss.cache.NodeNotValidException;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.config.Option;
import org.jboss.cache.lock.NodeLock;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.transaction.GlobalTransaction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* The delegate that users (and interceptor authors) interact with when they obtain a node from the cache or another node.
* This wrapper delegates calls down the interceptor chain.
*
* @author Manik Surtani (manik AT jboss DOT org )
* @since 2.1.0
*/
public class NodeInvocationDelegate extends AbstractInvocationDelegate implements NodeSPI
{
private static final Log log = LogFactory.getLog(NodeInvocationDelegate.class);
private static final boolean trace = log.isTraceEnabled();
protected volatile InternalNode node;
private CacheSPI spi;
public NodeInvocationDelegate(InternalNode node)
{
this.node = node;
}
public InternalNode getDelegationTarget()
{
return node;
}
public void injectDependencies(CacheSPI spi)
{
this.spi = spi;
}
public boolean isChildrenLoaded()
{
return node.isChildrenLoaded();
}
public void setChildrenLoaded(boolean loaded)
{
node.setChildrenLoaded(loaded);
}
public boolean isDataLoaded()
{
return node.isDataLoaded();
}
public void setDataLoaded(boolean dataLoaded)
{
node.setDataLoaded(dataLoaded);
}
public Map> getChildrenMapDirect()
{
return node.getChildrenMapDirect();
}
public void setChildrenMapDirect(Map> children)
{
node.setChildrenMapDirect(children);
}
public NodeSPI getOrCreateChild(Object name, GlobalTransaction tx)
{
return node.getOrCreateChild(name, tx);
}
@SuppressWarnings("deprecation")
public NodeLock getLock()
{
return node.getLock();
}
public void setFqn(Fqn f)
{
node.setFqn(f);
}
public boolean isDeleted()
{
return node.isRemoved();
}
public void markAsDeleted(boolean marker)
{
node.setRemoved(marker);
}
public void markAsDeleted(boolean marker, boolean recursive)
{
node.markAsRemoved(marker, recursive);
}
public void addChild(Object nodeName, Node nodeToAdd)
{
node.addChildDirect(nodeName, nodeToAdd);
}
public void printDetails(StringBuilder sb, int indent)
{
node.printDetails(sb, indent);
}
public void print(StringBuilder sb, int indent)
{
throw new CacheException("This method is deprecated!");
}
public void setVersion(DataVersion version)
{
node.setVersion(version);
}
public DataVersion getVersion()
{
return node.getVersion();
}
public Set> getChildrenDirect()
{
return node.getChildrenDirect();
}
public void removeChildrenDirect()
{
node.removeChildren();
}
public Set> getChildrenDirect(boolean includeMarkedAsDeleted)
{
return node.getChildrenDirect(includeMarkedAsDeleted);
}
public NodeSPI getChildDirect(Object childName)
{
return node.getChildDirect(childName);
}
public NodeSPI addChildDirect(Fqn childName)
{
return node.addChildDirect(childName);
}
public NodeSPI addChildDirect(Fqn f, boolean notify)
{
return node.addChildDirect(f, notify);
}
public NodeSPI addChildDirect(Object childName, boolean notify)
{
return node.addChildDirect(childName, notify);
}
public void addChildDirect(NodeSPI child)
{
node.addChildDirect(child);
}
public NodeSPI getChildDirect(Fqn childName)
{
return node.getChildDirect(childName);
}
public boolean removeChildDirect(Fqn fqn)
{
return node.removeChild(fqn);
}
public boolean removeChildDirect(Object childName)
{
return node.removeChild(childName);
}
public V removeDirect(K key)
{
return node.remove(key);
}
public V putDirect(K key, V value)
{
return node.put(key, value);
}
public void putAllDirect(Map extends K, ? extends V> data)
{
node.putAll(data);
}
public Map getDataDirect()
{
return node.getData();
}
public V getDirect(K key)
{
return node.get(key);
}
public void clearDataDirect()
{
node.clear();
}
public boolean containsKeyDirect(K key) {
return node.containsKey(key);
}
public Set getKeysDirect()
{
return node.getKeys();
}
public Set getChildrenNamesDirect()
{
return node.getChildrenNames();
}
public CacheSPI getCache()
{
return spi;
}
public NodeSPI getParent()
{
Fqn f = getFqn();
if (f.isRoot()) return this;
return spi.getNode(f.getParent());
}
public NodeSPI getParentDirect()
{
return node.getParent();
}
public Set> getChildren()
{
assertValid();
if (spi == null) return Collections.emptySet();
Set> children = new HashSet>();
for (Object c : spi.getChildrenNames(getFqn()))
{
Node n = spi.getNode(Fqn.fromRelativeElements(getFqn(), c));
if (n != null) children.add(n);
}
return Collections.unmodifiableSet(children);
}
public Set getChildrenNames()
{
assertValid();
return spi.getChildrenNames(getFqn());
}
public boolean isLeaf()
{
assertValid();
return getChildrenNames().isEmpty();
}
public Map getData()
{
assertValid();
if (spi == null) return Collections.emptyMap();
return spi.getData(getFqn());
}
public Set getKeys()
{
assertValid();
Set keys = spi.getKeys(getFqn());
if (keys == null)
return Collections.emptySet();
else
return Collections.unmodifiableSet(keys);
}
public Fqn getFqn()
{
return node.getFqn();
}
public Node addChild(Fqn f)
{
// TODO: Revisit. Is this really threadsafe? See comment in putIfAbsent() - same solution should be applied here too.
assertValid();
Fqn nf = Fqn.fromRelativeFqn(getFqn(), f);
Option o1 = spi.getInvocationContext().getOptionOverrides().copy();
Node child = getChild(f);
if (child == null)
{
if (trace) log.trace("Child is null; creating.");
Option o2 = o1.copy();
spi.getInvocationContext().setOptionOverrides(o1);
spi.put(nf, null);
if (trace) log.trace("Created. Now getting again.");
spi.getInvocationContext().setOptionOverrides(o2);
child = getChild(f);
if (trace) log.trace("Got child " + child);
}
return child;
}
public boolean removeChild(Fqn f)
{
assertValid();
return spi.removeNode(Fqn.fromRelativeFqn(getFqn(), f));
}
public boolean removeChild(Object childName)
{
assertValid();
return spi.removeNode(Fqn.fromRelativeElements(getFqn(), childName));
}
public Node getChild(Fqn f)
{
assertValid();
return spi.getNode(Fqn.fromRelativeFqn(getFqn(), f));
}
public Node getChild(Object name)
{
assertValid();
return spi.getNode(Fqn.fromRelativeElements(getFqn(), name));
}
public V put(K key, V value)
{
assertValid();
return spi.put(getFqn(), key, value);
}
public V putIfAbsent(K k, V v)
{
assertValid();
// TODO: Refactor this!! Synchronized block here sucks, this could lead to a deadlock since the locking interceptors will not use the same mutex.
// will only work once we make calls directly on the UnversionedNode in the CallInterceptor rather than multiple calls via the CacheImpl.
synchronized (this)
{
if (!getKeys().contains(k))
return put(k, v);
else
return get(k);
}
}
public V replace(K key, V value)
{
assertValid();
// TODO: Refactor this!! Synchronized block here sucks, this could lead to a deadlock since the locking interceptors will not use the same mutex.
// will only work once we make calls directly on the UnversionedNode in the CallInterceptor rather than multiple calls via the CacheImpl.
synchronized (this)
{
if (getKeys().contains(key))
{
return put(key, value);
}
else
return null;
}
}
public boolean replace(K key, V oldValue, V newValue)
{
assertValid();
// TODO: Refactor this!! Synchronized block here sucks, this could lead to a deadlock since the locking interceptors will not use the same mutex.
// will only work once we make calls directly on the UnversionedNode in the CallInterceptor rather than multiple calls via the CacheImpl.
synchronized (this)
{
if (oldValue.equals(get(key)))
{
put(key, newValue);
return true;
}
else
return false;
}
}
public void putAll(Map extends K, ? extends V> data)
{
assertValid();
spi.put(getFqn(), data);
}
public void replaceAll(Map extends K, ? extends V> data)
{
assertValid();
spi.clearData(getFqn());
spi.put(getFqn(), data);
}
public V get(K key)
{
assertValid();
return spi.get(getFqn(), key);
}
public V remove(K key)
{
assertValid();
return spi.remove(getFqn(), key);
}
public void clearData()
{
assertValid();
spi.clearData(getFqn());
}
public int dataSize()
{
assertValid();
return spi.getKeys(getFqn()).size();
}
public boolean hasChild(Fqn f)
{
// TODO: This could be made more efficient when calls are made directly on the node
assertValid();
return getChild(f) != null;
}
public boolean hasChild(Object o)
{
// TODO: This could be made more efficient when calls are made directly on the node
assertValid();
return getChild(o) != null;
}
public boolean isValid()
{
return node.isValid();
}
public boolean isResident()
{
return node.isResident();
}
public void setResident(boolean resident)
{
node.setResident(resident);
}
public boolean isLockForChildInsertRemove()
{
return node.isLockForChildInsertRemove();
}
public void setLockForChildInsertRemove(boolean lockForChildInsertRemove)
{
node.setLockForChildInsertRemove(lockForChildInsertRemove);
}
public void releaseObjectReferences(boolean recursive)
{
node.releaseObjectReferences(recursive);
}
public boolean hasChildrenDirect()
{
return node.hasChildren();
}
public Map getInternalState(boolean onlyInternalState)
{
return node.getInternalState(onlyInternalState);
}
public void setInternalState(Map state)
{
node.setInternalState(state);
}
public void setValid(boolean valid, boolean recursive)
{
node.setValid(valid, recursive);
}
protected void assertValid()
{
if (!getFqn().isRoot() && !node.isValid())
throw new NodeNotValidException("Node " + getFqn() + " is not valid. Perhaps it has been moved or removed.");
}
@Override
public String toString()
{
return node == null ? "null" : node.toString();
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeInvocationDelegate that = (NodeInvocationDelegate) o;
if (node != null ? !node.equals(that.node) : that.node != null) return false;
return true;
}
public int hashCode()
{
return (node != null ? node.hashCode() : 0);
}
// -------------- NO OP methods so that subclasses can work. Specifically for MVCC, todo: rethink once we drop PL/OL support -----------
public boolean isNullNode()
{
throw new UnsupportedOperationException();
}
public void markForUpdate(DataContainer container, boolean writeSkewCheck)
{
throw new UnsupportedOperationException();
}
public void commitUpdate(InvocationContext ctx, DataContainer container)
{
throw new UnsupportedOperationException();
}
public boolean isChanged()
{
throw new UnsupportedOperationException();
}
public boolean isCreated()
{
throw new UnsupportedOperationException();
}
public void setCreated(boolean b)
{
throw new UnsupportedOperationException();
}
public void rollbackUpdate()
{
throw new UnsupportedOperationException();
}
}