View Javadoc
1   package nl.tudelft.simulation.naming.context;
2   
3   import java.io.Serializable;
4   import java.net.URL;
5   import java.rmi.AccessException;
6   import java.rmi.AlreadyBoundException;
7   import java.rmi.RemoteException;
8   import java.util.Collection;
9   import java.util.LinkedHashSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import javax.naming.NameNotFoundException;
15  import javax.naming.NamingException;
16  
17  import org.djutils.event.EventListener;
18  import org.djutils.event.EventListenerMap;
19  import org.djutils.event.EventProducer;
20  import org.djutils.event.EventType;
21  import org.djutils.event.reference.Reference;
22  import org.djutils.event.reference.ReferenceType;
23  import org.djutils.event.rmi.RmiEventProducer;
24  import org.djutils.exceptions.Throw;
25  import org.djutils.rmi.RmiObject;
26  
27  import nl.tudelft.simulation.naming.context.util.ContextUtil;
28  
29  /**
30   * Context that exists on another computer.
31   * <p>
32   * Copyright (c) 2002-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
33   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
34   * project is distributed under a three-clause BSD-style license, which can be found at
35   * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
36   * https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
37   * </p>
38   * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
39   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
40   */
41  public class RemoteContext extends RmiObject implements RemoteContextInterface, EventProducer
42  {
43      /** The default serial version UID for serializable classes. */
44      private static final long serialVersionUID = 1L;
45  
46      /** The underlying event context. */
47      protected ContextInterface embeddedContext = null;
48  
49      /** The (remote) event producer for this context. */
50      protected RemoteChangeEventProducer remoteEventProducer;
51  
52      /**
53       * Constructs a new RemoteContext. Register the new context in the RMI registry. When the RMI registry does not exist yet,
54       * it will be created, but <b>only</b> on the local host. Remote creation of a registry on another computer is not possible.
55       * Any attempt to do so will cause an AccessException to be fired.
56       * @param host String; the host where the RMI registry resides or will be created. Creation is only possible on localhost.
57       * @param port int; the port where the RMI registry can be found or will be created
58       * @param bindingKey the key under which this context will be bound in the RMI registry
59       * @param embeddedContext ContextInterface; the underlying context
60       * @param eventProducerBindingKey String; the key under which the event producer will be bound
61       * @throws RemoteException when there is a problem with the RMI registry
62       * @throws AlreadyBoundException when there is already another object bound to the bindingKey
63       * @throws NullPointerException when host, path, or bindingKey is null
64       * @throws IllegalArgumentException when port &lt; 0 or port &gt; 65535
65       * @throws AccessException when there is an attempt to create a registry on a remote host
66       */
67      public RemoteContext(final String host, final int port, final String bindingKey, final ContextInterface embeddedContext,
68              final String eventProducerBindingKey) throws RemoteException, AlreadyBoundException
69      {
70          super(host, port, bindingKey);
71          Throw.whenNull(embeddedContext, "embedded context cannot be null");
72          this.embeddedContext = embeddedContext;
73          this.remoteEventProducer = new RemoteChangeEventProducer(host, port, eventProducerBindingKey);
74      }
75  
76      /**
77       * Constructs a new RemoteContext. Register the new context in the RMI registry. When the host has not been specified in the
78       * URL, 127.0.0.1 will be used. When the port has not been specified in the URL, the default RMI port 1099 will be used.
79       * When the RMI registry does not exist yet, it will be created, but <b>only</b> on the local host. Remote creation of a
80       * registry on another computer is not possible. Any attempt to do so will cause an AccessException to be fired.
81       * @param registryURL URL; the URL of the registry, e.g., "http://localhost:1099" or "http://130.161.185.14:28452"
82       * @param embeddedContext ContextInterface; the underlying context
83       * @param eventProducerBindingKey String; the key under which the event producer will be bound
84       * @throws RemoteException when there is a problem with the RMI registry
85       * @throws AlreadyBoundException when there is already another object bound to the bindingKey
86       * @throws NullPointerException when registryURL, bindingKey, or embeddedContext is null
87       * @throws AccessException when there is an attempt to create a registry on a remote host
88       */
89      public RemoteContext(final URL registryURL, final ContextInterface embeddedContext, final String eventProducerBindingKey)
90              throws RemoteException, AlreadyBoundException
91      {
92          super(registryURL, registryURL.getPath());
93          Throw.whenNull(embeddedContext, "embedded context cannot be null");
94          this.embeddedContext = embeddedContext;
95          String host = registryURL.getHost() == null ? "127.0.0.1" : registryURL.getHost();
96          int port = registryURL.getPort() == -1 ? 1099 : registryURL.getPort();
97          this.remoteEventProducer = new RemoteChangeEventProducer(host, port, eventProducerBindingKey);
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     public String getAtomicName() throws RemoteException
103     {
104         return this.embeddedContext.getAtomicName();
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public ContextInterface getParent() throws RemoteException
110     {
111         return this.embeddedContext.getParent();
112     }
113 
114     /** {@inheritDoc} */
115     @Override
116     public ContextInterface getRootContext() throws RemoteException
117     {
118         return this.embeddedContext.getRootContext();
119     }
120 
121     /** {@inheritDoc} */
122     @Override
123     public String getAbsolutePath() throws RemoteException
124     {
125         return this.embeddedContext.getAbsolutePath();
126     }
127 
128     /** {@inheritDoc} */
129     @Override
130     public Object get(final String name) throws NamingException, RemoteException
131     {
132         return this.embeddedContext.get(name);
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     public Object getObject(final String key) throws NamingException, RemoteException
138     {
139         return this.embeddedContext.getObject(key);
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     public boolean exists(final String name) throws NamingException, RemoteException
145     {
146         return this.embeddedContext.exists(name);
147     }
148 
149     /** {@inheritDoc} */
150     @Override
151     public boolean hasKey(final String key) throws NamingException, RemoteException
152     {
153         return this.embeddedContext.hasKey(key);
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     public boolean hasObject(final Object object) throws RemoteException
159     {
160         return this.embeddedContext.hasObject(object);
161     }
162 
163     /** {@inheritDoc} */
164     @Override
165     public boolean isEmpty() throws RemoteException
166     {
167         return this.embeddedContext.isEmpty();
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public void bind(final String name, final Object object) throws NamingException, RemoteException
173     {
174         this.embeddedContext.bind(name, object);
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     public void bindObject(final String key, final Object object) throws NamingException, RemoteException
180     {
181         this.embeddedContext.bindObject(key, object);
182     }
183 
184     /** {@inheritDoc} */
185     @Override
186     public void bindObject(final Object object) throws NamingException, RemoteException
187     {
188         this.embeddedContext.bindObject(object);
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public void unbind(final String name) throws NamingException, RemoteException
194     {
195         this.embeddedContext.unbind(name);
196     }
197 
198     /** {@inheritDoc} */
199     @Override
200     public void unbindObject(final String key) throws NamingException, RemoteException
201     {
202         this.embeddedContext.unbindObject(key);
203     }
204 
205     /** {@inheritDoc} */
206     @Override
207     public void rebind(final String name, final Object object) throws NamingException, RemoteException
208     {
209         this.embeddedContext.rebind(name, object);
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     public void rebindObject(final String key, final Object object) throws NamingException, RemoteException
215     {
216         this.embeddedContext.rebindObject(key, object);
217     }
218 
219     /** {@inheritDoc} */
220     @Override
221     public void rename(final String oldName, final String newName) throws NamingException, RemoteException
222     {
223         this.embeddedContext.rename(oldName, newName);
224     }
225 
226     /** {@inheritDoc} */
227     @Override
228     public ContextInterface createSubcontext(final String name) throws NamingException, RemoteException
229     {
230         return this.embeddedContext.createSubcontext(name);
231     }
232 
233     /** {@inheritDoc} */
234     @Override
235     public void destroySubcontext(final String name) throws NamingException, RemoteException
236     {
237         this.embeddedContext.destroySubcontext(name);
238     }
239 
240     /** {@inheritDoc} */
241     @Override
242     public Set<String> keySet() throws RemoteException
243     {
244         return new LinkedHashSet<String>(this.embeddedContext.keySet());
245     }
246 
247     /** {@inheritDoc} */
248     @Override
249     public Collection<Object> values() throws RemoteException
250     {
251         return new LinkedHashSet<Object>(this.embeddedContext.values());
252     }
253 
254     /** {@inheritDoc} */
255     @Override
256     public Map<String, Object> bindings() throws RemoteException
257     {
258         return this.embeddedContext.bindings();
259     }
260 
261     /** {@inheritDoc} */
262     @Override
263     public void checkCircular(final Object newObject) throws NamingException, RemoteException
264     {
265         this.embeddedContext.checkCircular(newObject);
266     }
267 
268     /** {@inheritDoc} */
269     @Override
270     public void close() throws NamingException, RemoteException
271     {
272         // TODO: see if connection needs to be closed
273         this.embeddedContext.close();
274     }
275 
276     /** {@inheritDoc} */
277     @Override
278     public void fireObjectChangedEventValue(final Object object)
279             throws NameNotFoundException, NullPointerException, NamingException, RemoteException
280     {
281         Throw.whenNull(object, "object cannot be null");
282         fireObjectChangedEventKey(makeObjectKey(object));
283     }
284 
285     /** {@inheritDoc} */
286     @Override
287     public void fireObjectChangedEventKey(final String key)
288             throws NameNotFoundException, NullPointerException, NamingException, RemoteException
289     {
290         Throw.whenNull(key, "key cannot be null");
291         Throw.when(key.length() == 0 || key.contains(ContextInterface.SEPARATOR), NamingException.class,
292                 "key [%s] is the empty string or key contains '/'", key);
293         if (!hasKey(key))
294         {
295             throw new NameNotFoundException("Could not find object with key " + key + " for fireObjectChangedEvent");
296         }
297         try
298         {
299             this.remoteEventProducer.fireChangedEvent(ContextInterface.OBJECT_CHANGED_EVENT,
300                     new Object[] {this, key, getObject(key)});
301         }
302         catch (Exception exception)
303         {
304             throw new NamingException(exception.getMessage());
305         }
306     }
307 
308     /**
309      * Make a key for the object based on object.toString() where the "/" characters are replaced by "#".
310      * @param object Object; the object for which the key has to be generated
311      * @return the key based on toString() where the "/" characters are replaced by "#"
312      */
313     private String makeObjectKey(final Object object)
314     {
315         return object.toString().replace(ContextInterface.SEPARATOR, ContextInterface.REPLACE_SEPARATOR);
316     }
317 
318     /** {@inheritDoc} */
319     @Override
320     public String toString()
321     {
322         if (this.embeddedContext != null)
323             return this.embeddedContext.toString();
324         return "RemoteContext[null]";
325     }
326 
327     /** {@inheritDoc} */
328     @Override
329     public String toString(final boolean verbose) throws RemoteException
330     {
331         if (!verbose)
332         {
333             return "RemoteContext[" + getAtomicName() + "]";
334         }
335         return ContextUtil.toText(this);
336     }
337 
338     /* ***************************************************************************************************************** */
339     /* **************************************** EVENTPRODUCER IMPLEMENTATION ******************************************* */
340     /* ***************************************************************************************************************** */
341 
342     /** {@inheritDoc} */
343     @Override
344     public synchronized boolean addListener(final EventListener listener, final EventType eventType) throws RemoteException
345     {
346         return this.embeddedContext.addListener(listener, eventType);
347     }
348 
349     /** {@inheritDoc} */
350     @Override
351     public synchronized boolean addListener(final EventListener listener, final EventType eventType,
352             final ReferenceType referenceType) throws RemoteException
353     {
354         return this.embeddedContext.addListener(listener, eventType, referenceType);
355     }
356 
357     /** {@inheritDoc} */
358     @Override
359     public synchronized boolean addListener(final EventListener listener, final EventType eventType, final int position) throws RemoteException
360     {
361         return this.embeddedContext.addListener(listener, eventType, position);
362     }
363 
364     /** {@inheritDoc} */
365     @Override
366     public synchronized boolean addListener(final EventListener listener, final EventType eventType, final int position,
367             final ReferenceType referenceType) throws RemoteException
368     {
369         return this.embeddedContext.addListener(listener, eventType, position, referenceType);
370     }
371 
372     /** {@inheritDoc} */
373     @Override
374     public synchronized int removeAllListeners() throws RemoteException
375     {
376         return this.remoteEventProducer.removeAllListeners();
377     }
378 
379     /** {@inheritDoc} */
380     @Override
381     public synchronized int removeAllListeners(final Class<?> ofClass) throws RemoteException
382     {
383         return this.remoteEventProducer.removeAllListeners(ofClass);
384     }
385 
386     /** {@inheritDoc} */
387     @Override
388     public synchronized boolean removeListener(final EventListener listener, final EventType eventType) throws RemoteException
389     {
390         return this.embeddedContext.removeListener(listener, eventType);
391     }
392 
393     /** {@inheritDoc} */
394     @Override
395     public List<Reference<EventListener>> getListenerReferences(final EventType eventType)  throws RemoteException
396     {
397         return this.remoteEventProducer.getListenerReferences(eventType);
398     }
399 
400     @Override
401     public EventListenerMap getEventListenerMap() throws RemoteException
402     {
403         return this.remoteEventProducer.getEventListenerMap();
404     }
405     
406     /* ***************************************************************************************************************** */
407     /* ****************************************** REMOTECHANGEEVENTPRODUCER ******************************************** */
408     /* ***************************************************************************************************************** */
409 
410     /**
411      * The RemoteChangeEventProducer is a RmiEventProducer that can fire an OBJECT_CHANGED_EVENT on behalf of an object that was
412      * changed, but does not extend an EventProducer itself.
413      * <p>
414      * Copyright (c) 2020-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
415      * See for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>.
416      * The DSOL project is distributed under a three-clause BSD-style license, which can be found at
417      * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
418      * https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
419      * </p>
420      * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
421      */
422     protected class RemoteChangeEventProducer extends RmiEventProducer
423     {
424         /** */
425         private static final long serialVersionUID = 20200208L;
426 
427         /** the stored binding key that acts as the source id. */
428         private final String bindingKey;
429 
430         /**
431          * Create a remote event listener and register the listener in the RMI registry. When the RMI registry does not exist
432          * yet, it will be created, but <b>only</b> on the local host. Remote creation of a registry on another computer is not
433          * possible. Any attempt to do so will cause an AccessException to be fired.
434          * @param host String; the host where the RMI registry resides or will be created. Creation is only possible on
435          *            localhost.
436          * @param port int; the port where the RMI registry can be found or will be created
437          * @param bindingKey the key under which this object will be bound in the RMI registry
438          * @throws RemoteException when there is a problem with the RMI registry
439          * @throws AlreadyBoundException when there is already another object bound to the bindingKey
440          * @throws NullPointerException when host, path, or bindingKey is null
441          * @throws IllegalArgumentException when port &lt; 0 or port &gt; 65535
442          * @throws AccessException when there is an attempt to create a registry on a remote host
443          */
444         public RemoteChangeEventProducer(final String host, final int port, final String bindingKey)
445                 throws RemoteException, AlreadyBoundException
446         {
447             super(host, port, bindingKey);
448             this.bindingKey = bindingKey;
449         }
450 
451         /**
452          * Transmit an changed event with a serializable object as payload to all interested listeners.
453          * @param eventType EventType; the eventType of the event
454          * @param value Serializable; the object sent with the event
455          * @throws RemoteException on network error
456          */
457         protected void fireChangedEvent(final EventType eventType, final Serializable value) throws RemoteException
458         {
459             super.fireEvent(eventType, value);
460         }
461     }
462 
463 }