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