001package jmri.server.json.signalhead;
002
003import static jmri.server.json.JSON.GET;
004import static jmri.server.json.JSON.NAME;
005import static jmri.server.json.JSON.PUT;
006import static jmri.server.json.signalhead.JsonSignalHead.SIGNAL_HEAD;
007import static jmri.server.json.signalhead.JsonSignalHead.SIGNAL_HEADS;
008
009import com.fasterxml.jackson.databind.JsonNode;
010import java.beans.PropertyChangeEvent;
011import java.beans.PropertyChangeListener;
012import java.io.IOException;
013import java.util.HashMap;
014import java.util.HashSet;
015import jmri.InstanceManager;
016import jmri.JmriException;
017import jmri.SignalHead;
018import jmri.SignalHeadManager;
019import jmri.server.json.JsonConnection;
020import jmri.server.json.JsonException;
021import jmri.server.json.JsonRequest;
022import jmri.server.json.JsonSocketService;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * @author Randall Wood (C) 2016
028 */
029public class JsonSignalHeadSocketService extends JsonSocketService<JsonSignalHeadHttpService> {
030
031    private final HashMap<String, SignalHeadListener> beanListeners = new HashMap<>();
032    private SignalHeadsListener managerListener = null;
033    private static final Logger log = LoggerFactory.getLogger(JsonSignalHeadSocketService.class);
034
035    public JsonSignalHeadSocketService(JsonConnection connection) {
036        this(connection, new JsonSignalHeadHttpService(connection.getObjectMapper()));
037    }
038
039    // package protected
040    public JsonSignalHeadSocketService(JsonConnection connection, JsonSignalHeadHttpService service) {
041        super(connection, service);
042    }
043
044    @Override
045    public void onMessage(String type, JsonNode data, JsonRequest request)
046            throws IOException, JmriException, JsonException {
047        String name = data.path(NAME).asText();
048        if (request.method.equals(PUT)) {
049            connection.sendMessage(service.doPut(type, name, data, request), request.id);
050        } else {
051            connection.sendMessage(service.doPost(type, name, data, request), request.id);
052        }
053        if (!beanListeners.containsKey(name)) {
054            SignalHead signalHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(name);
055            if (signalHead != null) {
056                SignalHeadListener listener = new SignalHeadListener(signalHead);
057                signalHead.addPropertyChangeListener(listener);
058                beanListeners.put(name, listener);
059            }
060        }
061    }
062
063    @Override
064    public void onList(String type, JsonNode data, JsonRequest request)
065            throws IOException, JmriException, JsonException {
066        connection.sendMessage(service.doGetList(type, data, request), request.id);
067        log.debug("adding SignalHeadsListener");
068        if (managerListener == null) {
069            managerListener = new SignalHeadsListener();
070            InstanceManager.getDefault(SignalHeadManager.class).addPropertyChangeListener(managerListener);
071        }
072    }
073
074    @Override
075    public void onClose() {
076        beanListeners.values().stream()
077                .forEach(listener -> listener.signalHead.removePropertyChangeListener(listener));
078        beanListeners.clear();
079        InstanceManager.getDefault(SignalHeadManager.class).removePropertyChangeListener(managerListener);
080        managerListener = null;
081    }
082
083    private class SignalHeadListener implements PropertyChangeListener {
084
085        protected final SignalHead signalHead;
086
087        public SignalHeadListener(SignalHead signalHead) {
088            this.signalHead = signalHead;
089        }
090
091        @Override
092        public void propertyChange(PropertyChangeEvent e) {
093            try {
094                try {
095                    connection.sendMessage(service.doGet(SIGNAL_HEAD, signalHead.getSystemName(),
096                            connection.getObjectMapper().createObjectNode(), new JsonRequest(getLocale(), getVersion(), GET, 0)), 0);
097                } catch (JsonException ex) {
098                    connection.sendMessage(ex.getJsonMessage(), 0);
099                }
100            } catch (IOException ie) {
101                // if we get an error, de-register
102                signalHead.removePropertyChangeListener(this);
103                beanListeners.remove(signalHead.getSystemName());
104            }
105        }
106    }
107
108    private class SignalHeadsListener implements PropertyChangeListener {
109        @Override
110        public void propertyChange(PropertyChangeEvent evt) {
111            log.debug("in SignalHeadsListener for '{}' ('{}' => '{}')", evt.getPropertyName(), evt.getOldValue(),
112                    evt.getNewValue());
113
114            try {
115                try {
116                    // send the new list
117                    connection.sendMessage(service.doGetList(SIGNAL_HEADS, service.getObjectMapper().createObjectNode(),
118                            new JsonRequest(getLocale(), getVersion(), GET, 0)), 0);
119                    // child added or removed, reset listeners
120                    if (evt.getPropertyName().equals("length")) { // NOI18N
121                        removeListenersFromRemovedBeans();
122                    }
123                } catch (JsonException ex) {
124                    log.warn("json error sending SignalHeads: {}", ex.getJsonMessage());
125                    connection.sendMessage(ex.getJsonMessage(), 0);
126                }
127            } catch (IOException ex) {
128                // do nothing; the listeners will be removed as the connection
129                // gets closed
130            }
131        }
132
133        private void removeListenersFromRemovedBeans() {
134            SignalHeadManager manager = InstanceManager.getDefault(SignalHeadManager.class);
135            for (String name : new HashSet<>(beanListeners.keySet())) {
136                if (manager.getBySystemName(name) == null) {
137                    beanListeners.remove(name);
138                }
139            }
140        }
141    }
142
143}