View Javadoc
1   package nl.tudelft.simulation.naming.context.event;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.lang.reflect.Constructor;
6   import java.lang.reflect.InvocationTargetException;
7   import java.util.Collection;
8   import java.util.Hashtable;
9   import java.util.Map;
10  import java.util.Properties;
11  import java.util.Set;
12  
13  import javax.naming.Context;
14  import javax.naming.InvalidNameException;
15  import javax.naming.NameNotFoundException;
16  import javax.naming.NamingException;
17  import javax.naming.NoInitialContextException;
18  import javax.naming.NotContextException;
19  
20  import org.djutils.event.EventListener;
21  import org.djutils.event.EventListenerMap;
22  import org.djutils.event.EventType;
23  import org.djutils.event.reference.ReferenceType;
24  import org.djutils.exceptions.Throw;
25  import org.djutils.io.ResourceResolver;
26  import org.djutils.logger.CategoryLogger;
27  
28  import nl.tudelft.simulation.naming.context.ContextFactory;
29  import nl.tudelft.simulation.naming.context.ContextInterface;
30  import nl.tudelft.simulation.naming.context.util.ContextUtil;
31  
32  /**
33   * InitialEventContext class. This class is the starting context for performing naming operations. The class is loosely based on
34   * InitialContext in the Java JNDI package, but creates a lightweight Context that implements the DSOL ContextInterface. The
35   * InitialEventContext is a singleton class.
36   * <p>
37   * Copyright (c) 2002-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
38   * for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
39   * project is distributed under a three-clause BSD-style license, which can be found at
40   * <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
41   * </p>
42   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
43   */
44  @SuppressWarnings("checkstyle:needbraces")
45  public final class InitialEventContext implements EventContext
46  {
47      /**
48       * Constant that holds the name of the environment property for specifying the initial context factory to use. The value of
49       * the property should be the fully qualified class name of the factory class that will create an initial context. This
50       * property may be specified in the environment parameter passed to the initial context constructor, a system property, or
51       * an application resource file. If it is not specified in any of these sources, {@code NoInitialContextException} is thrown
52       * when an initial context is required to complete an operation.
53       * <p>
54       * The value of this constant is "java.naming.factory.initial".
55       * </p>
56       */
57      public static final String INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial";
58  
59      /**
60       * Constant that holds the name of the environment property for specifying configuration information for the service
61       * provider to use. The value of the property should contain a URL string (e.g. "http://localhost:1099/remoteContext"). This
62       * property may be specified in the environment, a system property, or a resource file. If it is not specified in any of
63       * these sources, the default configuration is determined by the service provider.
64       * <p>
65       * The value of this constant is "java.naming.provider.url".
66       * </p>
67       */
68      public static final String PROVIDER_URL = "java.naming.provider.url";
69  
70      /**
71       * Constant that holds the name of the environment property for specifying the initial context factory to use, as embedded
72       * factory in a remote context that is specified by the INITIAL_CONTEXT_FACTORY constant. The value of the property should
73       * be the fully qualified class name of the factory class that will create an initial context that will be embedded within
74       * the remote context. This property may be specified in the environment parameter passed to the initial context
75       * constructor, a system property, or an application resource file. If it is not specified in any of these sources,
76       * {@code NoInitialContextException} is thrown when an initial context is required to complete an operation.
77       * <p>
78       * The value of this constant is "wrapped.naming.factory.initial".
79       * </p>
80       */
81      public static final String WRAPPED_CONTEXT_FACTORY = "wrapped.naming.factory.initial";
82  
83      /** the singleton instance of the InitialEventContext. */
84      private static InitialEventContext INSTANCE;
85  
86      /** the properties of the initialEventContext. */
87      protected Hashtable<?, ?> properties = null;
88  
89      /**
90       * The initial context, usually built by a factory. It is set by getDefaultInitCtx() the first time getDefaultInitCtx() is
91       * called. Subsequent invocations of getDefaultInitCtx() return the value of defaultInitCtx until close() is called.
92       */
93      protected ContextInterface defaultInitCtx = null;
94  
95      /** has the default context been generated? */
96      protected boolean gotDefault = false;
97  
98      /** The event producer to which context events will be delegated for handling. */
99      public ContextEventProducerImpl contextEventProducerImpl;
100 
101     /**
102      * Constructs an initial context. No environment properties are supplied. Equivalent to
103      * <code>new InitialContext(null)</code>.
104      * @param environment the overriding environment variables for the Factory that constructs the wrapped Context
105      * @param atomicName the name under which the root context will be registered
106      * @throws NamingException if a naming exception is encountered
107      */
108     private InitialEventContext(final Hashtable<?, ?> environment, final String atomicName) throws NamingException
109     {
110         init(environment, atomicName);
111         this.contextEventProducerImpl = new ContextEventProducerImpl(this);
112     }
113 
114     /**
115      * Constructs an initial context using the supplied environment; no overriding environment variables are provided.
116      * @param atomicName the name under which the root context will be registered
117      * @return a singleton instance of InitialEventContext
118      * @throws NamingException when the provided ContextFactory was not able to instantiate the wrapped context
119      */
120     public static InitialEventContext instantiate(final String atomicName) throws NamingException
121     {
122         return instantiate(null, atomicName);
123     }
124 
125     /**
126      * Constructs an initial context using the supplied environment.
127      * @param environment environment used to create the initial context. Null indicates an empty environment.
128      * @param atomicName the name under which the root context will be registered
129      * @return a singleton instance of InitialEventContext
130      * @throws NamingException when the provided ContextFactory was not able to instantiate the wrapped context
131      */
132     public static InitialEventContext instantiate(final Hashtable<?, ?> environment, final String atomicName)
133             throws NamingException
134     {
135         if (INSTANCE != null)
136         {
137             return INSTANCE;
138         }
139 
140         INSTANCE = new InitialEventContext(environment, atomicName);
141         INSTANCE.init(environment == null ? null : (Hashtable<?, ?>) environment.clone(), atomicName);
142         return INSTANCE;
143     }
144 
145     /**
146      * Initializes the initial context using the supplied environment.
147      * @param environment environment used to create the initial context. Null indicates an empty environment.
148      * @param atomicName the name under which the root context will be registered
149      * @throws NamingException when the provided ContextFactory was not able to instantiate the wrapped context
150      */
151     protected void init(final Hashtable<?, ?> environment, final String atomicName) throws NamingException
152     {
153         this.properties = buildEnvironment(environment);
154         if (this.properties.get(Context.INITIAL_CONTEXT_FACTORY) != null)
155         {
156             getDefaultInitCtx(atomicName);
157         }
158     }
159 
160     /**
161      * Build the properties that the generation of the initial event context will use. In sequence, the following sources of
162      * properties are explored: (1) default values, (2) system environment, (3) Java system properties, (4) content of the
163      * /resources/jndi.properties file, (5) the provided environment. If a property is available in a later evaluation, it takes
164      * precedence over an earlier definition.
165      * @param environment the final overwriting environment to use (can be null)
166      * @return a combined Hashtable with information from system properties, jndi.properties and the provided environment
167      *         Hashtable
168      */
169     protected Hashtable<?, ?> buildEnvironment(final Hashtable<?, ?> environment)
170     {
171         Hashtable<String, String> result = new Hashtable<>();
172         String[] keys = new String[] {INITIAL_CONTEXT_FACTORY, PROVIDER_URL, WRAPPED_CONTEXT_FACTORY};
173 
174         // (1) defaults
175         result.put(INITIAL_CONTEXT_FACTORY, "nl.tudelft.simulation.naming.context.JvmContextFactory");
176 
177         // (2) system properties
178         Map<String, String> sysEnv = System.getenv();
179         for (String key : keys)
180         {
181             if (sysEnv.containsKey(key))
182             {
183                 result.put(key, sysEnv.get(key));
184             }
185         }
186 
187         // (3) Java system properties
188         Properties javaProps = System.getProperties();
189         for (String key : keys)
190         {
191             if (javaProps.containsKey(key))
192             {
193                 result.put(key, javaProps.get(key).toString());
194             }
195         }
196 
197         // (4) content of the /resources/jndi.properties file
198         try
199         {
200             InputStream stream = ResourceResolver.resolve("/jndi.properties").openStream();
201             if (stream != null)
202             {
203                 Properties jndiProps = new Properties();
204                 jndiProps.load(stream);
205                 for (String key : keys)
206                 {
207                     if (jndiProps.containsKey(key))
208                     {
209                         result.put(key, jndiProps.get(key).toString());
210                     }
211                 }
212             }
213         }
214         catch (IOException exception)
215         {
216             CategoryLogger.always().error(exception);
217         }
218 
219         // (5) the provided environment (if provided)
220         if (environment != null)
221         {
222             for (String key : keys)
223             {
224                 if (environment.containsKey(key))
225                 {
226                     result.put(key, environment.get(key).toString());
227                 }
228             }
229         }
230 
231         return result;
232     }
233 
234     /**
235      * Retrieves the initial context by calling NamingManager.getInitialContext() and cache it in defaultInitCtx. Set
236      * <code>gotDefault</code> so that we know we've tried this before.
237      * @param atomicName the name under which the root context will be registered
238      * @return The non-null cached initial context.
239      * @throws NamingException if a naming exception was encountered
240      */
241     protected ContextInterface getDefaultInitCtx(final String atomicName) throws NamingException
242     {
243         try
244         {
245             if (!this.gotDefault)
246             {
247                 String factoryName = this.properties.get(Context.INITIAL_CONTEXT_FACTORY).toString();
248                 Throw.when(factoryName == null, NamingException.class,
249                         "value of " + Context.INITIAL_CONTEXT_FACTORY + " not in properties");
250                 @SuppressWarnings("unchecked")
251                 Class<ContextFactory> factoryClass = (Class<ContextFactory>) Class.forName(factoryName);
252                 Constructor<ContextFactory> factoryConstructor = factoryClass.getDeclaredConstructor();
253                 ContextFactory factory = factoryConstructor.newInstance();
254                 this.defaultInitCtx = factory.getInitialContext(this.properties, atomicName);
255                 this.gotDefault = true;
256             }
257             if (this.defaultInitCtx == null)
258             {
259                 throw new NoInitialContextException();
260             }
261             return this.defaultInitCtx;
262         }
263         catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException
264                 | IllegalAccessException | IllegalArgumentException | InvocationTargetException exception)
265         {
266             throw new NamingException("Unable to get default initial context: " + exception.getMessage());
267         }
268     }
269 
270     /**
271      * Return a safe copy of the used environment variables.
272      * @return a safe copy of the used environment variables
273      */
274     public Hashtable<?, ?> getEnvironment()
275     {
276         return (Hashtable<?, ?>) this.properties.clone();
277     }
278 
279     @Override
280     public void close() throws NamingException
281     {
282         this.properties = null;
283         if (this.defaultInitCtx != null)
284         {
285             this.defaultInitCtx.close();
286             this.defaultInitCtx = null;
287         }
288         this.gotDefault = false;
289     }
290 
291     @Override
292     public String getAtomicName()
293     {
294         if (this.defaultInitCtx != null)
295             return this.defaultInitCtx.getAtomicName();
296         throw new RuntimeException(new NoInitialContextException());
297     }
298 
299     @Override
300     public ContextInterface getParent()
301     {
302         if (this.defaultInitCtx != null)
303             return this.defaultInitCtx.getParent();
304         throw new RuntimeException(new NoInitialContextException());
305 
306     }
307 
308     @Override
309     public ContextInterface getRootContext()
310     {
311         if (this.defaultInitCtx != null)
312             return this.defaultInitCtx.getRootContext();
313         throw new RuntimeException(new NoInitialContextException());
314     }
315 
316     @Override
317     public String getAbsolutePath()
318     {
319         if (this.defaultInitCtx != null)
320             return this.defaultInitCtx.getAbsolutePath();
321         throw new RuntimeException(new NoInitialContextException());
322     }
323 
324     @Override
325     public Object get(final String name) throws NamingException
326     {
327         if (this.defaultInitCtx != null)
328             return this.defaultInitCtx.get(name);
329         throw new RuntimeException(new NoInitialContextException());
330     }
331 
332     @Override
333     public Object getObject(final String key) throws NamingException
334     {
335         if (this.defaultInitCtx != null)
336             return this.defaultInitCtx.getObject(key);
337         throw new NoInitialContextException();
338     }
339 
340     @Override
341     public boolean exists(final String name) throws NamingException
342     {
343         if (this.defaultInitCtx != null)
344             return this.defaultInitCtx.exists(name);
345         throw new NoInitialContextException();
346     }
347 
348     @Override
349     public boolean hasKey(final String key) throws NamingException
350     {
351         if (this.defaultInitCtx != null)
352             return this.defaultInitCtx.hasKey(key);
353         throw new NoInitialContextException();
354     }
355 
356     @Override
357     public boolean hasObject(final Object object)
358     {
359         if (this.defaultInitCtx != null)
360             return this.defaultInitCtx.hasObject(object);
361         throw new RuntimeException(new NoInitialContextException());
362     }
363 
364     @Override
365     public boolean isEmpty()
366     {
367         if (this.defaultInitCtx != null)
368             return this.defaultInitCtx.isEmpty();
369         throw new RuntimeException(new NoInitialContextException());
370     }
371 
372     @Override
373     public void bind(final String name, final Object object) throws NamingException
374     {
375         if (this.defaultInitCtx != null)
376             this.defaultInitCtx.bind(name, object);
377         else
378             throw new NoInitialContextException();
379     }
380 
381     @Override
382     public void bindObject(final String key, final Object object) throws NamingException
383     {
384         if (this.defaultInitCtx != null)
385             this.defaultInitCtx.bindObject(key, object);
386         else
387             throw new NoInitialContextException();
388     }
389 
390     @Override
391     public void bindObject(final Object object) throws NamingException
392     {
393         if (this.defaultInitCtx != null)
394             this.defaultInitCtx.bindObject(object);
395         else
396             throw new NoInitialContextException();
397     }
398 
399     @Override
400     public void unbind(final String name) throws NamingException
401     {
402         if (this.defaultInitCtx != null)
403             this.defaultInitCtx.unbind(name);
404         else
405             throw new NoInitialContextException();
406     }
407 
408     @Override
409     public void unbindObject(final String key) throws NamingException
410     {
411         if (this.defaultInitCtx != null)
412             this.defaultInitCtx.unbindObject(key);
413         else
414             throw new NoInitialContextException();
415     }
416 
417     @Override
418     public void rebind(final String name, final Object object) throws NamingException
419     {
420         if (this.defaultInitCtx != null)
421             this.defaultInitCtx.rebind(name, object);
422         else
423             throw new NoInitialContextException();
424     }
425 
426     @Override
427     public void rebindObject(final String key, final Object object) throws NamingException
428     {
429         if (this.defaultInitCtx != null)
430             this.defaultInitCtx.rebindObject(key, object);
431         else
432             throw new NoInitialContextException();
433     }
434 
435     @Override
436     public void rename(final String oldName, final String newName) throws NamingException
437     {
438         if (this.defaultInitCtx != null)
439             this.defaultInitCtx.rename(oldName, newName);
440         else
441             throw new NoInitialContextException();
442     }
443 
444     @Override
445     public ContextInterface createSubcontext(final String name) throws NamingException
446     {
447         if (this.defaultInitCtx != null)
448             return this.defaultInitCtx.createSubcontext(name);
449         else
450             throw new NoInitialContextException();
451     }
452 
453     @Override
454     public void destroySubcontext(final String name) throws NamingException
455     {
456         if (this.defaultInitCtx != null)
457             this.defaultInitCtx.destroySubcontext(name);
458         else
459             throw new NoInitialContextException();
460     }
461 
462     @Override
463     public void checkCircular(final Object newObject) throws NamingException
464     {
465         if (this.defaultInitCtx != null)
466             this.defaultInitCtx.checkCircular(newObject);
467         else
468             throw new NoInitialContextException();
469     }
470 
471     @Override
472     public Set<String> keySet()
473     {
474         if (this.defaultInitCtx != null)
475             return this.defaultInitCtx.keySet();
476         throw new RuntimeException(new NoInitialContextException());
477     }
478 
479     @Override
480     public Collection<Object> values()
481     {
482         if (this.defaultInitCtx != null)
483             return this.defaultInitCtx.values();
484         throw new RuntimeException(new NoInitialContextException());
485     }
486 
487     @Override
488     public Map<String, Object> bindings()
489     {
490         if (this.defaultInitCtx != null)
491             return this.defaultInitCtx.bindings();
492         throw new RuntimeException(new NoInitialContextException());
493     }
494 
495     @Override
496     public void fireObjectChangedEventValue(final Object object)
497             throws NameNotFoundException, NullPointerException, NamingException
498     {
499         if (this.defaultInitCtx != null)
500             this.defaultInitCtx.fireObjectChangedEventValue(object);
501         else
502             throw new NoInitialContextException();
503     }
504 
505     @Override
506     public void fireObjectChangedEventKey(final String key) throws NameNotFoundException, NullPointerException, NamingException
507     {
508         if (this.defaultInitCtx != null)
509             this.defaultInitCtx.fireObjectChangedEventKey(key);
510         else
511             throw new NoInitialContextException();
512     }
513 
514     @Override
515     public String toString()
516     {
517         if (this.defaultInitCtx != null)
518             return "InitialEventContext[" + this.defaultInitCtx.toString() + "]";
519         return "InitialEventContext[null]";
520     }
521 
522     @Override
523     public String toString(final boolean verbose)
524     {
525         if (!verbose)
526         {
527             return "InitialEventContext[" + getAtomicName() + "]";
528         }
529         return ContextUtil.toText(this);
530     }
531 
532     /* ***************************************************************************************************************** */
533     /* **************************************** EVENTPRODUCER IMPLEMENTATION ******************************************* */
534     /* ***************************************************************************************************************** */
535 
536     @Override
537     public synchronized boolean addListener(final EventListener listener, final EventType eventType)
538     {
539         if (this.defaultInitCtx != null)
540             return this.defaultInitCtx.addListener(listener, eventType);
541         throw new RuntimeException(new NoInitialContextException());
542     }
543 
544     @Override
545     public synchronized boolean addListener(final EventListener listener, final EventType eventType,
546             final ReferenceType referenceType)
547     {
548         if (this.defaultInitCtx != null)
549             return this.defaultInitCtx.addListener(listener, eventType, referenceType);
550         throw new RuntimeException(new NoInitialContextException());
551     }
552 
553     @Override
554     public synchronized boolean addListener(final EventListener listener, final EventType eventType, final int position)
555 
556     {
557         if (this.defaultInitCtx != null)
558             return this.defaultInitCtx.addListener(listener, eventType, position);
559         throw new RuntimeException(new NoInitialContextException());
560     }
561 
562     @Override
563     public synchronized boolean addListener(final EventListener listener, final EventType eventType, final int position,
564             final ReferenceType referenceType)
565     {
566         if (this.defaultInitCtx != null)
567             return this.defaultInitCtx.addListener(listener, eventType, position, referenceType);
568         throw new RuntimeException(new NoInitialContextException());
569     }
570 
571     @Override
572     public synchronized boolean removeListener(final EventListener listener, final EventType eventType)
573     {
574         if (this.defaultInitCtx != null)
575             return this.defaultInitCtx.removeListener(listener, eventType);
576         throw new RuntimeException(new NoInitialContextException());
577     }
578 
579     @Override
580     public EventListenerMap getEventListenerMap()
581     {
582         if (this.defaultInitCtx != null)
583             return this.defaultInitCtx.getEventListenerMap();
584         throw new RuntimeException(new NoInitialContextException());
585     }
586 
587     /* ***************************************************************************************************************** */
588     /* *************************************** EVENTCONTEXTINTERFACE LISTENERS ***************************************** */
589     /* ***************************************************************************************************************** */
590 
591     @Override
592     public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope)
593             throws NameNotFoundException, InvalidNameException, NotContextException, NamingException, NullPointerException
594     {
595         if (this.defaultInitCtx != null)
596             return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope);
597         throw new RuntimeException(new NoInitialContextException());
598     }
599 
600     @Override
601     public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope,
602             final ReferenceType referenceType)
603             throws NameNotFoundException, InvalidNameException, NotContextException, NamingException, NullPointerException
604     {
605         if (this.defaultInitCtx != null)
606             return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope, referenceType);
607         throw new RuntimeException(new NoInitialContextException());
608     }
609 
610     @Override
611     public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope,
612             final int position)
613             throws NameNotFoundException, InvalidNameException, NotContextException, NamingException, NullPointerException
614     {
615         if (this.defaultInitCtx != null)
616             return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope, position);
617         throw new RuntimeException(new NoInitialContextException());
618     }
619 
620     @Override
621     public boolean addListener(final EventListener listener, final String absolutePath, final ContextScope contextScope,
622             final int position, final ReferenceType referenceType)
623             throws NameNotFoundException, InvalidNameException, NotContextException, NamingException, NullPointerException
624     {
625         if (this.defaultInitCtx != null)
626             return this.contextEventProducerImpl.addListener(listener, absolutePath, contextScope, position, referenceType);
627         throw new RuntimeException(new NoInitialContextException());
628     }
629 
630     @Override
631     public boolean removeListener(final EventListener listener, final String absolutePath, final ContextScope contextScope)
632             throws InvalidNameException, NullPointerException
633     {
634         if (this.defaultInitCtx != null)
635             return this.contextEventProducerImpl.removeListener(listener, absolutePath, contextScope);
636         throw new RuntimeException(new NoInitialContextException());
637     }
638 
639     @Override
640     public int removeAllListeners()
641     {
642         if (this.defaultInitCtx != null)
643             return this.contextEventProducerImpl.removeAllListeners();
644         throw new RuntimeException(new NoInitialContextException());
645     }
646 
647 }