/* * 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 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 data) { assertValid(); spi.put(getFqn(), data); } public void replaceAll(Map 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(); } }