001package jmri.server.json.consist; 002 003import static jmri.server.json.JSON.ADDRESS; 004import static jmri.server.json.JSON.ENGINES; 005import static jmri.server.json.JSON.FORWARD; 006import static jmri.server.json.JSON.NAME; 007import static jmri.server.json.JSON.IS_LONG_ADDRESS; 008import static jmri.server.json.JSON.POSITION; 009import static jmri.server.json.JSON.SIZE_LIMIT; 010import static jmri.server.json.JSON.TYPE; 011import static jmri.server.json.consist.JsonConsist.CONSIST; 012import static jmri.server.json.consist.JsonConsist.CONSISTS; 013 014import com.fasterxml.jackson.databind.JsonNode; 015import com.fasterxml.jackson.databind.ObjectMapper; 016import com.fasterxml.jackson.databind.node.ArrayNode; 017import com.fasterxml.jackson.databind.node.ObjectNode; 018import java.io.IOException; 019import java.util.ArrayList; 020import javax.servlet.http.HttpServletResponse; 021import jmri.Consist; 022import jmri.DccLocoAddress; 023import jmri.InstanceManager; 024import jmri.LocoAddress; 025import jmri.jmrit.consisttool.ConsistFile; 026import jmri.server.json.JsonException; 027import jmri.server.json.JsonHttpService; 028import jmri.server.json.JsonRequest; 029import jmri.server.json.util.JsonUtilHttpService; 030 031/** 032 * @author Randall Wood Copyright 2016, 2018 033 */ 034public class JsonConsistHttpService extends JsonHttpService { 035 036 final JsonConsistManager manager; // default package visibility 037 038 public JsonConsistHttpService(ObjectMapper mapper) { 039 super(mapper); 040 this.manager = InstanceManager.getOptionalDefault(JsonConsistManager.class) 041 .orElseGet(() -> InstanceManager.setDefault(JsonConsistManager.class, new JsonConsistManager())); 042 } 043 044 @Override 045 public JsonNode doGet(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 046 if (!manager.isConsistManager()) { 047 throw new JsonException(503, Bundle.getMessage(request.locale, JsonConsist.ERROR_NO_CONSIST_MANAGER), 048 request.id); 049 } 050 return this.getConsist(JsonUtilHttpService.addressForString(name), request); 051 } 052 053 /** 054 * Change the properties and locomotives of a consist. This method takes as 055 * input the JSON representation of a consist as provided by 056 * {@link #getConsist }. If present in the 057 * JSON, this method sets the following consist properties: 058 * <ul> 059 * <li>consistID</li> 060 * <li>consistType</li> 061 * <li>locomotives (<em>engines</em> in the JSON representation)<br> 062 * <strong>NOTE</strong> Since this method adds, repositions, and deletes 063 * locomotives, the JSON representation must contain <em>every</em> 064 * locomotive that should be in the consist, if it contains the engines 065 * node.</li> 066 * </ul> 067 * 068 * @param type the JSON message type 069 * @param name the consist address, ignored if data contains an 070 * {@value jmri.server.json.JSON#ADDRESS} and 071 * {@value jmri.server.json.JSON#IS_LONG_ADDRESS} nodes 072 * @param data the consist as a JsonObject 073 * @param request the JSON request 074 * @return the JSON representation of the Consist 075 * @throws jmri.server.json.JsonException if there is no consist manager 076 * (code 503), the consist does not 077 * exist (code 404), or the consist 078 * cannot be saved (code 500). 079 */ 080 @Override 081 public JsonNode doPost(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 082 if (!this.manager.isConsistManager()) { 083 throw new JsonException(503, Bundle.getMessage(request.locale, JsonConsist.ERROR_NO_CONSIST_MANAGER), 084 request.id); // NOI18N 085 } 086 LocoAddress address; 087 if (data.path(ADDRESS).canConvertToInt()) { 088 address = new DccLocoAddress(data.path(ADDRESS).asInt(), data.path(IS_LONG_ADDRESS).asBoolean(false)); 089 } else { 090 address = JsonUtilHttpService.addressForString(data.path(ADDRESS).asText()); 091 } 092 if (!this.manager.getConsistList().contains(address)) { 093 throw new JsonException(404, Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, CONSIST, name), 094 request.id); 095 } 096 Consist consist = this.manager.getConsist(address); 097 if (data.path(NAME).isTextual()) { 098 consist.setConsistID(data.path(NAME).asText()); 099 } 100 if (data.path(TYPE).isInt()) { 101 consist.setConsistType(data.path(TYPE).asInt()); 102 } 103 if (data.path(ENGINES).isArray()) { 104 ArrayList<LocoAddress> engines = new ArrayList<>(); 105 // add every engine 106 for (JsonNode engine : data.path(ENGINES)) { 107 DccLocoAddress engineAddress = 108 new DccLocoAddress(engine.path(ADDRESS).asInt(), engine.path(IS_LONG_ADDRESS).asBoolean()); 109 if (!consist.contains(engineAddress)) { 110 consist.add(engineAddress, engine.path(FORWARD).asBoolean()); 111 } 112 consist.setPosition(engineAddress, engine.path(POSITION).asInt()); 113 engines.add(engineAddress); 114 } 115 // remove engines if needed 116 ArrayList<DccLocoAddress> consistEngines = new ArrayList<>(consist.getConsistList()); 117 consistEngines.stream() 118 .filter(engineAddress -> (!engines.contains(engineAddress))) 119 .forEach(consist::remove); 120 } 121 try { 122 (new ConsistFile()).writeFile(this.manager.getConsistList()); 123 } catch (IOException ex) { 124 throw new JsonException(500, ex.getLocalizedMessage(), request.id); 125 } 126 return this.getConsist(address, request); 127 } 128 129 @Override 130 public JsonNode doPut(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 131 if (!this.manager.isConsistManager()) { 132 throw new JsonException(503, Bundle.getMessage(request.locale, JsonConsist.ERROR_NO_CONSIST_MANAGER), 133 request.id); // NOI18N 134 } 135 LocoAddress address; 136 if (data.path(ADDRESS).canConvertToInt()) { 137 address = new DccLocoAddress(data.path(ADDRESS).asInt(), data.path(IS_LONG_ADDRESS).asBoolean(false)); 138 } else { 139 address = JsonUtilHttpService.addressForString(data.path(ADDRESS).asText()); 140 } 141 this.manager.getConsist(address); 142 return this.doPost(type, name, data, request); 143 } 144 145 @Override 146 public void doDelete(String type, String name, JsonNode data, JsonRequest request) throws JsonException { 147 if (!this.manager.isConsistManager()) { 148 throw new JsonException(503, Bundle.getMessage(request.locale, JsonConsist.ERROR_NO_CONSIST_MANAGER), 149 request.id); // NOI18N 150 } 151 if (!this.manager.getConsistList().contains(JsonUtilHttpService.addressForString(name))) { 152 throw new JsonException(404, Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, CONSIST, name), 153 request.id); // NOI18N 154 } 155 this.manager.delConsist(JsonUtilHttpService.addressForString(name)); 156 } 157 158 @Override 159 public JsonNode doGetList(String type, JsonNode data, JsonRequest request) throws JsonException { 160 if (!this.manager.isConsistManager()) { 161 throw new JsonException(503, Bundle.getMessage(request.locale, JsonConsist.ERROR_NO_CONSIST_MANAGER), 162 request.id); // NOI18N 163 } 164 ArrayNode array = mapper.createArrayNode(); 165 for (LocoAddress address : this.manager.getConsistList()) { 166 array.add(getConsist(address, request)); 167 } 168 return message(array, request.id); 169 } 170 171 /** 172 * Get the JSON representation of a consist. The JSON representation is an 173 * object with the following data attributes: 174 * <ul> 175 * <li>address - integer address</li> 176 * <li>isLongAddress - boolean true if address is long, false if short</li> 177 * <li>type - integer, see {@link jmri.Consist#getConsistType() }</li> 178 * <li>id - string with consist Id</li> 179 * <li>sizeLimit - the maximum number of locomotives the consist can 180 * contain</li> 181 * <li>engines - array listing every locomotive in the consist. Each entry 182 * in the array contains the following attributes: 183 * <ul> 184 * <li>address - integer address</li> 185 * <li>isLongAddress - boolean true if address is long, false if short</li> 186 * <li>forward - boolean true if the locomotive running is forward in the 187 * consists</li> 188 * <li>position - integer locomotive's position in the consist</li> 189 * </ul> 190 * </ul> 191 * 192 * @param address The address of the consist to get 193 * @param request the JSON request 194 * @return The JSON representation of the consist 195 * @throws JsonException This exception has code 404 if the consist does not 196 * exist 197 */ 198 public JsonNode getConsist(LocoAddress address, JsonRequest request) throws JsonException { 199 if (this.manager.getConsistList().contains(address)) { 200 ObjectNode data = mapper.createObjectNode(); 201 Consist consist = this.manager.getConsist(address); 202 data.put(ADDRESS, consist.getConsistAddress().getNumber()); 203 data.put(IS_LONG_ADDRESS, consist.getConsistAddress().isLongAddress()); 204 data.put(TYPE, consist.getConsistType()); 205 ArrayNode engines = data.putArray(ENGINES); 206 consist.getConsistList().stream().forEach(locomotive -> { 207 ObjectNode engine = mapper.createObjectNode(); 208 engine.put(ADDRESS, locomotive.getNumber()); 209 engine.put(IS_LONG_ADDRESS, locomotive.isLongAddress()); 210 engine.put(FORWARD, consist.getLocoDirection(locomotive)); 211 engine.put(POSITION, consist.getPosition(locomotive)); 212 engines.add(engine); 213 }); 214 data.put(NAME, consist.getConsistID()); 215 data.put(SIZE_LIMIT, consist.sizeLimit()); 216 return message(CONSIST, data, request.id); 217 } else { 218 throw new JsonException(404, 219 Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, CONSIST, address.toString()), 220 request.id); // NOI18N 221 } 222 } 223 224 @Override 225 public JsonNode doSchema(String type, boolean server, JsonRequest request) throws JsonException { 226 switch (type) { 227 case CONSIST: 228 case CONSISTS: 229 return doSchema(type, 230 server, 231 "jmri/server/json/consist/consist-server.json", 232 "jmri/server/json/consist/consist-client.json", 233 request.id); 234 default: 235 throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 236 Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), request.id); 237 } 238 } 239}