org.openremote.web.console.service.JSONControllerConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.openremote.web.console.service.JSONControllerConnector.java

Source

/* OpenRemote, the Home of the Digital Home.
* Copyright 2008-2012, OpenRemote Inc.
*
* See the contributors.txt file in the distribution for a
* full listing of individual contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openremote.web.console.service;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openremote.web.console.controller.EnumControllerCommand;
import org.openremote.web.console.controller.EnumControllerResponseCode;
import org.openremote.web.console.panel.Panel;
import org.openremote.web.console.panel.PanelIdentityList;
import org.openremote.web.console.panel.entity.Status;
import org.openremote.web.console.panel.entity.StatusList;
import org.openremote.web.console.util.BrowserUtils;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;

/**
 * JSON Controller Service uses standard Request Builder allowing headers
 * to be added to the request. Caveat is that it is restricted by the same
 * origin policy (need to look at HTML5 CORS to see how this could solve the
 * problem). 
 *  
 * @author <a href="mailto:richard@openremote.org">Richard Turner</a>
 */
public class JSONControllerConnector implements ControllerConnector {

    private static String getJsonMethodUrl(EnumControllerCommand command) {
        String methodUrl = "";
        switch (command) {
        case GET_PANEL_LIST:
            methodUrl = "rest/panels/";
            break;
        case IS_SECURE:
        case GET_PANEL_LAYOUT:
            methodUrl = "rest/panel/";
            break;
        case SEND_COMMAND:
            methodUrl = "rest/control/";
            break;
        case GET_SENSOR_STATUS:
            methodUrl = "rest/status/";
            break;
        case DO_SENSOR_POLLING:
            methodUrl = "rest/polling/";
            break;
        case GET_ROUND_ROBIN_LIST:
            methodUrl = "rest/servers/";
            break;
        case IS_ALIVE:
            methodUrl = "rest/panels/";
            break;
        case LOGOUT:
            methodUrl = "rest/logout/";
        }
        return methodUrl;
    }

    public JSONControllerConnector() {

    }

    // ------------------------   Interface Overrides   -------------------------------------------

