/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import static org.jboss.cache.AbstractNode.NodeFlags.*;
import org.jboss.cache.commands.write.CreateNodeCommand;
import org.jboss.cache.factories.CommandsFactory;
import org.jboss.cache.lock.IdentityLock;
import org.jboss.cache.lock.LockStrategyFactory;
import org.jboss.cache.marshall.MarshalledValue;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.transaction.GlobalTransaction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Basic data node class. Throws {@link UnsupportedOperationException} for version-specific methods like {@link #getVersion()} and
* {@link #setVersion(org.jboss.cache.optimistic.DataVersion)}, defined in {@link org.jboss.cache.NodeSPI}.
*
* @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
* @since 2.0.0
*/
@SuppressWarnings("unchecked")
public class UnversionedNode<K, V> extends AbstractNode<K, V>
{
/**
* Debug log.
*/
protected static Log log = LogFactory.getLog(UnversionedNode.class);
protected static final boolean trace = log.isTraceEnabled();
/**
* Lock manager that manages locks to be acquired when accessing the node inside a transaction. Lazy set just in case
* locking is not needed.
*/
protected transient IdentityLock lock = null;
/**
* A reference of the CacheImpl instance.
*/
private transient CacheSPI cache;
/**
* Map of general data keys to values.
*/
private final Map data = new HashMap();
protected NodeSPI delegate;
private CommandsFactory commandsFactory;
protected LockStrategyFactory lockStrategyFactory;
/**
* Constructs a new node with an FQN of Root.
*/
public UnversionedNode()
{
this.fqn = Fqn.ROOT;
initFlags();
}
/**
* Constructs a new node with a name, etc.
*
* @param mapSafe <code>true</code> if param <code>data</code> can safely be directly assigned to this object's
* {@link #data} field; <code>false</code> if param <code>data</code>'s contents should be copied into
* this object's {@link #data} field.
*/
protected UnversionedNode(Object child_name, Fqn fqn, Map data, boolean mapSafe, CacheSPI cache)
{
if (cache == null)
{
throw new IllegalArgumentException("no cache init for " + fqn);
}
if (!fqn.isRoot() && !child_name.equals(fqn.getLastElement()))
{
throw new IllegalArgumentException("Child " + child_name + " must be last part of " + fqn);
}
initFlags();
this.cache = cache;
this.fqn = fqn;
init();
setInternalState(data);
}
/**
* This method initialises flags on the node, by setting DATA_LOADED to true and VALID to true and all other flags to false.
* The flags are defined in the {@link NodeFlags} enum.
*/
protected void initFlags()
{
setFlag(DATA_LOADED);
setFlag(VALID);
}
public NodeSPI getDelegate()
{
return delegate;
}
public void setDelegate(NodeSPI delegate)
{
this.delegate = delegate;
}
public void injectDependencies(CacheSPI spi, CommandsFactory commandsFactory, LockStrategyFactory lockStrategyFactory)
{
this.cache = spi;
this.commandsFactory = commandsFactory;
this.lockStrategyFactory = lockStrategyFactory;
init();
}
/**
* Initializes with a name and FQN and cache.
*/
private void init()
{
if (cache != null && cache.getConfiguration() != null)
setLockForChildInsertRemove(cache.getConfiguration().isLockParentForChildInsertRemove());
}
/**
* Returns a parent by checking the TreeMap by name.
*/
public NodeSPI getParent()
{
if (fqn.isRoot())
{
return null;
}
return cache.peek(fqn.getParent(), true);
}
protected synchronized void initLock()
{
if (lock == null)
{
lock = new IdentityLock(lockStrategyFactory, delegate);
}
}
private synchronized Map<Object, Node<K, V>> children()
{
if (children == null)
{
if (getFqn().isRoot())
{
children = new ConcurrentHashMap<Object, Node<K, V>>(64, .5f, 16);
}
else
{
// Less segments to save memory
children = new ConcurrentHashMap<Object, Node<K, V>>(4, .75f, 4);
}
}
return children;
}
public CacheSPI getCache()
{
return cache;
}
public boolean isChildrenLoaded()
{
return isFlagSet(CHILDREN_LOADED);
}
public void setChildrenLoaded(boolean childrenLoaded)
{
setFlag(CHILDREN_LOADED, childrenLoaded);
}
private void assertValid()
{
if (!isValid())
throw new NodeNotValidException("Node " + getFqn() + " is not valid. Perhaps it has been moved or removed.");
}
public Object get(Object key)
{
assertValid();
return cache.get(getFqn(), key);
}
public Object getDirect(Object key)
{
return data.get(key);
}
private boolean isReadLocked()
{
return lock != null && lock.isReadLocked();
}
private boolean isWriteLocked()
{
return lock != null && lock.isWriteLocked();
}
public IdentityLock getLock()
{
initLock();
return lock;
}
public Map getDataDirect()
{
if (data == null) return Collections.emptyMap();
// return Collections.unmodifiableMap(data);
return data;
}
public Object put(Object key, Object value)
{
assertValid();
return cache.put(getFqn(), key, value);
}
public Object putDirect(Object key, Object value)
{
return data.put(key, value);
}
public NodeSPI getOrCreateChild(Object child_name, GlobalTransaction gtx, boolean notify)
{
return getOrCreateChild(child_name, gtx, true, notify);
}
private NodeSPI getOrCreateChild(Object child_name, GlobalTransaction gtx, boolean createIfNotExists, boolean notify)
{
NodeSPI child;
if (child_name == null)
{
throw new IllegalArgumentException("null child name");
}
child = (NodeSPI) children().get(child_name);
InvocationContext ctx = cache.getInvocationContext();
if (createIfNotExists && child == null)
{
// construct the new child outside the synchronized block to avoid
// spending any more time than necessary in the synchronized section
Fqn child_fqn = Fqn.fromRelativeElements(this.fqn, child_name);
NodeSPI newChild = (NodeSPI) cache.getConfiguration().getRuntimeConfig().getNodeFactory().createNode(child_name, delegate, null);
if (newChild == null)
{
throw new IllegalStateException();
}
synchronized (this)
{
// check again to see if the child exists
// after acquiring exclusive lock
child = (NodeSPI) children().get(child_name);
if (child == null)
{
if (notify) cache.getNotifier().notifyNodeCreated(child_fqn, true, ctx);
child = newChild;
children.put(child_name, child);
if (gtx != null)
{
CreateNodeCommand createNodeCommand = commandsFactory.buildCreateNodeCommand(child_fqn);
ctx.getTransactionEntry().addModification(createNodeCommand);
}
}
}
// notify if we actually created a new child
if (newChild == child)
{
if (trace)
{
log.trace("created child: fqn=" + child_fqn);
}
if (notify) cache.getNotifier().notifyNodeCreated(child_fqn, false, ctx);
}
}
return child;
}
public Object remove(Object key)
{
assertValid();
return cache.remove(getFqn(), key);
}
public Object removeDirect(Object key)
{
if (data == null) return null;
return data.remove(key);
}
public void printDetails(StringBuilder sb, int indent)
{
printDetailsInMap(sb, indent);
}
/**
* Returns a debug string.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
if (!isValid()) sb.append(" (INVALID!) ");
if (isDeleted())
{
sb.append(" (deleted) [ ").append(fqn);
}
else
{
sb.append("[ ").append(fqn);
}
if (data != null)
{
synchronized (data)
{
if (trace)
{
sb.append(" data=").append(data.keySet());
}
else
{
sb.append(" data=[");
Set keys = data.keySet();
int i = 0;
for (Object o : keys)
{
i++;
sb.append(o);
if (i == 5)
{
int more = keys.size() - 5;
if (more > 1)
{
sb.append(", and ");
sb.append(more);
sb.append(" more");
break;
}
}
else
{
sb.append(", ");
}
}
sb.append("]");
}
}
}
if (children != null && !children.isEmpty())
{
if (trace)
{
sb.append(" children=").append(getChildrenNamesDirect());
}
else
{
sb.append(" children=[");
Set names = getChildrenNamesDirect();
int i = 0;
for (Object o : names)
{
i++;
sb.append(o);
if (i == 5)
{
int more = names.size() - 5;
if (more > 1)
{
sb.append(", and ");
sb.append(more);
sb.append(" more");
break;
}
}
else
{
sb.append(", ");
}
}
sb.append("]");
}
}
if (lock != null)
{
if (isReadLocked())
{
sb.append(" RL");
}
if (isWriteLocked())
{
sb.append(" WL");
}
}
sb.append("]");
return sb.toString();
}
public void addChildDirect(NodeSPI child)
{
Fqn childFqn = child.getFqn();
if (childFqn.isDirectChildOf(fqn))
{
synchronized (this)
{
children().put(childFqn.getLastElement(), child);
}
}
else
throw new CacheException("Attempting to add a child [" + child.getFqn() + "] to [" + getFqn() + "]. Can only add direct children.");
}
public NodeSPI addChildDirect(Fqn f)
{
return addChildDirect(f, true);
}
public NodeSPI addChildDirect(Fqn f, boolean notify)
{
if (f.size() == 1)
{
GlobalTransaction gtx = cache.getInvocationContext().getGlobalTransaction();
return getOrCreateChild(f.getLastElement(), gtx, true, notify);
}
else
{
throw new UnsupportedOperationException("Cannot directly create children which aren't directly under the current node.");
}
}
public NodeSPI addChildDirect(Object childName, boolean notify)
{
GlobalTransaction gtx = cache.getInvocationContext().getGlobalTransaction();
return getOrCreateChild(childName, gtx, true, notify);
}
public void clearDataDirect()
{
if (data != null) data.clear();
}
public NodeSPI getChildDirect(Fqn fqn)
{
if (fqn.size() == 1)
{
return getChildDirect(fqn.getLastElement());
}
else
{
NodeSPI currentNode = delegate;
for (int i = 0; i < fqn.size(); i++)
{
Object nextChildName = fqn.get(i);
currentNode = currentNode.getChildDirect(nextChildName);
if (currentNode == null) return null;
}
return currentNode;
}
}
public Set<Object> getChildrenNamesDirect()
{
return children == null ? Collections.emptySet() : new HashSet<Object>(children.keySet());
}
public Set<Object> getKeysDirect()
{
if (data == null)
{
return Collections.emptySet();
}
return Collections.unmodifiableSet(new HashSet(data.keySet()));
}
public boolean removeChildDirect(Object childName)
{
return children != null && children.remove(childName) != null;
}
public boolean removeChildDirect(Fqn f)
{
if (f.size() == 1)
{
return removeChildDirect(f.getLastElement());
}
else
{
NodeSPI child = getChildDirect(f);
return child != null && child.getParent().removeChildDirect(f.getLastElement());
}
}
public Map<Object, Node<K, V>> getChildrenMapDirect()
{
return children;
}
public void setChildrenMapDirect(Map<Object, Node<K, V>> children)
{
this.children().clear();
this.children.putAll(children);
}
public void putAll(Map data)
{
assertValid();
cache.put(fqn, data);
}
public void putAllDirect(Map data)
{
if (data == null) return;
this.data.putAll(data);
}
public void removeChildrenDirect()
{
if (children != null)
{
children.clear();
}
children = null;
}
// versioning
public void setVersion(DataVersion version)
{
throw new UnsupportedOperationException("Versioning not supported");
}
public DataVersion getVersion()
{
throw new UnsupportedOperationException("Versioning not supported");
}
private void printIndent(StringBuilder sb, int indent)
{
if (sb != null)
{
for (int i = 0; i < indent; i++)
{
sb.append(" ");
}
}
}
public void addChild(Object child_name, Node n)
{
if (child_name != null)
{
children().put(child_name, n);
}
}
/**
* Returns the name of this node.
*/
private Object getName()
{
return fqn.getLastElement();
}
/**
* Returns the name of this node.
*/
public Fqn getFqn()
{
return fqn;
}
public void setFqn(Fqn fqn)
{
if (trace)
{
log.trace(getFqn() + " set FQN " + fqn);
}
this.fqn = fqn;
if (children == null)
{
return;
}
// invoke children
for (Map.Entry<Object, ? extends Node> me : children.entrySet())
{
NodeSPI n = (NodeSPI) me.getValue();
Fqn cfqn = Fqn.fromRelativeElements(fqn, me.getKey());
n.setFqn(cfqn);
}
}
public NodeSPI getChildDirect(Object childName)
{
if (childName == null) return null;
return (NodeSPI) (children == null ? null : children.get(childName));
}
public Set<NodeSPI> getChildrenDirect()
{
// strip out deleted child nodes...
if (children == null || children.size() == 0) return Collections.emptySet();
Set<NodeSPI> exclDeleted = new HashSet<NodeSPI>();
for (Node n : children.values())
{
NodeSPI spi = (NodeSPI) n;
if (!spi.isDeleted()) exclDeleted.add(spi);
}
return Collections.unmodifiableSet(exclDeleted);
}
public boolean hasChildrenDirect()
{
return children != null && children.size() != 0;
}
public Set<NodeSPI> getChildrenDirect(boolean includeMarkedForRemoval)
{
if (includeMarkedForRemoval)
{
if (children != null && !children.isEmpty())
{
return Collections.unmodifiableSet(new HashSet<NodeSPI>((Collection) children.values()));
}
else
{
return Collections.emptySet();
}
}
else
{
return getChildrenDirect();
}
}
/**
* Adds details of the node into a map as strings.
*/
private void printDetailsInMap(StringBuilder sb, int indent)
{
printIndent(sb, indent);
indent += 2;// increse it
if (!(getFqn()).isRoot())
{
sb.append(Fqn.SEPARATOR);
}
sb.append(getName());
sb.append(" ");
sb.append(data);
if (children != null)
{
for (Node n : children.values())
{
sb.append("\n");
((NodeSPI) n).printDetails(sb, indent);
}
}
}
/**
* Returns true if the data was loaded from the cache loader.
*/
public boolean isDataLoaded()
{
return isFlagSet(DATA_LOADED);
}
/**
* Sets if the data was loaded from the cache loader.
*/
public void setDataLoaded(boolean dataLoaded)
{
setFlag(DATA_LOADED, dataLoaded);
}
public boolean isValid()
{
return isFlagSet(VALID);
}
public void setValid(boolean valid, boolean recursive)
{
setFlag(VALID, valid);
if (trace) log.trace("Marking node " + getFqn() + " as " + (valid ? "" : "in") + "valid");
if (recursive)
{
for (Node child : children().values())
{
((NodeSPI) child).setValid(valid, recursive);
}
}
}
public boolean isLockForChildInsertRemove()
{
return isFlagSet(LOCK_FOR_CHILD_INSERT_REMOVE);
}
public void setLockForChildInsertRemove(boolean lockForChildInsertRemove)
{
setFlag(LOCK_FOR_CHILD_INSERT_REMOVE, lockForChildInsertRemove);
}
public void setInternalState(Map state)
{
// don't bother doing anything here
putAllDirect(state);
}
public Map getInternalState(boolean onlyInternalState)
{
if (onlyInternalState)
return new HashMap(0);
// don't bother doing anything here
if (data == null) return new HashMap(0);
return new HashMap(data);
}
public void releaseObjectReferences(boolean recursive)
{
if (recursive && children != null)
{
for (Node<?, ?> child : children.values())
{
child.releaseObjectReferences(recursive);
}
}
if (data != null)
{
for (Object key : data.keySet())
{
// get the key first, before attempting to serialize stuff since data.get() may deserialize the key if doing
// a hashcode() or equals().
Object value = data.get(key);
if (key instanceof MarshalledValue)
{
((MarshalledValue) key).compact(true, true);
}
if (value instanceof MarshalledValue)
{
((MarshalledValue) value).compact(true, true);
}
}
}
}
}