1 package org.opentrafficsim.demo.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 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.AbstractDoubleScalar;
18 import org.djunits.value.vfloat.scalar.base.AbstractFloatScalar;
19 import org.djutils.cli.Checkable;
20 import org.djutils.cli.CliUtil;
21 import org.djutils.io.URLResource;
22 import org.eclipse.jetty.server.Handler;
23 import org.eclipse.jetty.server.Request;
24 import org.eclipse.jetty.server.Server;
25 import org.eclipse.jetty.server.SessionIdManager;
26 import org.eclipse.jetty.server.handler.AbstractHandler;
27 import org.eclipse.jetty.server.handler.HandlerList;
28 import org.eclipse.jetty.server.handler.ResourceHandler;
29 import org.eclipse.jetty.server.session.DefaultSessionCache;
30 import org.eclipse.jetty.server.session.DefaultSessionIdManager;
31 import org.eclipse.jetty.server.session.NullSessionDataStore;
32 import org.eclipse.jetty.server.session.SessionCache;
33 import org.eclipse.jetty.server.session.SessionDataStore;
34 import org.eclipse.jetty.server.session.SessionHandler;
35 import org.eclipse.jetty.util.resource.Resource;
36 import org.opentrafficsim.core.animation.gtu.colorer.DefaultSwitchableGTUColorer;
37 import org.opentrafficsim.core.dsol.OTSAnimator;
38 import org.opentrafficsim.core.dsol.OTSModelInterface;
39 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
40 import org.opentrafficsim.demo.CircularRoadModel;
41 import org.opentrafficsim.demo.CrossingTrafficLightsModel;
42 import org.opentrafficsim.demo.NetworksModel;
43 import org.opentrafficsim.demo.ShortMerge;
44 import org.opentrafficsim.demo.StraightModel;
45 import org.opentrafficsim.demo.conflict.BusStreetDemo;
46 import org.opentrafficsim.demo.conflict.TJunctionDemo;
47 import org.opentrafficsim.demo.conflict.TurboRoundaboutDemo;
48 import org.opentrafficsim.demo.trafficcontrol.TrafCODDemo1;
49 import org.opentrafficsim.demo.trafficcontrol.TrafCODDemo2;
50 import org.opentrafficsim.draw.factory.DefaultAnimationFactory;
51
52 import nl.tudelft.simulation.dsol.jetty.sse.OTSWebModel;
53 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameter;
54 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterBoolean;
55 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDistContinuousSelection;
56 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDistDiscreteSelection;
57 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDouble;
58 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDoubleScalar;
59 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterFloat;
60 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterFloatScalar;
61 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterInteger;
62 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterLong;
63 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterMap;
64 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterSelectionList;
65 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterSelectionMap;
66 import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterString;
67 import picocli.CommandLine.Command;
68 import picocli.CommandLine.Option;
69
70
71
72
73
74
75
76
77
78 @Command(description = "OTSDemoServer is a web server to run the OTS demos in a browser", name = "OTSDemoServer",
79 mixinStandardHelpOptions = true, version = "1.02.02")
80 public class OTSFederatedDemoServer implements Checkable
81 {
82
83 final Map<String, OTSModelInterface> sessionModelMap = new LinkedHashMap<>();
84
85
86 final Map<String, OTSWebModel> sessionWebModelMap = new LinkedHashMap<>();
87
88
89 @SuppressWarnings("checkstyle:visibilitymodifier")
90 @Option(names = {"-r", "--rootDirectory"}, description = "Root directory of the web server", defaultValue = "/home")
91 String rootDirectory;
92
93
94 @SuppressWarnings("checkstyle:visibilitymodifier")
95 @Option(names = {"-h", "--homePage"}, description = "Home page for the web server", defaultValue = "superdemo.html")
96 String homePage;
97
98
99 @SuppressWarnings("checkstyle:visibilitymodifier")
100 @Option(names = {"-p", "--port"}, description = "Internet port to use", defaultValue = "8080")
101 int port;
102
103
104
105
106
107
108 public static void main(final String[] args) throws Exception
109 {
110 OTSFederatedDemoServer webApp = new OTSFederatedDemoServer();
111 CliUtil.execute(webApp, args);
112 webApp.init();
113 }
114
115
116 @Override
117 public void check() throws Exception
118 {
119 if (this.port < 1 || this.port > 65535)
120 {
121 throw new Exception("Port number should be between 1 and 65535");
122 }
123 }
124
125
126 private void init()
127 {
128 new ServerThread().start();
129 }
130
131
132 class ServerThread extends Thread
133 {
134 @Override
135 public void run()
136 {
137 Server server = new Server();
138 ResourceHandler resourceHandler = new MyResourceHandler();
139
140
141 URL homeFolder = URLResource.getResource(OTSFederatedDemoServer.this.rootDirectory);
142 String webRoot = homeFolder.toExternalForm();
143 System.out.println("webRoot is " + webRoot);
144
145 resourceHandler.setDirectoriesListed(true);
146 resourceHandler.setWelcomeFiles(new String[] {OTSFederatedDemoServer.this.homePage});
147 resourceHandler.setResourceBase(webRoot);
148
149 SessionIdManager idManager = new DefaultSessionIdManager(server);
150 server.setSessionIdManager(idManager);
151
152 SessionHandler sessionHandler = new SessionHandler();
153 SessionCache sessionCache = new DefaultSessionCache(sessionHandler);
154 SessionDataStore sessionDataStore = new NullSessionDataStore();
155 sessionCache.setSessionDataStore(sessionDataStore);
156 sessionHandler.setSessionCache(sessionCache);
157
158 HandlerList handlers = new HandlerList();
159 handlers.setHandlers(new Handler[] {resourceHandler, sessionHandler, new XHRHandler(OTSFederatedDemoServer.this)});
160 server.setHandler(handlers);
161
162 try
163 {
164 server.start();
165 server.join();
166 }
167 catch (Exception exception)
168 {
169 exception.printStackTrace();
170 }
171 }
172 }
173
174
175 class MyResourceHandler extends ResourceHandler
176 {
177
178
179 @Override
180 public Resource getResource(final String path)
181 {
182 System.out.println(path);
183 return super.getResource(path);
184 }
185
186
187 @Override
188 @SuppressWarnings("checkstyle:usebraces")
189 public void handle(final String target, final Request baseRequest, final HttpServletRequest request,
190 final HttpServletResponse response) throws IOException, ServletException
191 {
192
193
194
195
196
197
198
199
200 if (target.startsWith("/parameters.html"))
201 {
202 String modelId = request.getParameterMap().get("model")[0];
203 String sessionId = request.getParameterMap().get("sessionId")[0];
204 if (!OTSFederatedDemoServer.this.sessionModelMap.containsKey(sessionId))
205 {
206 System.out.println("parameters: " + modelId);
207 OTSAnimator simulator = new OTSAnimator("OTSFederatedDemoServer");
208 simulator.setAnimation(false);
209 OTSModelInterface model = null;
210
211 if (modelId.toLowerCase().contains("circularroad"))
212 model = new CircularRoadModel(simulator);
213 else if (modelId.toLowerCase().contains("straight"))
214 model = new StraightModel(simulator);
215 else if (modelId.toLowerCase().contains("shortmerge"))
216 model = new ShortMerge.ShortMergeModel(simulator);
217 else if (modelId.toLowerCase().contains("networksdemo"))
218 model = new NetworksModel(simulator);
219 else if (modelId.toLowerCase().contains("crossingtrafficlights"))
220 model = new CrossingTrafficLightsModel(simulator);
221 else if (modelId.toLowerCase().contains("trafcoddemosimple"))
222 {
223 URL url = URLResource.getResource("/resources/TrafCODDemo1/TrafCODDemo1.xml");
224 String xml = TrafCODDemo2.readStringFromURL(url);
225 model = new TrafCODDemo1.TrafCODModel(simulator, "TrafCODDemo1", "TrafCODDemo1", xml);
226 }
227 else if (modelId.toLowerCase().contains("trafcoddemocomplex"))
228 {
229 URL url = URLResource.getResource("/resources/TrafCODDemo2/TrafCODDemo2.xml");
230 String xml = TrafCODDemo2.readStringFromURL(url);
231 model = new TrafCODDemo2.TrafCODModel(simulator, "TrafCODDemo2", "TrafCODDemo2", xml);
232 }
233 else if (modelId.toLowerCase().contains("tjunction"))
234 model = new TJunctionDemo.TJunctionModel(simulator);
235 else if (modelId.toLowerCase().contains("busstreet"))
236 model = new BusStreetDemo.BusStreetModel(simulator);
237 else if (modelId.toLowerCase().contains("turboroundabout"))
238 model = new TurboRoundaboutDemo.TurboRoundaboutModel(simulator);
239
240 if (model != null)
241 OTSFederatedDemoServer.this.sessionModelMap.put(sessionId, model);
242 else
243 System.err.println("Could not find model " + modelId);
244 }
245 }
246
247 if (target.startsWith("/model.html"))
248 {
249 String modelId = request.getParameterMap().get("model")[0];
250 String sessionId = request.getParameterMap().get("sessionId")[0];
251 if (OTSFederatedDemoServer.this.sessionModelMap.containsKey(sessionId)
252 && !OTSFederatedDemoServer.this.sessionWebModelMap.containsKey(sessionId))
253 {
254 System.out.println("startModel: " + modelId);
255 OTSModelInterface model = OTSFederatedDemoServer.this.sessionModelMap.get(sessionId);
256 OTSSimulatorInterface simulator = model.getSimulator();
257 try
258 {
259 simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600.0), model);
260 OTSWebModel webModel = new OTSWebModel(model.getShortName(), simulator);
261 OTSFederatedDemoServer.this.sessionWebModelMap.put(sessionId, webModel);
262 DefaultAnimationFactory.animateNetwork(model.getNetwork(), simulator,
263 new DefaultSwitchableGTUColorer());
264 }
265 catch (Exception exception)
266 {
267 exception.printStackTrace();
268 }
269 }
270 }
271
272
273 super.handle(target, baseRequest, request, response);
274 }
275 }
276
277
278
279
280
281
282
283
284
285 public static class XHRHandler extends AbstractHandler
286 {
287
288 private final OTSFederatedDemoServer webServer;
289
290
291
292
293
294 public XHRHandler(final OTSFederatedDemoServer webServer)
295 {
296 this.webServer = webServer;
297 }
298
299
300 @Override
301 public void handle(final String target, final Request baseRequest, final HttpServletRequest request,
302 final HttpServletResponse response) throws IOException, ServletException
303 {
304 if (request.getParameterMap().containsKey("sessionId"))
305 {
306 String sessionId = request.getParameterMap().get("sessionId")[0];
307 if (this.webServer.sessionWebModelMap.containsKey(sessionId))
308 {
309 this.webServer.sessionWebModelMap.get(sessionId).handle(target, baseRequest, request, response);
310 }
311 else if (this.webServer.sessionModelMap.containsKey(sessionId))
312 {
313 OTSModelInterface model = this.webServer.sessionModelMap.get(sessionId);
314 String answer = "<message>ok</message>";
315
316 if (request.getParameter("message") != null)
317 {
318 String message = request.getParameter("message");
319 String[] parts = message.split("\\|");
320 String command = parts[0];
321
322 switch (command)
323 {
324 case "getTitle":
325 {
326 answer = "<title>" + model.getShortName() + "</title>";
327 break;
328 }
329
330 case "getParameterMap":
331 {
332 answer = makeParameterMap(model);
333 break;
334 }
335
336 case "setParameters":
337 {
338 answer = setParameters(model, message);
339 break;
340 }
341
342 default:
343 {
344 System.err.println("Got unknown message from client: " + command);
345 answer = "<message>" + request.getParameter("message") + "</message>";
346 break;
347 }
348 }
349 }
350
351 response.setContentType("text/xml");
352 response.setHeader("Cache-Control", "no-cache");
353 response.setContentLength(answer.length());
354 response.setStatus(HttpServletResponse.SC_OK);
355 response.getWriter().write(answer);
356 response.flushBuffer();
357 baseRequest.setHandled(true);
358 }
359 }
360 }
361
362
363
364
365
366
367 private String makeParameterMap(final OTSModelInterface model)
368 {
369 StringBuffer answer = new StringBuffer();
370 answer.append("<parameters>\n");
371 InputParameterMap inputParameterMap = model.getInputParameterMap();
372 for (InputParameter<?, ?> tab : inputParameterMap.getSortedSet())
373 {
374 if (!(tab instanceof InputParameterMap))
375 {
376 System.err.println("Input parameter " + tab.getShortName() + " cannot be displayed in a tab");
377 }
378 else
379 {
380 answer.append("<tab>" + tab.getDescription() + "</tab>\n");
381 InputParameterMap tabbedMap = (InputParameterMap) tab;
382 for (InputParameter<?, ?> parameter : tabbedMap.getSortedSet())
383 {
384 addParameterField(answer, parameter);
385 }
386 }
387 }
388 answer.append("</parameters>\n");
389 return answer.toString();
390 }
391
392
393
394
395
396
397 public void addParameterField(final StringBuffer answer, final InputParameter<?, ?> parameter)
398 {
399 if (parameter instanceof InputParameterDouble)
400 {
401 InputParameterDouble pd = (InputParameterDouble) parameter;
402 answer.append("<double key='" + pd.getExtendedKey() + "' name='" + pd.getShortName() + "' description='"
403 + pd.getDescription() + "'>" + pd.getValue() + "</double>\n");
404 }
405 else if (parameter instanceof InputParameterFloat)
406 {
407 InputParameterFloat pf = (InputParameterFloat) parameter;
408 answer.append("<float key='" + pf.getExtendedKey() + "' name='" + pf.getShortName() + "' description='"
409 + pf.getDescription() + "'>" + pf.getValue() + "</float>\n");
410 }
411 else if (parameter instanceof InputParameterBoolean)
412 {
413 InputParameterBoolean pb = (InputParameterBoolean) parameter;
414 answer.append("<boolean key='" + pb.getExtendedKey() + "' name='" + pb.getShortName() + "' description='"
415 + pb.getDescription() + "'>" + pb.getValue() + "</boolean>\n");
416 }
417 else if (parameter instanceof InputParameterLong)
418 {
419 InputParameterLong pl = (InputParameterLong) parameter;
420 answer.append("<long key='" + pl.getExtendedKey() + "' name='" + pl.getShortName() + "' description='"
421 + pl.getDescription() + "'>" + pl.getValue() + "</long>\n");
422 }
423 else if (parameter instanceof InputParameterInteger)
424 {
425 InputParameterInteger pi = (InputParameterInteger) parameter;
426 answer.append("<integer key='" + pi.getExtendedKey() + "' name='" + pi.getShortName() + "' description='"
427 + pi.getDescription() + "'>" + pi.getValue() + "</integer>\n");
428 }
429 else if (parameter instanceof InputParameterString)
430 {
431 InputParameterString ps = (InputParameterString) parameter;
432 answer.append("<string key='" + ps.getExtendedKey() + "' name='" + ps.getShortName() + "' description='"
433 + ps.getDescription() + "'>" + ps.getValue() + "</string>\n");
434 }
435 else if (parameter instanceof InputParameterDoubleScalar)
436 {
437 InputParameterDoubleScalar<?, ?> pds = (InputParameterDoubleScalar<?, ?>) parameter;
438 String val = getValueInUnit(pds);
439 List<String> units = getUnits(pds);
440 answer.append("<doubleScalar key='" + pds.getExtendedKey() + "' name='" + pds.getShortName() + "' description='"
441 + pds.getDescription() + "'><value>" + val + "</value>\n");
442 for (String unit : units)
443 {
444 Unit<?> unitValue = pds.getUnitParameter().getOptions().get(unit);
445 if (unitValue.equals(pds.getUnitParameter().getValue()))
446 answer.append("<unit chosen='true'>" + unit + "</unit>\n");
447 else
448 answer.append("<unit chosen='false'>" + unit + "</unit>\n");
449 }
450 answer.append("</doubleScalar>\n");
451 }
452 else if (parameter instanceof InputParameterFloatScalar)
453 {
454 InputParameterFloatScalar<?, ?> pds = (InputParameterFloatScalar<?, ?>) parameter;
455 String val = getValueInUnit(pds);
456 List<String> units = getUnits(pds);
457 answer.append("<floatScalar key='" + pds.getExtendedKey() + "' name='" + pds.getShortName() + "' description='"
458 + pds.getDescription() + "'><value>" + val + "</value>\n");
459 for (String unit : units)
460 {
461 Unit<?> unitValue = pds.getUnitParameter().getOptions().get(unit);
462 if (unitValue.equals(pds.getUnitParameter().getValue()))
463 answer.append("<unit chosen='true'>" + unit + "</unit>\n");
464 else
465 answer.append("<unit chosen='false'>" + unit + "</unit>\n");
466 }
467 answer.append("</floatScalar>\n");
468 }
469 else if (parameter instanceof InputParameterSelectionList<?>)
470 {
471
472 }
473 else if (parameter instanceof InputParameterDistDiscreteSelection)
474 {
475
476 }
477 else if (parameter instanceof InputParameterDistContinuousSelection)
478 {
479
480 }
481 else if (parameter instanceof InputParameterSelectionMap<?, ?>)
482 {
483
484 }
485 }
486
487
488
489
490
491
492
493 private <U extends Unit<U>,
494 T extends AbstractDoubleScalar<U, T>> String getValueInUnit(final InputParameterDoubleScalar<U, T> parameter)
495 {
496 return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit());
497 }
498
499
500
501
502
503
504
505 private <U extends Unit<U>,
506 T extends AbstractDoubleScalar<U, T>> List<String> getUnits(final InputParameterDoubleScalar<U, T> parameter)
507 {
508 List<String> unitList = new ArrayList<>();
509 for (String option : parameter.getUnitParameter().getOptions().keySet())
510 {
511 unitList.add(option.toString());
512 }
513 return unitList;
514 }
515
516
517
518
519
520
521
522 private <U extends Unit<U>,
523 T extends AbstractFloatScalar<U, T>> String getValueInUnit(final InputParameterFloatScalar<U, T> parameter)
524 {
525 return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit());
526 }
527
528
529
530
531
532
533
534 private <U extends Unit<U>,
535 T extends AbstractFloatScalar<U, T>> List<String> getUnits(final InputParameterFloatScalar<U, T> parameter)
536 {
537 List<String> unitList = new ArrayList<>();
538 for (String option : parameter.getUnitParameter().getOptions().keySet())
539 {
540 unitList.add(option.toString());
541 }
542 return unitList;
543 }
544
545
546
547
548
549
550
551 private String setParameters(final OTSModelInterface model, final String message)
552 {
553 String errors = "OK";
554 InputParameterMap inputParameters = model.getInputParameterMap();
555 String[] parts = message.split("\\|");
556 Map<String, String> unitMap = new LinkedHashMap<>();
557 for (int i = 1; i < parts.length - 3; i += 3)
558 {
559 String id = parts[i].trim().replaceFirst("model.", "");
560 String type = parts[i + 1].trim();
561 String val = parts[i + 2].trim();
562 if (type.equals("UNIT"))
563 {
564 unitMap.put(id, val);
565 }
566 }
567 for (int i = 1; i < parts.length - 3; i += 3)
568 {
569 String id = parts[i].trim().replaceFirst("model.", "");
570 String type = parts[i + 1].trim();
571 String val = parts[i + 2].trim();
572
573 try
574 {
575 if (type.equals("DOUBLE"))
576 {
577 InputParameterDouble param = (InputParameterDouble) inputParameters.get(id);
578 param.setDoubleValue(Double.valueOf(val));
579 }
580 else if (type.equals("FLOAT"))
581 {
582 InputParameterFloat param = (InputParameterFloat) inputParameters.get(id);
583 param.setFloatValue(Float.valueOf(val));
584 }
585 else if (type.equals("BOOLEAN"))
586 {
587 InputParameterBoolean param = (InputParameterBoolean) inputParameters.get(id);
588 param.setBooleanValue(val.toUpperCase().startsWith("T"));
589 }
590 else if (type.equals("LONG"))
591 {
592 InputParameterLong param = (InputParameterLong) inputParameters.get(id);
593 param.setLongValue(Long.valueOf(val));
594 }
595 else if (type.equals("INTEGER"))
596 {
597 InputParameterInteger param = (InputParameterInteger) inputParameters.get(id);
598 param.setIntValue(Integer.valueOf(val));
599 }
600 else if (type.equals("STRING"))
601 {
602 InputParameterString param = (InputParameterString) inputParameters.get(id);
603 param.setStringValue(val);
604 }
605 if (type.equals("DOUBLESCALAR"))
606 {
607 InputParameterDoubleScalar<?, ?> param = (InputParameterDoubleScalar<?, ?>) inputParameters.get(id);
608 param.getDoubleParameter().setDoubleValue(Double.valueOf(val));
609 String unitString = unitMap.get(id);
610 if (unitString == null)
611 System.err.println("Could not find unit for DoubleScalar parameter with id=" + id);
612 else
613 {
614 Unit<?> unit = param.getUnitParameter().getOptions().get(unitString);
615 if (unit == null)
616 System.err.println(
617 "Could not find unit " + unitString + " for DoubleScalar parameter with id=" + id);
618 else
619 {
620 param.getUnitParameter().setObjectValue(unit);
621 param.setCalculatedValue();
622 }
623 }
624 }
625 }
626 catch (Exception exception)
627 {
628 if (errors.equals("OK"))
629 errors = "ERRORS IN INPUT VALUES:\n";
630 errors += "Field " + id + ": " + exception.getMessage() + "\n";
631 }
632 }
633 return errors;
634 }
635
636 }
637
638 }