001package jmri.server.json; 002 003import static jmri.server.json.JSON.DATA; 004import static jmri.server.json.JSON.DELETE; 005import static jmri.server.json.JSON.GET; 006import static jmri.server.json.JSON.NAME; 007import static jmri.server.json.JSON.POST; 008import static jmri.server.json.JSON.PUT; 009 010import com.fasterxml.jackson.databind.JsonNode; 011import java.beans.PropertyChangeEvent; 012import java.beans.PropertyChangeListener; 013import java.io.IOException; 014import java.util.HashMap; 015import java.util.HashSet; 016import jmri.InstanceManager; 017import jmri.JmriException; 018import jmri.NamedBean; 019import jmri.ReporterManager; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Abstract implementation of JsonSocketService with specific support for 026 * {@link jmri.NamedBean} objects. Note that services requiring support for 027 * multiple classes of NamedBean cannot extend this class. 028 * 029 * @author Randall Wood (C) 2019 030 * @param <T> the NamedBean class supported by this service 031 * @param <H> the supporting JsonNamedBeanHttpService class 032 */ 033public class JsonNamedBeanSocketService<T extends NamedBean, H extends JsonNamedBeanHttpService<T>> extends JsonSocketService<H> { 034 035 protected final HashMap<T, NamedBeanListener> beanListeners = new HashMap<>(); 036 protected final ManagerListener managerListener = new ManagerListener(); 037 private static final Logger log = LoggerFactory.getLogger(JsonNamedBeanSocketService.class); 038 039 public JsonNamedBeanSocketService(JsonConnection connection, H service) { 040 super(connection, service); 041 service.getManager().addPropertyChangeListener(managerListener); 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 T bean = null; 049 // protect against a request made with a user name instead of a system name 050 if (!request.method.equals(PUT)) { 051 bean = service.getManager().getBySystemName(name); 052 if (bean == null) { 053 bean = service.getManager().getByUserName(name); 054 if (bean != null) { 055 // set to warn so users can provide specific feedback to developers of JSON clients 056 log.warn("{} request for {} made with user name \"{}\"; should use system name", request.method, type, name); 057 name = bean.getSystemName(); 058 } // service will throw appropriate error to client later if bean is still null 059 } 060 } 061 switch (request.method) { 062 case DELETE: 063 service.doDelete(type, name, data, request); 064 break; 065 case POST: 066 connection.sendMessage(service.doPost(type, name, data, request), request.id); 067 break; 068 case PUT: 069 JsonNode message = service.doPut(type, name, data, request); 070 connection.sendMessage(message, request.id); 071 bean = service.getManager().getBySystemName(message.path(DATA).path(NAME).asText()); 072 break; 073 case GET: 074 default: 075 connection.sendMessage(service.doGet(type, name, data, request), request.id); 076 } 077 if (!beanListeners.containsKey(bean)) { 078 addListenerToBean(bean); 079 } 080 } 081 082 @Override 083 public void onList(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException { 084 connection.sendMessage(service.doGetList(type, data, request), request.id); 085 } 086 087 @Override 088 public void onClose() { 089 beanListeners.values().stream().forEach(listener -> listener.bean.removePropertyChangeListener(listener)); 090 beanListeners.clear(); 091 service.getManager().removePropertyChangeListener(managerListener); 092 } 093 094 protected void addListenerToBean(String name) { 095 addListenerToBean(service.getManager().getBySystemName(name)); 096 } 097 098 protected void addListenerToBean(T bean) { 099 if (bean != null) { 100 NamedBeanListener listener = new NamedBeanListener(bean); 101 bean.addPropertyChangeListener(listener); 102 this.beanListeners.put(bean, listener); 103 } 104 } 105 106 protected void removeListenersFromRemovedBeans() { 107 for (T bean : new HashSet<>(beanListeners.keySet())) { 108 if (service.getManager().getBySystemName(bean.getSystemName()) == null) { 109 beanListeners.remove(bean); 110 } 111 } 112 } 113 114 protected class NamedBeanListener implements PropertyChangeListener { 115 116 public final T bean; 117 118 public NamedBeanListener(T bean) { 119 this.bean = bean; 120 } 121 122 @Override 123 public void propertyChange(PropertyChangeEvent evt) { 124 try { 125 connection.sendMessage(service.doGet(this.bean, this.bean.getSystemName(), service.getType(), new JsonRequest(getLocale(), getVersion(), JSON.GET, 0)), 0); 126 } catch ( 127 IOException | 128 JsonException ex) { 129 // if we get an error, unregister as listener 130 this.bean.removePropertyChangeListener(this); 131 beanListeners.remove(this.bean); 132 } 133 } 134 } 135 136 protected class ManagerListener implements PropertyChangeListener { 137 138 @Override 139 public void propertyChange(PropertyChangeEvent evt) { 140 try { 141 handleChange(evt); 142 } catch (IOException ex) { 143 // if we get an error, unregister as listener 144 log.debug("deregistering reportersListener due to IOException"); 145 InstanceManager.getDefault(ReporterManager.class).removePropertyChangeListener(this); 146 } 147 } 148 149 private void handleChange(PropertyChangeEvent evt) throws IOException { 150 try { 151 // send the new list 152 connection.sendMessage(service.doGetList(service.getType(), 153 service.getObjectMapper().createObjectNode(), new JsonRequest(getLocale(), getVersion(), JSON.GET, 0)), 0); 154 //child added or removed, reset listeners 155 if (evt.getPropertyName().equals("length")) { // NOI18N 156 removeListenersFromRemovedBeans(); 157 } 158 } catch (JsonException ex) { 159 log.warn("json error sending {}: {}", service.getType(), ex.getJsonMessage()); 160 connection.sendMessage(ex.getJsonMessage(), 0); 161 } 162 } 163 } 164}