001package jmri.server.json.util; 002 003import com.fasterxml.jackson.databind.JsonNode; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import java.io.IOException; 007import jmri.InstanceManager; 008import jmri.JmriException; 009import jmri.server.json.JSON; 010import jmri.server.json.JsonConnection; 011import jmri.server.json.JsonException; 012import jmri.server.json.JsonRequest; 013import jmri.server.json.JsonSocketService; 014import jmri.web.server.WebServerPreferences; 015import org.slf4j.Logger; 016import org.slf4j.LoggerFactory; 017 018/** 019 * 020 * @author Randall Wood 021 */ 022public class JsonUtilSocketService extends JsonSocketService<JsonUtilHttpService> { 023 024 private PropertyChangeListener rrNameListener; 025 private static final Logger log = LoggerFactory.getLogger(JsonUtilSocketService.class); 026 027 public JsonUtilSocketService(JsonConnection connection) { 028 super(connection, new JsonUtilHttpService(connection.getObjectMapper())); 029 } 030 031 /** 032 * Package protected method for unit testing that allows a test HTTP service 033 * to be used. 034 * 035 * @param connection the connection to use 036 * @param service the supporting HTTP service 037 */ 038 JsonUtilSocketService(JsonConnection connection, JsonUtilHttpService service) { 039 super(connection, service); 040 } 041 042 @Override 043 public void onMessage(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException { 044 String name = data.path(JSON.NAME).asText(); 045 switch (type) { 046 case JSON.LOCALE: 047 // do nothing - we only want to prevent an error at this point 048 break; 049 case JSON.PING: 050 this.connection.sendMessage(this.connection.getObjectMapper().createObjectNode().put(JSON.TYPE, JSON.PONG), request.id); 051 break; 052 case JSON.GOODBYE: 053 this.connection.sendMessage(this.connection.getObjectMapper().createObjectNode().put(JSON.TYPE, JSON.GOODBYE), request.id); 054 break; 055 case JSON.RAILROAD: 056 this.onRailroadNameMessage(type, data, request); 057 break; 058 case JSON.SESSION_LOGIN: 059 this.onSessionLoginMessage(type, data, request); 060 break; 061 case JSON.SESSION_LOGOUT: 062 this.onSessionLogoutMessage(type, data, request); 063 break; 064 default: 065 this.connection.sendMessage(this.service.doPost(type, name, data, request), request.id); 066 break; 067 } 068 } 069 070 /** 071 * Process an incoming POST login message 072 * 073 * Extract username, password 074 * Check against authentication backend 075 * 076 * On success, send a response containing a valid token 077 * On Failure, send an exception message 078 * 079 * @param type Message type 080 * @param data JSON payload 081 * @param request The original request as received 082 * @throws IOException 083 * @throws JmriException 084 * @throws JsonException 085 */ 086 private void onSessionLoginMessage(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException { 087 String username = data.path("username").asText(); 088 if (request.method.equals(JSON.POST)) { 089 log.debug("Processing login {} from socket service", username); 090 this.connection.sendMessage(this.service.doPost(type, username, data, request), request.id); 091 } 092// this.connection.sendMessage(this.service.doGet(type, name, data, request), request.id); 093 } 094 095 /** 096 * Process an incoming POST logout message 097 * 098 * Extract credential 099 * Check against authentication backend 100 * 101 * On success, invalidate token. Send invalidated token. 102 * On Failure, send an exception message. 103 * 104 * @param type 105 * @param data 106 * @param request 107 * @throws IOException 108 * @throws JmriException 109 * @throws JsonException 110 */ 111 private void onSessionLogoutMessage(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException { 112 if (request.method.equals(JSON.POST)) { 113 // JMRI developers assess the risk of logging the token further down the stack as low 114 this.connection.sendMessage(this.service.doPost(type, data.path(JSON.USERNAME).asText(), data, request), request.id); 115 } 116 } 117 118 private void onRailroadNameMessage(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException { 119 String name = data.path(JSON.NAME).asText(); 120 // Follow up handling a POST (change railroad name command) with the same answer as a GET (ask) 121 if (request.method.equals(JSON.POST)) { 122 log.debug("Processing change railroad name to {} from socket service", name); 123 this.connection.sendMessage(this.service.doPost(type, name, data, request), request.id); 124 } 125 this.connection.sendMessage(this.service.doGet(type, name, data, request), request.id); 126 this.rrNameListener = (PropertyChangeEvent evt) -> { 127 try { 128 this.handleRailroadChange(); 129 } catch (IOException ex) { 130 InstanceManager.getDefault(WebServerPreferences.class).removePropertyChangeListener(this.rrNameListener); 131 } 132 }; 133 InstanceManager.getOptionalDefault(WebServerPreferences.class).ifPresent(preferences -> preferences.addPropertyChangeListener(this.rrNameListener)); 134 } 135 136 @Override 137 public void onList(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException { 138 this.connection.sendMessage(this.service.doGetList(type, data, request), request.id); 139 } 140 141 @Override 142 public void onClose() { 143 InstanceManager.getOptionalDefault(WebServerPreferences.class).ifPresent(preferences -> preferences.removePropertyChangeListener(this.rrNameListener)); 144 } 145 146 private void handleRailroadChange() throws IOException { 147 try { 148 connection.sendMessage(service.doGet(JSON.RAILROAD, null, connection.getObjectMapper().createObjectNode(), new JsonRequest(this.connection.getLocale(), JSON.V5, JSON.GET, 0)), 0); 149 } catch (JsonException ex) { 150 this.connection.sendMessage(ex.getJsonMessage(), 0); 151 } 152 } 153}