1 package org.djutils.event.rmi;
2
3 import java.net.URL;
4 import java.rmi.AccessException;
5 import java.rmi.AlreadyBoundException;
6 import java.rmi.Remote;
7 import java.rmi.RemoteException;
8 import java.rmi.registry.Registry;
9 import java.util.ArrayList;
10 import java.util.Iterator;
11 import java.util.List;
12
13 import org.djutils.event.Event;
14 import org.djutils.event.EventListener;
15 import org.djutils.event.EventListenerMap;
16 import org.djutils.event.EventProducer;
17 import org.djutils.event.EventType;
18 import org.djutils.event.reference.Reference;
19 import org.djutils.exceptions.Throw;
20 import org.djutils.rmi.RmiObject;
21
22 /**
23 * The RmiEventProducer provides a remote implementation of the eventProducer using the RMI protocol.
24 * <p>
25 * Copyright (c) 2002-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
26 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
27 * distributed under a three-clause BSD-style license, which can be found at
28 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. This class was
29 * originally part of the DSOL project, see <a href="https://simulation.tudelft.nl/dsol/manual" target="_blank">
30 * https://simulation.tudelft.nl/dsol/manual</a>.
31 * </p>
32 * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
33 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
34 */
35 public class RmiEventProducer implements EventProducer, Remote
36 {
37 /** The embedded RmiObject class for the remote firing of events. */
38 private final RmiObject rmiObject;
39
40 /** the subscriber list. */
41 private final EventListenerMap eventListenerMap;
42
43 /**
44 * Create a remote event listener and register the listener in the RMI registry. When the RMI registry does not exist yet,
45 * it will be created, but <b>only</b> on the local host. Remote creation of a registry on another computer is not possible.
46 * Any attempt to do so will cause an AccessException to be fired.
47 * @param host the host where the RMI registry resides or will be created. Creation is only possible on localhost.
48 * @param port the port where the RMI registry can be found or will be created
49 * @param bindingKey the key under which this object will be bound in the RMI registry
50 * @throws RemoteException when there is a problem with the RMI registry
51 * @throws AlreadyBoundException when there is already another object bound to the bindingKey
52 * @throws NullPointerException when host, path, or bindingKey is null
53 * @throws IllegalArgumentException when port < 0 or port > 65535
54 * @throws AccessException when there is an attempt to create a registry on a remote host
55 */
56 public RmiEventProducer(final String host, final int port, final String bindingKey)
57 throws RemoteException, AlreadyBoundException
58 {
59 this.rmiObject = new RmiObject(host, port, bindingKey);
60 this.eventListenerMap = new EventListenerMap();
61 }
62
63 /**
64 * Create a remote event listener and register the listener in the RMI registry. When the host has not been specified in the
65 * 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.
66 * When the RMI registry does not exist yet, it will be created, but <b>only</b> on the local host. Remote creation of a
67 * registry on another computer is not possible. Any attempt to do so will cause an AccessException to be fired.
68 * @param registryURL the URL of the registry, e.g., "http://localhost:1099" or "http://130.161.185.14:28452"
69 * @param bindingKey the key under which this object will be bound in the RMI registry
70 * @throws RemoteException when there is a problem with the RMI registry
71 * @throws AlreadyBoundException when there is already another object bound to the bindingKey
72 * @throws NullPointerException when registryURL or bindingKey is null
73 * @throws AccessException when there is an attempt to create a registry on a remote host
74 */
75 public RmiEventProducer(final URL registryURL, final String bindingKey) throws RemoteException, AlreadyBoundException
76 {
77 this.rmiObject = new RmiObject(registryURL, bindingKey);
78 this.eventListenerMap = new EventListenerMap();
79 }
80
81 @Override
82 public void fireEvent(final Event event)
83 {
84 Throw.whenNull(event, "event may not be null");
85 EventListenerMap elm = getEventListenerMap();
86 if (elm.containsKey(event.getType()))
87 {
88 // make a safe copy because of possible removeListener() in notify() method during fireEvent
89 List<Reference<EventListener>> listenerList = new ArrayList<>(elm.get(event.getType()));
90 for (Reference<EventListener> reference : listenerList)
91 {
92 EventListener listener = reference.get();
93 try
94 {
95 if (listener != null)
96 {
97 // The garbage collection has not cleaned the referent
98 fireEvent(listener, event);
99 }
100 else
101 {
102 // The garbage collection cleaned the referent;
103 // there is no need to keep the subscription
104 removeListener(reference, event.getType());
105 }
106 }
107 catch (RemoteException remoteException)
108 {
109 // A network failure prevented the delivery,
110 // subscription is removed.
111 try
112 {
113 removeListener(reference, event.getType());
114 }
115 catch (RemoteException re)
116 {
117 throw new RuntimeException("removeListener failed", re);
118 }
119 }
120 }
121 }
122 }
123
124 /**
125 * Transmit an event to a listener. This method is a hook method. The default implementation simply invokes the notify on
126 * the listener. In specific cases (filtering, storing, queueing, this method can be overwritten.
127 * @param listener the listener for this event
128 * @param event the event to fire
129 * @throws RemoteException on network error
130 */
131 private void fireEvent(final EventListener listener, final Event event) throws RemoteException
132 {
133 listener.notify(event);
134 }
135
136 /**
137 * Remove one reference from the subscription list.
138 * @param reference the (strong or weak) reference to remove
139 * @param eventType the eventType for which reference must be removed
140 * @return true if the reference was removed; otherwise false
141 * @throws RemoteException on network error
142 */
143 private boolean removeListener(final Reference<EventListener> reference, final EventType eventType) throws RemoteException
144 {
145 Throw.whenNull(reference, "reference may not be null");
146 Throw.whenNull(eventType, "eventType may not be null");
147 EventListenerMap elm = getEventListenerMap();
148 boolean success = false;
149 for (Iterator<Reference<EventListener>> i = elm.get(eventType).iterator(); i.hasNext();)
150 {
151 if (i.next().equals(reference))
152 {
153 i.remove();
154 success = true;
155 }
156 }
157 if (elm.get(eventType).size() == 0)
158 {
159 elm.remove(eventType);
160 }
161 return success;
162 }
163
164 /**
165 * Returns the registry in which this object has been bound, e.g., to look up other objects in the registry.
166 * @return the registry in which this object has been bound
167 * @throws RemoteException on network error
168 */
169 public Registry getRegistry() throws RemoteException
170 {
171 return this.rmiObject.getRegistry();
172 }
173
174 @Override
175 public EventListenerMap getEventListenerMap()
176 {
177 return this.eventListenerMap;
178 }
179 }