View Javadoc
1   package org.opentrafficsim.sim0mq.publisher;
2   
3   import java.rmi.RemoteException;
4   import java.util.LinkedHashMap;
5   import java.util.Map;
6   
7   import org.djunits.Throw;
8   import org.djutils.event.EventProducerInterface;
9   import org.djutils.metadata.MetaData;
10  import org.djutils.metadata.ObjectDescriptor;
11  import org.djutils.serialization.SerializationException;
12  import org.opentrafficsim.core.gtu.GTU;
13  import org.opentrafficsim.core.network.Link;
14  import org.opentrafficsim.core.network.Network;
15  import org.opentrafficsim.core.network.OTSNetwork;
16  import org.opentrafficsim.road.network.lane.CrossSectionLink;
17  import org.sim0mq.Sim0MQException;
18  
19  /**
20   * Publish all available transceivers for an OTS network to a Sim0MQ master and handle its requests. <br>
21   * Example sequence of events: <br>
22   * <ol>
23   * <li>OTSNetwork is somehow constructed and then a Publisher for that network is constructed.</li>
24   * <li>Sim0MQ master requests names of all available subscription handlers</li>
25   * <li>Sim0MQ master decides that it wants all GTU MOVE events of all GTUs. To do that it needs to know about all GTUs when they
26   * are created and about all GTUs that have already been created. The Sim0MQ master issues to the publisher a request to
27   * subscribe to all NETWORK.GTU_ADD_EVENTs of the GTUs_in_network SubscriptionHandler</li>
28   * <li>This Publisher requests the GTUs_in_network SubscriptionHandler to subscribe to the add events. From now on, the
29   * GTUs_in_network SubscriptionHandler will receive these events generated by the OTSNetwork and transcribe those into a Sim0MQ
30   * events which are transmitted to the Sim0MQ master.</li>
31   * <li>Sim0MQ master requests publisher to list all the elements of the GTUs_in_network SubscriptionHandler</li>
32   * <li>This Publisher calls the list method of the GTUs_in_network SubscriptionHandler which results in a list of all active
33   * GTUs being sent to the Sim0MQ master</li>
34   * <li>The Sim0MQ master requests this Publisher to create a subscription for the update events of the GTU_move
35   * SubscriptionHandler, providing the GTU id as address. It does that once for every GTU id.</li>
36   * <li>This Publishers creates the subscriptions. From now on any GTU.MOVE_EVENT event is transcribed by the GTU_move
37   * SubscriptionHandler in to a corresponding Sim0MQ event and sent to the Sim0MQ master.</li>
38   * </ol>
39   * <p>
40   * Copyright (c) 2020-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
41   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
42   * <p>
43   * $LastChangedDate: 2020-02-13 11:08:16 +0100 (Thu, 13 Feb 2020) $, @version $Revision: 6383 $, by $Author: pknoppers $,
44   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
45   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
46   */
47  public class Publisher extends AbstractTransceiver
48  {
49      /** Map Publisher names to the corresponding Publisher object. */
50      @SuppressWarnings("checkstyle:visibilitymodifier")
51      final Map<String, SubscriptionHandler> subscriptionHandlerMap = new LinkedHashMap<>();
52  
53      /** The OTS network. */
54      @SuppressWarnings("checkstyle:visibilitymodifier")
55      final OTSNetwork network;
56  
57      /**
58       * Construct a Publisher for an OTS network.
59       * @param network OTSNetwork; the OTS network
60       * @throws RemoteException ...
61       */
62      public Publisher(final OTSNetwork network) throws RemoteException
63      {
64          super("Publisher for " + Throw.whenNull(network, "Network may not be null").getId(),
65                  new MetaData("Publisher for " + network.getId(), "Publisher",
66                          new ObjectDescriptor[] {new ObjectDescriptor("Name of subscription handler", "String", String.class)}),
67                  new MetaData("Subscription handlers", "Subscription handlers",
68                          new ObjectDescriptor[] {new ObjectDescriptor("Name of subscription handler", "String", String.class)}));
69          this.network = network;
70  
71          GTUIdTransceiveriver.html#GTUIdTransceiver">GTUIdTransceiver gtuIdTransceiver = new GTUIdTransceiver(network);
72          GTUTransceiverceiver.html#GTUTransceiver">GTUTransceiver gtuTransceiver = new GTUTransceiver(network, gtuIdTransceiver);
73          SubscriptionHandler gtuSubscriptionHandler =
74                  new SubscriptionHandler("GTU move", gtuTransceiver, new LookupEventProducerInterface()
75                  {
76                      @Override
77                      public EventProducerInterface lookup(final Object[] address, final ReturnWrapper returnWrapper)
78                              throws Sim0MQException, SerializationException
79                      {
80                          String bad = AbstractTransceiver.verifyMetaData(getAddressMetaData(), address);
81                          if (bad != null)
82                          {
83                              returnWrapper.nack(bad);
84                              return null;
85                          }
86                          EventProducerInterface result = network.getGTU((String) address[0]);
87                          if (null == result)
88                          {
89                              returnWrapper.nack("No GTU with id \"" + address[0] + "\" found");
90                          }
91                          return result;
92                      }
93  
94                      private final MetaData metaData = new MetaData("GTU Id", "GTU Id",
95                              new ObjectDescriptor[] {new ObjectDescriptor("GTU ID", "GTU Id", String.class)});
96  
97                      @Override
98                      public MetaData getAddressMetaData()
99                      {
100                         return this.metaData;
101                     }
102                 }, null, null, GTU.MOVE_EVENT, null);
103         addSubscriptionHandler(gtuSubscriptionHandler);
104         addSubscriptionHandler(new SubscriptionHandler("GTUs in network", gtuIdTransceiver, new LookupEventProducerInterface()
105         {
106             @Override
107             public EventProducerInterface lookup(final Object[] address, final ReturnWrapper returnWrapper)
108                     throws Sim0MQException, SerializationException
109             {
110                 String bad = AbstractTransceiver.verifyMetaData(getAddressMetaData(), address);
111                 if (bad != null)
112                 {
113                     returnWrapper.nack(bad);
114                     return null;
115                 }
116                 return network;
117             }
118 
119             @Override
120             public String toString()
121             {
122                 return "Subscription handler for GTUs in network";
123             }
124 
125             @Override
126             public MetaData getAddressMetaData()
127             {
128                 return MetaData.EMPTY;
129             }
130         }, Network.GTU_ADD_EVENT, Network.GTU_REMOVE_EVENT, null, gtuSubscriptionHandler));
131         LinkIdTransceiverver.html#LinkIdTransceiver">LinkIdTransceiver linkIdTransceiver = new LinkIdTransceiver(network);
132         LinkTransceivereiver.html#LinkTransceiver">LinkTransceiver linkTransceiver = new LinkTransceiver(network, linkIdTransceiver);
133         SubscriptionHandlerml#SubscriptionHandler">SubscriptionHandler linkSubscriptionHandler = new SubscriptionHandler("Link change", linkTransceiver, this.lookupLink,
134                 Link.GTU_ADD_EVENT, Link.GTU_REMOVE_EVENT, null, null);
135         addSubscriptionHandler(linkSubscriptionHandler);
136         addSubscriptionHandler(new SubscriptionHandler("Links in network", linkIdTransceiver, new LookupEventProducerInterface()
137         {
138             @Override
139             public EventProducerInterface lookup(final Object[] address, final ReturnWrapper returnWrapper)
140                     throws Sim0MQException, SerializationException
141             {
142                 String bad = AbstractTransceiver.verifyMetaData(getAddressMetaData(), address);
143                 if (bad != null)
144                 {
145                     returnWrapper.nack(bad);
146                     return null;
147                 }
148                 return network;
149             }
150 
151             @Override
152             public String toString()
153             {
154                 return "Subscription handler for Links in network";
155             }
156 
157             @Override
158             public MetaData getAddressMetaData()
159             {
160                 return MetaData.EMPTY;
161             }
162         }, Network.LINK_ADD_EVENT, Network.LINK_REMOVE_EVENT, null, linkSubscriptionHandler));
163         NodeIdTransceiverver.html#NodeIdTransceiver">NodeIdTransceiver nodeIdTransceiver = new NodeIdTransceiver(network);
164         NodeTransceivereiver.html#NodeTransceiver">NodeTransceiver nodeTransceiver = new NodeTransceiver(network, nodeIdTransceiver);
165         // addTransceiver(nodeIdTransceiver);
166         // addTransceiver(new NodeTransceiver(network, nodeIdTransceiver));
167         SubscriptionHandler nodeSubscriptionHandler =
168                 new SubscriptionHandler("Node change", nodeTransceiver, new LookupEventProducerInterface()
169                 {
170                     @Override
171                     public EventProducerInterface lookup(final Object[] address, final ReturnWrapper returnWrapper)
172                     {
173                         return null; // Nodes do not emit events
174                     }
175 
176                     @Override
177                     public String toString()
178                     {
179                         return "Subscription handler for Node change";
180                     }
181 
182                     private final MetaData metaData = new MetaData("Node Id", "Node Id",
183                             new ObjectDescriptor[] {new ObjectDescriptor("Node ID", "Node Id", String.class)});
184 
185                     @Override
186                     public MetaData getAddressMetaData()
187                     {
188                         return this.metaData;
189                     }
190                 }, null, null, null, null);
191         addSubscriptionHandler(nodeSubscriptionHandler);
192         addSubscriptionHandler(new SubscriptionHandler("Nodes in network", nodeIdTransceiver, new LookupEventProducerInterface()
193         {
194             @Override
195             public EventProducerInterface lookup(final Object[] address, final ReturnWrapper returnWrapper)
196                     throws Sim0MQException, SerializationException
197             {
198                 String bad = AbstractTransceiver.verifyMetaData(getAddressMetaData(), address);
199                 if (bad != null)
200                 {
201                     returnWrapper.nack(bad);
202                     return null;
203                 }
204                 return network;
205             }
206 
207             @Override
208             public String toString()
209             {
210                 return "Subscription handler for Nodes in network";
211             }
212 
213             @Override
214             public MetaData getAddressMetaData()
215             {
216                 return MetaData.EMPTY;
217             }
218         }, Network.NODE_ADD_EVENT, Network.NODE_REMOVE_EVENT, null, nodeSubscriptionHandler));
219         SubscriptionHandlerbscriptionHandler">SubscriptionHandler linkGTUIdSubscriptionHandler = new SubscriptionHandler("GTUs on Link",
220                 new LinkGTUIdTransceiver(network), this.lookupLink, Link.GTU_ADD_EVENT, Link.GTU_REMOVE_EVENT, null, null);
221         addSubscriptionHandler(linkGTUIdSubscriptionHandler);
222         addSubscriptionHandler(new SubscriptionHandler("Cross section elements on Link",
223                 new CrossSectionElementTransceiver(network), this.lookupLink, CrossSectionLink.LANE_ADD_EVENT,
224                 CrossSectionLink.LANE_REMOVE_EVENT, null, linkGTUIdSubscriptionHandler));
225         // addTransceiver(new LaneGTUIdTransceiver(network));
226         SimulatorStateTransceiverrStateTransceiver.html#SimulatorStateTransceiver">SimulatorStateTransceiver stt = new SimulatorStateTransceiver(network.getSimulator());
227         SubscriptionHandlerptionHandler">SubscriptionHandler simulatorStateSubscriptionHandler = new SubscriptionHandler("Simulator running", stt,
228                 stt.getLookupEventProducerInterface(), null, null, SimulatorStateTransceiver.SIMULATOR_STATE_CHANGED, null);
229         addSubscriptionHandler(simulatorStateSubscriptionHandler);
230 
231         addSubscriptionHandler(new SubscriptionHandler("", this, null, null, null, null, null)); // The meta transceiver
232     }
233 
234     /** Lookup a CrossSectionLink in the network. */
235     private LookupEventProducerInterfacer.html#LookupEventProducerInterface">LookupEventProducerInterface lookupLink = new LookupEventProducerInterface()
236     {
237         @Override
238         public EventProducerInterface lookup(final Object[] address, final ReturnWrapper returnWrapper)
239                 throws IndexOutOfBoundsException, Sim0MQException, SerializationException
240         {
241             Throw.whenNull(address, "LookupLink requires the name of a link");
242             Throw.when(address.length != 1 || !(address[1] instanceof String), IllegalArgumentException.class, "Bad address");
243             Link link = Publisher.this.network.getLink((String) address[0]);
244             if (null == link)
245             {
246                 returnWrapper.nack("Network does not contain a Link with id " + address[0]);
247                 return null;
248             }
249             if (!(link instanceof EventProducerInterface))
250             {
251                 returnWrapper.nack("Link \"" + address[0] + "\" is not able to handle subscriptions");
252                 return null;
253             }
254             return (CrossSectionLink) link;
255         }
256 
257         @Override
258         public String toString()
259         {
260             return "LookupProducerInterface that looks up a Link in the network";
261         }
262 
263         @Override
264         public MetaData getAddressMetaData()
265         {
266             return new MetaData("Link id", "Name of a link in the network",
267                     new ObjectDescriptor[] {new ObjectDescriptor("Link id", "Name of a link in the network", String.class)});
268         }
269     };
270 
271     /**
272      * Add a SubscriptionHandler to the map.
273      * @param subscriptionHandler SubscriptionHandler; the subscription handler to add to the map
274      */
275     private void addSubscriptionHandler(final SubscriptionHandler subscriptionHandler)
276     {
277         this.subscriptionHandlerMap.put(subscriptionHandler.getId(), subscriptionHandler);
278     }
279 
280     /** {@inheritDoc} */
281     @Override
282     public Object[] get(final Object[] address, final ReturnWrapper returnWrapper)
283             throws Sim0MQException, SerializationException
284     {
285         Throw.whenNull(returnWrapper, "returnWrapper may not be null");
286         String bad = verifyMetaData(getAddressFields(), address);
287         if (bad != null)
288         {
289             returnWrapper.nack("Bad address (should be the name of a transceiver): " + bad);
290             return null;
291         }
292         SubscriptionHandler subscriptionHandler = this.subscriptionHandlerMap.get(address[0]);
293         if (null == subscriptionHandler)
294         {
295             returnWrapper.nack("No transceiver with name \"" + address[0] + "\"");
296             return null;
297         }
298         return new Object[] {subscriptionHandler};
299     }
300 
301     /** Returned by the getIdSource method. */
302     private final TransceiverInterfaceverInterface.html#TransceiverInterface">TransceiverInterface idSource = new TransceiverInterface()
303     {
304         @Override
305         public String getId()
306         {
307             return "Transceiver for names of available transceivers in Publisher";
308         }
309 
310         @Override
311         public MetaData getAddressFields()
312         {
313             return MetaData.EMPTY;
314         }
315 
316         /** Result of getResultFields. */
317         private MetaData resultMetaData =
318                 new MetaData("Transceiver names available in Publisher", "String array", new ObjectDescriptor[] {
319                         new ObjectDescriptor("Transceiver names available in Publisher", "String array", String[].class)});
320 
321         @Override
322         public MetaData getResultFields()
323         {
324             return this.resultMetaData;
325         }
326 
327         @Override
328         public Object[] get(final Object[] address, final ReturnWrapper returnWrapper)
329                 throws RemoteException, Sim0MQException, SerializationException
330         {
331             Object[] result = new Object[Publisher.this.subscriptionHandlerMap.size()];
332             int index = 0;
333             for (String key : Publisher.this.subscriptionHandlerMap.keySet())
334             {
335                 result[index++] = key;
336             }
337             return result;
338         }
339     };
340 
341     /** {@inheritDoc} */
342     @Override
343     public TransceiverInterface getIdSource(final int addressLevel, final ReturnWrapper returnWrapper)
344             throws Sim0MQException, SerializationException
345     {
346         if (0 != addressLevel)
347         {
348             returnWrapper.encodeReplyAndTransmit("Address should be 0");
349             return null;
350         }
351         return this.idSource;
352     }
353 
354     /** {@inheritDoc} */
355     @Override
356     public boolean hasIdSource()
357     {
358         return true;
359     }
360 
361     /**
362      * Execute one command.
363      * @param subscriptionHandlerName String; name of the SubscriptionHandler for which the command is destined
364      * @param command SubscriptionHandler.Command; the operation to perform
365      * @param address Object[]; the address on which to perform the operation
366      * @param returnWrapper ReturnWrapper; to transmit the result
367      * @throws RemoteException on RMI network failure
368      * @throws SerializationException on illegal type in serialization
369      * @throws Sim0MQException on communication error
370      */
371     public void executeCommand(final String subscriptionHandlerName, final SubscriptionHandler.Command command,
372             final Object[] address, final ReturnWrapper returnWrapper)
373             throws RemoteException, Sim0MQException, SerializationException
374     {
375         SubscriptionHandler subscriptionHandler = this.subscriptionHandlerMap.get(subscriptionHandlerName);
376         if (null == subscriptionHandler)
377         {
378             returnWrapper.nack("No subscription handler for \"" + subscriptionHandlerName + "\"");
379             return;
380         }
381         subscriptionHandler.executeCommand(command, address, returnWrapper);
382     }
383 
384     /**
385      * Execute one command.
386      * @param subscriptionHandlerName String; name of the SubscriptionHandler for which the command is destined
387      * @param commandString String; the operation to perform
388      * @param address Object[]; the address on which to perform the operation
389      * @param returnWrapper ReturnWrapper; to transmit the result
390      * @throws RemoteException on RMI network failure
391      * @throws SerializationException on illegal type in serialization
392      * @throws Sim0MQException on communication error
393      */
394     public void executeCommand(final String subscriptionHandlerName, final String commandString, final Object[] address,
395             final ReturnWrapperImpl returnWrapper) throws RemoteException, Sim0MQException, SerializationException
396     {
397         executeCommand(subscriptionHandlerName,
398                 Throw.whenNull(SubscriptionHandler.lookupCommand(commandString), "Invalid command (%s)", commandString),
399                 address, returnWrapper);
400     }
401 
402 }