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;
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     * Get a JSON message with a metadata element from {@link jmri.Metadata}.
196     *
197     * @param name    The metadata element to get
198     * @param request the JSON request
199     * @return JSON metadata element
200     * @throws JsonException if name is not a recognized metadata element
201     */
202    public JsonNode getMetadata(@Nonnull String name, @Nonnull JsonRequest request) throws JsonException {
203        String metadata = Metadata.getBySystemName(name);
204        ObjectNode data = mapper.createObjectNode();
205        if (metadata != null) {
206            data.put(JSON.NAME, name);
207            data.put(JSON.VALUE, Metadata.getBySystemName(name));
208        } else {
209            throw new JsonException(404,
210                    Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, JSON.METADATA, name),
211                    request.id);
212        }
213        return message(JSON.METADATA, data, request.id);
214    }
215
216    /**
217     * Get a JSON message with a metadata element from {@link jmri.Metadata}.
218     *
219     * @param locale The client's Locale.
220     * @param name   The metadata element to get.
221     * @param id     message id set by client
222     * @return JSON metadata element.
223     * @throws JsonException if name is not a recognized metadata element.
224     */
225    public JsonNode getMetadata(Locale locale, String name, int id) throws JsonException {
226        return getMetadata(name, new JsonRequest(locale, JSON.V5, JSON.GET, id));
227    }
228
229    /**
230     * Get a JSON array of metadata elements as listed by
231     * {@link jmri.Metadata#getSystemNameList()}.
232     *
233     * @param request the JSON request
234     * @return Array of JSON metadata elements
235     * @throws JsonException if thrown by
236     *                       {@link #getMetadata(java.util.Locale, java.lang.String, int)}
237     */
238    public ArrayNode getMetadata(@Nonnull JsonRequest request) throws JsonException {
239        ArrayNode root = mapper.createArrayNode();
240        for (String name : Metadata.getSystemNameList()) {
241            root.add(getMetadata(name, request));
242        }
243        return root;
244    }
245
246    /**
247     * Get a running {@link jmri.util.zeroconf.ZeroConfService} using the
248     * protocol as the name of the service.
249     *
250     * @param name    the service protocol
251     * @param request the JSON request
252     * @return the JSON networkService message
253     * @throws JsonException if type is not a running zeroconf networking
254     *                       protocol
255     */
256    public JsonNode getNetworkService(@Nonnull String name, @Nonnull JsonRequest request) throws JsonException {
257        for (ZeroConfService service : InstanceManager.getDefault(ZeroConfServiceManager.class).allServices()) {
258            if (service.getType().equals(name)) {
259                return this.getNetworkService(service, request.id);
260            }
261        }
262        throw new JsonException(404,
263                Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, JSON.NETWORK_SERVICE, name),
264                request.id);
265    }
266
267    private JsonNode getNetworkService(ZeroConfService service, int id) {
268        ObjectNode data = mapper.createObjectNode();
269        data.put(JSON.NAME, service.getType());
270        data.put(JSON.USERNAME, service.getName());
271        data.put(JSON.PORT, service.getServiceInfo().getPort());
272        data.put(JSON.TYPE, service.getType());
273        Enumeration<String> pe = service.getServiceInfo().getPropertyNames();
274        while (pe.hasMoreElements()) {
275            String pn = pe.nextElement();
276            data.put(pn, service.getServiceInfo().getPropertyString(pn));
277        }
278        return message(JSON.NETWORK_SERVICE, data, id);
279    }
280
281    /**
282     * @param request the JSON request
283     * @return the JSON networkServices message.
284     */
285    public ArrayNode getNetworkServices(@Nonnull JsonRequest request) {
286        ArrayNode root = mapper.createArrayNode();
287        InstanceManager.getDefault(ZeroConfServiceManager.class).allServices().stream()
288                .forEach(service -> root.add(this.getNetworkService(service, request.id)));
289        return root;
290    }
291
292    /**
293     * @param locale the client's Locale.
294     * @param id     message id set by client
295     * @return the JSON networkServices message.
296     */
297    public ArrayNode getNetworkServices(Locale locale, int id) {
298        return getNetworkServices(new JsonRequest(locale, JSON.V5, JSON.GET, id));
299    }
300
301    /**
302     * Send a JSON {@link jmri.server.json.JSON#NODE} message containing the
303     * JMRI node identity and former identities.
304     *
305     * @param request the JSON request
306     * @return the JSON node message
307     * @see jmri.util.node.NodeIdentity
308     */
309    public JsonNode getNode(JsonRequest request) {
310        ObjectNode data = mapper.createObjectNode();
311        data.put(JSON.NODE, NodeIdentity.networkIdentity());
312        ArrayNode nodes = mapper.createArrayNode();
313        NodeIdentity.formerIdentities().stream().forEach(nodes::add);
314        data.set(JSON.FORMER_NODES, nodes);
315        return message(JSON.NODE, data, request.id);
316    }
317
318    /**
319     * return a JSON {@link jmri.server.json.JSON#NODE} message containing the
320     * requested panel details
321     *
322     * @param locale the client's Locale
323     * @param name   panel name to return
324     * @param id     message id set by client
325     * @return the JSON panel message.
326     * @throws JsonException if panel not found
327     */
328    public JsonNode getPanel(Locale locale, String name, int id) throws JsonException {
329        ArrayNode panels = getPanels(JSON.XML, id);
330        for (JsonNode panel : panels) {
331            if (panel.path(JSON.DATA).path(JSON.NAME).asText().equals(name)) {
332                return message(JSON.PANEL, panel.path(JSON.DATA), id);
333            }
334        }
335        throw new JsonException(404, Bundle.getMessage(locale, JsonException.ERROR_OBJECT, JSON.PANEL, name), id);
336    }
337
338    public ObjectNode getPanel(Editor editor, String format, int id) {
339        if (editor.getAllowInFrameServlet()) {
340            JFrame frame = editor.getTargetFrame();
341            if (frame != null) {
342                String title = frame.getTitle();
343                if (!title.isEmpty() &&
344                        !Arrays.asList(InstanceManager.getDefault(WebServerPreferences.class).getDisallowedFrames())
345                                .contains(title)) {
346                    String type = PANEL_PANEL;
347                    String name = "Panel";
348                    if (editor instanceof ControlPanelEditor) {
349                        type = CONTROL_PANEL;
350                        name = "ControlPanel";
351                    } else if (editor instanceof LayoutEditor) {
352                        type = LAYOUT_PANEL;
353                        name = "Layout";
354                    } else if (editor instanceof SwitchboardEditor) {
355                        type = SWITCHBOARD_PANEL;
356                        name = "Switchboard";
357                    }
358                    ObjectNode data = this.mapper.createObjectNode();
359                    data.put(NAME, name + "/" + title.replace(" ", "%20").replace("#", "%23")); // NOI18N
360                    data.put(URL, "/panel/" + data.path(NAME).asText() + "?format=" + format); // NOI18N
361                    data.put(USERNAME, title);
362                    data.put(TYPE, type);
363                    return message(PANEL, data, id);
364                }
365            }
366        }
367        return null;
368    }
369
370    public ArrayNode getPanels(String format, int id) {
371        ArrayNode root = mapper.createArrayNode();
372        // list loaded Panels (ControlPanelEditor, PanelEditor, LayoutEditor,
373        // SwitchboardEditor)
374        InstanceManager.getDefault(EditorManager.class).getAll().stream()
375                .map(editor -> this.getPanel(editor, format, id))
376                .filter(Objects::nonNull).forEach(root::add);
377        return root;
378    }
379
380    public ArrayNode getPanels(int id) {
381        return this.getPanels(JSON.XML, id);
382    }
383
384    /**
385     * return a JSON {@link jmri.server.json.JSON#NODE} message containing the
386     * Railroad from the Railroad Name preferences.
387     *
388     * @param request the JSON request
389     * @return the JSON railroad name message
390     */
391    public JsonNode getRailroad(@Nonnull JsonRequest request) {
392        ObjectNode data = mapper.createObjectNode();
393        data.put(JSON.NAME, InstanceManager.getDefault(WebServerPreferences.class).getRailroadName());
394        return message(JSON.RAILROAD, data, request.id);
395    }
396
397    /**
398     * return a JSON {@link jmri.server.json.JSON#NODE} message containing the
399     * requested systemConnection details
400     *
401     * @param name    system connection name to return
402     * @param request the JSON request
403     * @return the JSON systemConnections message
404     * @throws JsonException if systemConnection not found
405     */
406    public JsonNode getSystemConnection(String name, JsonRequest request) throws JsonException {
407        for (JsonNode connection : getSystemConnections(request)) {
408            JsonNode data = connection.path(JSON.DATA);
409            if (data.path(JSON.NAME).asText().equals(name)) {
410                return message(JSON.SYSTEM_CONNECTION, data, request.id);
411            }
412        }
413        throw new JsonException(HttpServletResponse.SC_NOT_FOUND,
414                Bundle.getMessage(request.locale, JsonException.ERROR_NOT_FOUND, JSON.SYSTEM_CONNECTION, name),
415                request.id);
416    }
417
418    /**
419     * return a JSON array containing the defined system connections
420     *
421     * @param request the JSON request
422     * @return the JSON systemConnections message.
423     */
424    public ArrayNode getSystemConnections(@Nonnull JsonRequest request) {
425        ArrayNode root = mapper.createArrayNode();
426        ArrayList<String> prefixes = new ArrayList<>();
427        for (ConnectionConfig config : InstanceManager.getDefault(ConnectionConfigManager.class)) {
428            if (!config.getDisabled()) {
429                ObjectNode data = mapper.createObjectNode();
430                data.put(JSON.NAME, config.getConnectionName());
431                data.put(JSON.PREFIX, config.getAdapter().getSystemConnectionMemo().getSystemPrefix());
432                data.put(JSON.MFG, config.getManufacturer());
433                data.put(JSON.DESCRIPTION,
434                        Bundle.getMessage(request.locale, "ConnectionSucceeded", config.getConnectionName(),
435                                config.name(), config.getInfo()));
436                prefixes.add(config.getAdapter().getSystemConnectionMemo().getSystemPrefix());
437                root.add(message(JSON.SYSTEM_CONNECTION, data, request.id));
438            }
439        }
440        InstanceManager.getList(SystemConnectionMemo.class).stream().map(instance -> instance)
441                .filter(memo -> (!memo.getDisabled() && !prefixes.contains(memo.getSystemPrefix())))
442                .forEach(memo -> {
443                    ObjectNode data = mapper.createObjectNode();
444                    data.put(JSON.NAME, memo.getUserName());
445                    data.put(JSON.PREFIX, memo.getSystemPrefix());
446                    data.putNull(JSON.MFG);
447                    data.putNull(JSON.DESCRIPTION);
448                    prefixes.add(memo.getSystemPrefix());
449                    root.add(message(JSON.SYSTEM_CONNECTION, data, request.id));
450                });
451        // Following is required because despite there being a
452        // SystemConnectionMemo for the default internal connection, it is not
453        // used for the default internal connection. This allows a client to map
454        // the server's internal objects.
455        SystemConnectionMemo internal = InstanceManager.getDefault(InternalSystemConnectionMemo.class);
456        if (!prefixes.contains(internal.getSystemPrefix())) {
457            ObjectNode data = mapper.createObjectNode();
458            data.put(JSON.NAME, internal.getUserName());
459            data.put(JSON.PREFIX, internal.getSystemPrefix());
460            data.putNull(JSON.MFG);
461            data.putNull(JSON.DESCRIPTION);
462            root.add(message(JSON.SYSTEM_CONNECTION, data, request.id));
463        }
464        return root;
465    }
466
467    /**
468     * Get a JSON message containing the requested configuration profile.
469     *
470     * @param profile the requested profile
471     * @param manager the in use profile manager
472     * @param request the JSON request
473     * @return the data for this profile as a JSON Node
474     */
475    private JsonNode getConfigProfile(@Nonnull Profile profile, @Nonnull ProfileManager manager,
476            @Nonnull JsonRequest request) {
477        boolean active = profile == manager.getActiveProfile();
478        boolean next = profile == manager.getNextActiveProfile();
479        boolean isAutoStart = (active && manager.isAutoStartActiveProfile());
480        ObjectNode data = mapper.createObjectNode();
481        data.put(JSON.USERNAME, profile.getName());
482        data.put(JSON.UNIQUE_ID, profile.getUniqueId());
483        data.put(JSON.NAME, profile.getId());
484        data.put(JSON.IS_ACTIVE_PROFILE, active);
485        if (request.version.equals(JSON.V5)) {
486            // this is not a property of a profile
487            data.put(JSON.IS_AUTO_START, isAutoStart);
488        }
489        data.put(JSON.IS_NEXT_PROFILE, next);
490        return message(JSON.CONFIG_PROFILE, data, request.id);
491    }
492
493    /**
494     * Get the named configuration profile.
495     *
496     * @param name    the Profile name
497     * @param request the JSON request
498     * @return the JSON configProfiles message
499     * @throws JsonException if the requested configProfile is not found
500     */
501    public JsonNode getConfigProfile(@Nonnull String name, @Nonnull JsonRequest request) throws JsonException {
502        ProfileManager manager = ProfileManager.getDefault();
503        for (Profile profile : manager.getProfiles()) {
504            if (profile.getId().equals(name)) {
505                return getConfigProfile(profile, manager, request);
506            }
507        }
508        throw new JsonException(HttpServletResponse.SC_NOT_FOUND,
509                Bundle.getMessage(request.locale, JsonException.ERROR_OBJECT, JSON.CONFIG_PROFILE, name),
510                request.id);
511    }
512
513    /**
514     * Get a JSON array of all configuration profiles.
515     *
516     * @param request the JSON request
517     * @return the JSON configProfiles message
518     */
519    public ArrayNode getConfigProfiles(@Nonnull JsonRequest request) {
520        ArrayNode root = mapper.createArrayNode();
521        ProfileManager manager = ProfileManager.getDefault();
522        for (Profile profile : manager.getProfiles()) {
523            if (profile != null) {
524                root.add(getConfigProfile(profile, manager, request));
525            }
526        }
527        return root;
528    }
529
530    /**
531     * Gets the {@link jmri.DccLocoAddress} for a String in the form
532     * {@code number(type)} or {@code number}.
533     * <p>
534     * Type may be {@code L} for long or {@code S} for short. If the type is not
535     * specified, type is assumed to be short.
536     *
537     * @param address the address
538     * @return The DccLocoAddress for address
539     */
540    public static DccLocoAddress addressForString(String address) {
541        String[] components = address.split("[()]");
542        int number = Integer.parseInt(components[0]);
543        boolean isLong = false;
544        if (components.length > 1 && "L".equalsIgnoreCase(components[1])) {
545            isLong = true;
546        }
547        return new DccLocoAddress(number, isLong);
548    }
549
550    @Override
551    public JsonNode doSchema(String type, boolean server, JsonRequest request) throws JsonException {
552        int id = request.id;
553        try {
554            switch (type) {
555                case JSON.CONFIG_PROFILE:
556                case JSON.CONFIG_PROFILES:
557                    return doSchema(type,
558                            server,
559                            "jmri/server/json/util/configProfile-server.json",
560                            "jmri/server/json/util/configProfile-client.json",
561                            id);
562                case JSON.NETWORK_SERVICE:
563                case JSON.NETWORK_SERVICES:
564                    return doSchema(type,
565                            server,
566                            "jmri/server/json/util/networkService-server.json",
567                            "jmri/server/json/util/networkService-client.json",
568                            id);
569                case JSON.PANEL:
570                case JSON.PANELS:
571                    return doSchema(type,
572                            server,
573                            "jmri/server/json/util/panel-server.json",
574                            "jmri/server/json/util/panel-client.json",
575                            id);
576                case JSON.SYSTEM_CONNECTION:
577                case JSON.SYSTEM_CONNECTIONS:
578                    return doSchema(type,
579                            server,
580                            "jmri/server/json/util/systemConnection-server.json",
581                            "jmri/server/json/util/systemConnection-client.json",
582                            id);
583                case JsonException.ERROR:
584                case JSON.LIST:
585                case JSON.PONG:
586                    if (server) {
587                        return doSchema(type, server,
588                                this.mapper.readTree(this.getClass().getClassLoader()
589                                        .getResource(RESOURCE_PATH + type + "-server.json")),
590                                id);
591                    } else {
592                        throw new JsonException(HttpServletResponse.SC_BAD_REQUEST,
593                                Bundle.getMessage(request.locale, "NotAClientType", type), id);
594                    }
595                case JSON.LOCALE:
596                case JSON.PING:
597                    if (!server) {
598                        return doSchema(type, server,
599                                this.mapper.readTree(this.getClass().getClassLoader()
600                                        .getResource(RESOURCE_PATH + type + "-client.json")),
601                                id);
602                    } else {
603                        throw new JsonException(HttpServletResponse.SC_BAD_REQUEST,
604                                Bundle.getMessage(request.locale, "NotAServerType", type), id);
605                    }
606                case JSON.GOODBYE:
607                case JSON.HELLO:
608                case JSON.METADATA:
609                case JSON.NODE:
610                case JSON.RAILROAD:
611                case JSON.VERSION:
612                    return doSchema(type,
613                            server,
614                            RESOURCE_PATH + type + "-server.json",
615                            RESOURCE_PATH + type + "-client.json",
616                            id);
617                default:
618                    throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
619                            Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), id);
620            }
621        } catch (IOException ex) {
622            throw new JsonException(500, ex, id);
623        }
624    }
625}