    @Override
    public void getPanelIdentities(String controllerUrl, String username, String password,
            AsyncControllerCallback<PanelIdentityList> callback) {
        EnumControllerCommand command = EnumControllerCommand.GET_PANEL_LIST;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl, command), username, password,
                new JSONControllerCallback(command, callback), 20000);
    }

    @Override
    public void getPanel(String controllerUrl, String username, String password, String panelName,
            AsyncControllerCallback<Panel> callback) {
        EnumControllerCommand command = EnumControllerCommand.GET_PANEL_LAYOUT;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl, new String[] { panelName }, command), username, password,
                new JSONControllerCallback(command, callback), 60000);
    }

    @Override
    public void isSecure(String controllerUrl, String username, String password,
            AsyncControllerCallback<Boolean> callback) {
        EnumControllerCommand command = EnumControllerCommand.IS_SECURE;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl, command), null, null,
                new JSONControllerCallback(command, callback), 20000);
    }

    @Override
    public void isAlive(String controllerUrl, String username, String password,
            AsyncControllerCallback<Boolean> callback) {
        EnumControllerCommand command = EnumControllerCommand.IS_ALIVE;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl, new String[] {}, command), username, password,
                new JSONControllerCallback(command, callback), 20000);
    }

    @Override
    public void sendCommand(String controllerUrl, String username, String password, String sendCommand,
            AsyncControllerCallback<Boolean> callback) {
        EnumControllerCommand command = EnumControllerCommand.SEND_COMMAND;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl, new String[] { sendCommand }, command), username,
                password, new JSONControllerCallback(command, callback));
    }

    @Override
    public void monitorSensors(String controllerUrl, String username, String password, Integer[] sensorIds,
            String uuid, AsyncControllerCallback<Map<Integer, String>> callback) {
        EnumControllerCommand command = EnumControllerCommand.DO_SENSOR_POLLING;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl,
                new String[] { uuid,
                        Arrays.toString(sensorIds).replace(", ", ",").replace("]", "").replace("[", "") },
                command), username, password, new JSONControllerCallback(command, callback), 55000);
    }

    @Override
    public void getSensorValues(String controllerUrl, String username, String password, Integer[] sensorIds,
            AsyncControllerCallback<Map<Integer, String>> callback) {
        EnumControllerCommand command = EnumControllerCommand.GET_SENSOR_STATUS;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl,
                new String[] { Arrays.toString(sensorIds).replace(", ", ",").replace("]", "").replace("[", "") },
                command), username, password, new JSONControllerCallback(command, callback), 20000);
    }

    @Override
    public void logout(String controllerUrl, AsyncControllerCallback<Boolean> callback) {
        EnumControllerCommand command = EnumControllerCommand.LOGOUT;
        doJsonRequest(buildCompleteJsonUrl(controllerUrl, new String[] {}, command), null, null,
                new JSONControllerCallback(command, callback));
    }

    // ------------------------   Interface Overrides End -------------------------------------------

    @SuppressWarnings("unchecked")
    private class JSONControllerCallback implements RequestCallback {
        EnumControllerCommand command;
        AsyncControllerCallback<?> callback;

        protected JSONControllerCallback(EnumControllerCommand command, AsyncControllerCallback<?> callback) {
            this.command = command;
            this.callback = callback;
        }

        @Override
        public void onError(Request request, Throwable caught) {
            callback.onFailure(caught);
        }

        @Override
        public void onResponseReceived(Request request, Response response) {
            int statusCode = response.getStatusCode();

            // No response timeouts seem to end up here
            if (statusCode == 0) {
                if (command == EnumControllerCommand.DO_SENSOR_POLLING) {
                    ((AsyncControllerCallback<Map<Integer, String>>) callback).onSuccess(null);
                    return;
                }

                // Timeout occurred
                ((AsyncControllerCallback<Boolean>) callback).onFailure(EnumControllerResponseCode.NO_RESPONSE);
            }

            // If logout then ignore response
            if (command == EnumControllerCommand.LOGOUT) {
                AsyncControllerCallback<Boolean> logoutCallback = (AsyncControllerCallback<Boolean>) callback;
                logoutCallback.onSuccess(true);
                return;
            }

            // Check secure request
            if (command == EnumControllerCommand.IS_SECURE) {
                AsyncControllerCallback<Boolean> isSecureCallback = (AsyncControllerCallback<Boolean>) callback;

                if (statusCode == 401) {
                    isSecureCallback.onSuccess(true);
                    return;
                }
            }

            JSONObject jsonObj = null;

            if (response != null && response.getStatusCode() == 200) {
                String str = response.getText();
                JSONValue value = JSONParser.parseStrict(str);
                jsonObj = (JSONObject) value;
            }

            if (jsonObj == null) {
                //callback.onFailure(new Exception(new Exception("Unknown Error JSON Response is Empty")));
                callback.onFailure(EnumControllerResponseCode.UNKNOWN_ERROR);
                return;
            }

            // Check for JSON Error Response and if set throw appropriate exception
            // Added support for string error codes to work with Controller Proxy
            int errorCode = 0;

            if (jsonObj.containsKey("error")) {
                JSONObject jsonObj2 = jsonObj.get("error").isObject();
                if (jsonObj2 == null) {
                    callback.onFailure(EnumControllerResponseCode.UNKNOWN_ERROR);
                    return;
                }
                JSONNumber number = jsonObj2.get("code").isNumber();
                if (number == null) {
                    JSONString str = jsonObj2.get("code").isString();
                    if (str != null && !str.equals("")) {
                        errorCode = Integer.parseInt(str.stringValue());
                    } else {
                        callback.onFailure(EnumControllerResponseCode.UNKNOWN_ERROR);
                        return;
                    }
                } else {
                    errorCode = (int) number.doubleValue();
                }

                if (!((command == EnumControllerCommand.IS_SECURE)
                        || (command == EnumControllerCommand.DO_SENSOR_POLLING && errorCode == 504)
                        || errorCode == 200)) {
                    callback.onFailure(EnumControllerResponseCode.getResponseCode(errorCode));
                    return;
                }
            }

            // If we've got this far then we assume JSON response is correctly formatted so we build the response object
            try {
                switch (command) {
                case GET_PANEL_LIST:
                    AsyncControllerCallback<PanelIdentityList> panelListCallback = (AsyncControllerCallback<PanelIdentityList>) callback;
                    PanelIdentityList panels = AutoBeanService.getInstance()
                            .fromJsonString(PanelIdentityList.class, jsonObj.toString()).as();
                    panelListCallback.onSuccess(panels);
                    break;
                case GET_PANEL_LAYOUT:
                    AsyncControllerCallback<Panel> panelLayoutCallback = (AsyncControllerCallback<Panel>) callback;
                    Panel panel = AutoBeanService.getInstance().fromJsonString(Panel.class, jsonObj.toString())
                            .as();
                    panelLayoutCallback.onSuccess(panel);
                    break;
                case IS_ALIVE:
                    AsyncControllerCallback<Boolean> isAliveCallback = (AsyncControllerCallback<Boolean>) callback;
                    isAliveCallback.onSuccess(true);
                    break;
                case IS_SECURE:
                    AsyncControllerCallback<Boolean> isSecureCallback = (AsyncControllerCallback<Boolean>) callback;
                    if (errorCode == 401) {
                        isSecureCallback.onSuccess(true);
                    } else {
                        isSecureCallback.onSuccess(false);
                    }
                    break;
                case SEND_COMMAND:
                    AsyncControllerCallback<Boolean> successCallback = (AsyncControllerCallback<Boolean>) callback;
                    if (errorCode == 200) {
                        successCallback.onSuccess(true);
                    } else {
                        successCallback.onSuccess(false);
                    }
                    break;
                case DO_SENSOR_POLLING:
                    AsyncControllerCallback<Map<Integer, String>> pollingCallback = (AsyncControllerCallback<Map<Integer, String>>) callback;
                    Map<Integer, String> pollValues = new HashMap<Integer, String>();
                    if (errorCode == 504) {
                        pollingCallback.onSuccess(null);
                    } else {
                        List<Status> statuses = AutoBeanService.getInstance()
                                .fromJsonString(StatusList.class, jsonObj.toString()).as().getStatus();
                        if (statuses != null) {
                            for (Status status : statuses) {
                                if (status != null) {
                                    pollValues.put(status.getId(), status.getContent());
                                }
                            }
                            pollingCallback.onSuccess(pollValues);
                        }
                    }
                    break;
                case GET_SENSOR_STATUS:
                    AsyncControllerCallback<Map<Integer, String>> statusCallback = (AsyncControllerCallback<Map<Integer, String>>) callback;
                    Map<Integer, String> statusValues = new HashMap<Integer, String>();
                    List<Status> statuses = AutoBeanService.getInstance()
                            .fromJsonString(StatusList.class, jsonObj.toString()).as().getStatus();
                    if (statuses != null) {
                        for (Status status : statuses) {
                            if (status != null) {
                                statusValues.put(status.getId(), status.getContent());
                            }
                        }
                        statusCallback.onSuccess(statusValues);
                    }
                    break;
                case LOGOUT:
                    AsyncControllerCallback<Boolean> logoutCallback = (AsyncControllerCallback<Boolean>) callback;
                    if (errorCode == 401) {
                        logoutCallback.onSuccess(true);
                    } else {
                        logoutCallback.onSuccess(false);
                    }
                    break;
                }
            } catch (Exception e) {
                callback.onFailure(EnumControllerResponseCode.UNKNOWN_ERROR);
            }
        }
    };

    private void doJsonRequest(String url, String username, String password, JSONControllerCallback callback) {
        doJsonRequest(url, username, password, callback, null);
    }

    private void doJsonRequest(String url, String username, String password, JSONControllerCallback callback,
            Integer timeout) {
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
        Request request = null;

        // Add accept header
        builder.setHeader("Accept", "application/json");

        if (username != null && username.length() > 0) {
            if (password == null)
                password = "";

            // Add authentication header
            String authStr = username + ":" + password;
            String authEnc = "Basic " + BrowserUtils.base64Encode(authStr);
            builder.setHeader("Authorization", authEnc);
        }

        builder.setCallback(callback);

        if (timeout != null) {
            builder.setTimeoutMillis(timeout);
        }

        try {
            request = builder.send();
        } catch (RequestException e) {
            callback.onError(request, e);
        }
    }

    // ------------------------   Internal JSON Methods ------------------------------------------

    private static String buildCompleteJsonUrl(String controllerUrl, EnumControllerCommand command) {
        return buildCompleteJsonUrl(controllerUrl, new String[0], command);
    }

    private static String buildCompleteJsonUrl(String controllerUrl, String[] params,
            EnumControllerCommand command) {
        String url = controllerUrl;
        int paramCounter = 0;
        url = url.endsWith("/") ? url : url + "/";
        String methodUrl = getJsonMethodUrl(command);

        if (methodUrl.equals("")) {
            return "";
        } else {
            url += methodUrl;
        }

        for (String param : params) {
            url += param;
            paramCounter++;
            if (paramCounter < params.length) {
                url = url.endsWith("/") ? url : url + "/";
            }
        }

        url = URL.encode(url);
        return url;
    }
}