org.yawlfoundation.yawl.engine.interfce.Interface_Client.java Source code

Java tutorial

Introduction

Here is the source code for org.yawlfoundation.yawl.engine.interfce.Interface_Client.java

Source

/*
 * Copyright (c) 2004-2012 The YAWL Foundation. All rights reserved.
 * The YAWL Foundation is a collaboration of individuals and
 * organisations who are committed to improving workflow technology.
 *
 * This file is part of YAWL. YAWL is free software: you can
 * redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation.
 *
 * YAWL 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 Lesser General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with YAWL. If not, see <http://www.gnu.org/licenses/>.
 */

package org.yawlfoundation.yawl.engine.interfce;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.cluster.OriginalYawlEngineApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.metrics.Metric;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is used by clients and servers to execute GET and POST requests
 * across the YAWL interfaces. Note that since v2.0up4 (12/08) all requests are sent as
 * POSTS - increases efficiency, security and allows 'extended' chars to be included.
 *
 * @author Lachlan Aldred
 * Date: 22/03/2004
 * Time: 17:49:42
 *
 * @author Michael Adams (refactored for v2.0, 06/2008; and again 12/2008 & 04/2010)
 */

public class Interface_Client {

    // allows the prevention of socket reads from blocking indefinitely
    private static int READ_TIMEOUT = 0; // default: wait indefinitely

    /**
     * Executes a HTTP POST request on the url specified.
     *
     * @param urlStr the URL to send the POST to
     * @param paramsMap a set of attribute-value pairs that make up the posted data
     * @return the result of the POST request
     * @throws IOException when there's some kind of communication problem
     */
    protected String executePost(String urlStr, Map<String, String> paramsMap) throws IOException {

        return send(urlStr, paramsMap, true);
    }

    protected void asyncExecutePost(String urlStr, Map<String, String> paramsMap) throws IOException {

        asyncSend(urlStr, paramsMap, true);
    }

    public String send(String urlStr, String data) throws IOException {
        HttpURLConnection connection = initPostConnection(urlStr);
        sendData(connection, data);
        String result = getReply(connection.getInputStream());
        connection.disconnect();

        return result;
    }

    /**
     * Executes a rerouted HTTP GET request as a POST on the specified URL
     *
     * @param urlStr the URL to send the GET to
     * @param paramsMap a set of attribute-value pairs that make up the posted data
     * @return the result of the request
     * @throws IOException when there's some kind of communication problem
     */
    protected String executeGet(String urlStr, Map<String, String> paramsMap) throws IOException {

        return send(urlStr, paramsMap, false);
    }

    /**
     * Initialises a map for transporting parameters - used by extending classes
     * @param action the name of the action to take
     * @param handle the current engine session handle
     * @return the initialised Map
     */
    protected Map<String, String> prepareParamMap(String action, String handle) {
        Map<String, String> paramMap = new HashMap<String, String>();
        paramMap.put("action", action);
        if (handle != null)
            paramMap.put("sessionHandle", handle);
        return paramMap;
    }

    /**
     * Set the read timeout value for future connections
     * @param timeout the timeout value in milliseconds. A value of -1 (the default)
     *                means a read will wait indefinitely.
     */
    protected void setReadTimeout(int timeout) {
        READ_TIMEOUT = timeout;
    }

    /**
     * Removes the outermost set of xml tags from a string, if any
     * @param xml the xml string to strip
     * @return the stripped xml string
     */
    protected String stripOuterElement(String xml) {
        if (xml != null) {
            int start = xml.indexOf('>') + 1;
            int end = xml.lastIndexOf('<');
            if (end > start) {
                return xml.substring(start, end);
            }
        }
        return xml;
    }

    /**
     * Sends data to the specified url via a HTTP POST, and returns the reply
     * @param connection the http url connection to send the request to
     * @param paramsMap a map of attribute=value pairs representing the data to send
     * @param stripOuterXML true if this was originally a POST request, false if a GET request
     * @return the response from the url
     * @throws IOException when there's some kind of communication problem
     */
    protected String send(HttpURLConnection connection, Map<String, String> paramsMap, boolean stripOuterXML)
            throws IOException {

        // encode data and send query
        sendData(connection, encodeData(paramsMap));

        //retrieve reply
        String result = getReply(connection.getInputStream());
        connection.disconnect();

        if (stripOuterXML)
            result = stripOuterElement(result);
        return result;

    }

    /**
     * Initialises a HTTP POST connection
     * @param urlStr the url to connect to
     * @return an initialised POST connection
     * @throws IOException when there's some kind of communication problem
     */
    protected HttpURLConnection initPostConnection(String urlStr) throws IOException {

        URL url = new URL(urlStr);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setDoOutput(true);
        connection.setRequestProperty("Accept-Charset", "UTF-8");
        connection.setReadTimeout(READ_TIMEOUT);

        // required to ensure the connection is not reused. When not set, spurious
        // intermittent problems (double posts, missing posts) occur under heavy load.
        connection.setRequestProperty("Connection", "close");
        return connection;
    }

    /**
     * Tests a response message for success or failure
     * @param message the response message to test
     * @return true if the response represents success
     */
    public boolean successful(String message) {
        return (message != null) && (message.length() > 0) && (!message.contains("<failure>"));
    }

    /*******************************************************************************/

    private final Logger logger = LoggerFactory.getLogger(Interface_Client.class);
    // PRIVATE METHODS //

    class RequestForwader {

        final CloseableHttpClient client = HttpClients.createDefault();

