Project: gatein-naming
/*
 * JBoss, Home of Professional Open Source. 
 * Copyright 2010, Red Hat, Inc., 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.gatein.naming; 
 
import javax.naming.Binding; 
import javax.naming.CannotProceedException; 
import javax.naming.CompositeName; 
import javax.naming.Context; 
import javax.naming.Name; 
import javax.naming.NameClassPair; 
import javax.naming.NameNotFoundException; 
import javax.naming.NamingException; 
import javax.naming.Reference; 
import javax.naming.event.EventContext; 
import javax.naming.event.NamingEvent; 
import javax.naming.event.NamingListener; 
import javax.naming.spi.ResolveResult; 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.Hashtable; 
import java.util.List; 
import java.util.Map; 
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 
import java.util.concurrent.locks.ReentrantLock; 
 
import static org.gatein.naming.util.NamingUtils.*; 
 
 
/**
 * In-memory implementation of the NamingStore.  The backing for the entries is a basic tree structure with either context 
 * nodes or binding nodes.  The context nodes are allowed to have children and can be represented by a NamingContext.  A 
 * binding node is only allowed to have a normal object binding. 
 * 
 * @author John E. Bailey 
 */
 
public class InMemoryNamingStore implements NamingStore { 
 
    /* The root node of the tree.  Represents a JNDI name of "" */ 
    private final ContextNode root = new ContextNode(nullnullnew CompositeName(), new NamingContext(thisnull)); 
 
    /* Naming Event Coordinator */ 
    private final NamingEventCoordinator eventCoordinator; 
 
    private final ReentrantLock writeLock = new ReentrantLock(); 
 
    /**
     * Construct instance with no event support. 
     */
 
    public InMemoryNamingStore() { 
        this(null); 
    } 
 
    /**
     * Construct instance with an event coordinator. 
     * 
     * @param eventCoordinator The event coordinator 
     */
 
    public InMemoryNamingStore(final NamingEventCoordinator eventCoordinator) { 
        this.eventCoordinator = eventCoordinator; 
    } 
 
    /** {@inheritDoc} */ 
    public void bind(Name name, Object object) throws NamingException { 
        bind(name, object, object.getClass()); 
    } 
 
    /** {@inheritDoc} */ 
    public void bind(final Name name, final Object object, final Class<?> bindType) throws NamingException { 
        if (isLastComponentEmpty(name)) { 
            throw emptyNameException(); 
        } 
 
        writeLock.lock(); 
        try { 
            root.accept(new BindVisitor(true, name, object, bindType.getName())); 
        } finally { 
            writeLock.unlock(); 
        } 
    } 
 
    /** {@inheritDoc} */ 
    public void rebind(Name name, Object object) throws NamingException { 
        rebind(name, object, object.getClass()); 
    } 
 
    /** {@inheritDoc} */ 
    public void rebind(final Name name, final Object object, final Class<?> bindType) throws NamingException { 
        if (isLastComponentEmpty(name)) { 
            throw emptyNameException(); 
        } 
 
        writeLock.lock(); 
        try { 
            root.accept(new RebindVisitor(name, object, bindType.getName())); 
        } finally { 
            writeLock.unlock(); 
        } 
    } 
 
    /**
     * Unbind the entry in the provided location.  This will remove the node in the tree and no longer manage it. 
     * 
     * @param name The entry name 
     * @throws javax.naming.NamingException 
     */
 
    public void unbind(final Name name) throws NamingException { 
        if (isLastComponentEmpty(name)) { 
            throw emptyNameException(); 
        } 
 
        writeLock.lock(); 
        try { 
            root.accept(new UnbindVisitor(name)); 
        } finally { 
            writeLock.unlock(); 
        } 
    } 
 
    /**
     * Lookup the object value of a binding node in the tree. 
     * 
     * @param name The entry name 
     * @return The object value of the binding 
     * @throws javax.naming.NamingException 
     */
 
    public Object lookup(final Name name) throws NamingException { 
        if (isEmpty(name)) { 
            final Name emptyName = new CompositeName(""); 
            return new NamingContext(emptyName, thisnew Hashtable<String, Object>()); 
        } 
        return root.accept(new LookupVisitor(name)); 
    } 
 
    /**
     * List all NameClassPair instances at a given location in the tree. 
     * 
     * @param name The entry name 
     * @return The NameClassPair instances 
     * @throws javax.naming.NamingException 
     */
 
