com.rapid.actions.Webservice.java Source code

Java tutorial

Introduction

Here is the source code for com.rapid.actions.Webservice.java

Source

/*
    
Copyright (C) 2015 - Gareth Edwards / Rapid Information Systems
    
gareth.edwards@rapid-is.co.uk
    
    
This file is part of the Rapid Application Platform
    
Rapid 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. The terms require you 
to include the original copyright, and the license notice in all redistributions.
    
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
in a file named "COPYING".  If not, see <http://www.gnu.org/licenses/>.
    
*/

package com.rapid.actions;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.rapid.core.Action;
import com.rapid.core.Application;
import com.rapid.core.Control;
import com.rapid.core.Page;
import com.rapid.core.Parameter;
import com.rapid.server.ActionCache;
import com.rapid.server.RapidHttpServlet;
import com.rapid.server.RapidRequest;
import com.rapid.soa.SOAData;
import com.rapid.soa.SOADataReader.SOAJSONReader;
import com.rapid.soa.SOADataWriter;
import com.rapid.soa.SOADataReader.SOAXMLReader;
import com.rapid.soa.SOADataWriter.SOARapidWriter;
import com.rapid.utils.Strings;
import com.rapid.utils.XML;

public class Webservice extends Action {

    // details of the request (inputs, sql, outputs)
    public static class Request {

        private String _type, _url, _action, _body, _transform, _root;
        private ArrayList<Parameter> _inputs, _outputs;

        public ArrayList<Parameter> getInputs() {
            return _inputs;
        }

        public void setInputs(ArrayList<Parameter> inputs) {
            _inputs = inputs;
        }

        public String getType() {
            return _type;
        }

        public void setType(String type) {
            _type = type;
        }

        public String getUrl() {
            return _url;
        }

        public void setUrl(String url) {
            _url = url;
        }

        public String getAction() {
            return _action;
        }

        public void setAction(String action) {
            _action = action;
        }

        public String getBody() {
            return _body;
        }

        public void setBody(String body) {
            _body = body;
        }

        public String getTransform() {
            return _transform;
        }

        public void setTransform(String transform) {
            _transform = transform;
        }

        public String getRoot() {
            return _root;
        }

        public void setRoot(String root) {
            _root = root;
        }

        public ArrayList<Parameter> getOutputs() {
            return _outputs;
        }

        public void setOutputs(ArrayList<Parameter> outputs) {
            _outputs = outputs;
        }

        public Request() {
        };

        public Request(ArrayList<Parameter> inputs, String type, String url, String action, String body,
                String transform, String root, ArrayList<Parameter> outputs) {
            _inputs = inputs;
            _type = type;
            _url = url;
            _action = action;
            _body = body;
            _transform = transform;
            _root = root;
            _outputs = outputs;
        }

    }

    // static variables
    private static Logger _logger = Logger.getLogger(Webservice.class);

    // instance variables

    private Request _request;
    private boolean _showLoading;
    private ArrayList<Action> _successActions, _errorActions, _childActions;

    // properties

    public Request getRequest() {
        return _request;
    }

    public void setRequest(Request request) {
        _request = request;
    }

    public boolean getShowLoading() {
        return _showLoading;
    }

    public void setShowLoading(boolean showLoading) {
        _showLoading = showLoading;
    }

    public ArrayList<Action> getSuccessActions() {
        return _successActions;
    }

    public void setSuccessActions(ArrayList<Action> successActions) {
        _successActions = successActions;
    }

    public ArrayList<Action> getErrorActions() {
        return _errorActions;
    }

    public void setErrorActions(ArrayList<Action> errorActions) {
        _errorActions = errorActions;
    }

    // constructors

    // jaxb
    public Webservice() {
        super();
    }

