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