001package jmri.server.json.roster; 002 003import static jmri.server.json.JSON.ADD; 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; 009import static jmri.server.json.JSON.REMOVE; 010 011import com.fasterxml.jackson.databind.JsonNode; 012import com.fasterxml.jackson.databind.node.NullNode; 013import com.fasterxml.jackson.databind.node.ObjectNode; 014import java.beans.PropertyChangeEvent; 015import java.beans.PropertyChangeListener; 016import java.io.IOException; 017import javax.servlet.http.HttpServletResponse; 018import jmri.JmriException; 019import jmri.beans.PropertyChangeProvider; 020import jmri.jmrit.roster.Roster; 021import jmri.jmrit.roster.RosterEntry; 022import jmri.server.json.JsonConnection; 023import jmri.server.json.JsonException; 024import jmri.server.json.JsonRequest; 025import jmri.server.json.JsonSocketService; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * Listen for changes in the roster and notify subscribed clients of changes to 031 * the roster, including roster groups 032 * 033 * @author Randall Wood Copyright (C) 2014, 2016 034 */ 035public class JsonRosterSocketService extends JsonSocketService<JsonRosterHttpService> { 036 037 private static final Logger log = LoggerFactory.getLogger(JsonRosterSocketService.class); 038 private final JsonRosterListener rosterListener = new JsonRosterListener(); 039 private final JsonRosterEntryListener rosterEntryListener = new JsonRosterEntryListener(); 040 private final JsonRosterGroupsListener rosterGroupsListener = new JsonRosterGroupsListener(); 041 private boolean listening = false; 042 043 public JsonRosterSocketService(JsonConnection connection) { 044 super(connection, new JsonRosterHttpService(connection.getObjectMapper())); 045 } 046 047 public void listen() { 048 if (!listening) { 049 Roster.getDefault().addPropertyChangeListener(rosterListener); 050 Roster.getDefault().addPropertyChangeListener(rosterGroupsListener); 051 Roster.getDefault().getEntriesInGroup(Roster.ALLENTRIES).stream().forEach(re -> { 052 re.addPropertyChangeListener(rosterEntryListener); 053 re.addPropertyChangeListener(rosterGroupsListener); 054 }); 055 listening = true; 056 } 057 } 058 059 @Override 060 public void onMessage(String type, JsonNode data, JsonRequest request) 061 throws IOException, JmriException, JsonException { 062 switch (request.method) { 063 case DELETE: 064 throw new JsonException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 065 Bundle.getMessage(request.locale, "DeleteNotAllowed", type), request.id); 066 case POST: 067 if (JsonRoster.ROSTER_ENTRY.equals(type)) { 068 connection 069 .sendMessage(service.postRosterEntry(request.locale, data.path(NAME).asText(), data, request.id), request.id); 070 } else { 071 throw new JsonException(HttpServletResponse.SC_NOT_IMPLEMENTED, 072 Bundle.getMessage(request.locale, "MethodNotImplemented", request.method, type), request.id); 073 } 074 break; 075 case PUT: 076 throw new JsonException(HttpServletResponse.SC_NOT_IMPLEMENTED, 077 Bundle.getMessage(request.locale, "MethodNotImplemented", request.method, type), request.id); 078 case GET: 079 switch (type) { 080 case JsonRoster.ROSTER: 081 connection.sendMessage(service.getRoster(request.locale, data, request.id), request.id); 082 break; 083 case JsonRoster.ROSTER_ENTRY: 084 connection.sendMessage(service.getRosterEntry(request.locale, data.path(NAME).asText(), request.id), 085 request.id); 086 break; 087 case JsonRoster.ROSTER_GROUP: 088 connection.sendMessage(service.getRosterGroup(request.locale, data.path(NAME).asText(), request.id), 089 request.id); 090 break; 091 case JsonRoster.ROSTER_GROUPS: 092 connection.sendMessage(service.getRosterGroups(request), request.id); 093 break; 094 default: 095 throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 096 Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), request.id); 097 } 098 break; 099 default: 100 throw new JsonException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 101 Bundle.getMessage(request.locale, "UnknownMethod", request.method), request.id); 102 } 103 listen(); 104 } 105 106 @Override 107 public void onList(String type, JsonNode data, JsonRequest request) 108 throws IOException, JmriException, JsonException { 109 connection.sendMessage(service.doGetList(type, data, request), request.id); 110 listen(); 111 } 112 113 @Override 114 public void onClose() { 115 Roster.getDefault().removePropertyChangeListener(rosterListener); 116 Roster.getDefault().removePropertyChangeListener(rosterGroupsListener); 117 118 Roster.getDefault().getEntriesInGroup(Roster.ALLENTRIES).stream().forEach(re -> { 119 re.removePropertyChangeListener(rosterEntryListener); 120 re.removePropertyChangeListener(rosterGroupsListener); 121 }); 122 listening = false; 123 } 124 125 private class JsonRosterEntryListener implements PropertyChangeListener { 126 127 @Override 128 public void propertyChange(PropertyChangeEvent evt) { 129 try { 130 sendRosterUpdate(evt); 131 } catch (IOException ex) { 132 onClose(); 133 } 134 } 135 136 private void sendRosterUpdate(PropertyChangeEvent evt) throws IOException { 137 try { 138 if (evt.getPropertyName().equals(RosterEntry.ID)) { 139 // send old roster entry and new roster entry to client 140 // as roster changes 141 ObjectNode data = connection.getObjectMapper().createObjectNode(); 142 RosterEntry old = new RosterEntry((RosterEntry) evt.getSource(), (String) evt.getOldValue()); 143 data.set(ADD, service.getRosterEntry(connection.getLocale(), (RosterEntry) evt.getSource(), 0)); 144 data.set(REMOVE, service.getRosterEntry(connection.getLocale(), old, 0)); 145 log.debug("Sending add and remove rosterEntry for {} ({} => {})", evt.getPropertyName(), 146 evt.getOldValue(), evt.getNewValue()); 147 connection.sendMessage(service.message(JsonRoster.ROSTER, data, 0), 0); 148 } else if (!evt.getPropertyName().equals(RosterEntry.DATE_UPDATED) && 149 !evt.getPropertyName().equals(RosterEntry.FILENAME) && 150 !evt.getPropertyName().equals(RosterEntry.COMMENT)) { 151 // don't send comment changes 152 log.debug("Sending updated rosterEntry for {} ({} => {})", evt.getPropertyName(), 153 evt.getOldValue(), evt.getNewValue()); 154 connection.sendMessage( 155 service.getRosterEntry(connection.getLocale(), (RosterEntry) evt.getSource(), 0), 0); 156 } 157 } catch (JsonException ex) { 158 connection.sendMessage(ex.getJsonMessage(), 0); 159 } 160 } 161 } 162 163 private class JsonRosterListener implements PropertyChangeListener { 164 165 @Override 166 public void propertyChange(PropertyChangeEvent evt) { 167 try { 168 sendRosterUpdate(evt); 169 } catch (IOException ex) { 170 onClose(); 171 } 172 } 173 174 private void sendRosterUpdate(PropertyChangeEvent evt) throws IOException { 175 try { 176 ObjectNode data = connection.getObjectMapper().createObjectNode(); 177 if (evt.getPropertyName().equals(Roster.ADD)) { 178 data.set(ADD, 179 service.getRosterEntry(connection.getLocale(), (RosterEntry) evt.getNewValue(), 0)); 180 ((PropertyChangeProvider) evt.getNewValue()).addPropertyChangeListener(rosterEntryListener); 181 connection.sendMessage(service.message(JsonRoster.ROSTER, data, 0), 0); 182 } else if (evt.getPropertyName().equals(Roster.REMOVE)) { 183 data.set(REMOVE, 184 service.getRosterEntry(connection.getLocale(), (RosterEntry) evt.getOldValue(), 0)); 185 connection.sendMessage(service.message(JsonRoster.ROSTER, data, 0), 0); 186 } else if (!evt.getPropertyName().equals(Roster.SAVED) && 187 !evt.getPropertyName().equals(Roster.ROSTER_GROUP_ADDED) && 188 !evt.getPropertyName().equals(Roster.ROSTER_GROUP_REMOVED) && 189 !evt.getPropertyName().equals(Roster.ROSTER_GROUP_RENAMED)) { 190 // catch all events other than SAVED and ROSTER_GROUP_* 191 // (handled elsewhere) 192 connection.sendMessage(service.getRoster(connection.getLocale(), NullNode.getInstance(), 0), 0); 193 } 194 } catch (JsonException ex) { 195 connection.sendMessage(ex.getJsonMessage(), 0); 196 } 197 } 198 } 199 200 private class JsonRosterGroupsListener implements PropertyChangeListener { 201 202 @Override 203 public void propertyChange(PropertyChangeEvent evt) { 204 try { 205 // handle direct roster change events 206 if (evt.getPropertyName().equals(Roster.ROSTER_GROUP_ADDED) || 207 evt.getPropertyName().equals(Roster.ROSTER_GROUP_REMOVED) || 208 evt.getPropertyName().equals(Roster.ROSTER_GROUP_RENAMED)) { 209 sendGroupsUpdate(); 210 // handle event names of format 211 // "attributeUpdated:RosterGroup:GROUPNAME" 212 } else if (evt.getPropertyName().startsWith(RosterEntry.ATTRIBUTE_UPDATED)) { 213 String attrName = evt.getPropertyName().substring(RosterEntry.ATTRIBUTE_UPDATED.length()); 214 if (attrName.startsWith(Roster.ROSTER_GROUP_PREFIX)) { 215 String groupName = attrName.substring(Roster.ROSTER_GROUP_PREFIX.length()); 216 if (Roster.getDefault().getRosterGroups().containsKey(groupName)) { 217 sendGroupNameUpdate(groupName); 218 } 219 } 220 // handle attribute deleted, old value is of form 221 // "RosterGroup:GROUPNAME" 222 } else if (evt.getPropertyName().startsWith(RosterEntry.ATTRIBUTE_DELETED) && 223 ((String) evt.getOldValue()).startsWith(Roster.ROSTER_GROUP_PREFIX)) { 224 String groupName = ((String) evt.getOldValue()).substring(Roster.ROSTER_GROUP_PREFIX.length()); 225 if (Roster.getDefault().getRosterGroups().containsKey(groupName)) { 226 sendGroupNameUpdate(groupName); 227 } 228 } 229 } catch (IOException ex) { 230 onClose(); 231 } 232 } 233 234 private void sendGroupsUpdate() throws IOException { 235 try { 236 connection.sendMessage(service.getRosterGroups(new JsonRequest(getLocale(), getVersion(), GET, 0)), 0); 237 } catch (JsonException ex) { 238 connection.sendMessage(ex.getJsonMessage(), 0); 239 } 240 } 241 242 private void sendGroupNameUpdate(String groupName) throws IOException { 243 try { 244 log.debug("sending changed rosterGroup {} and updated group array", groupName); 245 connection.sendMessage(service.getRosterGroup(getLocale(), groupName, 0), 0); 246 sendGroupsUpdate(); 247 } catch (JsonException ex) { 248 connection.sendMessage(ex.getJsonMessage(), 0); 249 } 250 } 251 } 252 253}