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