    // designer
    public Webservice(RapidHttpServlet rapidServlet, JSONObject jsonAction) throws Exception {
        // set the xml version
        super();

        // save all key/values from the json into the properties 
        for (String key : JSONObject.getNames(jsonAction)) {
            // add all json properties to our properties, except for query
            if (!"request".equals(key) && !"root".equals(key) && !"showLoading".equals(key)
                    && !"successActions".equals(key) && !"errorActions".equals(key))
                addProperty(key, jsonAction.get(key).toString());
        }

        // try and build the query object
        JSONObject jsonQuery = jsonAction.optJSONObject("request");

        // check we got one
        if (jsonQuery != null) {
            // get the parameters                  
            ArrayList<Parameter> inputs = getParameters(jsonQuery.optJSONArray("inputs"));
            String type = jsonQuery.optString("type");
            String url = jsonQuery.optString("url");
            String action = jsonQuery.optString("action");
            String body = jsonQuery.optString("body");
            String transform = jsonQuery.optString("transform");
            String root = jsonQuery.optString("root");
            ArrayList<Parameter> outputs = getParameters(jsonQuery.optJSONArray("outputs"));
            // make the object
            _request = new Request(inputs, type, url, action, body, transform, root, outputs);
        }

        // look for showLoading
        _showLoading = jsonAction.optBoolean("showLoading");

        // grab any successActions
        JSONArray jsonSuccessActions = jsonAction.optJSONArray("successActions");
        // if we had some
        if (jsonSuccessActions != null) {
            // instantiate our success actions collection
            _successActions = Control.getActions(rapidServlet, jsonSuccessActions);
        }

        // grab any errorActions
        JSONArray jsonErrorActions = jsonAction.optJSONArray("errorActions");
        // if we had some
        if (jsonErrorActions != null) {
            // instantiate our error actions collection
            _errorActions = Control.getActions(rapidServlet, jsonErrorActions);
        }

    }

    // this is used to get both input and output parameters
    private ArrayList<Parameter> getParameters(JSONArray jsonParameters) throws Exception {
        // prepare return
        ArrayList<Parameter> parameters = null;
        // check
        if (jsonParameters != null) {
            // instantiate collection
            parameters = new ArrayList<Parameter>();
            // loop
            for (int i = 0; i < jsonParameters.length(); i++) {
                // instaniate member
                Parameter parameter = new Parameter(jsonParameters.getJSONObject(i).optString("itemId"),
                        jsonParameters.getJSONObject(i).optString("field"));
                // add member
                parameters.add(parameter);
            }
        }
        // return
        return parameters;
    }

    public String getLoadingJS(Page page, List<Parameter> parameters, boolean show) {
        String js = "";
        // check there are parameters
        if (parameters != null) {
            // loop the output parameters
            for (int i = 0; i < parameters.size(); i++) {
                // get the parameter
                Parameter output = parameters.get(i);
                // get the control the data is going into
                Control control = page.getControl(output.getItemId());
                // check the control still exists
                if (control != null) {
                    if ("grid".equals(control.getType())) {
                        if (show) {
                            js += "  $('#" + control.getId() + "').showLoading();\n";
                        } else {
                            js += "  $('#" + control.getId() + "').hideLoading();\n";
                        }
                    }
                }
            }

        }
        return js;
    }

    // overrides

    @Override
    public List<Action> getChildActions() {
        // initialise and populate on first get
        if (_childActions == null) {
            // our list of all child actions
            _childActions = new ArrayList<Action>();
            // add child success actions
            if (_successActions != null) {
                for (Action action : _successActions)
                    _childActions.add(action);
            }
            // add child error actions
            if (_errorActions != null) {
                for (Action action : _errorActions)
                    _childActions.add(action);
            }
        }
        return _childActions;
    }

