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