InitialEventContext.java
package nl.tudelft.simulation.naming.context.event;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.rmi.RemoteException;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import javax.naming.NotContextException;
import org.djutils.event.EventListener;
import org.djutils.event.EventListenerMap;
import org.djutils.event.EventType;
import org.djutils.event.reference.ReferenceType;
import org.djutils.exceptions.Throw;
import org.djutils.io.URLResource;
import org.djutils.logger.CategoryLogger;
import nl.tudelft.simulation.naming.context.ContextFactory;
import nl.tudelft.simulation.naming.context.ContextInterface;
import nl.tudelft.simulation.naming.context.util.ContextUtil;
/**
* InitialEventContext class. This class is the starting context for performing naming operations. The class is loosely based on
* InitialContext in the Java JNDI package, but creates a lightweight Context that implements the DSOL ContextInterface. The
* InitialEventContext is a singleton class.
* <p>
* Copyright (c) 2002-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
* <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
* https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
* </p>
* @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
*/
@SuppressWarnings("checkstyle:needbraces")
public final class InitialEventContext implements EventContext
{
/** */
private static final long serialVersionUID = 20200101L;
/**
* Constant that holds the name of the environment property for specifying the initial context factory to use. The value of
* the property should be the fully qualified class name of the factory class that will create an initial context. This
* property may be specified in the environment parameter passed to the initial context constructor, a system property, or
* an application resource file. If it is not specified in any of these sources, {@code NoInitialContextException} is thrown
* when an initial context is required to complete an operation.
* <p>
* The value of this constant is "java.naming.factory.initial".
* </p>
*/
public static final String INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial";
/**
* Constant that holds the name of the environment property for specifying configuration information for the service
* provider to use. The value of the property should contain a URL string (e.g. "http://localhost:1099/remoteContext"). This
* property may be specified in the environment, a system property, or a resource file. If it is not specified in any of
* these sources, the default configuration is determined by the service provider.
* <p>
* The value of this constant is "java.naming.provider.url".
* </p>
*/
public static final String PROVIDER_URL = "java.naming.provider.url";
/**
* Constant that holds the name of the environment property for specifying the initial context factory to use, as embedded
* factory in a remote context that is specified by the INITIAL_CONTEXT_FACTORY constant. The value of the property should
* be the fully qualified class name of the factory class that will create an initial context that will be embedded within
* the remote context. This property may be specified in the environment parameter passed to the initial context
* constructor, a system property, or an application resource file. If it is not specified in any of these sources,
* {@code NoInitialContextException} is thrown when an initial context is required to complete an operation.
* <p>
* The value of this constant is "wrapped.naming.factory.initial".
* </p>
*/
public static final String WRAPPED_CONTEXT_FACTORY = "wrapped.naming.factory.initial";
/** the singleton instance of the InitialEventContext. */
private static InitialEventContext INSTANCE;
/** the properties of the initialEventContext. */
protected Hashtable<?, ?> properties = null;
/**
* The initial context, usually built by a factory. It is set by getDefaultInitCtx() the first time getDefaultInitCtx() is
* called. Subsequent invocations of getDefaultInitCtx() return the value of defaultInitCtx until close() is called.
*/
protected ContextInterface defaultInitCtx = null;
/** has the default context been generated? */
protected boolean gotDefault = false;
/** The event producer to which context events will be delegated for handling. */
public ContextEventProducerImpl contextEventProducerImpl;
/**
* Constructs an initial context. No environment properties are supplied. Equivalent to
* <code>new InitialContext(null)</code>.
* @param environment the overriding environment variables for the Factory that constructs the wrapped Context
* @param atomicName String; the name under which the root context will be registered
* @throws NamingException if a naming exception is encountered
* @throws RemoteException if a network connection failure occurs
*/
private InitialEventContext(final Hashtable<?, ?> environment, final String atomicName)
throws NamingException, RemoteException
{
init(environment, atomicName);
this.contextEventProducerImpl = new ContextEventProducerImpl(this);
}
/**
* Constructs an initial context using the supplied environment; no overriding environment variables are provided.
* @param atomicName String; the name under which the root context will be registered
* @return a singleton instance of InitialEventContext
* @throws NamingException when the provided ContextFactory was not able to instantiate the wrapped context
* @throws RemoteException if a network connection failure occurs
*/
public static InitialEventContext instantiate(final String atomicName) throws NamingException, RemoteException
{
return instantiate(null, atomicName);
}
/**
* Constructs an initial context using the supplied environment.
* @param environment Hashtable<?,?>; environment used to create the initial context. Null indicates an empty
* environment.
* @param atomicName String; the name under which the root context will be registered
* @return a singleton instance of InitialEventContext
* @throws NamingException when the provided ContextFactory was not able to instantiate the wrapped context
* @throws RemoteException if a network connection failure occurs
*/
public static InitialEventContext instantiate(final Hashtable<?, ?> environment, final String atomicName)
throws NamingException, RemoteException
{
if (INSTANCE != null)
{
return INSTANCE;
}
INSTANCE = new InitialEventContext(environment, atomicName);
INSTANCE.init(environment == null ? null : (Hashtable<?, ?>) environment.clone(), atomicName);
return INSTANCE;
}
/**
* Initializes the initial context using the supplied environment.
* @param environment Hashtable<?,?>; environment used to create the initial context. Null indicates an empty
* environment.
* @param atomicName String; the name under which the root context will be registered
* @throws NamingException when the provided ContextFactory was not able to instantiate the wrapped context
* @throws RemoteException if a network connection failure occurs
*/
protected void init(final Hashtable<?, ?> environment, final String atomicName) throws NamingException, RemoteException
{
this.properties = buildEnvironment(environment);
if (this.properties.get(Context.INITIAL_CONTEXT_FACTORY) != null)
{
getDefaultInitCtx(atomicName);
}
}
/**
* Build the properties that the generation of the initial event context will use. In sequence, the following sources of
* properties are explored: (1) default values, (2) system environment, (3) Java system properties, (4) content of the
* /resources/jndi.properties file, (5) the provided environment. If a property is available in a later evaluation, it takes
* precedence over an earlier definition.
* @param environment the final overwriting environment to use (can be null)
* @return Hashtable<?,?>; a combined Hashtable with information from system properties, jndi.properties and the
* provided environment Hashtable
*/
protected Hashtable<?, ?> buildEnvironment(final Hashtable<?, ?> environment)
{
Hashtable<String, String> result = new Hashtable<>();
String[] keys = new String[] {INITIAL_CONTEXT_FACTORY, PROVIDER_URL, WRAPPED_CONTEXT_FACTORY};
// (1) defaults
result.put(INITIAL_CONTEXT_FACTORY, "nl.tudelft.simulation.naming.context.JvmContextFactory");
// (2) system properties
Map<String, String> sysEnv = System.getenv();
for (String key : keys)
{
if (sysEnv.containsKey(key))
{
result.put(key, sysEnv.get(key));
}
}
// (3) Java system properties
Properties javaProps = System.getProperties();
for (String key : keys)
{
if (javaProps.containsKey(key))
{
result.put(key, javaProps.get(key).toString());
}
}
// (4) content of the /resources/jndi.properties file
InputStream stream = URLResource.getResourceAsStream("/resources/jndi.properties");
if (stream != null)
{
Properties jndiProps = new Properties();
try
{
jndiProps.load(stream);
for (String key : keys)
{
if (jndiProps.containsKey(key))
{
result.put(key, jndiProps.get(key).toString());
}
}
}
catch (IOException exception)
{
CategoryLogger.always().error(exception);
}
}
// (5) the provided environment (if provided)
if (environment != null)
{
for (String key : keys)
{
if (environment.containsKey(key))
{
result.put(key, environment.get(key).toString());
}
}
}
return result;
}
/**
* Retrieves the initial context by calling NamingManager.getInitialContext() and cache it in defaultInitCtx. Set
* <code>gotDefault</code> so that we know we've tried this before.
* @param atomicName String; the name under which the root context will be registered
* @return The non-null cached initial context.
* @throws NamingException if a naming exception was encountered
* @throws RemoteException if a network connection failure occurs
*/
protected ContextInterface getDefaultInitCtx(final String atomicName) throws NamingException, RemoteException
{
try
{
if (!this.gotDefault)
{
String factoryName = this.properties.get(Context.INITIAL_CONTEXT_FACTORY).toString();
Throw.when(factoryName == null, NamingException.class,
"value of " + Context.INITIAL_CONTEXT_FACTORY + " not in properties");
@SuppressWarnings("unchecked")
Class<ContextFactory> factoryClass = (Class<ContextFactory>) Class.forName(factoryName);
Constructor<ContextFactory> factoryConstructor = factoryClass.getDeclaredConstructor();
ContextFactory factory = factoryConstructor.newInstance();
this.defaultInitCtx = factory.getInitialContext(this.properties, atomicName);
this.gotDefault = true;
}
if (this.defaultInitCtx == null)
{
throw new NoInitialContextException();
}
return this.defaultInitCtx;
}
catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException
| IllegalAccessException | IllegalArgumentException | InvocationTargetException exception)
{
throw new NamingException("Unable to get default initial context: " + exception.getMessage());
}
}
/**
* Return a safe copy of the used environment variables.
* @return Hashtable<?, ?>; a safe copy of the used environment variables
*/
public Hashtable<?, ?> getEnvironment()
{
return (Hashtable<?, ?>) this.properties.clone();
}
/** {@inheritDoc} */
@Override
public void close() throws NamingException, RemoteException
{
this.properties = null;
if (this.defaultInitCtx != null)
{
this.defaultInitCtx.close();
this.defaultInitCtx = null;
}
this.gotDefault = false;
}
/** {@inheritDoc} */
@Override
public String getAtomicName() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.getAtomicName();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public ContextInterface getParent() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.getParent();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public ContextInterface getRootContext() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.getRootContext();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public String getAbsolutePath() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.getAbsolutePath();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public Object get(final String name) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.get(name);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public Object getObject(final String key) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.getObject(key);
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public boolean exists(final String name) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.exists(name);
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public boolean hasKey(final String key) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.hasKey(key);
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public boolean hasObject(final Object object) throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.hasObject(object);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public boolean isEmpty() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.isEmpty();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public void bind(final String name, final Object object) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.bind(name, object);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void bindObject(final String key, final Object object) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.bindObject(key, object);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void bindObject(final Object object) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.bindObject(object);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void unbind(final String name) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.unbind(name);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void unbindObject(final String key) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.unbindObject(key);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void rebind(final String name, final Object object) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.rebind(name, object);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void rebindObject(final String key, final Object object) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.rebindObject(key, object);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void rename(final String oldName, final String newName) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.rename(oldName, newName);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public ContextInterface createSubcontext(final String name) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.createSubcontext(name);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void destroySubcontext(final String name) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.destroySubcontext(name);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void checkCircular(final Object newObject) throws NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.checkCircular(newObject);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public Set<String> keySet() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.keySet();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public Collection<Object> values() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.values();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public Map<String, Object> bindings() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.bindings();
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public void fireObjectChangedEventValue(final Object object)
throws NameNotFoundException, NullPointerException, NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.fireObjectChangedEventValue(object);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public void fireObjectChangedEventKey(final String key)
throws NameNotFoundException, NullPointerException, NamingException, RemoteException
{
if (this.defaultInitCtx != null)
this.defaultInitCtx.fireObjectChangedEventKey(key);
else
throw new NoInitialContextException();
}
/** {@inheritDoc} */
@Override
public String toString()
{
if (this.defaultInitCtx != null)
return "InitialEventContext[" + this.defaultInitCtx.toString() + "]";
return "InitialEventContext[null]";
}
/** {@inheritDoc} */
@Override
public String toString(final boolean verbose) throws RemoteException
{
if (!verbose)
{
return "InitialEventContext[" + getAtomicName() + "]";
}
return ContextUtil.toText(this);
}
/* ***************************************************************************************************************** */
/* **************************************** EVENTPRODUCER IMPLEMENTATION ******************************************* */
/* ***************************************************************************************************************** */
/** {@inheritDoc} */
@Override
public synchronized boolean addListener(final EventListener listener, final EventType eventType) throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.addListener(listener, eventType);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public synchronized boolean addListener(final EventListener listener, final EventType eventType,
final ReferenceType referenceType) throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.addListener(listener, eventType, referenceType);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public synchronized boolean addListener(final EventListener listener, final EventType eventType, final int position)
throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.addListener(listener, eventType, position);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public synchronized boolean addListener(final EventListener listener, final EventType eventType, final int position,
final ReferenceType referenceType) throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.addListener(listener, eventType, position, referenceType);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public synchronized boolean removeListener(final EventListener listener, final EventType eventType) throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.removeListener(listener, eventType);
throw new RuntimeException(new NoInitialContextException());
}
@Override
public EventListenerMap getEventListenerMap() throws RemoteException
{
if (this.defaultInitCtx != null)
return this.defaultInitCtx.getEventListenerMap();
throw new RuntimeException(new NoInitialContextException());
}
/* ***************************************************************************************************************** */
/* *************************************** EVENTCONTEXTINTERFACE LISTENERS ***************************************** */
/* ***************************************************************************************************************** */
/** {@inheritDoc} */
@Override
public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope)
throws RemoteException, NameNotFoundException, InvalidNameException, NotContextException, NamingException,
NullPointerException
{
if (this.defaultInitCtx != null)
return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope,
final ReferenceType referenceType) throws RemoteException, NameNotFoundException, InvalidNameException,
NotContextException, NamingException, NullPointerException
{
if (this.defaultInitCtx != null)
return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope, referenceType);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope,
final int position) throws RemoteException, NameNotFoundException, InvalidNameException, NotContextException,
NamingException, NullPointerException
{
if (this.defaultInitCtx != null)
return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope, position);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope,
final int position, final ReferenceType referenceType) throws RemoteException, NameNotFoundException,
InvalidNameException, NotContextException, NamingException, NullPointerException
{
if (this.defaultInitCtx != null)
return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope, position, referenceType);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public boolean removeListener(final EventListener listener, final String absolutePath, final ContextScope contextScope)
throws RemoteException, InvalidNameException, NullPointerException
{
if (this.defaultInitCtx != null)
return this.contextEventProducerImpl.removeListener(listener, absolutePath, contextScope);
throw new RuntimeException(new NoInitialContextException());
}
/** {@inheritDoc} */
@Override
public int removeAllListeners()
{
if (this.defaultInitCtx != null)
return this.contextEventProducerImpl.removeAllListeners();
throw new RuntimeException(new NoInitialContextException());
}
}