package nl.tudelft.simulation.naming; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import javax.naming.Binding; import javax.naming.CompoundName; import javax.naming.Context; import javax.naming.Name; import javax.naming.NameClassPair; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.event.EventContext; import javax.naming.event.NamingEvent; import javax.naming.event.NamingListener; import org.djutils.logger.CategoryLogger; import nl.tudelft.simulation.dsol.logger.Cat; import nl.tudelft.simulation.event.Event; import nl.tudelft.simulation.event.EventProducer; import nl.tudelft.simulation.event.EventProducerInterface; import nl.tudelft.simulation.event.EventType; /** * The JVMContext as in-memory context implementation if the Context. *

* Copyright (c) 2002-2019 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See * for project information https://simulation.tudelft.nl. The DSOL * project is distributed under a three-clause BSD-style license, which can be found at * * https://simulation.tudelft.nl/dsol/3.0/license.html. *

* @author Peter Jacobs * @author Alexander Verbraeck * @author Niels Lang */ public class JVMContext extends EventProducer implements EventContext, EventProducerInterface, Serializable { /** The default serial version UID for serializable classes. */ private static final long serialVersionUID = 1L; /** NUMBER_CHANGED_EVENT is fired whenever the number of children changes. */ public static final EventType NUMBER_CHANGED_EVENT = new EventType("Number changed"); /** CHILD_ADDED_EVENT is fired whenever a child is added. */ public static final EventType CHILD_ADDED_EVENT = new EventType("Child added"); /** CHILD_REMOVED_EVENT is fired whenever a child is removed. */ public static final EventType CHILD_REMOVED_EVENT = new EventType("Child removed"); /** the syntax of this parser. */ private static Properties syntax = new Properties(); static { syntax.put("jndi.syntax.direction", "left_to_right"); syntax.put("jndi.syntax.separator", "/"); syntax.put("jndi.syntax.escape", "&"); syntax.put("jndi.syntax.beginquote", "\""); syntax.put("jndi.syntax.ava", ","); syntax.put("jndi.syntax.typeval", "="); } /** the parent context. */ protected Context parent; /** the atomicName. */ private String atomicName; /** the children. */ protected Map elements = Collections.synchronizedMap(new TreeMap()); /** the eventListeners. */ protected List eventListeners = Collections.synchronizedList(new ArrayList()); /** the nameParser. */ protected NameParser parser = new MyParser(JVMContext.syntax); /** * constructs a new JVMContext. */ public JVMContext() { this(null, ""); } /** * constructs a new JVMContext. * @param parent Context; the parent context * @param atomicName String; the atomicname */ public JVMContext(final Context parent, final String atomicName) { this.parent = parent; this.atomicName = atomicName; } /** {@inheritDoc} */ @Override public synchronized Object clone() throws CloneNotSupportedException { JVMContext clone = new JVMContext(); Map elementsMap = new HashMap(this.elements); for (String key : elementsMap.keySet()) { Object value = elementsMap.get(key); if (value instanceof JVMContext) { JVMContext item = (JVMContext) value; value = item.clone(); } elementsMap.put(key, value); } clone.elements = elementsMap; return clone; } /** * Returns true when this context is not root itself and name starts with '/' * @param name Name; the name * @return boolean * @throws NamingException on parse failure */ private boolean isRootForwardable(final Name name) throws NamingException { return (this.parent != null && name.startsWith(this.parser.parse(syntax.getProperty("jndi.syntax.separator")))); } /** * returns the root of this context * @return Context the root * @throws NamingException on lookup exception */ private Context getRoot() throws NamingException { return (Context) lookup(""); } /** * makes the name relative * @param name Name; the name * @return Name * @throws NamingException on parse failure */ private Name makeRelative(final Name name) throws NamingException { if (name.startsWith(this.parser.parse(syntax.getProperty("jndi.syntax.separator")))) { return name.getSuffix(1); } return name; } /** {@inheritDoc} */ @Override public synchronized Object lookup(final Name name) throws NamingException { // Handle absolute path if (isRootForwardable(name)) { return getRoot().lookup(name); } // Handle root context lookup if (name.size() == 0 && this.parent == null) { return this; } if (name.size() == 0) { return this.parent.lookup(name); } Name relativeName = makeRelative(name); // Check and handle delegation if (relativeName.size() > 1) { return ((Context) lookup(relativeName.get(0))).lookup(relativeName.getSuffix(1)); } // Lookup locally if (!this.elements.containsKey(relativeName.toString())) { throw new NamingException(relativeName + " not found."); } return this.elements.get(relativeName.toString()); } /** {@inheritDoc} */ @Override public Object lookup(final String arg0) throws NamingException { return lookup(this.parser.parse(arg0)); } /** {@inheritDoc} */ @Override public synchronized void bind(final Name name, final Object value) throws NamingException { if (isRootForwardable(name)) { getRoot().bind(name, value); return; } Name relativeName = makeRelative(name); if (relativeName.size() > 1) { ((Context) this.lookup(relativeName.get(0))).bind(relativeName.getSuffix(1), value); } else { this.elements.put(relativeName.get(0), value); fireEvent(new Event(NUMBER_CHANGED_EVENT, this, new Integer(this.elements.size()))); fireEvent(new Event(CHILD_ADDED_EVENT, this, value)); fireContextEvent(true, this.getNameInNamespace() + syntax.getProperty("jndi.syntax.separator") + relativeName, value); } } /** {@inheritDoc} */ @Override public void bind(final String name, final Object value) throws NamingException { bind(this.parser.parse(name), value); } /** {@inheritDoc} */ @Override public void rebind(final Name name, final Object value) throws NamingException { this.bind(name, value); } /** {@inheritDoc} */ @Override public void rebind(final String name, final Object value) throws NamingException { this.bind(name, value); } /** {@inheritDoc} */ @Override public synchronized void unbind(final Name name) throws NamingException { if (isRootForwardable(name)) { getRoot().unbind(name); return; } Name relativeName = makeRelative(name); if (relativeName.size() > 1) { ((Context) this.lookup(relativeName.get(0))).unbind(relativeName.getSuffix(1)); } else { Object old = this.elements.get(relativeName.get(0)); this.elements.remove(relativeName.get(0)); fireEvent(new Event(NUMBER_CHANGED_EVENT, this, new Integer(this.elements.size()))); fireEvent(new Event(CHILD_REMOVED_EVENT, this, old)); fireContextEvent(false, this.getNameInNamespace() + syntax.getProperty("jndi.syntax.separator") + relativeName, old); } } /** {@inheritDoc} */ @Override public void unbind(final String name) throws NamingException { unbind(this.parser.parse(name)); } /** {@inheritDoc} */ @Override public void rename(final Name nameOld, final Name nameNew) throws NamingException { rename(nameOld.toString(), nameNew.toString()); } /** {@inheritDoc} */ @Override public synchronized void rename(final String nameOld, final String nameNew) throws NamingException { if (!this.elements.containsKey(nameOld)) { throw new NamingException("Old name not found. Rename" + " operation canceled."); } Object value = this.elements.get(nameOld); this.elements.remove(nameOld); this.elements.put(nameNew, value); } /** {@inheritDoc} */ @Override public NamingEnumeration list(final Name name) { return this.list(name.toString()); } /** {@inheritDoc} */ @Override public NamingEnumeration list(final String name) { if (name == null) { CategoryLogger.filter(Cat.NAMING).info("list: name==null"); } return new NamingList(true); } /** {@inheritDoc} */ @Override public NamingEnumeration listBindings(final Name name) { if (name == null) { CategoryLogger.filter(Cat.NAMING).info("listBindings: name==null"); } return new NamingList(false); } /** {@inheritDoc} */ @Override public NamingEnumeration listBindings(final String name) { if (name == null) { CategoryLogger.filter(Cat.NAMING).info("listBindings: name==null"); } return new NamingList(false); } /** {@inheritDoc} */ @Override public void destroySubcontext(final Name name) throws NamingException { this.unbind(name); } /** {@inheritDoc} */ @Override public void destroySubcontext(final String name) throws NamingException { this.unbind(name); } /** {@inheritDoc} */ @Override public synchronized Context createSubcontext(final Name name) throws NamingException { if (name.size() == 1) { String subName = name.get(0); Context newContext = new JVMContext(this, subName); this.bind(subName, newContext); return newContext; } Context c = (Context) this.lookup(name.get(0)); return c.createSubcontext(name.getSuffix(1)); } /** {@inheritDoc} */ @Override public Context createSubcontext(final String arg0) throws NamingException { return createSubcontext(this.parser.parse(arg0)); } /** {@inheritDoc} */ @Override public Object lookupLink(final Name name) { return this.elements.get(name.toString()); } /** {@inheritDoc} */ @Override public Object lookupLink(final String name) throws NamingException { return lookup(name); } /** {@inheritDoc} */ @Override public NameParser getNameParser(final Name name) { if (name == null) { CategoryLogger.filter(Cat.NAMING).info("getNameParser: name==null"); } return this.parser; } /** {@inheritDoc} */ @Override public NameParser getNameParser(final String name) { if (name == null) { CategoryLogger.filter(Cat.NAMING).info("getNameParser: name==null"); } return this.parser; } /** {@inheritDoc} */ @Override public Name composeName(final Name arg0, final Name arg1) throws NamingException { throw new NamingException("composeName " + arg0 + ", " + arg1 + " is not supported."); } /** {@inheritDoc} */ @Override public String composeName(final String arg0, final String arg1) throws NamingException { throw new NamingException("composeName " + arg0 + ", " + arg1 + " is not supported."); } /** {@inheritDoc} */ @Override public Object addToEnvironment(final String arg0, final Object arg1) throws NamingException { throw new NamingException("addToEnvironment " + arg0 + ", " + arg1 + " is not supported."); } /** {@inheritDoc} */ @Override public Object removeFromEnvironment(final String arg0) throws NamingException { throw new NamingException("removeFromEnvironment " + arg0 + " is not supported."); } /** {@inheritDoc} */ @Override public Hashtable getEnvironment() throws NamingException { throw new NamingException("Not supported."); } /** {@inheritDoc} */ @Override public void close() { // We don't do anything on close } /** {@inheritDoc} */ @Override public synchronized String getNameInNamespace() throws NamingException { if (this.parent != null) { return (this.parent.getNameInNamespace() + syntax.get("jndi.syntax.separator") + this.atomicName); } return this.atomicName; } /** {@inheritDoc} */ @Override public void addNamingListener(final Name target, final int scope, final NamingListener l) { this.eventListeners.add(new EventContextListenerRecord(target, scope, l)); } /** {@inheritDoc} */ @Override public void addNamingListener(final String target, final int scope, final NamingListener l) throws NamingException { addNamingListener(this.parser.parse(target), scope, l); } /** {@inheritDoc} */ @Override public synchronized void removeNamingListener(final NamingListener l) { EventContextListenerRecord removable = null; for (EventContextListenerRecord current : this.eventListeners) { if (current.getListener().equals(l)) { removable = current; break; } } if (removable != null) { this.eventListeners.remove(removable); } } /** {@inheritDoc} */ @Override public boolean targetMustExist() { return false; } /** * fires a contextEvent * @param isAddition boolean; addition * @param name String; the name * @param value Object; the value * @throws NamingException on failure */ private void fireContextEvent(final boolean isAddition, final String name, final Object value) throws NamingException { fireContextEvent(isAddition, this.parser.parse(name), value); } /** * fires a contextEvent * @param isAddition boolean; addition * @param name Name; the name * @param value Object; the value */ private synchronized void fireContextEvent(final boolean isAddition, final Name name, final Object value) { for (EventContextListenerRecord record : this.eventListeners) { int scope = record.getScope(); NamingEvent namingEvent = null; if (isAddition) { namingEvent = new NamingEvent(this, NamingEvent.OBJECT_ADDED, new Binding(name.toString(), value), null, null); } else { namingEvent = new NamingEvent(this, NamingEvent.OBJECT_REMOVED, null, new Binding(name.toString(), value), null); } if (name.equals(record.getTarget()) || scope == EventContext.SUBTREE_SCOPE) { namingEvent.dispatch(record.getListener()); continue; } if (scope == EventContext.ONELEVEL_SCOPE) { // (Wrong) assumption that this is the root context if (record.getTarget().size() == 1) { namingEvent.dispatch(record.getListener()); } continue; } } } /** {@inheritDoc} */ @Override public String toString() { try { return "JVMContext: " + this.getNameInNamespace() + " "; } catch (Exception exception) { return super.toString(); } } /** * The EventContextListenerRecord */ private class EventContextListenerRecord { /** target name to which a subscription is made. */ private Name target; /** the scope. */ private int scope; /** the listener. */ private NamingListener listener; /** * constructs a new EventContextListenerRecord * @param target Name; the target * @param scope int; the scope * @param listener NamingListener; the listener */ public EventContextListenerRecord(final Name target, final int scope, final NamingListener listener) { this.target = target; this.scope = scope; this.listener = listener; } /** * returns the listener * @return NamingListener listener */ public NamingListener getListener() { return this.listener; } /** * gets scope * @return Returns the scope. */ public int getScope() { return this.scope; } /** * gets target * @return Returns the target. */ public Name getTarget() { return this.target; } } /** * The NamingList class */ private class NamingList extends ArrayList implements NamingEnumeration { /** The default serial version UID for serializable classes. */ private static final long serialVersionUID = 1L; /** the iterator. */ private Iterator myIterator = null; /** * constructs a new NamingList. * @param classList boolean; isClassList */ @SuppressWarnings("unchecked") public NamingList(final boolean classList) { for (String currentKey : JVMContext.this.elements.keySet()) { if (classList) { this.add((T) new NameClassPair(currentKey, JVMContext.this.elements.get(currentKey).getClass().toString())); } else { this.add((T) new Binding(currentKey, JVMContext.this.elements.get(currentKey))); } } } /** {@inheritDoc} */ @Override public void close() { this.myIterator = null; } /** {@inheritDoc} */ @Override public boolean hasMoreElements() { if (this.myIterator == null) { this.myIterator = this.iterator(); } boolean hasNext = this.myIterator.hasNext(); if (!hasNext) { this.myIterator = null; return false; } return true; } /** {@inheritDoc} */ @Override public T nextElement() { if (this.myIterator == null) { this.myIterator = this.iterator(); } return this.myIterator.next(); } /** {@inheritDoc} */ @Override public boolean hasMore() { return hasMoreElements(); } /** {@inheritDoc} */ @Override public T next() { return nextElement(); } } /** * A default name parser */ private class MyParser implements NameParser, Serializable { /** The default serial version UID for serializable classes. */ private static final long serialVersionUID = 1L; /** the syntax */ private Properties syntaxProperties = null; /** * constructs a new MyParser. * @param syntax Properties; the syntax properties */ public MyParser(final Properties syntax) { this.syntaxProperties = syntax; } /** {@inheritDoc} */ @Override public Name parse(final String name) throws NamingException { Name result = new CompoundName(name, this.syntaxProperties); if (result.size() > 0 && result.get(0).equals(".") && JVMContext.this.parent != null) { result = result.getSuffix(1); } return result; } } }