    public List<NameClassPair> list(final Name name) throws NamingException { 
        final Name nodeName = name.isEmpty() ? new CompositeName("") : name; 
        return root.accept(new ListVisitor(nodeName)); 
    } 
 
    /**
     * List all the Binding instances at a given location in the tree. 
     * 
     * @param name The entry name 
     * @return The Binding instances 
     * @throws javax.naming.NamingException 
     */
 
    public List<Binding> listBindings(final Name name) throws NamingException { 
        final Name nodeName = name.isEmpty() ? new CompositeName("") : name; 
        return root.accept(new ListBindingsVisitor(name)); 
    } 
 
    /**
     * Close the store.  This will clear all children from the root node. 
     * 
     * @throws javax.naming.NamingException 
     */
 
    public void close() throws NamingException { 
        writeLock.lock(); 
        try { 
            root.clear(); 
        } finally { 
            writeLock.unlock(); 
        } 
    } 
 
    /**
     * Add a {@code NamingListener} to the naming event coordinator. 
     * 
     * @param target The target name to add the listener to 
     * @param scope The listener scope 
     * @param listener The listener 
     */
 
    public void addNamingListener(final Name target, final int scope, final NamingListener listener) { 
        final NamingEventCoordinator coordinator = eventCoordinator; 
        if (coordinator != null) { 
            coordinator.addListener(target.toString(), scope, listener); 
        } 
    } 
 
    /**
     * Remove a {@code NamingListener} from the naming event coordinator. 
     * 
     * @param listener The listener 
     */
 
    public void removeNamingListener(final NamingListener listener) { 
        final NamingEventCoordinator coordinator = eventCoordinator; 
        if (coordinator != null) { 
            coordinator.removeListener(listener); 
        } 
    } 
 
    private void fireEvent(final ContextNode contextNode, final Name name, final Binding existingBinding, final Binding newBinding, final int type, final String changeInfo) { 
        final NamingEventCoordinator coordinator = eventCoordinator; 
        if (eventCoordinator != null) { 
            final Context context = Context.class.cast(contextNode.binding.getObject()); 
            if(context instanceof EventContext) { 
                coordinator.fireEvent(EventContext.class.cast(context), name, existingBinding, newBinding, type, changeInfo, NamingEventCoordinator.DEFAULT_SCOPES); 
            } 
        } 
    } 
 
    private void checkReferenceForContinuation(final Name name, final Object object) throws CannotProceedException { 
        if (object instanceof Reference) { 
            if (((Reference) object).get("nns") != null) { 
                throw cannotProceedException(object, name); 
            } 
        } 
    } 
 
    private abstract class TreeNode { 
        protected final Name fullName; 
        protected final Binding binding; 
 
        private TreeNode(final Name fullName, final Binding binding) { 
            this.fullName = fullName; 
            this.binding = binding; 
        } 
 
        protected abstract <T> T accept(NodeVisitor<T> visitor) throws NamingException; 
    } 
 
    private static final AtomicMapFieldUpdater<ContextNode, String, TreeNode> childrenUpdater = AtomicMapFieldUpdater.newMapUpdater(AtomicReferenceFieldUpdater.newUpdater(ContextNode.class, Map.class"children")); 
 
    private class ContextNode extends TreeNode { 
        volatile Map<String, TreeNode> children = Collections.emptyMap(); 
        protected final String name; 
        protected final ContextNode parentNode; 
 
        private ContextNode(final ContextNode parentNode, final String name, final Name fullName, final NamingContext context) { 
            super(fullName, new Binding(getLastComponent(fullName), Context.class.getName(), context)); 
            this.name = name; 
            this.parentNode = parentNode; 
        } 
 
        private void addChild(final String childName, final TreeNode childNode) throws NamingException { 
            if (childrenUpdater.putIfAbsent(this, childName, childNode) != null) { 
                throw nameAlreadyBoundException(fullName.add(childName)); 
            } 
        } 
 
        private TreeNode replaceChild(final String childName, final TreeNode childNode) throws NamingException { 
            return childrenUpdater.put(this, childName, childNode); 
        } 
 
        private TreeNode removeChild(final String childName) throws NameNotFoundException { 
            TreeNode old = childrenUpdater.remove(this, childName); 
            if (old == null) { 
                throw nameNotFoundException(childName, fullName); 
            } 
            if(parentNode != null && children.isEmpty()) { 
                childrenUpdater.remove(parentNode, name); 
            } 
            return old; 
        } 
 
