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