    @Override
    public String getJavaScript(RapidRequest rapidRequest, Application application, Page page, Control control,
            JSONObject jsonDetails) throws Exception {

        String js = "";

        if (_request != null) {

            // get the rapid servlet
            RapidHttpServlet rapidServlet = rapidRequest.getRapidServlet();

            // get the most recent sequence number for this action to stop slow-running early requests overwriting the results of fast later requests
            js += "var sequence = getWebserviceActionSequence('" + getId() + "');\n";

            // drop in the query variable which holds our inputs and sequence
            js += "var query = { inputs:[], sequence:sequence };\n";

            // build the inputs
            if (_request.getInputs() != null) {
                for (Parameter parameter : _request.getInputs()) {
                    String itemId = parameter.getItemId();
                    if (itemId != null) {
                        // get any parameter field
                        String field = parameter.getField();
                        // check if there was one
                        if (field == null) {
                            // no field
                            js += "  query.inputs.push({id:'" + itemId + "',value:" + Control.getDataJavaScript(
                                    rapidServlet.getServletContext(), application, page, itemId, null) + "});\n";
                        } else {
                            // got field so let in appear in the inputs for matching later
                            js += "  query.inputs.push({id:'" + itemId + "',value:"
                                    + Control.getDataJavaScript(rapidServlet.getServletContext(), application, page,
                                            itemId, field)
                                    + ",field:'" + field + "'});\n";
                        }
                    }
                }
            } // got inputs

            // control can be null when the action is called from the page load
            String controlParam = "";
            if (control != null)
                controlParam = "&c=" + control.getId();

            // get the outputs
            ArrayList<Parameter> outputs = _request.getOutputs();

            // instantiate the jsonDetails if required
            if (jsonDetails == null)
                jsonDetails = new JSONObject();
            // look for a working page in the jsonDetails
            String workingPage = jsonDetails.optString("workingPage", null);
            // look for an offline page in the jsonDetails
            String offlinePage = jsonDetails.optString("offlinePage", null);

            // get the js to show the loading (if applicable)
            if (_showLoading)
                js += "  " + getLoadingJS(page, outputs, true);

            // stringify the query
            js += "query = JSON.stringify(query);\n";

            // open the ajax call
            js += "$.ajax({ url : '~?a=" + application.getId() + "&v=" + application.getVersion() + "&p="
                    + page.getId() + controlParam + "&act=" + getId()
                    + "', type: 'POST', contentType: 'application/json', dataType: 'json',\n";
            js += "  data: query,\n";
            js += "  error: function(server, status, message) {\n";

            // if there is a working page
            if (workingPage != null) {
                // remove any working page dialogue 
                js += "    $('#" + workingPage + "dialogue').remove();\n";
                // remove any working page dialogue cover
                js += "    $('#" + workingPage + "cover').remove();\n";
                // remove the working page so as not to affect actions further down the tree
            }

            // hide the loading javascript (if applicable)
            if (_showLoading)
                js += "    " + getLoadingJS(page, outputs, false);

            // this avoids doing the errors if the page is unloading or the back button was pressed
            js += "    if (server.readyState > 0) {\n";

            // retain if error actions
            boolean errorActions = false;

            // prepare a default error hander we'll show if no error actions, or pass to child actions for them to use
            String defaultErrorHandler = "alert('Error with webservice action : ' + server.responseText||message);";
            // if we have an offline page
            if (offlinePage != null) {
                // update defaultErrorHandler to navigate to offline page
                defaultErrorHandler = "if (Action_navigate && typeof _rapidmobile != 'undefined' && !_rapidmobile.isOnline()) {\n        Action_navigate('~?a="
                        + application.getId() + "&v=" + application.getVersion() + "&p=" + offlinePage
                        + "&action=dialogue',true,'" + getId() + "');\n      } else {\n         "
                        + defaultErrorHandler + "\n      }";
                // remove the offline page so we don't interfere with actions down the three
                jsonDetails.remove("offlinePage");
            }

            // add any error actions
            if (_errorActions != null) {
                // count the actions
                int i = 0;
                // loop the actions
                for (Action action : _errorActions) {
                    // retain that we have custom error actions
                    errorActions = true;
                    // if this is the last error action add in the default error handler
                    if (i == _errorActions.size() - 1)
                        jsonDetails.put("defaultErrorHandler", defaultErrorHandler);
                    // add the js
                    js += "         " + action.getJavaScript(rapidRequest, application, page, control, jsonDetails)
                            .trim().replace("\n", "\n         ") + "\n";
                    // if this is the last error action and the default error handler is still present, remove it so it isn't sent down the success path
                    if (i == _errorActions.size() - 1 && jsonDetails.optString("defaultErrorHandler", null) != null)
                        jsonDetails.remove("defaultErrorHandler");
                    // increase the count
                    i++;
                }
            }
            // add default error handler if none in collection
            if (!errorActions)
                js += "      " + defaultErrorHandler + "\n";

            // close unloading check
            js += "    }\n";

            // close error actions
            js += "  },\n";

            // open success function
            js += "  success: function(data) {\n";

            // get the js to hide the loading (if applicable)
            if (_showLoading)
                js += "  " + getLoadingJS(page, outputs, false);

            // check there are outputs
            if (outputs != null) {
                if (outputs.size() > 0) {
                    // open if data check
                    js += "    if (data) {\n";
                    // the outputs array we're going to make
                    String jsOutputs = "";
                    // loop the output parameters
                    for (int i = 0; i < outputs.size(); i++) {

                        // get the parameter
                        Parameter output = outputs.get(i);
                        // get the control the data is going into
                        Control outputControl = page.getControl(output.getItemId());
                        // try the application if still null
                        if (outputControl == null)
                            outputControl = application.getControl(rapidServlet.getServletContext(),
                                    output.getItemId());
                        // check the control is still on the page
                        if (outputControl != null) {
                            // get any mappings we may have
                            String details = outputControl.getDetailsJavaScript(application, page);
                            // set to empty string or clean up
                            if (details == null) {
                                details = "";
                            } else {
                                details = ", details: " + details;
                            }
                            // append the javascript outputs
                            jsOutputs += "{id: '" + outputControl.getId() + "', type: '" + outputControl.getType()
                                    + "', field: '" + output.getField() + "'" + details + "}";
                            // add a comma if not the last
                            if (i < outputs.size() - 1)
                                jsOutputs += ",";
                        }

                    }
                    js += "      var outputs = [" + jsOutputs + "];\n";
                    // send them them and the data to the webservice action            
                    js += "      Action_webservice(ev, '" + getId() + "', data, outputs);\n";
                    // close if data check
                    js += "    }\n";
                }
            }

            // if there is a working page (from the details)
            if (workingPage != null) {
                // remove any working page dialogue 
                js += "  $('#" + workingPage + "dialogue').remove();\n";
                // remove any working page dialogue cover
                js += "  $('#" + workingPage + "cover').remove();\n";
                // remove the working page so as not to affect actions further down the tree
                jsonDetails.remove("workingPage");
            }

            // add any sucess actions
            if (_successActions != null) {
                for (Action action : _successActions) {
                    js += "    " + action.getJavaScript(rapidRequest, application, page, control, jsonDetails)
                            .trim().replace("\n", "\n    ") + "\n";
                }
            }

            // close success function
            js += "  }\n";

            // close ajax call
            js += "});";
        }

        // return what we built         
        return js;
    }

