1 package nl.tudelft.simulation.dsol.jetty.sse;
2
3 import java.awt.Dimension;
4 import java.awt.geom.Point2D;
5 import java.awt.geom.Rectangle2D;
6 import java.io.IOException;
7 import java.net.URL;
8 import java.rmi.RemoteException;
9 import java.util.ArrayList;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.SortedMap;
13 import java.util.TreeMap;
14
15 import javax.servlet.ServletException;
16 import javax.servlet.http.HttpServletRequest;
17 import javax.servlet.http.HttpServletResponse;
18
19 import org.djutils.event.EventInterface;
20 import org.djutils.event.EventListenerInterface;
21 import org.djutils.event.TimedEvent;
22 import org.djutils.io.URLResource;
23 import org.eclipse.jetty.server.Handler;
24 import org.eclipse.jetty.server.Request;
25 import org.eclipse.jetty.server.Server;
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.opentrafficsim.core.dsol.OTSSimulatorInterface;
30 import org.opentrafficsim.core.gtu.GTU;
31 import org.opentrafficsim.web.animation.WebAnimationToggles;
32
33 import nl.tudelft.simulation.dsol.SimRuntimeException;
34 import nl.tudelft.simulation.dsol.animation.Locatable;
35 import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface;
36 import nl.tudelft.simulation.dsol.experiment.Replication;
37 import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
38 import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeClock;
39 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
40 import nl.tudelft.simulation.dsol.web.animation.D2.HTMLAnimationPanel;
41 import nl.tudelft.simulation.dsol.web.animation.D2.HTMLGridPanel;
42 import nl.tudelft.simulation.dsol.web.animation.D2.ToggleButtonInfo;
43 import nl.tudelft.simulation.introspection.Property;
44 import nl.tudelft.simulation.introspection.beans.BeanIntrospector;
45
46
47
48
49
50
51
52
53
54 public abstract class OTSWebServer implements EventListenerInterface
55 {
56
57 private final String title;
58
59
60 private final OTSSimulatorInterface simulator;
61
62
63 private boolean dirtyControls = false;
64
65
66 private HTMLAnimationPanel animationPanel;
67
68
69
70
71
72
73
74 public OTSWebServer(final String title, final OTSSimulatorInterface simulator, final Rectangle2D.Double extent)
75 throws Exception
76 {
77 this.title = title;
78
79 this.simulator = simulator;
80 try
81 {
82 simulator.addListener(this, SimulatorInterface.START_EVENT);
83 simulator.addListener(this, SimulatorInterface.STOP_EVENT);
84 }
85 catch (RemoteException re)
86 {
87 simulator.getLogger().always().warn(re, "Problem adding listeners to Simulator");
88 }
89
90 if (this.simulator instanceof AnimatorInterface)
91 {
92 this.animationPanel = new HTMLAnimationPanel(extent, new Dimension(800, 600), this.simulator);
93 WebAnimationToggles.setTextAnimationTogglesStandard(this.animationPanel);
94
95 this.animationPanel.notify(new TimedEvent(Replication.START_REPLICATION_EVENT, this.simulator.getSourceId(), null,
96 this.simulator.getSimulatorTime()));
97 }
98
99 new ServerThread().start();
100 }
101
102
103 class ServerThread extends Thread
104 {
105 @Override
106 public void run()
107 {
108 Server server = new Server(8080);
109 ResourceHandler resourceHandler = new ResourceHandler();
110
111
112 URL homeFolder = URLResource.getResource("/home");
113 String webRoot = homeFolder.toExternalForm();
114 System.out.println("webRoot is " + webRoot);
115
116 resourceHandler.setDirectoriesListed(true);
117 resourceHandler.setWelcomeFiles(new String[] {"index.html"});
118 resourceHandler.setResourceBase(webRoot);
119
120 HandlerList handlers = new HandlerList();
121 handlers.setHandlers(new Handler[] {resourceHandler, new XHRHandler(OTSWebServer.this)});
122 server.setHandler(handlers);
123
124 try
125 {
126 server.start();
127 server.join();
128 }
129 catch (Exception exception)
130 {
131 exception.printStackTrace();
132 }
133 }
134 }
135
136
137
138
139 public final String getTitle()
140 {
141 return this.title;
142 }
143
144
145
146
147 public final OTSSimulatorInterface getSimulator()
148 {
149 return this.simulator;
150 }
151
152
153
154
155 public final HTMLAnimationPanel getAnimationPanel()
156 {
157 return this.animationPanel;
158 }
159
160
161
162
163
164 protected boolean startSimulator()
165 {
166 if (getSimulator() == null)
167 {
168 System.out.println("SIMULATOR == NULL");
169 return false;
170 }
171 try
172 {
173 System.out.println("START THE SIMULATOR");
174 getSimulator().start();
175 }
176 catch (SimRuntimeException exception)
177 {
178 getSimulator().getLogger().always().warn(exception, "Problem starting Simulator");
179 }
180 if (getSimulator().isStartingOrRunning())
181 {
182 return true;
183 }
184 this.dirtyControls = false;
185 return false;
186 }
187
188
189
190
191
192 protected boolean stopSimulator()
193 {
194 if (getSimulator() == null)
195 {
196 return true;
197 }
198 try
199 {
200 System.out.println("STOP THE SIMULATOR");
201 getSimulator().stop();
202 }
203 catch (SimRuntimeException exception)
204 {
205 getSimulator().getLogger().always().warn(exception, "Problem stopping Simulator");
206 }
207 if (!getSimulator().isStartingOrRunning())
208 {
209 return true;
210 }
211 this.dirtyControls = false;
212 return false;
213 }
214
215
216
217
218 protected void setSpeedFactor(final double speedFactor)
219 {
220 if (this.simulator instanceof DEVSRealTimeClock)
221 {
222 ((DEVSRealTimeClock<?, ?, ?>) this.simulator).setSpeedFactor(speedFactor);
223 }
224 }
225
226
227 @Override
228 public void notify(EventInterface event) throws RemoteException
229 {
230 if (event.getType().equals(SimulatorInterface.START_EVENT))
231 {
232 this.dirtyControls = true;
233 }
234 else if (event.getType().equals(SimulatorInterface.STOP_EVENT))
235 {
236 this.dirtyControls = true;
237 }
238 }
239
240
241
242
243
244
245
246
247
248 public static class XHRHandler extends AbstractHandler
249 {
250
251 final OTSWebServer webServer;
252
253
254 private long lastWallTIme = -1;
255
256
257 private double prevSimTime = 0;
258
259
260
261
262
263 public XHRHandler(final OTSWebServer webServer)
264 {
265 this.webServer = webServer;
266 }
267
268 @Override
269 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
270 throws IOException, ServletException
271 {
272
273
274
275
276 Map<String, String[]> params = request.getParameterMap();
277
278
279 String answer = "<message>ok</message>";
280
281 if (request.getParameter("message") != null)
282 {
283 String message = request.getParameter("message");
284 String[] parts = message.split("\\|");
285 String command = parts[0];
286 HTMLAnimationPanel animationPanel = this.webServer.getAnimationPanel();
287
288 switch (command)
289 {
290 case "getTitle":
291 {
292 answer = "<title>" + this.webServer.getTitle() + "</title>";
293 break;
294 }
295
296 case "init":
297 {
298 boolean simOk = this.webServer.getSimulator() != null;
299 boolean started = simOk ? this.webServer.getSimulator().isStartingOrRunning() : false;
300 answer = controlButtonResponse(simOk, started);
301 break;
302 }
303
304 case "windowSize":
305 {
306 if (parts.length != 3)
307 System.err.println("wrong windowSize commmand: " + message);
308 else
309 {
310 int width = Integer.parseInt(parts[1]);
311 int height = Integer.parseInt(parts[2]);
312 animationPanel.setSize(new Dimension(width, height));
313 }
314 break;
315 }
316
317 case "startStop":
318 {
319 boolean simOk = this.webServer.getSimulator() != null;
320 boolean started = simOk ? this.webServer.getSimulator().isStartingOrRunning() : false;
321 if (simOk && started)
322 started = !this.webServer.stopSimulator();
323 else if (simOk && !started)
324 started = this.webServer.startSimulator();
325 answer = controlButtonResponse(simOk, started);
326 break;
327 }
328
329 case "oneEvent":
330 {
331
332 boolean started = false;
333 answer = controlButtonResponse(this.webServer.getSimulator() != null, started);
334 break;
335 }
336
337 case "allEvents":
338 {
339
340 boolean started = false;
341 answer = controlButtonResponse(this.webServer.getSimulator() != null, started);
342 break;
343 }
344
345 case "reset":
346 {
347
348 boolean started = false;
349 answer = controlButtonResponse(this.webServer.getSimulator() != null, started);
350 break;
351 }
352
353 case "animate":
354 {
355 answer = animationPanel.getDrawingCommands();
356 break;
357 }
358
359 case "arrowDown":
360 {
361 animationPanel.pan(HTMLGridPanel.DOWN, 0.1);
362 break;
363 }
364
365 case "arrowUp":
366 {
367 animationPanel.pan(HTMLGridPanel.UP, 0.1);
368 break;
369 }
370
371 case "arrowLeft":
372 {
373 animationPanel.pan(HTMLGridPanel.LEFT, 0.1);
374 break;
375 }
376
377 case "arrowRight":
378 {
379 animationPanel.pan(HTMLGridPanel.RIGHT, 0.1);
380 break;
381 }
382
383 case "pan":
384 {
385 if (parts.length == 3)
386 {
387 int dx = Integer.parseInt(parts[1]);
388 int dy = Integer.parseInt(parts[2]);
389 double scale =
390 Renderable2DInterface.Util.getScale(animationPanel.getExtent(), animationPanel.getSize());
391 Rectangle2D.Double extent = (Rectangle2D.Double) animationPanel.getExtent();
392 extent.setRect((extent.getMinX() - dx * scale), (extent.getMinY() + dy * scale), extent.getWidth(),
393 extent.getHeight());
394 }
395 break;
396 }
397
398 case "introspect":
399 {
400 if (parts.length == 3)
401 {
402 int x = Integer.parseInt(parts[1]);
403 int y = Integer.parseInt(parts[2]);
404 List<Locatable> targets = new ArrayList<Locatable>();
405 try
406 {
407 Point2D point = Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(x, y),
408 animationPanel.getExtent(), animationPanel.getSize());
409 for (Renderable2DInterface<?> renderable : animationPanel.getElements())
410 {
411 if (animationPanel.isShowElement(renderable)
412 && renderable.contains(point, animationPanel.getExtent(), animationPanel.getSize()))
413 {
414 if (renderable.getSource() instanceof GTU)
415 {
416 targets.add(renderable.getSource());
417 }
418 }
419 }
420 }
421 catch (Exception exception)
422 {
423 this.webServer.getSimulator().getLogger().always().warn(exception, "getSelectedObjects");
424 }
425 if (targets.size() > 0)
426 {
427 Object introspectedObject = targets.get(0);
428 Property[] properties = new BeanIntrospector().getProperties(introspectedObject);
429 SortedMap<String, Property> propertyMap = new TreeMap<>();
430 for (Property property : properties)
431 propertyMap.put(property.getName(), property);
432 answer = "<introspection>\n";
433 for (Property property : propertyMap.values())
434 {
435 answer += "<property><field>" + property.getName() + "</field><value>" + property.getValue()
436 + "</value></property>\n";
437 }
438 answer += "<introspection>\n";
439 }
440 else
441 {
442 answer = "<none />";
443 }
444 }
445 break;
446 }
447
448 case "zoomIn":
449 {
450 if (parts.length == 1)
451 animationPanel.zoom(0.9);
452 else
453 {
454 int x = Integer.parseInt(parts[1]);
455 int y = Integer.parseInt(parts[2]);
456 animationPanel.zoom(0.9, x, y);
457 }
458 break;
459 }
460
461 case "zoomOut":
462 {
463 if (parts.length == 1)
464 animationPanel.zoom(1.1);
465 else
466 {
467 int x = Integer.parseInt(parts[1]);
468 int y = Integer.parseInt(parts[2]);
469 animationPanel.zoom(1.1, x, y);
470 }
471 break;
472 }
473
474 case "zoomAll":
475 {
476 animationPanel.zoomAll();
477 break;
478 }
479
480 case "home":
481 {
482 animationPanel.home();
483 break;
484 }
485
486 case "toggleGrid":
487 {
488 animationPanel.setShowGrid(!animationPanel.isShowGrid());
489 break;
490 }
491
492 case "getTime":
493 {
494 double now = Math.round(this.webServer.getSimulator().getSimulatorTime().si * 1000) / 1000d;
495 int seconds = (int) Math.floor(now);
496 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
497 String timeText = String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60,
498 seconds % 60, fractionalSeconds);
499 answer = timeText;
500 break;
501 }
502
503 case "getSpeed":
504 {
505 double simTime = this.webServer.getSimulator().getSimulatorTime().si;
506 double speed = getSimulationSpeed(simTime);
507 String speedText = "";
508 if (!Double.isNaN(speed))
509 {
510 speedText = String.format("% 5.2fx ", speed);
511 }
512 answer = speedText;
513 break;
514 }
515
516 case "getToggles":
517 {
518 answer = getToggles(animationPanel);
519 break;
520 }
521
522
523 case "toggle":
524 {
525 if (parts.length != 4)
526 System.err.println("wrong toggle commmand: " + message);
527 else
528 {
529 String toggleName = parts[1];
530 boolean gis = parts[2].equals("gis");
531 boolean show = parts[3].equals("true");
532 if (gis)
533 {
534 if (show)
535 animationPanel.showGISLayer(toggleName);
536 else
537 animationPanel.hideGISLayer(toggleName);
538 }
539 else
540 {
541 if (show)
542 animationPanel.showClass(toggleName);
543 else
544 animationPanel.hideClass(toggleName);
545 }
546 }
547 break;
548 }
549
550 default:
551 {
552 System.err.println("Got unknown message from client: " + command);
553 answer = "<message>" + request.getParameter("message") + "</message>";
554 break;
555 }
556 }
557 }
558
559 if (request.getParameter("slider") != null)
560 {
561
562 try
563 {
564 int value = Integer.parseInt(request.getParameter("slider"));
565
566 double speedFactor = 1.0;
567 if (value > 1398)
568 speedFactor = Double.MAX_VALUE;
569 else
570 speedFactor = Math.pow(2.15444, value / 100.0) / 21.5444;
571 this.webServer.setSpeedFactor(speedFactor);
572
573 }
574 catch (NumberFormatException exception)
575 {
576 answer = "<message>Error: " + exception.getMessage() + "</message>";
577 }
578 }
579
580
581
582 response.setContentType("text/xml");
583 response.setHeader("Cache-Control", "no-cache");
584 response.setContentLength(answer.length());
585 response.setStatus(HttpServletResponse.SC_OK);
586 response.getWriter().write(answer);
587 response.flushBuffer();
588 baseRequest.setHandled(true);
589 }
590
591
592
593
594
595
596 private String controlButtonResponse(final boolean active, final boolean started)
597 {
598 if (!active)
599 {
600 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n"
601 + "<allEventsActive>false</allEventsActive>\n" + "<startStop>start</startStop>\n"
602 + "<startStopActive>false</startStopActive>\n" + "<resetActive>false</resetActive>\n" + "</controls>\n";
603 }
604 if (started)
605 {
606 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n"
607 + "<allEventsActive>false</allEventsActive>\n" + "<startStop>stop</startStop>\n"
608 + "<startStopActive>true</startStopActive>\n" + "<resetActive>false</resetActive>\n" + "</controls>\n";
609 }
610 else
611 {
612 return "<controls>\n" + "<oneEventActive>true</oneEventActive>\n" + "<allEventsActive>true</allEventsActive>\n"
613 + "<startStop>start</startStop>\n" + "<startStopActive>true</startStopActive>\n"
614 + "<resetActive>false</resetActive>\n" + "</controls>\n";
615 }
616 }
617
618
619
620
621
622
623 private String getToggles(final HTMLAnimationPanel panel)
624 {
625 String ret = "<toggles>\n";
626 for (ToggleButtonInfo toggle : panel.getToggleButtons())
627 {
628 if (toggle instanceof ToggleButtonInfo.Text)
629 {
630 ret += "<text>" + toggle.getName() + "</text>\n";
631 }
632 else if (toggle instanceof ToggleButtonInfo.LocatableClass)
633 {
634 ret += "<class>" + toggle.getName() + "," + toggle.isVisible() + "</class>\n";
635 }
636 else if (toggle instanceof ToggleButtonInfo.Gis)
637 {
638 ret += "<gis>" + toggle.getName() + "," + ((ToggleButtonInfo.Gis) toggle).getLayerName() + ","
639 + toggle.isVisible() + "</gis>\n";
640 }
641 }
642 ret += "</toggles>\n";
643 return ret;
644 }
645
646
647
648
649
650
651 private double getSimulationSpeed(final double simTime)
652 {
653 long now = System.currentTimeMillis();
654 if (this.lastWallTIme < 0 || this.lastWallTIme == now)
655 {
656 this.lastWallTIme = now;
657 this.prevSimTime = simTime;
658 return Double.NaN;
659 }
660 double speed = (simTime - this.prevSimTime) / (0.001 * (now - this.lastWallTIme));
661 this.prevSimTime = simTime;
662 this.lastWallTIme = now;
663 return speed;
664 }
665
666 }
667
668 }