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}