package org.opentrafficsim.demo.web; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.djunits.unit.Unit; import org.djunits.value.vdouble.scalar.Duration; import org.djunits.value.vdouble.scalar.Time; import org.djunits.value.vdouble.scalar.base.AbstractDoubleScalar; import org.djunits.value.vfloat.scalar.base.AbstractFloatScalar; import org.djutils.cli.Checkable; import org.djutils.cli.CliUtil; import org.djutils.io.URLResource; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.SessionIdManager; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.session.DefaultSessionCache; import org.eclipse.jetty.server.session.DefaultSessionIdManager; import org.eclipse.jetty.server.session.NullSessionDataStore; import org.eclipse.jetty.server.session.SessionCache; import org.eclipse.jetty.server.session.SessionDataStore; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.resource.Resource; import org.opentrafficsim.core.animation.gtu.colorer.DefaultSwitchableGTUColorer; import org.opentrafficsim.core.dsol.OTSAnimator; import org.opentrafficsim.core.dsol.OTSModelInterface; import org.opentrafficsim.core.dsol.OTSSimulatorInterface; import org.opentrafficsim.demo.CircularRoadModel; import org.opentrafficsim.demo.CrossingTrafficLightsModel; import org.opentrafficsim.demo.NetworksModel; import org.opentrafficsim.demo.ShortMerge; import org.opentrafficsim.demo.StraightModel; import org.opentrafficsim.demo.conflict.BusStreetDemo; import org.opentrafficsim.demo.conflict.TJunctionDemo; import org.opentrafficsim.demo.conflict.TurboRoundaboutDemo; import org.opentrafficsim.demo.trafficcontrol.TrafCODDemo1; import org.opentrafficsim.demo.trafficcontrol.TrafCODDemo2; import org.opentrafficsim.draw.factory.DefaultAnimationFactory; import nl.tudelft.simulation.dsol.jetty.sse.OTSWebModel; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameter; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterBoolean; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDistContinuousSelection; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDistDiscreteSelection; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDouble; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDoubleScalar; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterFloat; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterFloatScalar; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterInteger; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterLong; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterMap; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterSelectionList; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterSelectionMap; import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterString; import picocli.CommandLine.Command; import picocli.CommandLine.Option; /** * OTSDemoServer.java.
*
* Copyright (c) 2003-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See * for project information www.simulation.tudelft.nl. The * source code and binary code of this software is proprietary information of Delft University of Technology. * @author Alexander Verbraeck */ @Command(description = "OTSDemoServer is a web server to run the OTS demos in a browser", name = "OTSDemoServer", mixinStandardHelpOptions = true, version = "1.02.02") public class OTSDemoServer implements Checkable { /** the map of sessionIds to OTSModelInterface that handles the animation and updates for the started model. */ @SuppressWarnings("checkstyle:visibilitymodifier") final Map sessionModelMap = new LinkedHashMap<>(); /** the map of sessionIds to OTSWebModel that handles the animation and updates for the started model. */ @SuppressWarnings("checkstyle:visibilitymodifier") final Map sessionWebModelMap = new LinkedHashMap<>(); /** the map of sessionIds to the time in msec when the model has to be killed. */ @SuppressWarnings("checkstyle:visibilitymodifier") final Map sessionKillMap = new LinkedHashMap<>(); /** how many processes max? */ @SuppressWarnings("checkstyle:visibilitymodifier") @Option(names = {"-m", "--maxProcesses"}, description = "Maximum number of concurrent demo processes", defaultValue = "10") int maxProcesses; /** how much time max before being killed? */ @SuppressWarnings("checkstyle:visibilitymodifier") @Option(names = {"-t", "--killDuration"}, description = "Maximum duration a demo process stays alive before being killed", defaultValue = "10min") Duration killDuration; /** root directory for the web server. */ @SuppressWarnings("checkstyle:visibilitymodifier") @Option(names = {"-r", "--rootDirectory"}, description = "Root directory of the web server", defaultValue = "/home") String rootDirectory; /** home page for the web server. */ @SuppressWarnings("checkstyle:visibilitymodifier") @Option(names = {"-w", "--homePage"}, description = "Home page for the web server", defaultValue = "superdemo.html") String homePage; /** internet port for the web server. */ @SuppressWarnings("checkstyle:visibilitymodifier") @Option(names = {"-p", "--port"}, description = "Internet port to use", defaultValue = "8081") int port; /** * Run a SuperDemo OTS Web server. * @param args String[]; param=value style parameters. Used: maxProcesses=20 maxTimeMinutes=30 * @throws Exception on Jetty error */ public static void main(final String[] args) throws Exception { OTSDemoServer otsDemoServer = new OTSDemoServer(); CliUtil.execute(otsDemoServer, args); otsDemoServer.init(); } /** {@inheritDoc} */ @Override public void check() throws Exception { if (this.port <= 0 || this.port > 65535) { throw new Exception("Port should be between 1 and 65535"); } } /** Init the server. */ private void init() { System.out.println("Kill duration = " + this.killDuration); new ServerThread().start(); new KillThread().start(); } /** * Constructor to set any variables to default values if needed. */ public OTSDemoServer() { } /** Handle the kills of models that ran maxTime minutes. */ class KillThread extends Thread { @Override public void run() { while (true) { try { Thread.sleep(10000); // 10 seconds List kills = new ArrayList<>(); long timeNow = System.currentTimeMillis(); for (String sessionId : OTSDemoServer.this.sessionKillMap.keySet()) { if (timeNow > OTSDemoServer.this.sessionKillMap.get(sessionId)) { kills.add(sessionId); } } for (String sessionId : kills) { OTSWebModel webModel = OTSDemoServer.this.sessionWebModelMap.get(sessionId); if (webModel != null) { webModel.setKilled(true); OTSDemoServer.this.sessionWebModelMap.remove(sessionId); } OTSModelInterface model = OTSDemoServer.this.sessionModelMap.get(sessionId); if (model != null) { try { model.getSimulator().stop(); } catch (Exception e) { // ignore } OTSDemoServer.this.sessionModelMap.remove(sessionId); } OTSDemoServer.this.sessionKillMap.remove(sessionId); } } catch (Exception exception) { // } } } } /** Handle in separate thread to avoid 'lock' of the main application. */ class ServerThread extends Thread { @Override public void run() { Server server = new Server(OTSDemoServer.this.port); ResourceHandler resourceHandler = new MyResourceHandler(); // root folder; to work in Eclipse, as an external jar, and in an embedded jar URL homeFolder = URLResource.getResource(OTSDemoServer.this.rootDirectory); String webRoot = homeFolder.toExternalForm(); System.out.println("webRoot is " + webRoot); resourceHandler.setDirectoriesListed(true); resourceHandler.setWelcomeFiles(new String[] {OTSDemoServer.this.homePage}); resourceHandler.setResourceBase(webRoot); SessionIdManager idManager = new DefaultSessionIdManager(server); server.setSessionIdManager(idManager); SessionHandler sessionHandler = new SessionHandler(); SessionCache sessionCache = new DefaultSessionCache(sessionHandler); SessionDataStore sessionDataStore = new NullSessionDataStore(); sessionCache.setSessionDataStore(sessionDataStore); sessionHandler.setSessionCache(sessionCache); HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[] {resourceHandler, sessionHandler, new XHRHandler(OTSDemoServer.this)}); server.setHandler(handlers); try { server.start(); server.join(); } catch (Exception exception) { exception.printStackTrace(); } } } /** */ class MyResourceHandler extends ResourceHandler { /** {@inheritDoc} */ @Override public Resource getResource(final String path) { System.out.println(path); if (path.contains("/parameters.html")) { if (OTSDemoServer.this.sessionModelMap.size() > OTSDemoServer.this.maxProcesses) { System.out.println("NO MORE PROCESSES -- MAXMODELS returned"); return super.getResource("/maxmodels.html"); } } return super.getResource(path); } /** {@inheritDoc} */ @Override @SuppressWarnings("checkstyle:usebraces") public void handle(final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { /*- System.out.println("target = " + target); System.out.println("baseRequest = " + baseRequest); System.out.println("request = " + request); System.out.println("request.param " + request.getParameterMap()); System.out.println(); */ if (target.startsWith("/parameters.html")) { if (OTSDemoServer.this.sessionModelMap.size() > OTSDemoServer.this.maxProcesses) { super.handle(target, baseRequest, request, response); return; } String modelId = request.getParameterMap().get("model")[0]; String sessionId = request.getParameterMap().get("sessionId")[0]; if (!OTSDemoServer.this.sessionModelMap.containsKey(sessionId)) { System.out.println("parameters: " + modelId); OTSAnimator simulator = new OTSAnimator("OTSDemoServer"); simulator.setAnimation(false); OTSModelInterface model = null; if (modelId.toLowerCase().contains("circularroad")) { model = new CircularRoadModel(simulator); } else if (modelId.toLowerCase().contains("straight")) { model = new StraightModel(simulator); } else if (modelId.toLowerCase().contains("shortmerge")) { model = new ShortMerge.ShortMergeModel(simulator); } else if (modelId.toLowerCase().contains("networksdemo")) { model = new NetworksModel(simulator); } else if (modelId.toLowerCase().contains("crossingtrafficlights")) { model = new CrossingTrafficLightsModel(simulator); } else if (modelId.toLowerCase().contains("trafcoddemosimple")) { URL url = URLResource.getResource("/TrafCODDemo1/TrafCODDemo1.xml"); String xml = TrafCODDemo2.readStringFromURL(url); model = new TrafCODDemo1.TrafCODModel(simulator, "TrafCODDemo1", "TrafCODDemo1", xml); } else if (modelId.toLowerCase().contains("trafcoddemocomplex")) { URL url = URLResource.getResource("/TrafCODDemo2/TrafCODDemo2.xml"); String xml = TrafCODDemo2.readStringFromURL(url); model = new TrafCODDemo2.TrafCODModel(simulator, "TrafCODDemo2", "TrafCODDemo2", xml); } else if (modelId.toLowerCase().contains("tjunction")) { model = new TJunctionDemo.TJunctionModel(simulator); } else if (modelId.toLowerCase().contains("busstreet")) { model = new BusStreetDemo.BusStreetModel(simulator); } else if (modelId.toLowerCase().contains("turboroundabout")) { model = new TurboRoundaboutDemo.TurboRoundaboutModel(simulator); } if (model != null) { OTSDemoServer.this.sessionModelMap.put(sessionId, model); long currentMsec = System.currentTimeMillis(); long killMsec = currentMsec + (long) OTSDemoServer.this.killDuration.si * 1000L; OTSDemoServer.this.sessionKillMap.put(sessionId, killMsec); } else { System.err.println("Could not find model " + modelId); } } } if (target.startsWith("/model.html")) { String modelId = request.getParameterMap().get("model")[0]; String sessionId = request.getParameterMap().get("sessionId")[0]; if (OTSDemoServer.this.sessionModelMap.containsKey(sessionId) && !OTSDemoServer.this.sessionWebModelMap.containsKey(sessionId)) { System.out.println("startModel: " + modelId); OTSModelInterface model = OTSDemoServer.this.sessionModelMap.get(sessionId); OTSSimulatorInterface simulator = model.getSimulator(); try { simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600.0), model); OTSWebModel webModel = new OTSWebModel(model.getShortName(), simulator); OTSDemoServer.this.sessionWebModelMap.put(sessionId, webModel); DefaultAnimationFactory.animateNetwork(model.getNetwork(), simulator, new DefaultSwitchableGTUColorer()); } catch (Exception exception) { exception.printStackTrace(); } } } // handle whatever needs to be done... super.handle(target, baseRequest, request, response); } } /** * Answer handles the events from the web-based user interface for a demo.
*
* Copyright (c) 2003-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. * See for project information www.simulation.tudelft.nl. * The source code and binary code of this software is proprietary information of Delft University of Technology. * @author Alexander Verbraeck */ public static class XHRHandler extends AbstractHandler { /** web server for callback of actions. */ private final OTSDemoServer webServer; /** * Create the handler for Servlet requests. * @param webServer DSOLWebServer; web server for callback of actions */ public XHRHandler(final OTSDemoServer webServer) { this.webServer = webServer; } /** {@inheritDoc} */ @Override public void handle(final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { if (request.getParameterMap().containsKey("sessionId")) { String sessionId = request.getParameterMap().get("sessionId")[0]; if (this.webServer.sessionWebModelMap.containsKey(sessionId)) { if (this.webServer.sessionWebModelMap.get(sessionId).isKilled()) { return; } this.webServer.sessionWebModelMap.get(sessionId).handle(target, baseRequest, request, response); } else if (this.webServer.sessionModelMap.containsKey(sessionId)) { OTSModelInterface model = this.webServer.sessionModelMap.get(sessionId); String answer = "ok"; if (request.getParameter("message") != null) { String message = request.getParameter("message"); String[] parts = message.split("\\|"); String command = parts[0]; switch (command) { case "getTitle": { answer = "" + model.getShortName() + ""; break; } case "getParameterMap": { answer = makeParameterMap(model); break; } case "setParameters": { answer = setParameters(model, message); break; } default: { System.err.println("Got unknown message from client: " + command); answer = "" + request.getParameter("message") + ""; break; } } } response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.setContentLength(answer.length()); response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write(answer); response.flushBuffer(); baseRequest.setHandled(true); } } } /** * Make the parameter set that can be interpreted by the parameters.html page. * @param model the model with parameters * @return an XML string with the parameters */ private String makeParameterMap(final OTSModelInterface model) { StringBuffer answer = new StringBuffer(); answer.append("\n"); InputParameterMap inputParameterMap = model.getInputParameterMap(); for (InputParameter tab : inputParameterMap.getSortedSet()) { if (!(tab instanceof InputParameterMap)) { System.err.println("Input parameter " + tab.getShortName() + " cannot be displayed in a tab"); } else { answer.append("" + tab.getDescription() + "\n"); InputParameterMap tabbedMap = (InputParameterMap) tab; for (InputParameter parameter : tabbedMap.getSortedSet()) { addParameterField(answer, parameter); } } } answer.append("\n"); return answer.toString(); } /** * Add the right type of field for this parameter to the string buffer. * @param answer StringBuffer; the buffer to add the XML-info for the parameter * @param parameter InputParameter<?,?>; the input parameter to display */ public void addParameterField(final StringBuffer answer, final InputParameter parameter) { if (parameter instanceof InputParameterDouble) { InputParameterDouble pd = (InputParameterDouble) parameter; answer.append("" + pd.getValue() + "\n"); } else if (parameter instanceof InputParameterFloat) { InputParameterFloat pf = (InputParameterFloat) parameter; answer.append("" + pf.getValue() + "\n"); } else if (parameter instanceof InputParameterBoolean) { InputParameterBoolean pb = (InputParameterBoolean) parameter; answer.append("" + pb.getValue() + "\n"); } else if (parameter instanceof InputParameterLong) { InputParameterLong pl = (InputParameterLong) parameter; answer.append("" + pl.getValue() + "\n"); } else if (parameter instanceof InputParameterInteger) { InputParameterInteger pi = (InputParameterInteger) parameter; answer.append("" + pi.getValue() + "\n"); } else if (parameter instanceof InputParameterString) { InputParameterString ps = (InputParameterString) parameter; answer.append("" + ps.getValue() + "\n"); } else if (parameter instanceof InputParameterDoubleScalar) { InputParameterDoubleScalar pds = (InputParameterDoubleScalar) parameter; String val = getValueInUnit(pds); List units = getUnits(pds); answer.append("" + val + "\n"); for (String unit : units) { Unit unitValue = pds.getUnitParameter().getOptions().get(unit); if (unitValue.equals(pds.getUnitParameter().getValue())) answer.append("" + unit + "\n"); else answer.append("" + unit + "\n"); } answer.append("\n"); } else if (parameter instanceof InputParameterFloatScalar) { InputParameterFloatScalar pds = (InputParameterFloatScalar) parameter; String val = getValueInUnit(pds); List units = getUnits(pds); answer.append("" + val + "\n"); for (String unit : units) { Unit unitValue = pds.getUnitParameter().getOptions().get(unit); if (unitValue.equals(pds.getUnitParameter().getValue())) answer.append("" + unit + "\n"); else answer.append("" + unit + "\n"); } answer.append("\n"); } else if (parameter instanceof InputParameterSelectionList) { // TODO InputParameterSelectionList } else if (parameter instanceof InputParameterDistDiscreteSelection) { // TODO InputParameterSelectionList } else if (parameter instanceof InputParameterDistContinuousSelection) { // TODO InputParameterDistContinuousSelection } else if (parameter instanceof InputParameterSelectionMap) { // TODO InputParameterSelectionMap } } /** * @param the unit * @param the scalar type * @param parameter double scalar input parameter * @return default value in the unit */ private , T extends AbstractDoubleScalar> String getValueInUnit(final InputParameterDoubleScalar parameter) { return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit()); } /** * @param the unit * @param the scalar type * @param parameter double scalar input parameter * @return abbreviations for the units */ private , T extends AbstractDoubleScalar> List getUnits(final InputParameterDoubleScalar parameter) { List unitList = new ArrayList<>(); for (String option : parameter.getUnitParameter().getOptions().keySet()) { unitList.add(option.toString()); } return unitList; } /** * @param the unit * @param the scalar type * @param parameter double scalar input parameter * @return default value in the unit */ private , T extends AbstractFloatScalar> String getValueInUnit(final InputParameterFloatScalar parameter) { return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit()); } /** * @param the unit * @param the scalar type * @param parameter double scalar input parameter * @return abbreviations for the units */ private , T extends AbstractFloatScalar> List getUnits(final InputParameterFloatScalar parameter) { List unitList = new ArrayList<>(); for (String option : parameter.getUnitParameter().getOptions().keySet()) { unitList.add(option.toString()); } return unitList; } /** * Make the parameter set that can be interpreted by the parameters.html page. * @param model the model with parameters * @param message the key-value pairs of the set parameters * @return the errors if they are detected. If none, errors is set to "OK" */ private String setParameters(final OTSModelInterface model, final String message) { String errors = "OK"; InputParameterMap inputParameters = model.getInputParameterMap(); String[] parts = message.split("\\|"); Map unitMap = new LinkedHashMap<>(); for (int i = 1; i < parts.length - 3; i += 3) { String id = parts[i].trim().replaceFirst("model.", ""); String type = parts[i + 1].trim(); String val = parts[i + 2].trim(); if (type.equals("UNIT")) { unitMap.put(id, val); } } for (int i = 1; i < parts.length - 3; i += 3) { String id = parts[i].trim().replaceFirst("model.", ""); String type = parts[i + 1].trim(); String val = parts[i + 2].trim(); try { if (type.equals("DOUBLE")) { InputParameterDouble param = (InputParameterDouble) inputParameters.get(id); param.setDoubleValue(Double.valueOf(val)); } else if (type.equals("FLOAT")) { InputParameterFloat param = (InputParameterFloat) inputParameters.get(id); param.setFloatValue(Float.valueOf(val)); } else if (type.equals("BOOLEAN")) { InputParameterBoolean param = (InputParameterBoolean) inputParameters.get(id); param.setBooleanValue(val.toUpperCase().startsWith("T")); } else if (type.equals("LONG")) { InputParameterLong param = (InputParameterLong) inputParameters.get(id); param.setLongValue(Long.valueOf(val)); } else if (type.equals("INTEGER")) { InputParameterInteger param = (InputParameterInteger) inputParameters.get(id); param.setIntValue(Integer.valueOf(val)); } else if (type.equals("STRING")) { InputParameterString param = (InputParameterString) inputParameters.get(id); param.setStringValue(val); } if (type.equals("DOUBLESCALAR")) { InputParameterDoubleScalar param = (InputParameterDoubleScalar) inputParameters.get(id); param.getDoubleParameter().setDoubleValue(Double.valueOf(val)); String unitString = unitMap.get(id); if (unitString == null) System.err.println("Could not find unit for DoubleScalar parameter with id=" + id); else { Unit unit = param.getUnitParameter().getOptions().get(unitString); if (unit == null) System.err.println( "Could not find unit " + unitString + " for DoubleScalar parameter with id=" + id); else { param.getUnitParameter().setObjectValue(unit); param.setCalculatedValue(); // it will retrieve the set double value and unit } } } } catch (Exception exception) { if (errors.equals("OK")) errors = "ERRORS IN INPUT VALUES:\n"; errors += "Field " + id + ": " + exception.getMessage() + "\n"; } } return errors; } } }