package de.srsoftware.web4rail; import java.awt.Desktop; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.util.HashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; import de.keawe.localconfig.Configuration; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.actions.ActionList; import de.srsoftware.web4rail.conditions.Condition; import de.srsoftware.web4rail.moving.Car; import de.srsoftware.web4rail.moving.Locomotive; import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tiles.Contact; /** * Entry point class for the Web4Rail application * * @author Stephan Richter, SRSoftware * */ public class Application extends BaseClass{ private static final Logger LOG = LoggerFactory.getLogger(Application.class); private static final String START_TRAINS = "--start-trains"; /** * entry point for the application:
* creates a http server, loads a plan and directs a browser to the respective page * @param args * @throws IOException * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException * @throws NoSuchMethodException * @throws SecurityException */ public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Configuration config = new Configuration(Configuration.dir("Web4Rail")+"/app.config"); LOG.debug("config: {}",config); InetSocketAddress addr = new InetSocketAddress(config.getOrAdd(PORT, 8080)); HttpServer server = HttpServer.create(addr, 0); server.createContext("/plan", client -> sendPlan(client)); server.createContext("/css" , client -> sendFile(client)); server.createContext("/js" , client -> sendFile(client)); server.createContext("/stream", client -> stream(client)); server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool()); server.start(); try { Plan.load(Plan.DEFAULT_NAME); } catch (FileNotFoundException|NoSuchFileException e) { plan = new Plan(); } for (String arg : args) { switch (arg) { case START_TRAINS: Train.startAll(); break; } } Desktop.getDesktop().browse(URI.create("http://"+InetAddress.getLocalHost().getHostName()+":"+config.getInt(PORT)+"/plan")); } /** * handles request from clients by delegating them to respective classes * @param params * @return * @throws IOException * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException * @throws NoSuchMethodException * @throws SecurityException */ private static Object handle(HashMap params) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { String realm = params.get(REALM); if (realm == null) throw new NullPointerException(REALM+" should not be null!"); String action = params.get(ACTION); if (action == null) throw new NullPointerException(ACTION+" should not be null!"); switch (realm) { case REALM_ACTIONS: return ActionList.process(params,plan); case REALM_CAR: return Car.action(params,plan); case REALM_CONTACT: return Contact.process(params); case REALM_CONDITION: return Condition.action(params,plan); case REALM_CU: return plan.controlUnit().process(params); case REALM_LOCO: return Locomotive.action(params,plan); case REALM_PLAN: return plan.action(params); case REALM_ROUTE: return Route.action(params); case REALM_TRAIN: return Train.action(params,plan); } return t("Unknown realm: {}",params.get(REALM)); } /** * creates a map from url-encoded data * @param data * @return */ private static HashMap inflate(String data) { //LOG.debug("inflate({})",data); HashMap params = new HashMap(); if (data == null || data.trim().isEmpty()) return params; String[] parts = data.split("&"); for (String part : parts) { String[] entry = part.split("=", 2); params.put(URLDecoder.decode(entry[0],UTF8),URLDecoder.decode(entry[1], UTF8)); } return params; } /** * creates a map from url-encoded data * @param data * @return */ private static HashMap inflate(byte[] data) { return inflate(new String(data,UTF8)); } private static String mimeOf(File file) throws IOException { String[] parts = file.toString().split("\\."); switch (parts[parts.length-1].toLowerCase()) { case "js": return "application/javascript"; case "css": return "text/css"; } LOG.warn("No conten type stored for {}!",file); return Files.probeContentType(file.toPath()); } /** * sends a response generated from the application to a given client * @param client * @param response * @throws IOException */ private static void send(HttpExchange client, Object response) throws IOException { byte[] html; if (response instanceof Page) { html = ((Page)response).html().toString().getBytes(UTF8); client.getResponseHeaders().add("content-type", "text/html"); } else { html = (response == null ? "" : response.toString()).getBytes(UTF8); client.getResponseHeaders().add("content-type", "text/plain"); } client.sendResponseHeaders(200, html.length); OutputStream os = client.getResponseBody(); os.write(html); os.close(); } /** * sends an error to a given client * @param client * @param code * @param msg * @throws IOException */ private static void sendError(HttpExchange client, int code, String msg) throws IOException { client.sendResponseHeaders(code, msg.length()); LOG.error(msg); OutputStream out = client.getResponseBody(); out.write(msg.getBytes(UTF8)); out.close(); } /** * sends a requested file to the given client * @param client * @throws IOException */ private static void sendFile(HttpExchange client) throws IOException { URI uri = client.getRequestURI(); File file = new File(System.getProperty("user.dir")+"/resources"+uri); LOG.debug("requesting file: {}",file); if (file.exists()) { client.getResponseHeaders().add("Content-Type", mimeOf(file)); client.sendResponseHeaders(200, file.length()); OutputStream out = client.getResponseBody(); FileInputStream in = new FileInputStream(file); in.transferTo(out); out.flush(); in.close(); out.close(); return; } sendError(client,404,t("Could not find \"{}\"",uri)); } /** * sends a response to a given client * @param client * @throws IOException */ private static void sendPlan(HttpExchange client) throws IOException { try { HashMap params = inflate(client.getRequestBody().readAllBytes()); LOG.debug("sendPlan({})",params); if (params.isEmpty()) { send(client,plan.html()); return; } Object response = handle(params); if (isSet(response)) LOG.debug("response ({}): {}",response.getClass().getSimpleName(),response); send(client,response instanceof String || response instanceof Tag ? response : plan.html()); } catch (Exception e) { LOG.error("Error during sendPlan(): {}",e); send(client,new Page().append(e.getMessage())); } } /** * establishes an event stream connection between the application and a given client * @param client * @throws IOException */ private static void stream(HttpExchange client) throws IOException { client.getResponseHeaders().set("content-type", "text/event-stream"); client.sendResponseHeaders(200, 0); OutputStreamWriter sseWriter = new OutputStreamWriter(client.getResponseBody()); plan.addClient(sseWriter); } }