        private void clear() { 
            childrenUpdater.clear(this); 
        } 
 
        protected final <T> T accept(NodeVisitor<T> visitor) throws NamingException { 
            return visitor.visit(this); 
        } 
 
        public TreeNode addOrGetChild(final String childName, final TreeNode childNode) { 
            TreeNode appearing = childrenUpdater.putIfAbsent(this, childName, childNode); 
            return appearing == null ? childNode : appearing; 
        } 
    } 
 
    private class BindingNode extends TreeNode { 
        private BindingNode(final Name fullName, final Binding binding) { 
            super(fullName, binding); 
        } 
 
        protected final <T> T accept(NodeVisitor<T> visitor) throws NamingException { 
            return visitor.visit(this); 
        } 
    } 
 
    private interface NodeVisitor<T> { 
        T visit(BindingNode bindingNode) throws NamingException; 
 
        T visit(ContextNode contextNode) throws NamingException; 
    } 
 
    private abstract class NodeTraversingVisitor<T> implements NodeVisitor<T> { 
        private final boolean createIfMissing; 
        private Name currentName; 
        private Name traversedName; 
        protected final Name targetName; 
 
        protected NodeTraversingVisitor(final boolean createIfMissing, final Name targetName) { 
            this.createIfMissing = createIfMissing; 
            this.targetName = currentName = targetName; 
            this.traversedName = new CompositeName(); 
        } 
 
        protected NodeTraversingVisitor(final Name targetName) { 
            this(false, targetName); 
        } 
 
        public final T visit(final BindingNode bindingNode) throws NamingException { 
            if (isEmpty(currentName)) { 
                return found(bindingNode); 
            } 
            return foundReferenceInsteadOfContext(bindingNode); 
        } 
 
        public final T visit(final ContextNode contextNode) throws NamingException { 
            if (isEmpty(currentName)) { 
                return found(contextNode); 
            } 
            final String childName = currentName.get(0); 
            traversedName.add(childName); 
            currentName = currentName.getSuffix(1); 
            final TreeNode node = contextNode.children.get(childName); 
            if (node == null) { 
                if (createIfMissing) { 
                    final NamingContext subContext = new NamingContext(traversedName, InMemoryNamingStore.thisnew Hashtable<String, Object>()); 
                    return contextNode.addOrGetChild(childName, new ContextNode(contextNode, childName, traversedName, subContext)).accept(this); 
                } else { 
                    throw nameNotFoundException(childName, contextNode.fullName); 
                } 
            } 
            return node.accept(this); 
        } 
 
        protected abstract T found(ContextNode contextNode) throws NamingException; 
 
        protected abstract T found(BindingNode bindingNode) throws NamingException; 
 
        protected T foundReferenceInsteadOfContext(BindingNode bindingNode) throws NamingException { 
            final Object object = bindingNode.binding.getObject(); 
            checkReferenceForContinuation(currentName, object); 
            throw notAContextException(bindingNode.fullName); 
        } 
    } 
 
    private abstract class BindingContextVisitor<T> extends NodeTraversingVisitor<T> { 
        protected final Name targetName; 
 
        protected BindingContextVisitor(final boolean createIfMissing, final Name targetName) { 
            super(createIfMissing, targetName.getPrefix(targetName.size() - 1)); 
            this.targetName = targetName; 
        } 
 
        protected BindingContextVisitor(final Name targetName) { 
            this(false, targetName); 
        } 
 
        protected final T found(final ContextNode contextNode) throws NamingException { 
            return foundBindContext(contextNode); 
        } 
 
        protected final T found(final BindingNode bindingNode) throws NamingException { 
            checkReferenceForContinuation(targetName.getSuffix(bindingNode.fullName.size()), bindingNode.binding.getObject()); 
            throw notAContextException(targetName); 
        } 
 
        protected abstract T foundBindContext(final ContextNode contextNode) throws NamingException; 
    } 
 
    private final class BindVisitor extends BindingContextVisitor<Void> { 
        private final Object object; 
        private final String className; 
 
        private BindVisitor(final boolean createIfMissing, final Name name, final Object object, final String className) { 
            super(createIfMissing, name); 
            this.object = object; 
            this.className = className; 
        } 
 