        public String forwardRequest(String uri, List<NameValuePair> parameterMap) throws IOException {

            String result;
            if (!uri.startsWith("http")) {
                uri = "http://" + uri;
            }
            HttpPost httpPost = new HttpPost(uri);

            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameterMap, "UTF-8");

            httpPost.setEntity(entity);
            HttpContext httpContext = new BasicHttpContext();
            CloseableHttpResponse response;
            response = client.execute(httpPost, httpContext);
            result = EntityUtils.toString(response.getEntity());

            return result;
        }

        CloseableHttpAsyncClient httpAsyncClient = HttpAsyncClients.createDefault();

        public RequestForwader() {
            httpAsyncClient.start();
        }

        class FutureCallbackWithStartTime implements FutureCallback<HttpResponse> {

            private long start;

            public FutureCallbackWithStartTime(long start) {
                this.start = start;
            }

            @Override
            public void completed(HttpResponse httpResponse) {
                OriginalYawlEngineApplication.writer
                        .set(new Metric<Number>("time_span", System.currentTimeMillis() - start));
            }

            @Override
            public void failed(Exception e) {

            }

            @Override
            public void cancelled() {

            }
        }

        public void asyncForwardRequest(String uri, List<NameValuePair> parameterMap) {

            try {

                if (!uri.startsWith("http")) {
                    uri = "http://" + uri;
                }
                final HttpPost httpPost = new HttpPost(uri);

                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameterMap, "UTF-8");

                httpPost.setEntity(entity);

                httpAsyncClient.execute(httpPost, new FutureCallbackWithStartTime(System.currentTimeMillis()));

            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private final RequestForwader client = new RequestForwader();

    // PRIVATE METHODS //

    /**
     * Sends data to the specified url via a HTTP POST, and returns the reply
     * @param urlStr the url to connect to
     * @param paramsMap a map of attribute=value pairs representing the data to send
     * @param post true if this was originally a POST request, false if a GET request
     * @return the response from the url
     * @throws IOException when there's some kind of communication problem
     */
    private String send(String urlStr, Map<String, String> paramsMap, boolean post) throws IOException {

        String p = "http://(.*):";
        Pattern pattern = Pattern.compile(p);

        Matcher matcher = pattern.matcher(urlStr);

        String host = null;
        while (matcher.find()) {
            host = matcher.group(1);
        }

        if (!Character.isDigit(host.charAt(0))) {
            InetAddress address = InetAddress.getByName(host);

            String a = address.toString();

            a = a.substring(a.indexOf('/') + 1);

            urlStr = urlStr.replace(host, a);
        }
        List<NameValuePair> list = new ArrayList<NameValuePair>();
        for (String key : paramsMap.keySet()) {

            BasicNameValuePair pair = new BasicNameValuePair(key, paramsMap.get(key));
            list.add(pair);
        }

        String result = client.forwardRequest(urlStr, list);
        if (post) {
            return stripOuterElement(result);
        } else {
            return result;
        }

    }

    private void asyncSend(String urlStr, Map<String, String> paramsMap, boolean post) throws IOException {

        String p = "http://(.*):";
        Pattern pattern = Pattern.compile(p);

        Matcher matcher = pattern.matcher(urlStr);

        String host = null;
        while (matcher.find()) {
            host = matcher.group(1);
        }

        if (!Character.isDigit(host.charAt(0))) {
            InetAddress address = InetAddress.getByName(host);

            String a = address.toString();

            a = a.substring(a.indexOf('/') + 1);

            urlStr = urlStr.replace(host, a);
        }
        List<NameValuePair> list = new ArrayList<NameValuePair>();
        for (String key : paramsMap.keySet()) {

            BasicNameValuePair pair = new BasicNameValuePair(key, paramsMap.get(key));
            list.add(pair);
        }

        client.asyncForwardRequest(urlStr, list);

    }

    /**
    * Encodes parameter values for HTTP transport
    * @param params a map of the data parameter values, of the form
    *        [param1=value1],[param2=value2]...
    * @return a formatted http data string with the data values encoded
    */
    private String encodeData(Map<String, String> params) {
        StringBuilder result = new StringBuilder("");
        for (String param : params.keySet()) {
            String value = params.get(param);
            if (value != null) {
                if (result.length() > 0)
                    result.append("&");
                result.append(param).append("=").append(ServletUtils.urlEncode(value));
            }
        }
        return result.toString();
    }

    /**
     * Submits data on a HTTP connection
     * @param connection a valid, open HTTP connection
     * @param data the data to submit
     * @throws IOException when there's some kind of communication problem
     */
    private void sendData(HttpURLConnection connection, String data) throws IOException {
        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
        out.write(data);
        out.close();
    }

    /**
     * Receives a reply from a HTTP submission
     * @param is the InputStream of a URL or Connection object
     * @return the stream's contents (ie. the HTTP reply)
     * @throws IOException when there's some kind of communication problem
     */
    private String getReply(InputStream is) throws IOException {
        final int BUF_SIZE = 16384;

        // read reply into a buffered byte stream - to preserve UTF-8
        BufferedInputStream inStream = new BufferedInputStream(is);
        ByteArrayOutputStream outStream = new ByteArrayOutputStream(BUF_SIZE);
        byte[] buffer = new byte[BUF_SIZE];

        // read chunks from the input stream and write them out
        int bytesRead;
        while ((bytesRead = inStream.read(buffer, 0, BUF_SIZE)) > 0) {
            outStream.write(buffer, 0, bytesRead);
        }

        outStream.close();
        inStream.close();

        // convert the bytes to a UTF-8 string
        return outStream.toString("UTF-8");
    }
}