package nl.tudelft.simulation.dsol.jetty.sse;
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.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.draw.factory.DefaultAnimationFactory;
import org.opentrafficsim.web.test.CircularRoadModel;
import org.opentrafficsim.web.test.TJunctionModel;
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;
/**
* DSOLWebServer.java.
*
* Copyright (c) 2003-2022 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 class TestDemoServer
{
/** the map of sessionIds to OTSModelInterface that handles the animation and updates for the started model. */
final Map sessionModelMap = new LinkedHashMap<>();
/** the map of sessionIds to OTSWebModel that handles the animation and updates for the started model. */
final Map sessionWebModelMap = new LinkedHashMap<>();
/**
* Run a SuperDemo OTS Web server.
* @param args String[]; not used
* @throws Exception o Jetty error
*/
public static void main(final String[] args) throws Exception
{
new TestDemoServer();
}
/**
* @throws Exception in case jetty crashes
*/
public TestDemoServer() throws Exception
{
new ServerThread().start();
}
/** Handle in separate thread to avoid 'lock' of the main application. */
class ServerThread extends Thread
{
@Override
public void run()
{
Server server = new Server(8080);
ResourceHandler resourceHandler = new MyResourceHandler();
// root folder; to work in Eclipse, as an external jar, and in an embedded jar
URL homeFolder = URLResource.getResource("/home");
String webRoot = homeFolder.toExternalForm();
System.out.println("webRoot is " + webRoot);
resourceHandler.setDirectoriesListed(true);
resourceHandler.setWelcomeFiles(new String[] {"testdemo.html"});
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(TestDemoServer.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);
return super.getResource(path);
}
/** {@inheritDoc} */
@Override
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"))
{
String modelId = request.getParameterMap().get("model")[0];
String sessionId = request.getParameterMap().get("sessionId")[0];
if (!TestDemoServer.this.sessionModelMap.containsKey(sessionId))
{
System.out.println("parameters: " + modelId);
OTSAnimator simulator = new OTSAnimator("TestDemoServer");
simulator.setAnimation(false);
OTSModelInterface model = null;
if (modelId.toLowerCase().contains("circularroad"))
model = new CircularRoadModel(simulator);
else if (modelId.toLowerCase().contains("tjunction"))
model = new TJunctionModel(simulator);
if (model != null)
TestDemoServer.this.sessionModelMap.put(sessionId, model);
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 (TestDemoServer.this.sessionModelMap.containsKey(sessionId)
&& !TestDemoServer.this.sessionWebModelMap.containsKey(sessionId))
{
System.out.println("startModel: " + modelId);
OTSModelInterface model = TestDemoServer.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);
TestDemoServer.this.sessionWebModelMap.put(sessionId, webModel);
DefaultAnimationFactory.animateNetwork(model.getNetwork(), model.getNetwork().getSimulator(),
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-2022 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 TestDemoServer webServer;
/**
* Create the handler for Servlet requests.
* @param webServer DSOLWebServer; web server for callback of actions
*/
public XHRHandler(final TestDemoServer 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))
{
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(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 parameter double scalar input parameter
* @return default value in the unit
*/
private ,
T extends AbstractDoubleScalar> String getValueInUnit(InputParameterDoubleScalar parameter)
{
return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit());
}
/**
* @param parameter double scalar input parameter
* @return abbreviations for the units
*/
private ,
T extends AbstractDoubleScalar> List getUnits(InputParameterDoubleScalar parameter)
{
List unitList = new ArrayList<>();
for (String option : parameter.getUnitParameter().getOptions().keySet())
{
unitList.add(option.toString());
}
return unitList;
}
/**
* @param parameter double scalar input parameter
* @return default value in the unit
*/
private ,
T extends AbstractFloatScalar> String getValueInUnit(InputParameterFloatScalar parameter)
{
return "" + parameter.getDefaultTypedValue().getInUnit(parameter.getDefaultTypedValue().getDisplayUnit());
}
/**
* @param parameter double scalar input parameter
* @return abbreviations for the units
*/
private ,
T extends AbstractFloatScalar> List getUnits(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(OTSModelInterface model, 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 Doublevalie parameter with id=" + id);
else
{
Unit> unit = param.getUnitParameter().getOptions().get(unitString);
if (unit == null)
System.err.println(
"Could not find unit " + unitString + " for Doublevalie 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;
}
}
}