001package jmri.server.json.operations; 002 003import static jmri.server.json.reporter.JsonReporter.REPORTER; 004 005import java.util.Locale; 006 007import javax.annotation.Nonnull; 008import javax.servlet.http.HttpServletResponse; 009 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import com.fasterxml.jackson.databind.ObjectMapper; 014import com.fasterxml.jackson.databind.node.ArrayNode; 015import com.fasterxml.jackson.databind.node.ObjectNode; 016 017import jmri.InstanceManager; 018import jmri.Reporter; 019import jmri.jmrit.operations.locations.*; 020import jmri.jmrit.operations.rollingstock.RollingStock; 021import jmri.jmrit.operations.rollingstock.cars.Car; 022import jmri.jmrit.operations.rollingstock.cars.CarManager; 023import jmri.jmrit.operations.rollingstock.engines.Engine; 024import jmri.jmrit.operations.rollingstock.engines.EngineManager; 025import jmri.jmrit.operations.routes.RouteLocation; 026import jmri.jmrit.operations.trains.Train; 027import jmri.jmrit.operations.trains.TrainManager; 028import jmri.jmrit.operations.trains.trainbuilder.TrainCommon; 029import jmri.server.json.JSON; 030import jmri.server.json.JsonException; 031import jmri.server.json.consist.JsonConsist; 032 033/** 034 * Utilities used by JSON services for Operations 035 * 036 * @author Randall Wood Copyright 2019 037 */ 038public class JsonUtil { 039 040 private final ObjectMapper mapper; 041 private static final Logger log = LoggerFactory.getLogger(JsonUtil.class); 042 043 /** 044 * Create utilities. 045 * 046 * @param mapper the mapper used to create JSON nodes 047 */ 048 public JsonUtil(ObjectMapper mapper) { 049 this.mapper = mapper; 050 } 051 052 /** 053 * Get the JSON representation of a Car. 054 * 055 * @param name the ID of the Car 056 * @param locale the client's locale 057 * @param id the message id set by the client 058 * @return the JSON representation of the Car 059 * @throws JsonException if no car by name exists 060 */ 061 public ObjectNode getCar(String name, Locale locale, int id) throws JsonException { 062 Car car = carManager().getById(name); 063 if (car == null) { 064 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 065 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, JsonOperations.CAR, name), id); 066 } 067 return this.getCar(car, locale); 068 } 069 070 /** 071 * Get the JSON representation of an Engine. 072 * 073 * @param engine the Engine 074 * @param locale the client's locale 075 * @return the JSON representation of engine 076 */ 077 public ObjectNode getEngine(Engine engine, Locale locale) { 078 return getEngine(engine, getRollingStock(engine, locale), locale); 079 } 080 081 /** 082 * Get the JSON representation of an Engine. 083 * 084 * @param engine the Engine 085 * @param data the JSON data from 086 * {@link #getRollingStock(RollingStock, Locale)} 087 * @param locale the client's locale 088 * @return the JSON representation of engine 089 */ 090 public ObjectNode getEngine(Engine engine, ObjectNode data, Locale locale) { 091 data.put(JsonOperations.MODEL, engine.getModel()); 092 data.put(JsonOperations.HP, engine.getHp()); 093 data.put(JsonConsist.CONSIST, engine.getConsistName()); 094 return data; 095 } 096 097 /** 098 * Get the JSON representation of an Engine. 099 * 100 * @param name the ID of the Engine 101 * @param locale the client's locale 102 * @param id the message id set by the client 103 * @return the JSON representation of engine 104 * @throws JsonException if no engine exists by name 105 */ 106 public ObjectNode getEngine(String name, Locale locale, int id) throws JsonException { 107 Engine engine = engineManager().getById(name); 108 if (engine == null) { 109 throw new JsonException(HttpServletResponse.SC_NOT_FOUND, 110 Bundle.getMessage(locale, JsonException.ERROR_NOT_FOUND, JsonOperations.ENGINE, name), id); 111 } 112 return this.getEngine(engine, locale); 113 } 114 115 /** 116 * Get a JSON representation of a Car. 117 * 118 * @param car the Car 119 * @param locale the client's locale 120 * @return the JSON representation of car 121 */ 122 public ObjectNode getCar(@Nonnull Car car, Locale locale) { 123 return getCar(car, getRollingStock(car, locale), locale); 124 } 125 126 /** 127 * Get a JSON representation of a Car. 128 * 129 * @param car the Car 130 * @param data the JSON data from 131 * {@link #getRollingStock(RollingStock, Locale)} 132 * @param locale the client's locale 133 * @return the JSON representation of car 134 */ 135 public ObjectNode getCar(@Nonnull Car car, @Nonnull ObjectNode data, Locale locale) { 136 data.put(JsonOperations.LOAD, car.getLoadName().split(TrainCommon.HYPHEN)[0]); // NOI18N 137 data.put(JsonOperations.HAZARDOUS, car.isHazardous()); 138 data.put(JsonOperations.CABOOSE, car.isCaboose()); 139 data.put(JsonOperations.PASSENGER, car.isPassenger()); 140 data.put(JsonOperations.FRED, car.hasFred()); 141 data.put(JsonOperations.SETOUT_COMMENT, car.getDropComment()); 142 data.put(JsonOperations.PICKUP_COMMENT, car.getPickupComment()); 143 data.put(JsonOperations.KERNEL, car.getKernelName()); 144 data.put(JsonOperations.UTILITY, car.isUtility()); 145 data.put(JsonOperations.IS_LOCAL, car.isLocalMove()); 146 data.put(JsonOperations.LAST_TRAIN, car.getLastTrainName()); 147 data.put(JsonOperations.LAST_MOVED, car.getLastDate()); 148 data.put(JsonOperations.LAST_LOCATION, car.getLastLocationName()); 149 if (car.getFinalDestinationTrack() != null) { 150 data.set(JsonOperations.FINAL_DESTINATION, this.getRSLocationAndTrack(car.getFinalDestinationTrack(), null, locale)); 151 } else if (car.getFinalDestination() != null) { 152 data.set(JsonOperations.FINAL_DESTINATION, 153 this.getRSLocation(car.getFinalDestination(), (RouteLocation) null, locale)); 154 } else { 155 data.set(JsonOperations.FINAL_DESTINATION, null); 156 } 157 if (car.getReturnWhenEmptyDestTrack() != null) { 158 data.set(JsonOperations.RETURN_WHEN_EMPTY, 159 this.getRSLocationAndTrack(car.getReturnWhenEmptyDestTrack(), null, locale)); 160 } else if (car.getReturnWhenEmptyDestination() != null) { 161 data.set(JsonOperations.RETURN_WHEN_EMPTY, 162 this.getRSLocation(car.getReturnWhenEmptyDestination(), (RouteLocation) null, locale)); 163 } else { 164 data.set(JsonOperations.RETURN_WHEN_EMPTY, null); 165 } 166 if (car.getReturnWhenLoadedDestTrack() != null) { 167 data.set(JsonOperations.RETURN_WHEN_LOADED, 168 this.getRSLocationAndTrack(car.getReturnWhenLoadedDestTrack(), null, locale)); 169 } else if (car.getReturnWhenLoadedDestination() != null) { 170 data.set(JsonOperations.RETURN_WHEN_LOADED, 171 this.getRSLocation(car.getReturnWhenLoadedDestination(), (RouteLocation) null, locale)); 172 } else { 173 data.set(JsonOperations.RETURN_WHEN_LOADED, null); 174 } 175 data.put(JsonOperations.DIVISION, car.getDivisionName()); 176 data.put(JsonOperations.BLOCKING_ORDER, car.isPassenger() ? Integer.toString(car.getBlocking()) : ""); 177 data.put(JSON.STATUS, car.getStatus().replace("<", "<").replace(">", ">")); 178 return data; 179 } 180 181 /** 182 * Get the JSON representation of a Location. 183 * <p> 184 * <strong>Note:</strong>use {@link #getRSLocation(Location, Locale)} if 185 * including in rolling stock or train. 186 * 187 * @param location the location 188 * @param locale the client's locale 189 * @return the JSON representation of location 190 */ 191 public ObjectNode getLocation(@Nonnull Location location, Locale locale) { 192 ObjectNode data = mapper.createObjectNode(); 193 data.put(JSON.USERNAME, location.getName()); 194 data.put(JSON.NAME, location.getId()); 195 data.put(JSON.LENGTH, location.getLength()); 196 data.put(JSON.COMMENT, location.getCommentWithColor()); 197 Reporter reporter = location.getReporter(); 198 data.put(REPORTER, reporter != null ? reporter.getSystemName() : ""); 199 // note type defaults to all in-use rolling stock types 200 ArrayNode types = data.putArray(JsonOperations.CAR_TYPE); 201 for (String type : location.getTypeNames()) { 202 types.add(type); 203 } 204 ArrayNode tracks = data.putArray(JSON.TRACK); 205 for (Track track : location.getTracksList()) { 206 tracks.add(getTrack(track, locale)); 207 } 208 return data; 209 } 210 211 /** 212 * Get the JSON representation of a Location. 213 * 214 * @param name the ID of the location 215 * @param locale the client's locale 216 * @param id the message id set by the client 217 * @return the JSON representation of the location 218 * @throws JsonException if id does not match a known location 219 */ 220 public ObjectNode getLocation(String name, Locale locale, int id) throws JsonException { 221 if (locationManager().getLocationById(name) == null) { 222 log.error("Unable to get location id [{}].", name); 223 throw new JsonException(404, 224 Bundle.getMessage(locale, JsonException.ERROR_OBJECT, JSON.LOCATION, name), id); 225 } 226 return getLocation(locationManager().getLocationById(name), locale); 227 } 228 229 /** 230 * Get a Track in JSON. 231 * <p> 232 * <strong>Note:</strong>use {@link #getRSTrack(Track, Locale)} if including 233 * in rolling stock or train. 234 * 235 * @param track the track to get 236 * @param locale the client's locale 237 * @return a JSON representation of the track 238 */ 239 public ObjectNode getTrack(Track track, Locale locale) { 240 ObjectNode node = mapper.createObjectNode(); 241 node.put(JSON.USERNAME, track.getName()); 242 node.put(JSON.NAME, track.getId()); 243 node.put(JSON.COMMENT, track.getComment()); 244 node.put(JSON.LENGTH, track.getLength()); 245 // only includes location ID to avoid recursion 246 node.put(JSON.LOCATION, track.getLocation().getId()); 247 Reporter reporter = track.getReporter(); 248 node.put(REPORTER, reporter != null ? reporter.getSystemName() : ""); 249 node.put(JSON.TYPE, track.getTrackType()); 250 // note type defaults to all in-use rolling stock types 251 ArrayNode types = node.putArray(JsonOperations.CAR_TYPE); 252 for (String type : track.getTypeNames()) { 253 types.add(type); 254 } 255 return node; 256 } 257 258 /** 259 * Get the JSON representation of a Location for use in rolling stock or 260 * train. 261 * <p> 262 * <strong>Note:</strong>use {@link #getLocation(Location, Locale)} if not 263 * including in rolling stock or train. 264 * 265 * @param location the location 266 * @param locale the client's locale 267 * @return the JSON representation of location 268 */ 269 public ObjectNode getRSLocation(@Nonnull Location location, Locale locale) { 270 ObjectNode data = mapper.createObjectNode(); 271 data.put(JSON.USERNAME, location.getName()); 272 data.put(JSON.NAME, location.getId()); 273 return data; 274 } 275 276 private ObjectNode getRSLocation(Location location, RouteLocation routeLocation, Locale locale) { 277 ObjectNode node = getRSLocation(location, locale); 278 if (routeLocation != null) { 279 node.put(JSON.ROUTE, routeLocation.getId()); 280 } else { 281 node.put(JSON.ROUTE, (String) null); 282 } 283 return node; 284 } 285 286 private ObjectNode getRSLocationAndTrack(Track track, RouteLocation routeLocation, Locale locale) { 287 ObjectNode node = this.getRSLocation(track.getLocation(), routeLocation, locale); 288 node.set(JSON.TRACK, this.getRSTrack(track, locale)); 289 return node; 290 } 291 292 /** 293 * Get a Track in JSON for use in rolling stock or train. 294 * <p> 295 * <strong>Note:</strong>use {@link #getTrack(Track, Locale)} if not 296 * including in rolling stock or train. 297 * 298 * @param track the track to get 299 * @param locale the client's locale 300 * @return a JSON representation of the track 301 */ 302 public ObjectNode getRSTrack(Track track, Locale locale) { 303 ObjectNode node = mapper.createObjectNode(); 304 node.put(JSON.USERNAME, track.getName()); 305 node.put(JSON.NAME, track.getId()); 306 return node; 307 } 308 309 public ObjectNode getRollingStock(@Nonnull RollingStock rs, Locale locale) { 310 ObjectNode node = mapper.createObjectNode(); 311 node.put(JSON.NAME, rs.getId()); 312 node.put(JsonOperations.NUMBER, TrainCommon.splitString(rs.getNumber())); 313 node.put(JsonOperations.ROAD, rs.getRoadName().split(TrainCommon.HYPHEN)[0]); 314 node.put(JSON.RFID, rs.getRfid()); 315 if (!rs.getWhereLastSeenName().equals(Car.NONE)) { 316 node.put(JSON.WHERELASTSEEN, rs.getWhereLastSeenName() + 317 (rs.getTrackLastSeenName().equals(Car.NONE) ? "" : " (" + rs.getTrackLastSeenName() + ")")); 318 } else { 319 node.set(JSON.WHERELASTSEEN, null); 320 } 321 if (!rs.getWhenLastSeenDate().equals(Car.NONE)) { 322 node.put(JSON.WHENLASTSEEN, rs.getWhenLastSeenDate()); 323 } else { 324 node.set(JSON.WHENLASTSEEN, null); 325 } 326 // second half of string can be anything 327 String[] type = rs.getTypeName().split(TrainCommon.HYPHEN, 2); 328 node.put(JsonOperations.TYPE, type[0]); 329 node.put(JsonOperations.CAR_SUB_TYPE, type.length == 2 ? type[1] : ""); 330 node.put(JsonOperations.LENGTH, rs.getLengthInteger()); 331 node.put(JsonOperations.WEIGHT, rs.getAdjustedWeightTons()); 332 node.put(JsonOperations.WEIGHT_TONS, rs.getWeightTons()); 333 node.put(JsonOperations.COLOR, rs.getColor()); 334 node.put(JsonOperations.OWNER, rs.getOwnerName()); 335 node.put(JsonOperations.BUILT, rs.getBuilt()); 336 node.put(JsonOperations.COMMENT, rs.getComment()); 337 node.put(JsonOperations.OUT_OF_SERVICE, rs.isOutOfService()); 338 node.put(JsonOperations.LOCATION_UNKNOWN, rs.isLocationUnknown()); 339 if (rs.getTrack() != null) { 340 node.set(JsonOperations.LOCATION, this.getRSLocationAndTrack(rs.getTrack(), rs.getRouteLocation(), locale)); 341 } else if (rs.getLocation() != null) { 342 node.set(JsonOperations.LOCATION, this.getRSLocation(rs.getLocation(), rs.getRouteLocation(), locale)); 343 } else { 344 node.set(JsonOperations.LOCATION, null); 345 } 346 if (rs.getTrain() != null) { 347 node.put(JsonOperations.TRAIN_ID, rs.getTrain().getId()); 348 node.put(JsonOperations.TRAIN_NAME, rs.getTrain().getName()); 349 node.put(JsonOperations.TRAIN_ICON_NAME, rs.getTrain().getIconName()); 350 } else { 351 node.set(JsonOperations.TRAIN_ID, null); 352 node.set(JsonOperations.TRAIN_NAME, null); 353 node.set(JsonOperations.TRAIN_ICON_NAME, null); 354 } 355 if (rs.getDestinationTrack() != null) { 356 node.set(JsonOperations.DESTINATION, 357 this.getRSLocationAndTrack(rs.getDestinationTrack(), rs.getRouteDestination(), locale)); 358 } else if (rs.getDestination() != null) { 359 node.set(JsonOperations.DESTINATION, this.getRSLocation(rs.getDestination(), rs.getRouteDestination(), locale)); 360 } else { 361 node.set(JsonOperations.DESTINATION, null); 362 } 363 return node; 364 } 365 366 /** 367 * Get the JSON representation of a Train. 368 * 369 * @param train the train 370 * @param locale the client's locale 371 * @return the JSON representation of train 372 */ 373 public ObjectNode getTrain(Train train, Locale locale) { 374 ObjectNode data = this.mapper.createObjectNode(); 375 data.put(JSON.USERNAME, train.getName()); 376 data.put(JSON.ICON_NAME, train.getIconName()); 377 data.put(JSON.NAME, train.getId()); 378 data.put(JSON.DEPARTURE_TIME, train.getFormatedDepartureTime()); 379 data.put(JSON.DESCRIPTION, train.getDescription()); 380 data.put(JSON.COMMENT, train.getComment()); 381 if (train.getRoute() != null) { 382 data.put(JSON.ROUTE, train.getRoute().getName()); 383 data.put(JSON.ROUTE_ID, train.getRoute().getId()); 384 data.set(JSON.LOCATIONS, this.getRouteLocationsForTrain(train, locale)); 385 } 386 data.set(JSON.ENGINES, this.getEnginesForTrain(train, locale)); 387 data.set(JsonOperations.CARS, this.getCarsForTrain(train, locale)); 388 if (train.getTrainDepartsName() != null) { 389 data.put(JSON.DEPARTURE_LOCATION, train.getTrainDepartsName()); 390 } 391 if (train.getTrainTerminatesName() != null) { 392 data.put(JSON.TERMINATES_LOCATION, train.getTrainTerminatesName()); 393 } 394 data.put(JSON.LOCATION, train.getCurrentLocationName()); 395 if (train.getCurrentRouteLocation() != null) { 396 data.put(JsonOperations.LOCATION_ID, train.getCurrentRouteLocation().getId()); 397 } 398 data.put(JSON.STATUS, train.getStatus(locale)); 399 data.put(JSON.STATUS_CODE, train.getStatusCode()); 400 data.put(JSON.LENGTH, train.getTrainLength()); 401 data.put(JSON.WEIGHT, train.getTrainWeight()); 402 if (train.getLeadEngine() != null) { 403 data.put(JsonOperations.LEAD_ENGINE, train.getLeadEngine().toString()); 404 } 405 data.put(JsonOperations.CABOOSE, train.getCabooseRoadAndNumber()); 406 return data; 407 } 408 409 /** 410 * Get the JSON representation of a Train. 411 * 412 * @param name the id of the train 413 * @param locale the client's locale 414 * @param id the message id set by the client 415 * @return the JSON representation of the train with id 416 * @throws JsonException if id does not represent a known train 417 */ 418 public ObjectNode getTrain(String name, Locale locale, int id) throws JsonException { 419 if (trainManager().getTrainById(name) == null) { 420 log.error("Unable to get train id [{}].", name); 421 throw new JsonException(404, 422 Bundle.getMessage(locale, JsonException.ERROR_OBJECT, JsonOperations.TRAIN, name), id); 423 } 424 return getTrain(trainManager().getTrainById(name), locale); 425 } 426 427 /** 428 * Get all trains. 429 * 430 * @param locale the client's locale 431 * @return an array of all trains 432 */ 433 public ArrayNode getTrains(Locale locale) { 434 ArrayNode array = this.mapper.createArrayNode(); 435 trainManager().getTrainsByNameList() 436 .forEach(train -> array.add(getTrain(train, locale))); 437 return array; 438 } 439 440 private ArrayNode getCarsForTrain(Train train, Locale locale) { 441 ArrayNode array = mapper.createArrayNode(); 442 carManager().getByTrainDestinationList(train) 443 .forEach(car -> array.add(getCar(car, locale))); 444 return array; 445 } 446 447 private ArrayNode getEnginesForTrain(Train train, Locale locale) { 448 ArrayNode array = mapper.createArrayNode(); 449 engineManager().getByTrainBlockingList(train) 450 .forEach(engine -> array.add(getEngine(engine, locale))); 451 return array; 452 } 453 454 private ArrayNode getRouteLocationsForTrain(Train train, Locale locale) { 455 ArrayNode array = mapper.createArrayNode(); 456 train.getRoute().getLocationsBySequenceList().forEach(route -> { 457 ObjectNode root = mapper.createObjectNode(); 458 RouteLocation rl = route; 459 root.put(JSON.NAME, rl.getId()); 460 root.put(JSON.USERNAME, rl.getName()); 461 root.put(JSON.TRAIN_DIRECTION, rl.getTrainDirectionString()); 462 root.put(JSON.COMMENT, rl.getCommentWithColor()); 463 root.put(JSON.SEQUENCE, rl.getSequenceNumber()); 464 root.put(JSON.EXPECTED_ARRIVAL, train.getExpectedArrivalTime(rl)); 465 root.put(JSON.EXPECTED_DEPARTURE, train.getExpectedDepartureTime(rl)); 466 root.set(JSON.LOCATION, getRSLocation(rl.getLocation(), locale)); 467 array.add(root); 468 }); 469 return array; 470 } 471 472 private CarManager carManager() { 473 return InstanceManager.getDefault(CarManager.class); 474 } 475 476 private EngineManager engineManager() { 477 return InstanceManager.getDefault(EngineManager.class); 478 } 479 480 private LocationManager locationManager() { 481 return InstanceManager.getDefault(LocationManager.class); 482 } 483 484 private TrainManager trainManager() { 485 return InstanceManager.getDefault(TrainManager.class); 486 } 487}