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