View Javadoc
1   package org.opentrafficsim.web;
2   
3   import java.net.URL;
4   import java.util.ArrayList;
5   import java.util.Collections;
6   import java.util.LinkedHashMap;
7   import java.util.List;
8   import java.util.Map;
9   
10  import org.djunits.unit.Unit;
11  import org.djunits.value.vdouble.scalar.Duration;
12  import org.djunits.value.vdouble.scalar.base.DoubleScalar;
13  import org.djunits.value.vfloat.scalar.base.FloatScalar;
14  import org.djutils.io.ResourceResolver;
15  import org.eclipse.jetty.ee10.servlet.SessionHandler;
16  import org.eclipse.jetty.io.Content;
17  import org.eclipse.jetty.server.Handler;
18  import org.eclipse.jetty.server.Request;
19  import org.eclipse.jetty.server.Response;
20  import org.eclipse.jetty.server.Server;
21  import org.eclipse.jetty.server.handler.ContextHandler;
22  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
23  import org.eclipse.jetty.server.handler.ResourceHandler;
24  import org.eclipse.jetty.session.DefaultSessionCache;
25  import org.eclipse.jetty.session.DefaultSessionIdManager;
26  import org.eclipse.jetty.session.NullSessionDataStore;
27  import org.eclipse.jetty.session.SessionCache;
28  import org.eclipse.jetty.session.SessionDataStore;
29  import org.eclipse.jetty.util.Callback;
30  import org.eclipse.jetty.util.Fields;
31  import org.opentrafficsim.animation.data.util.DefaultAnimationFactory;
32  import org.opentrafficsim.base.logger.Logger;
33  import org.opentrafficsim.core.dsol.OtsAnimator;
34  import org.opentrafficsim.core.dsol.OtsModelInterface;
35  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
36  import org.opentrafficsim.core.perception.HistoryManagerDevs;
37  import org.opentrafficsim.web.test.CircularRoadModel;
38  import org.opentrafficsim.web.test.TJunctionModel;
39  
40  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameter;
41  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterBoolean;
42  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDistContinuousSelection;
43  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDistDiscreteSelection;
44  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDouble;
45  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDoubleScalar;
46  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterFloat;
47  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterFloatScalar;
48  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterInteger;
49  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterLong;
50  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterMap;
51  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterSelectionList;
52  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterSelectionMap;
53  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterString;
54  
55  /**
56   * DSOLWebServer.java.
57   * <p>
58   * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
59   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
60   * </p>
61   * @author Alexander Verbraeck
62   */
63  public class TestDemoServer
64  {
65      /** the map of sessionIds to OtsModelInterface that handles the animation and updates for the started model. */
66      final Map<String, OtsModelInterface> sessionModelMap = new LinkedHashMap<>();
67  
68      /** the map of sessionIds to OTSWebModel that handles the animation and updates for the started model. */
69      final Map<String, OtsWebModel> sessionWebModelMap = new LinkedHashMap<>();
70  
71      /**
72       * Run a SuperDemo OTS Web server.
73       * @param args not used
74       * @throws Exception on Jetty error
75       */
76      public static void main(final String[] args) throws Exception
77      {
78          new TestDemoServer();
79      }
80  
81      /**
82       * Constructor.
83       * @throws Exception in case jetty crashes
84       */
85      public TestDemoServer() throws Exception
86      {
87          new ServerThread().start();
88      }
89  
90      /** Handle in separate thread to avoid 'lock' of the main application. */
91      class ServerThread extends Thread
92      {
93          /**
94           * Constructor.
95           */
96          public ServerThread()
97          {
98              //
99          }
100 
101         @Override
102         public void run()
103         {
104             Server server = new Server(8080);
105             ResourceHandler resourceHandler = new MyResourceHandler();
106 
107             // root folder; to work in Eclipse, as an external jar, and in an embedded jar
108             URL homeFolder = ResourceResolver.resolve("/resources/home").asUrl();
109             String webRoot = homeFolder.toExternalForm();
110             Logger.ots().trace("webRoot is " + webRoot);
111 
112             resourceHandler.setDirAllowed(true);
113             resourceHandler.setWelcomeFiles(new String[] {"testdemo.html"});
114             resourceHandler.setBaseResourceAsString(webRoot);
115 
116             DefaultSessionIdManager idManager = new DefaultSessionIdManager(server);
117             idManager.setWorkerName("testDemoServer");
118             server.addBean(idManager, true);
119 
120             SessionHandler sessionHandler = new SessionHandler();
121             SessionCache sessionCache = new DefaultSessionCache(sessionHandler);
122             SessionDataStore sessionDataStore = new NullSessionDataStore();
123             sessionCache.setSessionDataStore(sessionDataStore);
124             sessionHandler.setSessionCache(sessionCache);
125 
126             ContextHandler handler1 = new ContextHandler(resourceHandler, "/");
127             ContextHandler handler2 = new ContextHandler(sessionHandler, "/");
128             ContextHandler handler3 = new ContextHandler(new XHRHandler(TestDemoServer.this), "/");
129             ContextHandlerCollection handlers = new ContextHandlerCollection();
130             handlers.setHandlers(new Handler[] {handler1, handler2, handler3});
131             handlers.mapContexts();
132             server.setHandler(handlers);
133 
134             try
135             {
136                 server.start();
137                 server.join();
138             }
139             catch (Exception exception)
140             {
141                 exception.printStackTrace();
142             }
143         }
144     }
145 
146     /** Resource handler. */
147     class MyResourceHandler extends ResourceHandler
148     {
149         /**
150          * Constructor.
151          */
152         MyResourceHandler()
153         {
154             //
155         }
156 
157         @Override
158         public boolean handle(final Request request, final Response response, final Callback callback) throws Exception
159         {
160 
161             // https://jetty.org/docs/jetty/12.1/programming-guide/migration/11-to-12.html#api-changes
162 
163             String target = request.getHttpURI().getPathQuery();
164 
165             if (target.startsWith("/parameters.html"))
166             {
167                 Fields fields = Request.getParameters(request);
168                 String modelId = fields.getValue("model");
169                 String sessionId = fields.getValue("sessionId");
170                 if (!TestDemoServer.this.sessionModelMap.containsKey(sessionId))
171                 {
172                     Logger.ots().trace("parameters: " + modelId);
173                     OtsAnimator simulator = new OtsAnimator("TestDemoServer");
174                     simulator.setAnimation(false);
175                     OtsModelInterface model = null;
176                     if (modelId.toLowerCase().contains("circularroad"))
177                         model = new CircularRoadModel(simulator);
178                     else if (modelId.toLowerCase().contains("tjunction"))
179                         model = new TJunctionModel(simulator);
180                     if (model != null)
181                         TestDemoServer.this.sessionModelMap.put(sessionId, model);
182                     else
183                         Logger.ots().error("Could not find model " + modelId);
184                 }
185             }
186 
187             if (target.startsWith("/model.html"))
188             {
189                 Fields fields = Request.getParameters(request);
190                 String modelId = fields.getValue("model");
191                 String sessionId = fields.getValue("sessionId");
192                 if (TestDemoServer.this.sessionModelMap.containsKey(sessionId)
193                         && !TestDemoServer.this.sessionWebModelMap.containsKey(sessionId))
194                 {
195                     Logger.ots().trace("startModel: " + modelId);
196                     OtsModelInterface model = TestDemoServer.this.sessionModelMap.get(sessionId);
197                     OtsSimulatorInterface simulator = model.getSimulator();
198                     try
199                     {
200                         simulator.initialize(Duration.ZERO, Duration.ZERO, Duration.ofSI(3600.0), model,
201                                 HistoryManagerDevs.noHistory(simulator));
202                         OtsWebModel webModel = new OtsWebModel(model.getShortName(), simulator);
203                         TestDemoServer.this.sessionWebModelMap.put(sessionId, webModel);
204                         DefaultAnimationFactory.animateNetwork(model.getNetwork(), model.getNetwork().getSimulator(),
205                                 webModel.getAnimationPanel().getGtuColorerManager(), Collections.emptyMap());
206                     }
207                     catch (Exception exception)
208                     {
209                         exception.printStackTrace();
210                     }
211                 }
212             }
213 
214             // handle whatever needs to be done...
215             return super.handle(request, response, callback);
216         }
217     }
218 
219     /**
220      * Answer handles the events from the web-based user interface for a demo. <br>
221      * <br>
222      * Copyright (c) 2003-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
223      * See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>.
224      * The source code and binary code of this software is proprietary information of Delft University of Technology.
225      * @author Alexander Verbraeck
226      */
227     public static class XHRHandler extends Handler.Abstract
228     {
229         /** web server for callback of actions. */
230         private final TestDemoServer webServer;
231 
232         /**
233          * Create the handler for Servlet requests.
234          * @param webServer web server for callback of actions
235          */
236         public XHRHandler(final TestDemoServer webServer)
237         {
238             this.webServer = webServer;
239         }
240 
241         /** {@inheritDoc} */
242         @Override
243         public boolean handle(final Request request, final Response response, final Callback callback) throws Exception
244         {
245 
246             // https://jetty.org/docs/jetty/12.1/programming-guide/migration/11-to-12.html#api-changes
247 
248             Fields fields = Request.getParameters(request);
249             String sessionId = fields.getValue("sessionId");
250             if (sessionId != null)
251             {
252                 if (this.webServer.sessionWebModelMap.containsKey(sessionId))
253                 {
254                     boolean handled = this.webServer.sessionWebModelMap.get(sessionId).handle(request, response, callback);
255                     if (handled)
256                     {
257                         return true;
258                     }
259                 }
260                 else if (this.webServer.sessionModelMap.containsKey(sessionId))
261                 {
262                     OtsModelInterface model = this.webServer.sessionModelMap.get(sessionId);
263                     String answer = "<message>ok</message>";
264 
265                     String message = fields.getValue("message");
266                     if (message != null)
267                     {
268                         String[] parts = message.split("\\|");
269                         String command = parts[0];
270 
271                         switch (command)
272                         {
273                             case "getTitle":
274                             {
275                                 answer = "<title>" + model.getShortName() + "</title>";
276                                 break;
277                             }
278 
279                             case "getParameterMap":
280                             {
281                                 answer = makeParameterMap(model);
282                                 break;
283                             }
284 
285                             case "setParameters":
286                             {
287                                 answer = setParameters(model, message);
288                                 break;
289                             }
290 
291                             default:
292                             {
293                                 Logger.ots().error("Got unknown message from client: {}", command);
294                                 answer = "<message>" + request.getAttribute("message") + "</message>";
295                                 break;
296                             }
297                         }
298                     }
299 
300                     Content.Sink.write(response, true, answer, callback);
301 
302                     return true; // handled
303                 }
304             }
305 
306             return false;
307         }
308 
309         /**
310          * Make the parameter set that can be interpreted by the parameters.html page.
311          * @param model the model with parameters
312          * @return an XML string with the parameters
313          */
314         private String makeParameterMap(final OtsModelInterface model)
315         {
316             StringBuffer answer = new StringBuffer();
317             answer.append("<parameters>\n");
318             InputParameterMap inputParameterMap = model.getInputParameterMap();
319             for (InputParameter<?, ?> tab : inputParameterMap.getSortedSet())
320             {
321                 if (!(tab instanceof InputParameterMap))
322                 {
323                     Logger.ots().error("Input parameter {} cannot be displayed in a tab", tab.getShortName());
324                 }
325                 else
326                 {
327                     answer.append("<tab>" + tab.getDescription() + "</tab>\n");
328                     InputParameterMap tabbedMap = (InputParameterMap) tab;
329                     for (InputParameter<?, ?> parameter : tabbedMap.getSortedSet())
330                     {
331                         addParameterField(answer, parameter);
332                     }
333                 }
334             }
335             answer.append("</parameters>\n");
336             return answer.toString();
337         }
338 
339         /**
340          * Add the right type of field for this parameter to the string buffer.
341          * @param answer the buffer to add the XML-info for the parameter
342          * @param parameter the input parameter to display
343          */
344         public void addParameterField(final StringBuffer answer, final InputParameter<?, ?> parameter)
345         {
346             if (parameter instanceof InputParameterDouble)
347             {
348                 InputParameterDouble pd = (InputParameterDouble) parameter;
349                 answer.append("<double key='" + pd.getExtendedKey() + "' name='" + pd.getShortName() + "' description='"
350                         + pd.getDescription() + "'>" + pd.getValue() + "</double>\n");
351             }
352             else if (parameter instanceof InputParameterFloat)
353             {
354                 InputParameterFloat pf = (InputParameterFloat) parameter;
355                 answer.append("<float key='" + pf.getExtendedKey() + "' name='" + pf.getShortName() + "' description='"
356                         + pf.getDescription() + "'>" + pf.getValue() + "</float>\n");
357             }
358             else if (parameter instanceof InputParameterBoolean)
359             {
360                 InputParameterBoolean pb = (InputParameterBoolean) parameter;
361                 answer.append("<boolean key='" + pb.getExtendedKey() + "' name='" + pb.getShortName() + "' description='"
362                         + pb.getDescription() + "'>" + pb.getValue() + "</boolean>\n");
363             }
364             else if (parameter instanceof InputParameterLong)
365             {
366                 InputParameterLong pl = (InputParameterLong) parameter;
367                 answer.append("<long key='" + pl.getExtendedKey() + "' name='" + pl.getShortName() + "' description='"
368                         + pl.getDescription() + "'>" + pl.getValue() + "</long>\n");
369             }
370             else if (parameter instanceof InputParameterInteger)
371             {
372                 InputParameterInteger pi = (InputParameterInteger) parameter;
373                 answer.append("<integer key='" + pi.getExtendedKey() + "' name='" + pi.getShortName() + "' description='"
374                         + pi.getDescription() + "'>" + pi.getValue() + "</integer>\n");
375             }
376             else if (parameter instanceof InputParameterString)
377             {
378                 InputParameterString ps = (InputParameterString) parameter;
379                 answer.append("<string key='" + ps.getExtendedKey() + "' name='" + ps.getShortName() + "' description='"
380                         + ps.getDescription() + "'>" + ps.getValue() + "</string>\n");
381             }
382             else if (parameter instanceof InputParameterDoubleScalar)
383             {
384                 InputParameterDoubleScalar<?, ?> pds = (InputParameterDoubleScalar<?, ?>) parameter;
385                 String val = getValueInUnit(pds);
386                 List<String> units = getUnits(pds);
387                 answer.append("<doubleScalar key='" + pds.getExtendedKey() + "' name='" + pds.getShortName() + "' description='"
388                         + pds.getDescription() + "'><value>" + val + "</value>\n");
389                 for (String unit : units)
390                 {
391                     Unit<?> unitValue = pds.getUnitParameter().getOptions().get(unit);
392                     if (unitValue.equals(pds.getUnitParameter().getValue()))
393                         answer.append("<unit chosen='true'>" + unit + "</unit>\n");
394                     else
395                         answer.append("<unit chosen='false'>" + unit + "</unit>\n");
396                 }
397                 answer.append("</doubleScalar>\n");
398             }
399             else if (parameter instanceof InputParameterFloatScalar)
400             {
401                 InputParameterFloatScalar<?, ?> pds = (InputParameterFloatScalar<?, ?>) parameter;
402                 String val = getValueInUnit(pds);
403                 List<String> units = getUnits(pds);
404                 answer.append("<floatScalar key='" + pds.getExtendedKey() + "' name='" + pds.getShortName() + "' description='"
405                         + pds.getDescription() + "'><value>" + val + "</value>\n");
406                 for (String unit : units)
407                 {
408                     Unit<?> unitValue = pds.getUnitParameter().getOptions().get(unit);
409                     if (unitValue.equals(pds.getUnitParameter().getValue()))
410                         answer.append("<unit chosen='true'>" + unit + "</unit>\n");
411                     else
412                         answer.append("<unit chosen='false'>" + unit + "</unit>\n");
413                 }
414                 answer.append("</floatScalar>\n");
415             }
416             else if (parameter instanceof InputParameterSelectionList<?>)
417             {
418                 // TODO InputParameterSelectionList
419             }
420             else if (parameter instanceof InputParameterDistDiscreteSelection)
421             {
422                 // TODO InputParameterSelectionList
423             }
424             else if (parameter instanceof InputParameterDistContinuousSelection)
425             {
426                 // TODO InputParameterDistContinuousSelection
427             }
428             else if (parameter instanceof InputParameterSelectionMap<?, ?>)
429             {
430                 // TODO InputParameterSelectionMap
431             }
432         }
433 
434         /**
435          * @param parameter double scalar input parameter
436          * @return default value in the unit
437          */
438         private <U extends Unit<U>,
439                 T extends DoubleScalar<U, T>> String getValueInUnit(final InputParameterDoubleScalar<U, T> parameter)
440         {
441             return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit());
442         }
443 
444         /**
445          * @param parameter double scalar input parameter
446          * @return abbreviations for the units
447          */
448         private <U extends Unit<U>,
449                 T extends DoubleScalar<U, T>> List<String> getUnits(final InputParameterDoubleScalar<U, T> parameter)
450         {
451             List<String> unitList = new ArrayList<>();
452             for (String option : parameter.getUnitParameter().getOptions().keySet())
453             {
454                 unitList.add(option.toString());
455             }
456             return unitList;
457         }
458 
459         /**
460          * @param parameter double scalar input parameter
461          * @return default value in the unit
462          */
463         private <U extends Unit<U>,
464                 T extends FloatScalar<U, T>> String getValueInUnit(final InputParameterFloatScalar<U, T> parameter)
465         {
466             return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit());
467         }
468 
469         /**
470          * @param parameter double scalar input parameter
471          * @return abbreviations for the units
472          */
473         private <U extends Unit<U>,
474                 T extends FloatScalar<U, T>> List<String> getUnits(final InputParameterFloatScalar<U, T> parameter)
475         {
476             List<String> unitList = new ArrayList<>();
477             for (String option : parameter.getUnitParameter().getOptions().keySet())
478             {
479                 unitList.add(option.toString());
480             }
481             return unitList;
482         }
483 
484         /**
485          * Make the parameter set that can be interpreted by the parameters.html page.
486          * @param model the model with parameters
487          * @param message the key-value pairs of the set parameters
488          * @return the errors if they are detected. If none, errors is set to "OK"
489          */
490         private String setParameters(final OtsModelInterface model, final String message)
491         {
492             String errors = "OK";
493             InputParameterMap inputParameters = model.getInputParameterMap();
494             String[] parts = message.split("\\|");
495             Map<String, String> unitMap = new LinkedHashMap<>();
496             for (int i = 1; i < parts.length - 3; i += 3)
497             {
498                 String id = parts[i].trim().replaceFirst("model.", "");
499                 String type = parts[i + 1].trim();
500                 String val = parts[i + 2].trim();
501                 if (type.equals("UNIT"))
502                 {
503                     unitMap.put(id, val);
504                 }
505             }
506             for (int i = 1; i < parts.length - 3; i += 3)
507             {
508                 String id = parts[i].trim().replaceFirst("model.", "");
509                 String type = parts[i + 1].trim();
510                 String val = parts[i + 2].trim();
511 
512                 try
513                 {
514                     if (type.equals("DOUBLE"))
515                     {
516                         InputParameterDouble param = (InputParameterDouble) inputParameters.get(id);
517                         param.setDoubleValue(Double.valueOf(val));
518                     }
519                     else if (type.equals("FLOAT"))
520                     {
521                         InputParameterFloat param = (InputParameterFloat) inputParameters.get(id);
522                         param.setFloatValue(Float.valueOf(val));
523                     }
524                     else if (type.equals("BOOLEAN"))
525                     {
526                         InputParameterBoolean param = (InputParameterBoolean) inputParameters.get(id);
527                         param.setBooleanValue(val.toUpperCase().startsWith("T"));
528                     }
529                     else if (type.equals("LONG"))
530                     {
531                         InputParameterLong param = (InputParameterLong) inputParameters.get(id);
532                         param.setLongValue(Long.valueOf(val));
533                     }
534                     else if (type.equals("INTEGER"))
535                     {
536                         InputParameterInteger param = (InputParameterInteger) inputParameters.get(id);
537                         param.setIntValue(Integer.valueOf(val));
538                     }
539                     else if (type.equals("STRING"))
540                     {
541                         InputParameterString param = (InputParameterString) inputParameters.get(id);
542                         param.setStringValue(val);
543                     }
544                     if (type.equals("DOUBLESCALAR"))
545                     {
546                         InputParameterDoubleScalar<?, ?> param = (InputParameterDoubleScalar<?, ?>) inputParameters.get(id);
547                         param.getDoubleParameter().setDoubleValue(Double.valueOf(val));
548                         String unitString = unitMap.get(id);
549                         if (unitString == null)
550                             Logger.ots().error("Could not find unit for Doublevalie parameter with id={}", id);
551                         else
552                         {
553                             Unit<?> unit = param.getUnitParameter().getOptions().get(unitString);
554                             if (unit == null)
555                                 Logger.ots().error("Could not find unit {} for Doublevalie parameter with id={}", unitString,
556                                         id);
557                             else
558                             {
559                                 param.getUnitParameter().setObjectValue(unit);
560                                 param.setCalculatedValue(); // it will retrieve the set double value and unit
561                             }
562                         }
563                     }
564                 }
565                 catch (Exception exception)
566                 {
567                     if (errors.equals("OK"))
568                         errors = "ERRORS IN INPUT VALUES:\n";
569                     errors += "Field " + id + ": " + exception.getMessage() + "\n";
570                 }
571             }
572             return errors;
573         }
574 
575     }
576 
577 }