        protected Void foundBindContext(final ContextNode contextNode) throws NamingException { 
            final String childName = getLastComponent(targetName); 
            final Binding binding = new Binding(childName, className, object, true); 
            final BindingNode bindingNode = new BindingNode(targetName, binding); 
            contextNode.addChild(childName, bindingNode); 
            fireEvent(contextNode, targetName, null, binding, NamingEvent.OBJECT_ADDED, "bind"); 
            return null
        } 
    } 
 
    private final class RebindVisitor extends BindingContextVisitor<Void> { 
        private final Object object; 
        private final String className; 
 
        private RebindVisitor(final Name name, final Object object, final String className) { 
            super(name); 
            this.object = object; 
            this.className = className; 
        } 
 
        protected Void foundBindContext(final ContextNode contextNode) throws NamingException { 
            final String childName = getLastComponent(targetName); 
            final Binding binding = new Binding(childName, className, object, true); 
            final BindingNode bindingNode = new BindingNode(targetName, binding); 
            final TreeNode previous = contextNode.replaceChild(childName, bindingNode); 
 
            final Binding previousBinding = previous != null ? previous.binding : null
            fireEvent(contextNode, targetName, previousBinding, binding, previousBinding != null ? NamingEvent.OBJECT_CHANGED : NamingEvent.OBJECT_ADDED, "rebind"); 
            return null
        } 
    } 
 
    private final class UnbindVisitor extends BindingContextVisitor<Void> { 
 
        private UnbindVisitor(final Name targetName) throws NamingException { 
            super(targetName); 
        } 
 
        protected Void foundBindContext(final ContextNode contextNode) throws NamingException { 
            final TreeNode previous = contextNode.removeChild(getLastComponent(targetName)); 
            fireEvent(contextNode, targetName, previous.binding, null, NamingEvent.OBJECT_REMOVED, "unbind"); 
            return null
        } 
    } 
 
    private final class LookupVisitor extends NodeTraversingVisitor<Object> { 
        private LookupVisitor(final Name targetName) { 
            super(targetName); 
        } 
 
        protected Object found(final ContextNode contextNode) throws NamingException { 
            return contextNode.binding.getObject(); 
        } 
 
        protected Object found(final BindingNode bindingNode) throws NamingException { 
            return bindingNode.binding.getObject(); 
        } 
 
        protected Object foundReferenceInsteadOfContext(final BindingNode bindingNode) throws NamingException { 
            final Name remainingName = targetName.getSuffix(bindingNode.fullName.size()); 
            final Object boundObject = bindingNode.binding.getObject(); 
            checkReferenceForContinuation(remainingName, boundObject); 
            return new ResolveResult(boundObject, remainingName); 
        } 
    } 
 
    private final class ListVisitor extends NodeTraversingVisitor<List<NameClassPair>> { 
        private ListVisitor(final Name targetName) { 
            super(targetName); 
        } 
 
        protected List<NameClassPair> found(final ContextNode contextNode) throws NamingException { 
            final List<NameClassPair> nameClassPairs = new ArrayList<NameClassPair>(); 
            for (TreeNode childNode : contextNode.children.values()) { 
                final Binding binding = childNode.binding; 
                nameClassPairs.add(new NameClassPair(binding.getName(), binding.getClassName(), true)); 
            } 
            return nameClassPairs; 
        } 
 
        protected List<NameClassPair> found(final BindingNode bindingNode) throws NamingException { 
            checkReferenceForContinuation(new CompositeName(), bindingNode.binding.getObject()); 
            throw notAContextException(targetName); 
        } 
    } 
 
    private final class ListBindingsVisitor extends NodeTraversingVisitor<List<Binding>> { 
        private ListBindingsVisitor(final Name targetName) { 
            super(targetName); 
        } 
 
        protected List<Binding> found(final ContextNode contextNode) throws NamingException { 
            final List<Binding> bindings = new ArrayList<Binding>(); 
            for (TreeNode childNode : contextNode.children.values()) { 
                bindings.add(childNode.binding); 
            } 
            return bindings; 
        } 
 
        protected List<Binding> found(final BindingNode bindingNode) throws NamingException { 
            checkReferenceForContinuation(new CompositeName(), bindingNode.binding.getObject()); 
            throw notAContextException(targetName); 
        } 
    } 
 
}