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