001package jmri.server.json.operations; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.io.IOException; 006import java.util.HashMap; 007import java.util.Objects; 008 009import javax.annotation.Nonnull; 010import javax.servlet.http.HttpServletResponse; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import com.fasterxml.jackson.databind.JsonNode; 016 017import jmri.InstanceManager; 018import jmri.JmriException; 019import jmri.beans.Identifiable; 020import jmri.beans.PropertyChangeProvider; 021import jmri.jmrit.operations.locations.Location; 022import jmri.jmrit.operations.locations.LocationManager; 023import jmri.jmrit.operations.rollingstock.RollingStock; 024import jmri.jmrit.operations.rollingstock.cars.Car; 025import jmri.jmrit.operations.rollingstock.cars.CarManager; 026import jmri.jmrit.operations.rollingstock.engines.Engine; 027import jmri.jmrit.operations.rollingstock.engines.EngineManager; 028import jmri.jmrit.operations.trains.Train; 029import jmri.jmrit.operations.trains.TrainManager; 030import jmri.server.json.*; 031 032/** 033 * @author Randall Wood (C) 2016, 2019, 2020 034 */ 035public class JsonOperationsSocketService extends JsonSocketService<JsonOperationsHttpService> { 036 037 private final HashMap<String, BeanListener<Car>> carListeners = new HashMap<>(); 038 private final HashMap<String, BeanListener<Engine>> engineListeners = new HashMap<>(); 039 private final HashMap<String, BeanListener<Location>> locationListeners = new HashMap<>(); 040 private final HashMap<String, BeanListener<Train>> trainListeners = new HashMap<>(); 041 private final CarsListener carsListener = new CarsListener(); 042 private final EnginesListener enginesListener = new EnginesListener(); 043 private final LocationsListener locationsListener = new LocationsListener(); 044 private final TrainsListener trainsListener = new TrainsListener(); 045 046 private static final Logger log = LoggerFactory.getLogger(JsonOperationsSocketService.class); 047 048 public JsonOperationsSocketService(JsonConnection connection) { 049 this(connection, new JsonOperationsHttpService(connection.getObjectMapper())); 050 } 051 052 protected JsonOperationsSocketService(JsonConnection connection, JsonOperationsHttpService service) { 053 super(connection, service); 054 } 055 056 @Override 057 public void onMessage(String type, JsonNode data, JsonRequest request) 058 throws IOException, JmriException, JsonException { 059 String name = data.path(JSON.NAME).asText(); 060 switch (request.method) { 061 case JSON.GET: 062 connection.sendMessage(service.doGet(type, name, data, request), request.id); 063 break; 064 case JSON.DELETE: 065 service.doDelete(type, name, data, request); 066 // remove listener to object being deleted 067 switch (type) { 068 case JsonOperations.CAR: 069 carListeners.remove(name); 070 break; 071 case JsonOperations.ENGINE: 072 engineListeners.remove(name); 073 break; 074 case JSON.LOCATION: 075 locationListeners.remove(name); 076 break; 077 case JsonOperations.TRAIN: 078 trainListeners.remove(name); 079 break; 080 default: 081 // other types ignored 082 break; 083 } 084 break; 085 case JSON.PUT: 086 connection.sendMessage(service.doPut(type, name, data, request), request.id); 087 break; 088 case JSON.POST: 089 default: 090 connection.sendMessage(service.doPost(type, name, data, request), request.id); 091 } 092 // add listener to name if not already listening 093 if (!request.method.equals(JSON.DELETE)) { 094 if (request.method.equals(JSON.PUT) && name.isEmpty()) { 095 // cover situations where object was just created, so client could not specify correct name 096 if (JsonOperations.CAR.equals(type) || JsonOperations.ENGINE.equals(type)) { 097 name = RollingStock.createId(data.path(JSON.ROAD).asText(), data.path(JSON.NUMBER).asText()); 098 } else if (JSON.LOCATION.equals(type)) { 099 name = InstanceManager.getDefault(LocationManager.class).getLocationByName(data.path(JSON.USERNAME).asText()).getId(); 100 } else { 101 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, "ErrorMissingName", request.id); 102 } 103 } 104 switch (type) { 105 case JsonOperations.CAR: 106 carListeners.computeIfAbsent(name, id -> { 107 CarListener l = new CarListener(id); 108 InstanceManager.getDefault(CarManager.class).getById(id).addPropertyChangeListener(l); 109 return l; 110 }); 111 break; 112 case JsonOperations.ENGINE: 113 engineListeners.computeIfAbsent(name, id -> { 114 EngineListener l = new EngineListener(id); 115 InstanceManager.getDefault(EngineManager.class).getById(id).addPropertyChangeListener(l); 116 return l; 117 }); 118 break; 119 case JSON.LOCATION: 120 locationListeners.computeIfAbsent(name, id -> { 121 LocationListener l = new LocationListener(id); 122 InstanceManager.getDefault(LocationManager.class).getLocationById(id).addPropertyChangeListener(l); 123 return l; 124 }); 125 break; 126 case JsonOperations.TRAIN: 127 trainListeners.computeIfAbsent(name, id -> { 128 TrainListener l = new TrainListener(id); 129 InstanceManager.getDefault(TrainManager.class).getTrainById(id).addPropertyChangeListener(l); 130 return l; 131 }); 132 break; 133 default: 134 // other types ignored 135 break; 136 } 137 } 138 } 139 140 @Override 141 public void onList(String type, JsonNode data, JsonRequest request) 142 throws IOException, JmriException, JsonException { 143 connection.sendMessage(service.doGetList(type, data, request), request.id); 144 switch (type) { 145 case JsonOperations.CAR: 146 case JsonOperations.CARS: 147 log.debug("adding CarsListener"); 148 InstanceManager.getDefault(CarManager.class).addPropertyChangeListener(carsListener); 149 break; 150 case JsonOperations.ENGINE: 151 case JsonOperations.ENGINES: 152 log.debug("adding EnginesListener"); 153 InstanceManager.getDefault(EngineManager.class).addPropertyChangeListener(enginesListener); 154 break; 155 case JSON.LOCATION: 156 case JSON.LOCATIONS: 157 log.debug("adding LocationsListener"); 158 InstanceManager.getDefault(LocationManager.class).addPropertyChangeListener(locationsListener); 159 break; 160 case JsonOperations.TRAIN: 161 case JsonOperations.TRAINS: 162 log.debug("adding TrainsListener"); 163 InstanceManager.getDefault(TrainManager.class).addPropertyChangeListener(trainsListener); 164 break; 165 default: 166 break; 167 } 168 } 169 170 @Override 171 public void onClose() { 172 carListeners.values().forEach(listener -> listener.bean.removePropertyChangeListener(listener)); 173 carListeners.clear(); 174 engineListeners.values().forEach(listener -> listener.bean.removePropertyChangeListener(listener)); 175 engineListeners.clear(); 176 locationListeners.values().forEach(listener -> listener.bean.removePropertyChangeListener(listener)); 177 locationListeners.clear(); 178 trainListeners.values().forEach(listener -> listener.bean.removePropertyChangeListener(listener)); 179 trainListeners.clear(); 180 InstanceManager.getDefault(CarManager.class).removePropertyChangeListener(carsListener); 181 InstanceManager.getDefault(EngineManager.class).removePropertyChangeListener(enginesListener); 182 InstanceManager.getDefault(LocationManager.class).removePropertyChangeListener(locationsListener); 183 InstanceManager.getDefault(TrainManager.class).removePropertyChangeListener(trainsListener); 184 } 185 186 protected abstract class BeanListener<B extends Identifiable & PropertyChangeProvider> implements PropertyChangeListener { 187 188 protected final B bean; 189 190 protected BeanListener(@Nonnull B bean) { 191 this.bean = bean; 192 } 193 194 protected void propertyChange(String type, HashMap<String, BeanListener<B>> map) { 195 try { 196 sendSingleChange(type); 197 } catch (IOException ex) { 198 // stop listening to this object on error 199 bean.removePropertyChangeListener(this); 200 map.remove(bean.getId()); 201 } 202 } 203 204 private void sendSingleChange(String type) throws IOException { 205 try { 206 connection.sendMessage(service.doGet(type, bean.getId(), 207 connection.getObjectMapper().createObjectNode(), 208 new JsonRequest(getLocale(), getVersion(), JSON.GET, 0)), 0); 209 } catch (JsonException ex) { 210 log.warn("json error sending {}: {}", type, ex.getJsonMessage()); 211 connection.sendMessage(ex.getJsonMessage(), 0); 212 } 213 } 214 } 215 216 protected abstract class ManagerListener<M extends PropertyChangeProvider> implements PropertyChangeListener { 217 218 protected final M manager; 219 220 protected ManagerListener(@Nonnull M mgr) { 221 Objects.requireNonNull(mgr); 222 this.manager = mgr; 223 } 224 225 protected void propertyChange(String type) { 226 try { 227 sendListChange(type); 228 } catch (IOException ex) { 229 manager.removePropertyChangeListener(this); 230 } 231 } 232 233 private void sendListChange(String type) throws IOException { 234 try { 235 connection.sendMessage(service.doGetList(type, service.getObjectMapper().createObjectNode(), 236 new JsonRequest(getLocale(), getVersion(), JSON.GET, 0)), 0); 237 } catch (JsonException ex) { 238 log.warn("json error sending {}: {}", type, ex.getJsonMessage()); 239 connection.sendMessage(ex.getJsonMessage(), 0); 240 } 241 } 242 } 243 244 private class CarListener extends BeanListener<Car> { 245 246 protected CarListener(String id) { 247 super(InstanceManager.getDefault(CarManager.class).getById(id)); 248 } 249 250 @Override 251 public void propertyChange(PropertyChangeEvent evt) { 252 propertyChange(JsonOperations.CAR, carListeners); 253 } 254 } 255 256 private class CarsListener extends ManagerListener<CarManager> { 257 258 protected CarsListener() { 259 super(InstanceManager.getDefault(CarManager.class)); 260 } 261 262 @Override 263 public void propertyChange(PropertyChangeEvent evt) { 264 propertyChange(JsonOperations.CAR); 265 } 266 } 267 268 private class EngineListener extends BeanListener<Engine> { 269 270 protected EngineListener(String id) { 271 super(InstanceManager.getDefault(EngineManager.class).getById(id)); 272 } 273 274 @Override 275 public void propertyChange(PropertyChangeEvent evt) { 276 propertyChange(JsonOperations.ENGINE, engineListeners); 277 } 278 } 279 280 private class EnginesListener extends ManagerListener<EngineManager> { 281 282 protected EnginesListener() { 283 super(InstanceManager.getDefault(EngineManager.class)); 284 } 285 286 @Override 287 public void propertyChange(PropertyChangeEvent evt) { 288 propertyChange(JsonOperations.ENGINE); 289 } 290 } 291 292 private class LocationListener extends BeanListener<Location> { 293 294 protected LocationListener(String id) { 295 super(InstanceManager.getDefault(LocationManager.class).getLocationById(id)); 296 } 297 298 @Override 299 public void propertyChange(PropertyChangeEvent evt) { 300 propertyChange(JSON.LOCATION, locationListeners); 301 } 302 } 303 304 private class LocationsListener extends ManagerListener<LocationManager> { 305 306 protected LocationsListener() { 307 super(InstanceManager.getDefault(LocationManager.class)); 308 } 309 310 @Override 311 public void propertyChange(PropertyChangeEvent evt) { 312 propertyChange(JSON.LOCATION); 313 } 314 } 315 316 private class TrainListener extends BeanListener<Train> { 317 318 protected TrainListener(String id) { 319 super(InstanceManager.getDefault(TrainManager.class).getTrainById(id)); 320 } 321 322 @Override 323 public void propertyChange(PropertyChangeEvent evt) { 324 propertyChange(JsonOperations.TRAIN, trainListeners); 325 } 326 } 327 328 private class TrainsListener extends ManagerListener<TrainManager> { 329 330 protected TrainsListener() { 331 super(InstanceManager.getDefault(TrainManager.class)); 332 } 333 334 @Override 335 public void propertyChange(PropertyChangeEvent evt) { 336 propertyChange(JsonOperations.TRAIN); 337 } 338 } 339}