1 package nl.tudelft.simulation.dsol.jetty.sse;
2
3 import java.awt.Dimension;
4 import java.awt.geom.Point2D;
5 import java.io.IOException;
6 import java.net.URL;
7 import java.rmi.RemoteException;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.SortedMap;
12 import java.util.TreeMap;
13
14 import javax.servlet.ServletException;
15 import javax.servlet.http.HttpServletRequest;
16 import javax.servlet.http.HttpServletResponse;
17
18 import org.djutils.draw.bounds.Bounds2d;
19 import org.djutils.draw.point.Point2d;
20 import org.djutils.event.EventInterface;
21 import org.djutils.event.EventListenerInterface;
22 import org.djutils.event.TimedEvent;
23 import org.djutils.io.URLResource;
24 import org.eclipse.jetty.server.Handler;
25 import org.eclipse.jetty.server.Request;
26 import org.eclipse.jetty.server.Server;
27 import org.eclipse.jetty.server.handler.AbstractHandler;
28 import org.eclipse.jetty.server.handler.HandlerList;
29 import org.eclipse.jetty.server.handler.ResourceHandler;
30 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
31 import org.opentrafficsim.core.gtu.GTU;
32 import org.opentrafficsim.web.animation.WebAnimationToggles;
33
34 import nl.tudelft.simulation.dsol.SimRuntimeException;
35 import nl.tudelft.simulation.dsol.animation.Locatable;
36 import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface;
37 import nl.tudelft.simulation.dsol.experiment.ReplicationInterface;
38 import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
39 import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeAnimator;
40 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
41 import nl.tudelft.simulation.dsol.web.animation.D2.HTMLAnimationPanel;
42 import nl.tudelft.simulation.dsol.web.animation.D2.HTMLGridPanel;
43 import nl.tudelft.simulation.dsol.web.animation.D2.ToggleButtonInfo;
44 import nl.tudelft.simulation.introspection.Property;
45 import nl.tudelft.simulation.introspection.beans.BeanIntrospector;
46
47
48
49
50
51
52
53
54
55 public abstract class OTSWebServer implements EventListenerInterface
56 {
57
58 private final String title;
59
60
61 private final OTSSimulatorInterface simulator;
62
63
64 private boolean dirtyControls = false;
65
66
67 private HTMLAnimationPanel animationPanel;
68
69
70
71
72
73
74
75 public OTSWebServer(final String title, final OTSSimulatorInterface simulator, final Bounds2d extent) 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, this.simulator);
93 WebAnimationToggles.setTextAnimationTogglesStandard(this.animationPanel);
94
95 this.animationPanel.notify(new TimedEvent(ReplicationInterface.START_REPLICATION_EVENT,
96 this.simulator.getSourceId(), null, 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 DEVSRealTimeAnimator)
221 {
222 ((DEVSRealTimeAnimator<?, ?, ?>) this.simulator).setSpeedFactor(speedFactor);
223 }
224 }
225
226
227 @Override
228 public void notify(final 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(final String target, final Request baseRequest, final HttpServletRequest request, final 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 scaleX = animationPanel.getRenderableScale().getXScale(animationPanel.getExtent(),
390 animationPanel.getSize());
391 double scaleY = animationPanel.getRenderableScale().getYScale(animationPanel.getExtent(),
392 animationPanel.getSize());
393 Bounds2d extent = animationPanel.getExtent();
394 animationPanel.setExtent(new Bounds2d(extent.getMinX() - dx * scaleX,
395 extent.getMinX() - dx * scaleX + extent.getDeltaX(), extent.getMinY() + dy * scaleY,
396 extent.getMinY() + dy * scaleY + extent.getDeltaY()));
397 }
398 break;
399 }
400
401 case "introspect":
402 {
403 if (parts.length == 3)
404 {
405 int x = Integer.parseInt(parts[1]);
406 int y = Integer.parseInt(parts[2]);
407 List<Locatable> targets = new ArrayList<Locatable>();
408 try
409 {
410 Point2d point = animationPanel.getRenderableScale().getWorldCoordinates(
411 new Point2D.Double(x, y), animationPanel.getExtent(), animationPanel.getSize());
412 for (Renderable2DInterface<?> renderable : animationPanel.getElements())
413 {
414 if (animationPanel.isShowElement(renderable)
415 && renderable.contains(point, animationPanel.getExtent()))
416 {
417 if (renderable.getSource() instanceof GTU)
418 {
419 targets.add(renderable.getSource());
420 }
421 }
422 }
423 }
424 catch (Exception exception)
425 {
426 this.webServer.getSimulator().getLogger().always().warn(exception, "getSelectedObjects");
427 }
428 if (targets.size() > 0)
429 {
430 Object introspectedObject = targets.get(0);
431 Property[] properties = new BeanIntrospector().getProperties(introspectedObject);
432 SortedMap<String, Property> propertyMap = new TreeMap<>();
433 for (Property property : properties)
434 propertyMap.put(property.getName(), property);
435 answer = "<introspection>\n";
436 for (Property property : propertyMap.values())
437 {
438 answer += "<property><field>" + property.getName() + "</field><value>" + property.getValue()
439 + "</value></property>\n";
440 }
441 answer += "<introspection>\n";
442 }
443 else
444 {
445 answer = "<none />";
446 }
447 }
448 break;
449 }
450
451 case "zoomIn":
452 {
453 if (parts.length == 1)
454 animationPanel.zoom(0.9);
455 else
456 {
457 int x = Integer.parseInt(parts[1]);
458 int y = Integer.parseInt(parts[2]);
459 animationPanel.zoom(0.9, x, y);
460 }
461 break;
462 }
463
464 case "zoomOut":
465 {
466 if (parts.length == 1)
467 animationPanel.zoom(1.1);
468 else
469 {
470 int x = Integer.parseInt(parts[1]);
471 int y = Integer.parseInt(parts[2]);
472 animationPanel.zoom(1.1, x, y);
473 }
474 break;
475 }
476
477 case "zoomAll":
478 {
479 animationPanel.zoomAll();
480 break;
481 }
482
483 case "home":
484 {
485 animationPanel.home();
486 break;
487 }
488
489 case "toggleGrid":
490 {
491 animationPanel.setShowGrid(!animationPanel.isShowGrid());
492 break;
493 }
494
495 case "getTime":
496 {
497 double now = Math.round(this.webServer.getSimulator().getSimulatorTime().si * 1000) / 1000d;
498 int seconds = (int) Math.floor(now);
499 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
500 String timeText = String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60,
501 seconds % 60, fractionalSeconds);
502 answer = timeText;
503 break;
504 }
505
506 case "getSpeed":
507 {
508 double simTime = this.webServer.getSimulator().getSimulatorTime().si;
509 double speed = getSimulationSpeed(simTime);
510 String speedText = "";
511 if (!Double.isNaN(speed))
512 {
513 speedText = String.format("% 5.2fx ", speed);
514 }
515 answer = speedText;
516 break;
517 }
518
519 case "getToggles":
520 {
521 answer = getToggles(animationPanel);
522 break;
523 }
524
525
526 case "toggle":
527 {
528 if (parts.length != 4)
529 System.err.println("wrong toggle commmand: " + message);
530 else
531 {
532 String toggleName = parts[1];
533 boolean gis = parts[2].equals("gis");
534 boolean show = parts[3].equals("true");
535 if (gis)
536 {
537 if (show)
538 animationPanel.showGISLayer(toggleName);
539 else
540 animationPanel.hideGISLayer(toggleName);
541 }
542 else
543 {
544 if (show)
545 animationPanel.showClass(toggleName);
546 else
547 animationPanel.hideClass(toggleName);
548 }
549 }
550 break;
551 }
552
553 default:
554 {
555 System.err.println("Got unknown message from client: " + command);
556 answer = "<message>" + request.getParameter("message") + "</message>";
557 break;
558 }
559 }
560 }
561
562 if (request.getParameter("slider") != null)
563 {
564
565 try
566 {
567 int value = Integer.parseInt(request.getParameter("slider"));
568
569 double speedFactor = 1.0;
570 if (value > 1398)
571 speedFactor = Double.MAX_VALUE;
572 else
573 speedFactor = Math.pow(2.15444, value / 100.0) / 21.5444;
574 this.webServer.setSpeedFactor(speedFactor);
575
576 }
577 catch (NumberFormatException exception)
578 {
579 answer = "<message>Error: " + exception.getMessage() + "</message>";
580 }
581 }
582
583
584
585 response.setContentType("text/xml");
586 response.setHeader("Cache-Control", "no-cache");
587 response.setContentLength(answer.length());
588 response.setStatus(HttpServletResponse.SC_OK);
589 response.getWriter().write(answer);
590 response.flushBuffer();
591 baseRequest.setHandled(true);
592 }
593
594
595
596
597
598
599 private String controlButtonResponse(final boolean active, final boolean started)
600 {
601 if (!active)
602 {
603 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n"
604 + "<allEventsActive>false</allEventsActive>\n" + "<startStop>start</startStop>\n"
605 + "<startStopActive>false</startStopActive>\n" + "<resetActive>false</resetActive>\n" + "</controls>\n";
606 }
607 if (started)
608 {
609 return "<controls>\n" + "<oneEventActive>false</oneEventActive>\n"
610 + "<allEventsActive>false</allEventsActive>\n" + "<startStop>stop</startStop>\n"
611 + "<startStopActive>true</startStopActive>\n" + "<resetActive>false</resetActive>\n" + "</controls>\n";
612 }
613 else
614 {
615 return "<controls>\n" + "<oneEventActive>true</oneEventActive>\n" + "<allEventsActive>true</allEventsActive>\n"
616 + "<startStop>start</startStop>\n" + "<startStopActive>true</startStopActive>\n"
617 + "<resetActive>false</resetActive>\n" + "</controls>\n";
618 }
619 }
620
621
622
623
624
625
626 private String getToggles(final HTMLAnimationPanel panel)
627 {
628 String ret = "<toggles>\n";
629 for (ToggleButtonInfo toggle : panel.getToggleButtons())
630 {
631 if (toggle instanceof ToggleButtonInfo.Text)
632 {
633 ret += "<text>" + toggle.getName() + "</text>\n";
634 }
635 else if (toggle instanceof ToggleButtonInfo.LocatableClass)
636 {
637 ret += "<class>" + toggle.getName() + "," + toggle.isVisible() + "</class>\n";
638 }
639 else if (toggle instanceof ToggleButtonInfo.Gis)
640 {
641 ret += "<gis>" + toggle.getName() + "," + ((ToggleButtonInfo.Gis) toggle).getLayerName() + ","
642 + toggle.isVisible() + "</gis>\n";
643 }
644 }
645 ret += "</toggles>\n";
646 return ret;
647 }
648
649
650
651
652
653
654 private double getSimulationSpeed(final double simTime)
655 {
656 long now = System.currentTimeMillis();
657 if (this.lastWallTIme < 0 || this.lastWallTIme == now)
658 {
659 this.lastWallTIme = now;
660 this.prevSimTime = simTime;
661 return Double.NaN;
662 }
663 double speed = (simTime - this.prevSimTime) / (0.001 * (now - this.lastWallTIme));
664 this.prevSimTime = simTime;
665 this.lastWallTIme = now;
666 return speed;
667 }
668
669 }
670
671 }