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