001package jmri.server.json.util;
002
003import static jmri.server.json.JSON.CONTROL_PANEL;
004import static jmri.server.json.JSON.LAYOUT_PANEL;
005import static jmri.server.json.JSON.NAME;
006import static jmri.server.json.JSON.PANEL;
007import static jmri.server.json.JSON.PANEL_PANEL;
008import static jmri.server.json.JSON.SWITCHBOARD_PANEL;
009import static jmri.server.json.JSON.TYPE;
010import static jmri.server.json.JSON.URL;
011import static jmri.server.json.JSON.USERNAME;
012
013import com.fasterxml.jackson.databind.JsonNode;
014import com.fasterxml.jackson.databind.ObjectMapper;
015import com.fasterxml.jackson.databind.node.ArrayNode;
016import com.fasterxml.jackson.databind.node.ObjectNode;
017
018import java.io.IOException;
019import java.lang.reflect.Field;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Enumeration;
023import java.util.Locale;
024import java.util.Objects;
025
026import javax.annotation.CheckForNull;
027import javax.annotation.Nonnull;
028import javax.servlet.http.HttpServletResponse;
029import javax.swing.JFrame;
030
031import jmri.DccLocoAddress;
032import jmri.InstanceManager;
033import jmri.Metadata;
034import jmri.PermissionManager;
035import jmri.server.json.JsonServerPreferences;
036import jmri.jmrit.display.Editor;
037import jmri.jmrit.display.EditorManager;
038import jmri.jmrit.display.controlPanelEditor.ControlPanelEditor;
039import jmri.jmrit.display.layoutEditor.LayoutEditor;
040import jmri.jmrit.display.switchboardEditor.SwitchboardEditor;
041import jmri.jmrix.ConnectionConfig;
042import jmri.jmrix.ConnectionConfigManager;
043import jmri.SystemConnectionMemo;
044import jmri.jmrix.internal.InternalSystemConnectionMemo;
045import jmri.profile.Profile;
046import jmri.profile.ProfileManager;
047import jmri.server.json.JSON;
048import jmri.server.json.JsonException;
049import jmri.server.json.JsonHttpService;
050import jmri.server.json.JsonRequest;
051import jmri.util.node.NodeIdentity;
052import jmri.util.zeroconf.ZeroConfService;
053import jmri.util.zeroconf.ZeroConfServiceManager;
054import jmri.web.server.WebServerPreferences;
055import org.slf4j.Logger;
056import org.slf4j.LoggerFactory;
057
058/**
059 * @author Randall Wood Copyright 2016, 2017, 2018
060 */
061public class JsonUtilHttpService extends JsonHttpService {
062
063    private static final String RESOURCE_PATH = "jmri/server/json/util/";
064    private static final Logger log = LoggerFactory.getLogger(JsonUtilHttpService.class);
065
066    public JsonUtilHttpService(ObjectMapper mapper) {
067        super(mapper);
068    }
069
070    @Override
071    // use @CheckForNull to override @Nonnull specified in superclass
072    public JsonNode doGet(String type, @CheckForNull String name, JsonNode data, JsonRequest request)
073            throws JsonException {
074        switch (type) {
075            case JSON.HELLO:
076                return this.getHello(
077                        InstanceManager.getDefault(JsonServerPreferences.class).getHeartbeatInterval(), request);
078            case JSON.METADATA:
079                if (name == null) {
080                    return this.getMetadata(request);
081                }
082                return this.getMetadata(request.locale, name, request.id);
083            case JSON.NETWORK_SERVICE:
084            case JSON.NETWORK_SERVICES:
085                if (name == null) {
086                    return this.getNetworkServices(request.locale, request.id);
087                }
088                return this.getNetworkService(name, request);
089            case JSON.NODE:
090                return this.getNode(request);
091            case JSON.PANEL:
092            case JSON.PANELS:
093                if (name == null) {
094                    return this.getPanels(request.id);
095                }
096                return this.getPanel(request.locale, name, request.id);
097            case JSON.RAILROAD:
098                return this.getRailroad(request);
099            case JSON.SYSTEM_CONNECTION:
100            case JSON.SYSTEM_CONNECTIONS:
101                if (name == null) {
102                    return this.getSystemConnections(request);
103                }
104                return this.getSystemConnection(name, request);
105            case JSON.CONFIG_PROFILE:
106            case JSON.CONFIG_PROFILES:
107                if (name == null) {
108                    return this.getConfigProfiles(request);
109                }
110                return this.getConfigProfile(name, request);
111            case JSON.VERSION:
112                return this.getVersion();
113            default:
114                throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
115                        Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), request.id);
116        }
117    }
118
119    @Override
120    public ArrayNode doGetList(String type, JsonNode data, JsonRequest request) throws JsonException {
121        switch (type) {
122            case JSON.METADATA:
123                return this.getMetadata(request);
124            case JSON.NETWORK_SERVICE:
125            case JSON.NETWORK_SERVICES:
126                return this.getNetworkServices(request);
127            case JSON.PANEL:
128            case JSON.PANELS:
129                return this.getPanels(request.id);
130            case JSON.SYSTEM_CONNECTION:
131            case JSON.SYSTEM_CONNECTIONS:
132                return this.getSystemConnections(request);
133            case JSON.CONFIG_PROFILE:
134            case JSON.CONFIG_PROFILES:
135                return this.getConfigProfiles(request);
136            default:
137                ArrayNode array = this.mapper.createArrayNode();
138                JsonNode node = this.doGet(type, null, data, request);
139                if (node.isArray()) {
140                    array.addAll((ArrayNode) node);
141                } else {
142                    array.add(node);
143                }
144                return array;
145        }
146    }
147
148    @Override
149    // Use @CheckForNull to override non-null requirement of superclass
150    public JsonNode doPost(String type, @CheckForNull String name,
151            JsonNode data, JsonRequest request) throws JsonException {
152        log.debug("doPost(type='{}', name='{}', data='{}')", type, name, data);
153        // This will be expanded with more cases and warrants a CASE rather than an IF
154        switch (type) {
155            case JSON.RAILROAD:
156                InstanceManager.getDefault(WebServerPreferences.class).setRailroadName(name);
157                break;
158            case JSON.SESSION_LOGIN:
159                return this.postSessionLogin(name, data, request);
160            case JSON.SESSION_LOGOUT:
161                return this.postSessionLogout(name, data, request);
162            default:
163                log.debug("Received unexpected POST command: '{}'", type);
164                break;
165        }
166        // Implicitly answer all doPost the way an equivalent doGet would be answered.
167        return this.doGet(type, name, data, request);
168    }
169
170    /**
171     * @return JSON map of complete versions and URL part for protocols
172     * @throws JsonException if a protocol version is not available
173     */
174    public JsonNode getVersion() throws JsonException {
175        ObjectNode data = mapper.createObjectNode();
176        for (String version : JSON.VERSIONS) {
177            try {
178                Field field;
179                field = JSON.class.getDeclaredField(version.toUpperCase() + "_PROTOCOL_VERSION");
180                data.put(field.get(null).toString(), version);
181            } catch (
182                    IllegalAccessException |
183                    IllegalArgumentException |
184                    NoSuchFieldException |
185                    SecurityException ex) {
186                throw new JsonException(500, ex, 0);
187            }
188        }
189        return message(JSON.VERSION, data, 0);
190    }
191
192    /**
193     * Send a JSON {@link jmri.server.json.JSON#HELLO} message.
194     *
195     * @param heartbeat seconds in which a client must send a message before its
196     *                  connection is broken
197     * @param request   the JSON request
198     * @return the JSON hello message
199     */
200    public JsonNode getHello(int heartbeat, @Nonnull JsonRequest request) {
201        ObjectNode data = mapper.createObjectNode();
202        data.put(JSON.JMRI, jmri.Version.name());
203        data.put(JSON.JSON, JSON.V5_PROTOCOL_VERSION);
204        data.put(JSON.VERSION, JSON.V5);
205        data.put(JSON.HEARTBEAT, Math.round(heartbeat * 0.9f));
206        data.put(JSON.RAILROAD, InstanceManager.getDefault(WebServerPreferences.class).getRailroadName());
207        data.put(JSON.NODE, NodeIdentity.networkIdentity());
208        Profile activeProfile = ProfileManager.getDefault().getActiveProfile();
209        data.put(JSON.ACTIVE_PROFILE, activeProfile != null ? activeProfile.getName() : null);
210        return message(JSON.HELLO, data, request.id);
211    }
212
213    /**
214     * Get a JSON message with a metadata element from {@link jmri.Metadata}.
215     *
216     * @param name    The metadata element to get
217     * @param request the JSON request
218     * @return JSON metadata element
219     * @throws JsonException if name is not a recognized metadata element
220     */
221    public JsonNode getMetadata(@Nonnull String name, @Nonnull JsonRequest request) throws JsonException {
222        String metadata = Metadata.getBySystemName(name);
223        ObjectNode data = mapper.createObjectNode();
224        if (metadata != null) {
225            data.put(JSON.NAME, name);
226            data.put(JSON.VALUE, Metadata.getBySystemName(name));
227        } else {
228            throw new JsonException(404,
229                    Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, JSON.METADATA, name),
230                    request.id);
231        }
232        return message(JSON.METADATA, data, request.id);
233    }
234
235    /**
236     * Get a JSON message with a metadata element from {@link jmri.Metadata}.
237     *
238     * @param locale The client's Locale.
239     * @param name   The metadata element to get.
240     * @param id     message id set by client
241     * @return JSON metadata element.
242     * @throws JsonException if name is not a recognized metadata element.
243     */
244    public JsonNode getMetadata(Locale locale, String name, int id) throws JsonException {
245        return getMetadata(name, new JsonRequest(locale, JSON.V5, JSON.GET, id));
246    }
247
248    /**
249     * Get a JSON array of metadata elements as listed by
250     * {@link jmri.Metadata#getSystemNameList()}.
251     *
252     * @param request the JSON request
253     * @return Array of JSON metadata elements
254     * @throws JsonException if thrown by
255     *                       {@link #getMetadata(java.util.Locale, java.lang.String, int)}
256     */
257    public ArrayNode getMetadata(@Nonnull JsonRequest request) throws JsonException {
258        ArrayNode root = mapper.createArrayNode();
259        for (String name : Metadata.getSystemNameList()) {
260            root.add(getMetadata(name, request));
261        }
262        return root;
263    }
264
265    /**
266     * Get a running {@link jmri.util.zeroconf.ZeroConfService} using the
267     * protocol as the name of the service.
268     *
269     * @param name    the service protocol
270     * @param request the JSON request
271     * @return the JSON networkService message
272     * @throws JsonException if type is not a running zeroconf networking
273     *                       protocol
274     */
275    public JsonNode getNetworkService(@Nonnull String name, @Nonnull JsonRequest request) throws JsonException {
276        for (ZeroConfService service : InstanceManager.getDefault(ZeroConfServiceManager.class).allServices()) {
277            if (service.getType().equals(name)) {
278                return this.getNetworkService(service, request.id);
279            }
280        }
281        throw new JsonException(404,
282                Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, JSON.NETWORK_SERVICE, name),
283                request.id);
284    }
285
286    private JsonNode getNetworkService(ZeroConfService service, int id) {
287        ObjectNode data = mapper.createObjectNode();
288        data.put(JSON.NAME, service.getType());
289        data.put(JSON.USERNAME, service.getName());
290        data.put(JSON.PORT, service.getServiceInfo().getPort());
291        data.put(JSON.TYPE, service.getType());
292        Enumeration<String> pe = service.getServiceInfo().getPropertyNames();
293        while (pe.hasMoreElements()) {
294            String pn = pe.nextElement();
295            data.put(pn, service.getServiceInfo().getPropertyString(pn));
296        }
297        return message(JSON.NETWORK_SERVICE, data, id);
298    }
299
300    /**
301     * @param request the JSON request
302     * @return the JSON networkServices message.
303     */
304    public ArrayNode getNetworkServices(@Nonnull JsonRequest request) {
305        ArrayNode root = mapper.createArrayNode();
306        InstanceManager.getDefault(ZeroConfServiceManager.class).allServices().stream()
307                .forEach(service -> root.add(this.getNetworkService(service, request.id)));
308        return root;
309    }
310
311    /**
312     * @param locale the client's Locale.
313     * @param id     message id set by client
314     * @return the JSON networkServices message.
315     */
316    public ArrayNode getNetworkServices(Locale locale, int id) {
317        return getNetworkServices(new JsonRequest(locale, JSON.V5, JSON.GET, id));
318    }
319
320    /**
321     * Send a JSON {@link jmri.server.json.JSON#NODE} message containing the
322     * JMRI node identity and former identities.
323     *
324     * @param request the JSON request
325     * @return the JSON node message
326     * @see jmri.util.node.NodeIdentity
327     */
328    public JsonNode getNode(JsonRequest request) {
329        ObjectNode data = mapper.createObjectNode();
330        data.put(JSON.NODE, NodeIdentity.networkIdentity());
331        ArrayNode nodes = mapper.createArrayNode();
332        NodeIdentity.formerIdentities().stream().forEach(nodes::add);
333        data.set(JSON.FORMER_NODES, nodes);
334        return message(JSON.NODE, data, request.id);
335    }
336
337    /**
338     * return a JSON {@link jmri.server.json.JSON#NODE} message containing the
339     * requested panel details
340     *
341     * @param locale the client's Locale
342     * @param name   panel name to return
343     * @param id     message id set by client
344     * @return the JSON panel message.
345     * @throws JsonException if panel not found
346     */
347    public JsonNode getPanel(Locale locale, String name, int id) throws JsonException {
348        ArrayNode panels = getPanels(JSON.XML, id);
349        for (JsonNode panel : panels) {
350            if (panel.path(JSON.DATA).path(JSON.NAME).asText().equals(name)) {
351                return message(JSON.PANEL, panel.path(JSON.DATA), id);
352            }
353        }
354        throw new JsonException(404, Bundle.getMessage(locale, JsonException.ERROR_OBJECT, JSON.PANEL, name), id);
355    }
356
357    public ObjectNode getPanel(Editor editor, String format, int id) {
358        if (editor.getAllowInFrameServlet()) {
359            JFrame frame = editor.getTargetFrame();
360            if (frame != null) {
361                String title = frame.getTitle();
362                if (!title.isEmpty() &&
363                        !Arrays.asList(InstanceManager.getDefault(WebServerPreferences.class).getDisallowedFrames())
364                                .contains(title)) {
365                    String type = PANEL_PANEL;
366                    String name = "Panel";
367                    if (editor instanceof ControlPanelEditor) {
368                        type = CONTROL_PANEL;
369                        name = "ControlPanel";
370                    } else if (editor instanceof LayoutEditor) {
371                        type = LAYOUT_PANEL;
372                        name = "Layout";
373                    } else if (editor instanceof SwitchboardEditor) {
374                        type = SWITCHBOARD_PANEL;
375                        name = "Switchboard";
376                    }
377                    ObjectNode data = this.mapper.createObjectNode();
378                    data.put(NAME, name + "/" + title.replace(" ", "%20").replace("#", "%23")); // NOI18N
379                    data.put(URL, "/panel/" + data.path(NAME).asText() + "?format=" + format); // NOI18N
380                    data.put(USERNAME, title);
381                    data.put(TYPE, type);
382                    return message(PANEL, data, id);
383                }
384            }
385        }
386        return null;
387    }
388
389    public ArrayNode getPanels(String format, int id) {
390        ArrayNode root = mapper.createArrayNode();
391        // list loaded Panels (ControlPanelEditor, PanelEditor, LayoutEditor,
392        // SwitchboardEditor)
393        InstanceManager.getDefault(EditorManager.class).getAll().stream()
394                .map(editor -> this.getPanel(editor, format, id))
395                .filter(Objects::nonNull).forEach(root::add);
396        return root;
397    }
398
399    public ArrayNode getPanels(int id) {
400        return this.getPanels(JSON.XML, id);
401    }
402
403    /**
404     * return a JSON {@link jmri.server.json.JSON#NODE} message containing the
405     * Railroad from the Railroad Name preferences.
406     *
407     * @param request the JSON request
408     * @return the JSON railroad name message
409     */
410    public JsonNode getRailroad(@Nonnull JsonRequest request) {
411        ObjectNode data = mapper.createObjectNode();
412        data.put(JSON.NAME, InstanceManager.getDefault(WebServerPreferences.class).getRailroadName());
413        return message(JSON.RAILROAD, data, request.id);
414    }
415
416    /**
417     * return a JSON {@link jmri.server.json.JSON#NODE} message containing the
418     * requested systemConnection details
419     *
420     * @param name    system connection name to return
421     * @param request the JSON request
422     * @return the JSON systemConnections message
423     * @throws JsonException if systemConnection not found
424     */
425    public JsonNode getSystemConnection(String name, JsonRequest request) throws JsonException {
426        for (JsonNode connection : getSystemConnections(request)) {
427            JsonNode data = connection.path(JSON.DATA);
428            if (data.path(JSON.NAME).asText().equals(name)) {
429                return message(JSON.SYSTEM_CONNECTION, data, request.id);
430            }
431        }
432        throw new JsonException(HttpServletResponse.SC_NOT_FOUND,
433                Bundle.getMessage(request.locale, JsonException.ERROR_NOT_FOUND, JSON.SYSTEM_CONNECTION, name),
434                request.id);
435    }
436
437    /**
438     * return a JSON array containing the defined system connections
439     *
440     * @param request the JSON request
441     * @return the JSON systemConnections message.
442     */
443    public ArrayNode getSystemConnections(@Nonnull JsonRequest request) {
444        ArrayNode root = mapper.createArrayNode();
445        ArrayList<String> prefixes = new ArrayList<>();
446        for (ConnectionConfig config : InstanceManager.getDefault(ConnectionConfigManager.class)) {
447            if (!config.getDisabled()) {
448                ObjectNode data = mapper.createObjectNode();
449                data.put(JSON.NAME, config.getConnectionName());
450                data.put(JSON.PREFIX, config.getAdapter().getSystemConnectionMemo().getSystemPrefix());
451                data.put(JSON.MFG, config.getManufacturer());
452                data.put(JSON.DESCRIPTION,
453                        Bundle.getMessage(request.locale, "ConnectionSucceeded", config.getConnectionName(),
454                                config.name(), config.getInfo()));
455                prefixes.add(config.getAdapter().getSystemConnectionMemo().getSystemPrefix());
456                root.add(message(JSON.SYSTEM_CONNECTION, data, request.id));
457            }
458        }
459        InstanceManager.getList(SystemConnectionMemo.class).stream().map(instance -> instance)
460                .filter(memo -> (!memo.getDisabled() && !prefixes.contains(memo.getSystemPrefix())))
461                .forEach(memo -> {
462                    ObjectNode data = mapper.createObjectNode();
463                    data.put(JSON.NAME, memo.getUserName());
464                    data.put(JSON.PREFIX, memo.getSystemPrefix());
465                    data.putNull(JSON.MFG);
466                    data.putNull(JSON.DESCRIPTION);
467                    prefixes.add(memo.getSystemPrefix());
468                    root.add(message(JSON.SYSTEM_CONNECTION, data, request.id));
469                });
470        // Following is required because despite there being a
471        // SystemConnectionMemo for the default internal connection, it is not
472        // used for the default internal connection. This allows a client to map
473        // the server's internal objects.
474        SystemConnectionMemo internal = InstanceManager.getDefault(InternalSystemConnectionMemo.class);
475        if (!prefixes.contains(internal.getSystemPrefix())) {
476            ObjectNode data = mapper.createObjectNode();
477            data.put(JSON.NAME, internal.getUserName());
478            data.put(JSON.PREFIX, internal.getSystemPrefix());
479            data.putNull(JSON.MFG);
480            data.putNull(JSON.DESCRIPTION);
481            root.add(message(JSON.SYSTEM_CONNECTION, data, request.id));
482        }
483        return root;
484    }
485
486    /**
487     * Get a JSON message containing the requested configuration profile.
488     *
489     * @param profile the requested profile
490     * @param manager the in use profile manager
491     * @param request the JSON request
492     * @return the data for this profile as a JSON Node
493     */
494    private JsonNode getConfigProfile(@Nonnull Profile profile, @Nonnull ProfileManager manager,
495            @Nonnull JsonRequest request) {
496        boolean active = profile == manager.getActiveProfile();
497        boolean next = profile == manager.getNextActiveProfile();
498        boolean isAutoStart = (active && manager.isAutoStartActiveProfile());
499        ObjectNode data = mapper.createObjectNode();
500        data.put(JSON.USERNAME, profile.getName());
501        data.put(JSON.UNIQUE_ID, profile.getUniqueId());
502        data.put(JSON.NAME, profile.getId());
503        data.put(JSON.IS_ACTIVE_PROFILE, active);
504        if (request.version.equals(JSON.V5)) {
505            // this is not a property of a profile
506            data.put(JSON.IS_AUTO_START, isAutoStart);
507        }
508        data.put(JSON.IS_NEXT_PROFILE, next);
509        return message(JSON.CONFIG_PROFILE, data, request.id);
510    }
511
512    /**
513     * Get the named configuration profile.
514     *
515     * @param name    the Profile name
516     * @param request the JSON request
517     * @return the JSON configProfiles message
518     * @throws JsonException if the requested configProfile is not found
519     */
520    public JsonNode getConfigProfile(@Nonnull String name, @Nonnull JsonRequest request) throws JsonException {
521        ProfileManager manager = ProfileManager.getDefault();
522        for (Profile profile : manager.getProfiles()) {
523            if (profile.getId().equals(name)) {
524                return getConfigProfile(profile, manager, request);
525            }
526        }
527        throw new JsonException(HttpServletResponse.SC_NOT_FOUND,
528                Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, JSON.CONFIG_PROFILE, name),
529                request.id);
530    }
531
532    /**
533     * Get a JSON array of all configuration profiles.
534     *
535     * @param request the JSON request
536     * @return the JSON configProfiles message
537     */
538    public ArrayNode getConfigProfiles(@Nonnull JsonRequest request) {
539        ArrayNode root = mapper.createArrayNode();
540        ProfileManager manager = ProfileManager.getDefault();
541        for (Profile profile : manager.getProfiles()) {
542            if (profile != null) {
543                root.add(getConfigProfile(profile, manager, request));
544            }
545        }
546        return root;
547    }
548
549    /**
550     * Handle session login request.
551     *
552     * @param username the username (may be null if not provided in path)
553     * @param data JSON data containing username and password
554     * @param request the JSON request
555     * @return JSON response with session token or error
556     * @throws JsonException if login fails or parameters are missing
557     */
558    private JsonNode postSessionLogin(@CheckForNull String username, JsonNode data, JsonRequest request) throws JsonException {
559        // Extract username and password from JSON data
560        String user = username != null ? username : data.path("username").asText("");
561        String password = data.path("password").asText("");
562
563        if (user.isEmpty()) {
564            throw new JsonException(HttpServletResponse.SC_BAD_REQUEST,
565                    Bundle.getMessage(request.locale, "ErrorMissingParameter", "username"), request.id);
566        }
567
568        PermissionManager mngr = InstanceManager.getDefault(PermissionManager.class);
569
570        // Check if guest user
571        if (mngr.isAGuestUser(user)) {
572            throw new JsonException(HttpServletResponse.SC_FORBIDDEN,
573                    Bundle.getMessage(request.locale, "ErrorGuestLogin"), request.id);
574        }
575
576        // Attempt login
577        StringBuilder sessionId = new StringBuilder("");
578        boolean result = mngr.remoteLogin(sessionId, request.locale, user, password);
579
580        if (!result) {
581            throw new JsonException(HttpServletResponse.SC_UNAUTHORIZED,
582                    Bundle.getMessage(request.locale, "ErrorInvalidCredentials"), request.id);
583        }
584
585        // Build success response
586        ObjectNode root = mapper.createObjectNode();
587        root.put(TYPE, JSON.SESSION_LOGIN);
588        ObjectNode dataNode = root.putObject(JSON.DATA);
589        dataNode.put("authenticationToken", sessionId.toString());
590
591        log.debug("Successful login for user: {}", user);
592        return root;
593    }
594
595    /**
596     * Handle session logout request.
597     *
598     * @param username the username (may be null)
599     * @param data JSON data containing session token or username
600     * @param request the JSON request
601     * @return JSON response confirming logout
602     * @throws JsonException if logout fails
603     */
604    private JsonNode postSessionLogout(@CheckForNull String username, JsonNode data, JsonRequest request) throws JsonException {
605        // Extract token
606        String token = data.path("token").asText("");
607
608        PermissionManager mngr = InstanceManager.getDefault(PermissionManager.class);
609        mngr.remoteLogout(token);
610
611        // Build success response
612        ObjectNode root = mapper.createObjectNode();
613        root.put(TYPE, JSON.SESSION_LOGOUT);
614        ObjectNode dataNode = root.putObject(JSON.DATA);
615        dataNode.put("authenticationToken", token);
616
617        log.debug("Successful logout for token: {}", token);
618        return root;
619    }
620
621    /**
622     * Gets the {@link jmri.DccLocoAddress} for a String in the form
623     * {@code number(type)} or {@code number}.
624     * <p>
625     * Type may be {@code L} for long or {@code S} for short. If the type is not
626     * specified, type is assumed to be short.
627     *
628     * @param address the address
629     * @return The DccLocoAddress for address
630     */
631    public static DccLocoAddress addressForString(String address) {
632        String[] components = address.split("[()]");
633        int number = Integer.parseInt(components[0]);
634        boolean isLong = false;
635        if (components.length > 1 && "L".equalsIgnoreCase(components[1])) {
636            isLong = true;
637        }
638        return new DccLocoAddress(number, isLong);
639    }
640
641    @Override
642    public JsonNode doSchema(String type, boolean server, JsonRequest request) throws JsonException {
643        int id = request.id;
644        try {
645            switch (type) {
646                case JSON.CONFIG_PROFILE:
647                case JSON.CONFIG_PROFILES:
648                    return doSchema(type,
649                            server,
650                            "jmri/server/json/util/configProfile-server.json",
651                            "jmri/server/json/util/configProfile-client.json",
652                            id);
653                case JSON.NETWORK_SERVICE:
654                case JSON.NETWORK_SERVICES:
655                    return doSchema(type,
656                            server,
657                            "jmri/server/json/util/networkService-server.json",
658                            "jmri/server/json/util/networkService-client.json",
659                            id);
660                case JSON.PANEL:
661                case JSON.PANELS:
662                    return doSchema(type,
663                            server,
664                            "jmri/server/json/util/panel-server.json",
665                            "jmri/server/json/util/panel-client.json",
666                            id);
667                case JSON.SYSTEM_CONNECTION:
668                case JSON.SYSTEM_CONNECTIONS:
669                    return doSchema(type,
670                            server,
671                            "jmri/server/json/util/systemConnection-server.json",
672                            "jmri/server/json/util/systemConnection-client.json",
673                            id);
674                case JsonException.ERROR:
675                case JSON.LIST:
676                case JSON.PONG:
677                    if (server) {
678                        return doSchema(type, server,
679                                this.mapper.readTree(this.getClass().getClassLoader()
680                                        .getResource(RESOURCE_PATH + type + "-server.json")),
681                                id);
682                    } else {
683                        throw new JsonException(HttpServletResponse.SC_BAD_REQUEST,
684                                Bundle.getMessage(request.locale, "NotAClientType", type), id);
685                    }
686                case JSON.LOCALE:
687                case JSON.PING:
688                    if (!server) {
689                        return doSchema(type, server,
690                                this.mapper.readTree(this.getClass().getClassLoader()
691                                        .getResource(RESOURCE_PATH + type + "-client.json")),
692                                id);
693                    } else {
694                        throw new JsonException(HttpServletResponse.SC_BAD_REQUEST,
695                                Bundle.getMessage(request.locale, "NotAServerType", type), id);
696                    }
697                case JSON.GOODBYE:
698                case JSON.HELLO:
699                case JSON.METADATA:
700                case JSON.NODE:
701                case JSON.RAILROAD:
702                case JSON.VERSION:
703                case JSON.SESSION_LOGIN:
704                case JSON.SESSION_LOGOUT:
705                    return doSchema(type,
706                            server,
707                            RESOURCE_PATH + type + "-server.json",
708                            RESOURCE_PATH + type + "-client.json",
709                            id);
710                default:
711                    throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
712                            Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), id);
713            }
714        } catch (IOException ex) {
715            throw new JsonException(500, ex, id);
716        }
717    }
718}