001package jmri.server.json.operations; 002 003import static jmri.server.json.JSON.*; 004import static jmri.server.json.JSON.COMMENT; 005import static jmri.server.json.JSON.DESTINATION; 006import static jmri.server.json.JSON.ENGINES; 007import static jmri.server.json.JSON.KERNEL; 008import static jmri.server.json.JSON.LENGTH; 009import static jmri.server.json.JSON.LOCATION; 010import static jmri.server.json.JSON.MODEL; 011import static jmri.server.json.JSON.NUMBER; 012import static jmri.server.json.JSON.ROAD; 013import static jmri.server.json.JSON.TRACK; 014import static jmri.server.json.JSON.TYPE; 015import static jmri.server.json.JSON.WEIGHT; 016import static jmri.server.json.operations.JsonOperations.*; 017import static jmri.server.json.operations.JsonOperations.OUT_OF_SERVICE; 018import static jmri.server.json.reporter.JsonReporter.REPORTER; 019 020import java.util.*; 021 022import javax.annotation.Nonnull; 023import javax.servlet.http.HttpServletResponse; 024 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028import com.fasterxml.jackson.databind.JsonNode; 029import com.fasterxml.jackson.databind.ObjectMapper; 030import com.fasterxml.jackson.databind.node.ArrayNode; 031import com.fasterxml.jackson.databind.node.ObjectNode; 032 033import jmri.*; 034import jmri.jmrit.operations.locations.*; 035import jmri.jmrit.operations.rollingstock.RollingStock; 036import jmri.jmrit.operations.rollingstock.cars.*; 037import jmri.jmrit.operations.rollingstock.engines.Engine; 038import jmri.jmrit.operations.rollingstock.engines.EngineManager; 039import jmri.jmrit.operations.trains.Train; 040import jmri.jmrit.operations.trains.TrainManager; 041import jmri.server.json.*; 042 043/** 044 * @author Randall Wood (C) 2016, 2018, 2019, 2020 045 */ 046public class JsonOperationsHttpService extends JsonHttpService { 047 048 private final JsonUtil utilities; 049 050 private static final Logger log = LoggerFactory.getLogger(JsonOperationsHttpService.class); 051 052 public JsonOperationsHttpService(ObjectMapper mapper) { 053 super(mapper); 054 utilities = new JsonUtil(mapper); 055 } 056 057 @Override 058 public JsonNode doGet(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 059 log.debug("doGet(type='{}', name='{}', data='{}')", type, name, data); 060 Locale locale = request.locale; 061 int id = request.id; 062 ObjectNode result; 063 switch (type) { 064 case CAR: 065 result = utilities.getCar(name, locale, id); 066 break; 067 case CAR_TYPE: 068 result = getCarType(name, locale, id); 069 break; 070 case ENGINE: 071 result = utilities.getEngine(name, locale, id); 072 break; 073 case KERNEL: 074 Kernel kernel = InstanceManager.getDefault(KernelManager.class).getKernelByName(name); 075 if (kernel == null) { 076 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 077 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, type, name), id); 078 } 079 result = getKernel(kernel, locale, id); 080 break; 081 case LOCATION: 082 result = utilities.getLocation(name, locale, id); 083 break; 084 case ROLLING_STOCK: 085 throw new JsonException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 086 Bundle.getMessage(locale, "GetNotAllowed", type), id); 087 case TRAIN: 088 case TRAINS: 089 type = TRAIN; 090 result = utilities.getTrain(name, locale, id); 091 break; 092 case TRACK: 093 result = utilities.getTrack(getTrackByName(name, data, locale, id), locale); 094 break; 095 default: 096 throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 097 Bundle.getMessage(locale, "ErrorInternal", type), id); 098 } 099 return message(type, result, id); 100 } 101 102 @Override 103 public JsonNode doPost(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 104 log.debug("doPost(type='{}', name='{}', data='{}')", type, name, data); 105 Locale locale = request.locale; 106 int id = request.id; 107 String newName = name; 108 switch (type) { 109 case CAR: 110 return message(type, postCar(name, data, locale, id), id); 111 case CAR_TYPE: 112 if (data.path(RENAME).isTextual()) { 113 newName = data.path(RENAME).asText(); 114 InstanceManager.getDefault(CarTypes.class).replaceName(name, newName); 115 } 116 return message(type, getCarType(newName, locale, id).put(RENAME, name), id); 117 case ENGINE: 118 return message(type, postEngine(name, data, locale, id), id); 119 case KERNEL: 120 if (data.path(RENAME).isTextual()) { 121 newName = data.path(RENAME).asText(); 122 InstanceManager.getDefault(KernelManager.class).replaceKernelName(name, newName); 123 InstanceManager.getDefault(KernelManager.class).deleteKernel(name); 124 } 125 return message(type, getKernel(InstanceManager.getDefault(KernelManager.class).getKernelByName(newName), locale, id).put(RENAME, name), id); 126 case LOCATION: 127 return message(type, postLocation(name, data, locale, id), id); 128 case TRAIN: 129 setTrain(name, data, locale, id); 130 break; 131 case TRACK: 132 return message(type, postTrack(name, data, locale, id), id); 133 case TRAINS: 134 // do nothing 135 break; 136 default: 137 throw new JsonException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 138 Bundle.getMessage(locale, "PostNotAllowed", type), id); // NOI18N 139 } 140 return doGet(type, name, data, request); 141 } 142 143 @Override 144 public JsonNode doPut(String type, String name, JsonNode data, JsonRequest request) 145 throws JsonException { 146 log.debug("doPut(type='{}', name='{}', data='{}')", type, name, data); 147 Locale locale = request.locale; 148 int id = request.id; 149 switch (type) { 150 case CAR: 151 if (data.path(ROAD).isMissingNode()) { 152 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 153 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, ROAD, type), id); // NOI18N 154 } 155 if (data.path(NUMBER).isMissingNode()) { 156 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 157 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, NUMBER, type), id); // NOI18N 158 } 159 String road = data.path(ROAD).asText(); 160 String number = data.path(NUMBER).asText(); 161 if (carManager().getById(name) != null || carManager().getByRoadAndNumber(road, number) != null) { 162 throw new JsonException(HttpServletResponse.SC_CONFLICT, 163 Bundle.getMessage(locale, "ErrorPutRollingStockConflict", type, road, number), id); // NOI18N 164 } 165 return message(type, postCar(carManager().newRS(road, number), data, locale, id), id); 166 case CAR_TYPE: 167 if (name.isEmpty()) { 168 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 169 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, NAME, type), id); // NOI18N 170 } 171 InstanceManager.getDefault(CarTypes.class).addName(name); 172 return message(type, getCarType(name, locale, id), id); 173 case ENGINE: 174 if (data.path(ROAD).isMissingNode()) { 175 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 176 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, ROAD, type), id); // NOI18N 177 } 178 if (data.path(NUMBER).isMissingNode()) { 179 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 180 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, NUMBER, type), id); // NOI18N 181 } 182 road = data.path(ROAD).asText(); 183 number = data.path(NUMBER).asText(); 184 if (engineManager().getById(name) != null || engineManager().getByRoadAndNumber(road, number) != null) { 185 throw new JsonException(HttpServletResponse.SC_CONFLICT, 186 Bundle.getMessage(locale, "ErrorPutRollingStockConflict", type, road, number), id); // NOI18N 187 } 188 return message(type, postEngine(engineManager().newRS(road, number), data, locale, id), id); 189 case KERNEL: 190 if (name.isEmpty()) { 191 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 192 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, NAME, type), id); // NOI18N 193 } 194 return message(type, getKernel(InstanceManager.getDefault(KernelManager.class).newKernel(name), locale, id), id); 195 case LOCATION: 196 if (data.path(USERNAME).isMissingNode()) { 197 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 198 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, USERNAME, type), id); // NOI18N 199 } 200 String userName = data.path(USERNAME).asText(); 201 if (locationManager().getLocationById(name) != null) { 202 throw new JsonException(HttpServletResponse.SC_CONFLICT, 203 Bundle.getMessage(locale, "ErrorPutNameConflict", type, name), id); // NOI18N 204 } 205 if (locationManager().getLocationByName(userName) != null) { 206 throw new JsonException(HttpServletResponse.SC_CONFLICT, 207 Bundle.getMessage(locale, "ErrorPutUserNameConflict", type, userName), id); // NOI18N 208 } 209 return message(type, postLocation(locationManager().newLocation(userName), data, locale, id), id); 210 case TRACK: 211 if (data.path(USERNAME).isMissingNode()) { 212 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 213 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, USERNAME, type), id); // NOI18N 214 } 215 userName = data.path(USERNAME).asText(); 216 if (data.path(TYPE).isMissingNode()) { 217 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 218 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, TYPE, type), id); // NOI18N 219 } 220 String trackType = data.path(TYPE).asText(); 221 if (data.path(LOCATION).isMissingNode()) { 222 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 223 Bundle.getMessage(locale, JsonException.ERROR_MISSING_PROPERTY_PUT, LOCATION, type), id); // NOI18N 224 } 225 String locationName = data.path(LOCATION).asText(); 226 Location location = locationManager().getLocationById(locationName); 227 if (location == null) { 228 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 229 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, LOCATION, locationName), id); // NOI18N 230 } 231 if (location.getTrackById(name) != null) { 232 throw new JsonException(HttpServletResponse.SC_CONFLICT, 233 Bundle.getMessage(locale, "ErrorPutNameConflict", type, name), id); // NOI18N 234 } 235 if (location.getTrackByName(userName, trackType) != null) { 236 throw new JsonException(HttpServletResponse.SC_CONFLICT, 237 Bundle.getMessage(locale, "ErrorPutUserNameConflict", type, userName), id); // NOI18N 238 } 239 return message(type, postTrack(location.addTrack(userName, trackType), data, locale, id), id); 240 default: 241 return super.doPut(type, name, data, request); 242 } 243 } 244 245 @Override 246 public JsonNode doGetList(String type, JsonNode data, JsonRequest request) throws JsonException { 247 log.debug("doGetList(type='{}', data='{}')", type, data); 248 Locale locale = request.locale; 249 int id = request.id; 250 switch (type) { 251 case CAR: 252 case CARS: 253 return message(getCars(locale, id), id); 254 case CAR_TYPE: 255 return getCarTypes(locale, id); 256 case ENGINE: 257 case ENGINES: 258 return message(getEngines(locale, id), id); 259 case KERNEL: 260 return getKernels(locale, id); 261 case LOCATION: 262 case LOCATIONS: 263 return getLocations(locale, id); 264 case ROLLING_STOCK: 265 return message(getCars(locale, id).addAll(getEngines(locale, id)), id); 266 case TRAIN: 267 case TRAINS: 268 return getTrains(locale, id); 269 default: 270 throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 271 Bundle.getMessage(locale, "ErrorInternal", type), id); // NOI18N 272 } 273 } 274 275 @Override 276 public void doDelete(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 277 log.debug("doDelete(type='{}', name='{}', data='{}')", type, name, data); 278 Locale locale = request.locale; 279 int id = request.id; 280 String token = data.path(FORCE_DELETE).asText(); 281 switch (type) { 282 case CAR: 283 // TODO: do not remove an in use car 284 deleteCar(name, locale, id); 285 break; 286 case CAR_TYPE: 287 List<Car> cars = carManager().getByTypeList(name); 288 List<Location> locations = new ArrayList<>(); 289 locationManager().getList().stream().filter(l -> l.acceptsTypeName(name)).forEach(locations::add); 290 if ((!cars.isEmpty() || !locations.isEmpty()) && !acceptForceDeleteToken(type, name, token)) { 291 ArrayNode conflicts = mapper.createArrayNode(); 292 cars.forEach(car -> conflicts.add(message(CAR, utilities.getCar(car, locale), 0))); 293 locations.forEach( 294 location -> conflicts.add(message(LOCATION, utilities.getLocation(location, locale), 0))); 295 throwDeleteConflictException(type, name, conflicts, request); 296 } 297 InstanceManager.getDefault(CarTypes.class).deleteName(name); 298 break; 299 case ENGINE: 300 // TODO: do not remove an in use engine 301 deleteEngine(name, locale, id); 302 break; 303 case KERNEL: 304 Kernel kernel = InstanceManager.getDefault(KernelManager.class).getKernelByName(name); 305 if (kernel == null) { 306 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 307 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, type, name), id); 308 } 309 if (kernel.getSize() != 0 && !acceptForceDeleteToken(type, name, token)) { 310 throwDeleteConflictException(type, name, getKernelCars(kernel, true, locale), request); 311 } 312 InstanceManager.getDefault(KernelManager.class).deleteKernel(name); 313 break; 314 case LOCATION: 315 // TODO: do not remove an in use location 316 deleteLocation(name, locale, id); 317 break; 318 case TRACK: 319 // TODO: do not remove an in use track 320 deleteTrack(name, data, locale, id); 321 break; 322 default: 323 super.doDelete(type, name, data, request); 324 } 325 } 326 327 private ObjectNode getCarType(String name, Locale locale, int id) throws JsonException { 328 CarTypes manager = InstanceManager.getDefault(CarTypes.class); 329 if (!manager.containsName(name)) { 330 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 331 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, CAR_TYPE, name), id); 332 } 333 ObjectNode data = mapper.createObjectNode(); 334 data.put(NAME, name); 335 ArrayNode cars = data.putArray(CARS); 336 carManager().getByTypeList(name).forEach(car -> cars.add(utilities.getCar(car, locale))); 337 ArrayNode locations = data.putArray(LOCATIONS); 338 locationManager().getList().stream() 339 .filter(location -> location.acceptsTypeName(name)) 340 .forEach(location -> locations.add(utilities.getLocation(location, locale))); 341 return data; 342 } 343 344 private JsonNode getCarTypes(Locale locale, int id) throws JsonException { 345 ArrayNode array = mapper.createArrayNode(); 346 for (String name : InstanceManager.getDefault(CarTypes.class).getNames()) { 347 array.add(message(CAR_TYPE, getCarType(name, locale, id), id)); 348 } 349 return message(array, id); 350 } 351 352 private ObjectNode getKernel(Kernel kernel, Locale locale, int id) { 353 ObjectNode data = mapper.createObjectNode(); 354 data.put(NAME, kernel.getName()); 355 data.put(WEIGHT, kernel.getAdjustedWeightTons()); 356 data.put(LENGTH, kernel.getTotalLength()); 357 Car lead = kernel.getLead(); 358 if (lead != null) { 359 data.set(LEAD, utilities.getCar(kernel.getLead(), locale)); 360 } else { 361 data.putNull(LEAD); 362 } 363 data.set(CARS, getKernelCars(kernel, false, locale)); 364 return data; 365 } 366 367 private ArrayNode getKernelCars(Kernel kernel, boolean asMessage, Locale locale) { 368 ArrayNode array = mapper.createArrayNode(); 369 kernel.getCars().forEach(car -> { 370 if (asMessage) { 371 array.add(message(CAR, utilities.getCar(car, locale), 0)); 372 } else { 373 array.add(utilities.getCar(car, locale)); 374 } 375 }); 376 return array; 377 } 378 379 private JsonNode getKernels(Locale locale, int id) { 380 ArrayNode array = mapper.createArrayNode(); 381 InstanceManager.getDefault(KernelManager.class).getNameList() 382 // individual kernels should not have id in array, but same 383 // method is used to get single kernels as requested, so pass 384 // additive inverse of id to allow errors 385 .forEach(kernel -> array.add(message(KERNEL, getKernel(InstanceManager.getDefault(KernelManager.class).getKernelByName(kernel), locale, id * -1), id * -1))); 386 return message(array, id); 387 } 388 389 public ArrayNode getCars(Locale locale, int id) { 390 ArrayNode array = mapper.createArrayNode(); 391 carManager().getByIdList() 392 .forEach(car -> array.add(message(CAR, utilities.getCar(car, locale), id))); 393 return array; 394 } 395 396 public ArrayNode getEngines(Locale locale, int id) { 397 ArrayNode array = mapper.createArrayNode(); 398 engineManager().getByIdList() 399 .forEach(engine -> array.add(message(ENGINE, utilities.getEngine(engine, locale), id))); 400 return array; 401 } 402 403 public JsonNode getLocations(Locale locale, int id) { 404 ArrayNode array = mapper.createArrayNode(); 405 locationManager().getLocationsByIdList() 406 .forEach(location -> array.add(message(LOCATION, utilities.getLocation(location, locale), id))); 407 return message(array, id); 408 } 409 410 public JsonNode getTrains(Locale locale, int id) { 411 ArrayNode array = mapper.createArrayNode(); 412 trainManager().getTrainsByIdList() 413 .forEach(train -> array.add(message(TRAIN, utilities.getTrain(train, locale), id))); 414 return message(array, id); 415 } 416 417 /** 418 * Set the properties in the data parameter for the train with the given id. 419 * <p> 420 * Currently only moves the train to the location given with the key 421 * LOCATION. If the move cannot be completed, throws error code 428. 422 * 423 * @param name id of the train 424 * @param data train data to change 425 * @param locale locale to throw exceptions in 426 * @param id message id set by client 427 * @throws jmri.server.json.JsonException if the train cannot move to the 428 * location in data. 429 */ 430 public void setTrain(String name, JsonNode data, Locale locale, int id) throws JsonException { 431 Train train = InstanceManager.getDefault(TrainManager.class).getTrainById(name); 432 JsonNode location = data.path(LOCATION); 433 if (!location.isMissingNode()) { 434 if (location.isNull()) { 435 train.terminate(); 436 } else if (!train.move(location.asText())) { 437 throw new JsonException(428, Bundle.getMessage(locale, "ErrorTrainMovement", name, location.asText()), 438 id); 439 } 440 } 441 } 442 443 public ObjectNode postLocation(String name, JsonNode data, Locale locale, int id) throws JsonException { 444 return postLocation(getLocationByName(name, locale, id), data, locale, id); 445 } 446 447 public ObjectNode postLocation(Location location, JsonNode data, Locale locale, int id) throws JsonException { 448 // set things that throw exceptions first 449 if (!data.path(REPORTER).isMissingNode()) { 450 String name = data.path(REPORTER).asText(); 451 Reporter reporter = InstanceManager.getDefault(ReporterManager.class).getBySystemName(name); 452 if (reporter != null) { 453 location.setReporter(reporter); 454 } else { 455 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 456 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, REPORTER, name), id); 457 } 458 } 459 location.setName(data.path(USERNAME).asText(location.getName())); 460 location.setComment(data.path(COMMENT).asText(location.getCommentWithColor())); 461 return utilities.getLocation(location, locale); 462 } 463 464 public ObjectNode postTrack(String name, JsonNode data, Locale locale, int id) throws JsonException { 465 return postTrack(getTrackByName(name, data, locale, id), data, locale, id); 466 } 467 468 public ObjectNode postTrack(Track track, JsonNode data, Locale locale, int id) throws JsonException { 469 // set things that throw exceptions first 470 if (!data.path(REPORTER).isMissingNode()) { 471 String name = data.path(REPORTER).asText(); 472 Reporter reporter = InstanceManager.getDefault(ReporterManager.class).getBySystemName(name); 473 if (reporter != null) { 474 track.setReporter(reporter); 475 } else { 476 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 477 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, REPORTER, name), id); 478 } 479 } 480 track.setName(data.path(USERNAME).asText(track.getName())); 481 track.setLength(data.path(LENGTH).asInt(track.getLength())); 482 track.setComment(data.path(COMMENT).asText(track.getComment())); 483 return utilities.getTrack(track, locale); 484 } 485 486 /** 487 * Set the properties in the data parameter for the given car. 488 * <p> 489 * <strong>Note</strong> returns the modified car because changing the road 490 * or number of a car changes its name in the JSON representation. 491 * 492 * @param name the operations id of the car to change 493 * @param data car data to change 494 * @param locale locale to throw exceptions in 495 * @param id message id set by client 496 * @return the JSON representation of the car 497 * @throws JsonException if a car by name cannot be found 498 */ 499 public ObjectNode postCar(String name, JsonNode data, Locale locale, int id) throws JsonException { 500 return postCar(getCarByName(name, locale, id), data, locale, id); 501 } 502 503 /** 504 * Set the properties in the data parameter for the given car. 505 * <p> 506 * <strong>Note</strong> returns the modified car because changing the road 507 * or number of a car changes its name in the JSON representation. 508 * 509 * @param car the car to change 510 * @param data car data to change 511 * @param locale locale to throw exceptions in 512 * @param id message id set by client 513 * @return the JSON representation of the car 514 * @throws JsonException if unable to set location 515 */ 516 public ObjectNode postCar(@Nonnull Car car, JsonNode data, Locale locale, int id) throws JsonException { 517 ObjectNode result = postRollingStock(car, data, locale, id); 518 car.setCaboose(data.path(CABOOSE).asBoolean(car.isCaboose())); 519 car.setCarHazardous(data.path(HAZARDOUS).asBoolean(car.isHazardous())); 520 car.setPassenger(data.path(PASSENGER).asBoolean(car.isPassenger())); 521 car.setFred(data.path(FRED).asBoolean(car.hasFred())); 522 car.setUtility(data.path(UTILITY).asBoolean(car.isUtility())); 523 return utilities.getCar(car, result, locale); 524 } 525 526 /** 527 * Set the properties in the data parameter for the given engine. 528 * <p> 529 * <strong>Note</strong> returns the modified engine because changing the 530 * road or number of an engine changes its name in the JSON representation. 531 * 532 * @param name the operations id of the engine to change 533 * @param data engine data to change 534 * @param locale locale to throw exceptions in 535 * @param id message id set by client 536 * @return the JSON representation of the engine 537 * @throws JsonException if a engine by name cannot be found 538 */ 539 public ObjectNode postEngine(String name, JsonNode data, Locale locale, int id) throws JsonException { 540 return postEngine(getEngineByName(name, locale, id), data, locale, id); 541 } 542 543 /** 544 * Set the properties in the data parameter for the given engine. 545 * <p> 546 * <strong>Note</strong> returns the modified engine because changing the 547 * road or number of an engine changes its name in the JSON representation. 548 * 549 * @param engine the engine to change 550 * @param data engine data to change 551 * @param locale locale to throw exceptions in 552 * @param id message id set by client 553 * @return the JSON representation of the engine 554 * @throws JsonException if unable to set location 555 */ 556 public ObjectNode postEngine(@Nonnull Engine engine, JsonNode data, Locale locale, int id) throws JsonException { 557 // set model early, since setting other values depend on it 558 engine.setModel(data.path(MODEL).asText(engine.getModel())); 559 ObjectNode result = postRollingStock(engine, data, locale, id); 560 return utilities.getEngine(engine, result, locale); 561 } 562 563 /** 564 * Set the properties in the data parameter for the given rolling stock. 565 * <p> 566 * <strong>Note</strong> returns the modified rolling stock because changing 567 * the road or number of a rolling stock changes its name in the JSON 568 * representation. 569 * 570 * @param rs the rolling stock to change 571 * @param data rolling stock data to change 572 * @param locale locale to throw exceptions in 573 * @param id message id set by client 574 * @return the JSON representation of the rolling stock 575 * @throws JsonException if unable to set location 576 */ 577 public ObjectNode postRollingStock(@Nonnull RollingStock rs, JsonNode data, Locale locale, int id) 578 throws JsonException { 579 // make changes that can throw an exception first 580 String name = rs.getId(); 581 //handle removal (only) from Train 582 JsonNode node = data.path(TRAIN_ID); 583 if (!node.isMissingNode()) { 584 //new value must be null, adding or changing train not supported here 585 if (node.isNull()) { 586 if (rs.getTrain() != null) { 587 rs.setTrain(null); 588 rs.setDestination(null, null); 589 rs.setRouteLocation(null); 590 rs.setRouteDestination(null); 591 } 592 } else { 593 throw new JsonException(HttpServletResponse.SC_CONFLICT, 594 Bundle.getMessage(locale, "ErrorRemovingTrain", rs.getId()), id); 595 } 596 } 597 //handle change in Location 598 node = data.path(LOCATION); 599 if (!node.isMissingNode()) { 600 //can't move a car that is on a train 601 if (rs.getTrain() != null) { 602 throw new JsonException(HttpServletResponse.SC_CONFLICT, 603 Bundle.getMessage(locale, "ErrorIsOnTrain", rs.getId(), rs.getTrainName()), id); 604 } 605 if (!node.isNull()) { 606 //move car to new location and track 607 Location location = locationManager().getLocationById(node.path(NAME).asText()); 608 if (location != null) { 609 String trackId = node.path(TRACK).path(NAME).asText(); 610 Track track = location.getTrackById(trackId); 611 if (trackId.isEmpty() || track != null) { 612 String status = rs.setLocation(location, track); 613 if (!status.equals(Track.OKAY)) { 614 throw new JsonException(HttpServletResponse.SC_CONFLICT, 615 Bundle.getMessage(locale, "ErrorMovingCar", 616 rs.getId(), LOCATION, location.getId(), trackId, status), id); 617 } 618 } else { 619 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 620 Bundle.getMessage(locale, "ErrorNotFound", TRACK, trackId), id); 621 } 622 } else { 623 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 624 Bundle.getMessage(locale, "ErrorNotFound", LOCATION, node.path(NAME).asText()), id); 625 } 626 } else { 627 //if new location is null, remove car from current location 628 String status = rs.setLocation(null, null); 629 if (!status.equals(Track.OKAY)) { 630 throw new JsonException(HttpServletResponse.SC_CONFLICT, 631 Bundle.getMessage(locale, "ErrorMovingCar", 632 rs.getId(), LOCATION, null, null, status), id); 633 } 634 } 635 } 636 //handle change in LocationUnknown 637 node = data.path(LOCATION_UNKNOWN); 638 if (!node.isMissingNode()) { 639 //can't move a car that is on a train 640 if (rs.getTrain() != null) { 641 throw new JsonException(HttpServletResponse.SC_CONFLICT, 642 Bundle.getMessage(locale, "ErrorIsOnTrain", rs.getId(), rs.getTrainName()), id); 643 } 644 //set LocationUnknown flag to new value 645 rs.setLocationUnknown(data.path(LOCATION_UNKNOWN).asBoolean()); 646 } 647 //handle change in DESTINATION 648 node = data.path(DESTINATION); 649 if (!node.isMissingNode()) { 650 Location location = locationManager().getLocationById(node.path(NAME).asText()); 651 if (location != null) { 652 String trackId = node.path(TRACK).path(NAME).asText(); 653 Track track = location.getTrackById(trackId); 654 if (trackId.isEmpty() || track != null) { 655 String status = rs.setDestination(location, track); 656 if (!status.equals(Track.OKAY)) { 657 throw new JsonException(HttpServletResponse.SC_CONFLICT, 658 Bundle.getMessage(locale, "ErrorMovingCar", rs.getId(), 659 DESTINATION, location.getId(), trackId, status), 660 id); 661 } 662 } else { 663 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 664 Bundle.getMessage(locale, "ErrorNotFound", TRACK, trackId), id); 665 } 666 } else { 667 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 668 Bundle.getMessage(locale, "ErrorNotFound", DESTINATION, node.path(NAME).asText()), id); 669 } 670 } 671 // set properties using the existing property as the default 672 rs.setRoadName(data.path(JsonOperations.ROAD).asText(rs.getRoadName())); 673 rs.setNumber(data.path(JsonOperations.NUMBER).asText(rs.getNumber())); 674 rs.setColor(data.path(COLOR).asText(rs.getColor())); 675 rs.setComment(data.path(JsonOperations.COMMENT).asText(rs.getComment())); 676 rs.setOwnerName(data.path(JsonOperations.OWNER).asText(rs.getOwnerName())); 677 rs.setBuilt(data.path(BUILT).asText(rs.getBuilt())); 678 679 rs.setWeightTons(data.path(WEIGHT_TONS).asText()); 680 rs.setRfid(data.path(RFID).asText(rs.getRfid())); 681 rs.setLength(Integer.toString(data.path(LENGTH).asInt(rs.getLengthInteger()))); 682 rs.setOutOfService(data.path(OUT_OF_SERVICE).asBoolean(rs.isOutOfService())); 683 rs.setTypeName(data.path(JsonOperations.TYPE).asText(rs.getTypeName())); 684 ObjectNode result = utilities.getRollingStock(rs, locale); 685 if (!rs.getId().equals(name)) { 686 result.put(RENAME, name); 687 } 688 return result; 689 } 690 691 public void deleteCar(@Nonnull String name, @Nonnull Locale locale, int id) 692 throws JsonException { 693 carManager().deregister(getCarByName(name, locale, id)); 694 } 695 696 public void deleteEngine(@Nonnull String name, @Nonnull Locale locale, int id) 697 throws JsonException { 698 engineManager().deregister(getEngineByName(name, locale, id)); 699 } 700 701 public void deleteLocation(@Nonnull String name, @Nonnull Locale locale, int id) 702 throws JsonException { 703 locationManager().deregister(getLocationByName(name, locale, id)); 704 } 705 706 public void deleteTrack(@Nonnull String name, @Nonnull JsonNode data, @Nonnull Locale locale, int id) 707 throws JsonException { 708 Track track = getTrackByName(name, data, locale, id); 709 track.getLocation().deleteTrack(track); 710 } 711 712 @Nonnull 713 protected Car getCarByName(@Nonnull String name, @Nonnull Locale locale, int id) throws JsonException { 714 Car car = carManager().getById(name); 715 if (car == null) { 716 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 717 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, CAR, name), id); 718 } 719 return car; 720 } 721 722 @Nonnull 723 protected Engine getEngineByName(@Nonnull String name, @Nonnull Locale locale, int id) 724 throws JsonException { 725 Engine engine = engineManager().getById(name); 726 if (engine == null) { 727 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 728 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, ENGINE, name), id); 729 } 730 return engine; 731 } 732 733 @Nonnull 734 protected Location getLocationByName(@Nonnull String name, @Nonnull Locale locale, int id) 735 throws JsonException { 736 Location location = locationManager().getLocationById(name); 737 if (location == null) { 738 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 739 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, LOCATION, name), id); 740 } 741 return location; 742 } 743 744 @Nonnull 745 protected Track getTrackByName(@Nonnull String name, @Nonnull JsonNode data, @Nonnull Locale locale, 746 int id) throws JsonException { 747 if (data.path(LOCATION).isMissingNode()) { 748 throw new JsonException(HttpServletResponse.SC_BAD_REQUEST, 749 Bundle.getMessage(locale, "ErrorMissingAttribute", LOCATION, TRACK), id); 750 } 751 Location location = getLocationByName(data.path(LOCATION).asText(), locale, id); 752 Track track = location.getTrackById(name); 753 if (track == null) { 754 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 755 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, TRACK, name), id); 756 } 757 return track; 758 } 759 760 protected CarManager carManager() { 761 return InstanceManager.getDefault(CarManager.class); 762 } 763 764 protected EngineManager engineManager() { 765 return InstanceManager.getDefault(EngineManager.class); 766 } 767 768 protected LocationManager locationManager() { 769 return InstanceManager.getDefault(LocationManager.class); 770 } 771 772 protected TrainManager trainManager() { 773 return InstanceManager.getDefault(TrainManager.class); 774 } 775 776 @Override 777 public JsonNode doSchema(String type, boolean server, JsonRequest request) throws JsonException { 778 int id = request.id; 779 switch (type) { 780 case CAR: 781 case CARS: 782 return doSchema(type, 783 server, 784 "jmri/server/json/operations/car-server.json", 785 "jmri/server/json/operations/car-client.json", 786 id); 787 case CAR_TYPE: 788 case KERNEL: 789 case ROLLING_STOCK: 790 case TRACK: 791 return doSchema(type, 792 server, 793 "jmri/server/json/operations/" + type + "-server.json", 794 "jmri/server/json/operations/" + type + "-client.json", 795 id); 796 case ENGINE: 797 case ENGINES: 798 return doSchema(type, 799 server, 800 "jmri/server/json/operations/engine-server.json", 801 "jmri/server/json/operations/engine-client.json", 802 id); 803 case LOCATION: 804 case LOCATIONS: 805 return doSchema(type, 806 server, 807 "jmri/server/json/operations/location-server.json", 808 "jmri/server/json/operations/location-client.json", 809 id); 810 case TRAIN: 811 case TRAINS: 812 return doSchema(type, 813 server, 814 "jmri/server/json/operations/train-server.json", 815 "jmri/server/json/operations/train-client.json", 816 id); 817 default: 818 throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 819 Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), id); 820 } 821 } 822 823}