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