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