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