    @Override
    public JSONObject doAction(RapidRequest rapidRequest, JSONObject jsonAction) throws Exception {

        _logger.trace("Webservice action : " + jsonAction);

        // get the application
        Application application = rapidRequest.getApplication();

        // get the page
        Page page = rapidRequest.getPage();

        // get the webservice action call sequence
        int sequence = jsonAction.optInt("sequence", 1);

        // placeholder for the object we're about to return
        JSONObject jsonData = null;

        // only proceed if there is a request and application and page
        if (_request != null && application != null && page != null) {

            // get any json inputs 
            JSONArray jsonInputs = jsonAction.optJSONArray("inputs");

            // placeholder for the action cache
            ActionCache actionCache = rapidRequest.getRapidServlet().getActionCache();

            // if an action cache was found
            if (actionCache != null) {

                // log that we found action cache
                _logger.debug("Webservice action cache found");

                // attempt to fetch data from the cache
                jsonData = actionCache.get(application.getId(), getId(), jsonInputs.toString());

            }

            // if there is either no cache or we got no data
            if (jsonData == null) {

                // get the body into a string
                String body = _request.getBody().trim();
                // remove prolog if present
                if (body.indexOf("\"?>") > 0)
                    body = body.substring(body.indexOf("\"?>") + 3).trim();
                // check number of parameters
                int pCount = Strings.occurrences(body, "?");
                // throw error if incorrect
                if (pCount != jsonInputs.length())
                    throw new Exception("Request has " + pCount + " parameter" + (pCount == 1 ? "" : "s") + ", "
                            + jsonInputs.length() + " provided");
                // retain the current position
                int pos = body.indexOf("?");
                // keep track of the index of the ?
                int index = 0;
                // if there are any question marks
                if (pos > 0 && jsonInputs.length() > index) {
                    // loop, but check condition at the end
                    do {
                        // get the input
                        JSONObject input = jsonInputs.getJSONObject(index);
                        // url escape the value
                        String value = XML.escape(input.optString("value"));
                        // replace the ? with the input value
                        body = body.substring(0, pos) + value + body.substring(pos + 1);
                        // look for the next question mark
                        pos = body.indexOf("?", pos + 1);
                        // inc the index for the next round
                        index++;
                        // stop looping if no more ?
                    } while (pos > 0);
                }

                // retrieve the action
                String action = _request.getAction();
                // create a placeholder for the request url
                URL url = null;
                // get the request url
                String requestURL = _request.getUrl();

                // if we got one
                if (requestURL != null) {

                    // if the given request url starts with http use it as is, otherwise use the soa servlet
                    if (_request.getUrl().startsWith("http")) {
                        // trim it
                        requestURL = requestURL.trim();
                        // insert any parameters
                        requestURL = application
                                .insertParameters(rapidRequest.getRapidServlet().getServletContext(), requestURL);
                        // use the url 
                        url = new URL(requestURL);
                    } else {
                        // get our request
                        HttpServletRequest httpRequest = rapidRequest.getRequest();
                        // make a url for the soa servlet
                        url = new URL(httpRequest.getScheme(), httpRequest.getServerName(),
                                httpRequest.getServerPort(), httpRequest.getContextPath() + "/soa");
                        // check whether we have any id / version seperators
                        String[] actionParts = action.split("/");
                        // add this app and version if none
                        if (actionParts.length < 2)
                            action = application.getId() + "/" + application.getVersion() + "/" + action;
                    }

                    // establish the connection
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                    // if we are requesting from ourself
                    if (url.getPath().startsWith(rapidRequest.getRequest().getContextPath())) {
                        // get our session id
                        String sessionId = rapidRequest.getRequest().getSession().getId();
                        // add it to the call for internal authentication
                        connection.setRequestProperty("Cookie", "JSESSIONID=" + sessionId);
                    }

                    // set the content type and action header accordingly
                    if ("SOAP".equals(_request.getType())) {
                        connection.setRequestProperty("Content-Type", "text/xml");
                        connection.setRequestProperty("SOAPAction", action);
                    } else if ("JSON".equals(_request.getType())) {
                        connection.setRequestProperty("Content-Type", "application/json");
                        if (action.length() > 0)
                            connection.setRequestProperty("Action", action);
                    } else if ("XML".equals(_request.getType())) {
                        connection.setRequestProperty("Content-Type", "text/xml");
                        if (action.length() > 0)
                            connection.setRequestProperty("Action", action);
                    }

                    // if a body has been specified
                    if (body.length() > 0) {

                        // Triggers POST.
                        connection.setDoOutput(true);

                        // get the output stream from the connection into which we write the request
                        OutputStream output = connection.getOutputStream();

                        // write the processed body string into the request output stream
                        output.write(body.getBytes("UTF8"));
                    }

                    // check the response code
                    int responseCode = connection.getResponseCode();

                    // read input stream if all ok, otherwise something meaningful should be in error stream
                    if (responseCode == 200) {

                        // get the input stream
                        InputStream response = connection.getInputStream();
                        // prepare an soaData object
                        SOAData soaData = null;

                        // read the response accordingly
                        if ("JSON".equals(_request.getType())) {
                            SOAJSONReader jsonReader = new SOAJSONReader();
                            String jsonResponse = Strings.getString(response);
                            soaData = jsonReader.read(jsonResponse);
                        } else {
                            SOAXMLReader xmlReader = new SOAXMLReader(_request.getRoot());
                            soaData = xmlReader.read(response);
                        }

                        SOADataWriter jsonWriter = new SOARapidWriter(soaData);

                        String jsonString = jsonWriter.write();

                        jsonData = new JSONObject(jsonString);

                        if (actionCache != null)
                            actionCache.put(application.getId(), getId(), jsonInputs.toString(), jsonData);

                        response.close();

                    } else {

                        InputStream response = connection.getErrorStream();

                        String errorMessage = null;

                        if ("SOAP".equals(_request.getType())) {

                            String responseXML = Strings.getString(response);

                            errorMessage = XML.getElementValue(responseXML, "faultcode");

                        }

                        if (errorMessage == null) {

                            BufferedReader rd = new BufferedReader(new InputStreamReader(response));

                            errorMessage = rd.readLine();

                            rd.close();

                        }

                        // log the error
                        _logger.error(errorMessage);

                        // only if there is no application cache show the error, otherwise it sends an empty response
                        if (actionCache == null) {

                            throw new JSONException(
                                    " response code " + responseCode + " from server : " + errorMessage);

                        } else {

                            _logger.debug("Error not shown to user due to cache : " + errorMessage);

                        }

                    }

                    connection.disconnect();

                } // request url != null

            } // jsonData == null

        } // got app and page

        // if the jsonData is still null make an empty one
        if (jsonData == null)
            jsonData = new JSONObject();

        // add the sequence
        jsonData.put("sequence", sequence);

        // return the object
        return jsonData;

    }

}