001package jmri.server.json; 002 003import static jmri.server.json.JSON.GOODBYE; 004import static jmri.server.json.JSON.JSON; 005import static jmri.server.json.JSON.JSON_PROTOCOL_VERSION; 006import static jmri.server.json.JSON.TYPE; 007import static jmri.server.json.JSON.ZEROCONF_SERVICE_TYPE; 008 009import com.fasterxml.jackson.core.JsonParser.Feature; 010import com.fasterxml.jackson.databind.ObjectMapper; 011import com.fasterxml.jackson.databind.ObjectReader; 012import java.io.DataInputStream; 013import java.io.DataOutputStream; 014import java.io.IOException; 015import java.io.InputStream; 016import java.util.HashMap; 017import java.util.NoSuchElementException; 018import java.util.concurrent.TimeUnit; 019import jmri.InstanceManager; 020import jmri.InstanceManagerAutoDefault; 021import jmri.implementation.AbstractShutDownTask; 022import jmri.jmris.JmriServer; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026/** 027 * This is an implementation of a JSON server for JMRI. See 028 * {@link jmri.server.json} for more details. 029 * 030 * @author Paul Bender Copyright (C) 2010 031 * @author Randall Wood Copyright (C) 2016 032 */ 033public class JsonServer extends JmriServer implements InstanceManagerAutoDefault { 034 035 private static final Logger log = LoggerFactory.getLogger(JsonServer.class); 036 private ObjectMapper mapper; 037 038 /** 039 * Create a new server using the default port. 040 */ 041 public JsonServer() { 042 this(InstanceManager.getDefault(JsonServerPreferences.class).getPort(), InstanceManager.getDefault(JsonServerPreferences.class).getHeartbeatInterval()); 043 } 044 045 /** 046 * Create a new server. 047 * 048 * @param port the port to listen on 049 * @param timeout the timeout before closing unresponsive connections 050 */ 051 public JsonServer(int port, int timeout) { 052 super(port, timeout); 053 this.mapper = new ObjectMapper().configure(Feature.AUTO_CLOSE_SOURCE, false); 054 shutDownTask = new AbstractShutDownTask("Stop JSON Server") { // NOI18N 055 @Override 056 public void run() { 057 try { 058 JsonServer.this.stop(); 059 } catch (Exception ex) { 060 log.warn("Exception shutting down JSON Server", ex); 061 } 062 } 063 }; 064 } 065 066 @Override 067 public void start() { 068 log.info("Starting JSON Server on port {}", this.portNo); 069 super.start(); 070 } 071 072 @Override 073 public void stop() { 074 log.info("Stopping JSON Server."); 075 super.stop(); 076 } 077 078 @Override 079 protected void advertise() { 080 HashMap<String, String> properties = new HashMap<>(); 081 properties.put(JSON, JSON_PROTOCOL_VERSION); 082 this.advertise(ZEROCONF_SERVICE_TYPE, properties); 083 } 084 085 // Handle communication to a client through inStream and outStream 086 @Override 087 public void handleClient(DataInputStream inStream, DataOutputStream outStream) throws IOException { 088 ObjectReader reader = this.mapper.reader(); 089 JsonClientHandler handler = new JsonClientHandler(new JsonConnection(outStream)); 090 091 // Start by sending a welcome message 092 handler.onMessage(JsonClientHandler.HELLO_MSG); 093 094 boolean handling = true; 095 while (handling) { 096 try { 097 handler.onMessage(reader.readTree((InputStream) inStream)); 098 // Read the command from the client 099 } catch (IOException e) { 100 // attempt to close the connection and throw the exception 101 handler.onClose(); 102 throw e; 103 } catch (NoSuchElementException nse) { 104 // we get an NSE when we are finished with this client 105 // so break out of the loop 106 handling = false; 107 } 108 } 109 handler.onClose(); 110 } 111 112 @Override 113 public void stopClient(DataInputStream inStream, DataOutputStream outStream) throws IOException { 114 outStream.writeBytes(this.mapper.writeValueAsString(this.mapper.createObjectNode().put(TYPE, GOODBYE))); 115 try { 116 // without this delay, the output stream could be closed before the 117 // preparing to disconnect message is sent 118 TimeUnit.MILLISECONDS.sleep(100); 119 } catch (InterruptedException ex) { 120 // log for debugging only, since we are most likely shutting down the 121 // server or the program entirely at this point, so it doesn't matter 122 log.debug("Wait to send clean shutdown message interrupted."); 123 Thread.currentThread().interrupt(); 124 } 125 } 126}