ru.runa.wf.logic.bot.WebServiceTaskHandler.java Source code

Java tutorial

Introduction

Here is the source code for ru.runa.wf.logic.bot.WebServiceTaskHandler.java

Source

/*
 * This file is part of the RUNA WFE project.
 * 
 * This program 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; version 2.1 
 * of the License. 
 * 
 * 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 Lesser General Public License for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */
package ru.runa.wf.logic.bot;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.codec.binary.Base64;

import ru.runa.wf.logic.bot.webservice.ErrorResponseProcessingResult;
import ru.runa.wf.logic.bot.webservice.Interaction;
import ru.runa.wf.logic.bot.webservice.WebServiceTaskHandlerSettings;
import ru.runa.wf.logic.bot.webservice.WebServiceTaskHandlerXmlParser;
import ru.runa.wf.logic.bot.webservice.WebServiceTaskHandlerXsltHelper;
import ru.runa.wfe.commons.ClassLoaderUtil;
import ru.runa.wfe.commons.TypeConversionUtil;
import ru.runa.wfe.extension.handler.TaskHandlerBase;
import ru.runa.wfe.service.ExecutionService;
import ru.runa.wfe.service.delegate.Delegates;
import ru.runa.wfe.task.dto.WfTask;
import ru.runa.wfe.user.User;
import ru.runa.wfe.var.VariableProvider;
import ru.runa.wfe.var.dto.WfVariable;

import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;

/**
 * Web service task handler. Making web requests to web services and receiving
 * responses. Some data from WFE system can be added to requests using XSLT
 * transformation and special XSLT tags. Responses can be processed with XSLT to
 * fill some variables, or can be stored completely in variables.
 */
public class WebServiceTaskHandler extends TaskHandlerBase {
    /**
     * XSLT transformation applied in bot thread, so
     * {@link WebServiceTaskHandlerXsltHelper} to process tag is stored in
     * {@link ThreadLocal}.
     */
    private static ThreadLocal<WebServiceTaskHandlerXsltHelper> xsltHelper = new ThreadLocal<WebServiceTaskHandlerXsltHelper>();

    /**
     * Web service bot settings.
     */
    private WebServiceTaskHandlerSettings settings;

    @Override
    public void setConfiguration(String configuration) throws Exception {
        settings = WebServiceTaskHandlerXmlParser.read(configuration);
    }

    @Override
    public Map<String, Object> handle(User user, VariableProvider variableProvider, WfTask task) throws Exception {
        Map<String, Object> variables = Maps.newHashMap();
        xsltHelper.set(new WebServiceTaskHandlerXsltHelper(task, user));
        URL url = getWebServiceUrl(user, task);
        for (int index = getStartInteraction(user, task); index < settings.interactions.size(); ++index) {
            Interaction interaction = settings.interactions.get(index);
            byte[] soapData = prepareRequest(task, interaction);
            HttpURLConnection connection = sendRequest(url, soapData);
            if (connection.getResponseCode() < 200 || connection.getResponseCode() >= 300) {
                // Something goes wrong
                if (!onErrorResponse(user, task, connection, interaction)) {
                    variables.put(SKIP_TASK_COMPLETION_VARIABLE_NAME, Boolean.TRUE);
                    return variables;
                }
            } else {
                onResponse(task, connection, interaction);
            }
        }
        xsltHelper.get().mergeVariablesIn(variables);
        return variables;
    }

    /**
     * WFE XSLT tag support: process id for current task.
     * 
     * @return Process id.
     */
    public static Long getProcessId() {
        return xsltHelper.get().getProcessId();
    }

    /**
     * WFE XSLT tag support: variable with specified name for current task.
     * 
     * @param name
     *            Variable name
     * @return Variable value.
     */
    public static String getVariable(String name) throws Exception {
        return xsltHelper.get().getVariable(name);
    }

    /**
     * WFE XSLT tag support: Read process instance id from variable and returns
     * process instance graph for this process instance encoded in
     * {@link Base64}.
     * 
     * @param processIdVariable
     *            Variable name to read process instance id.
     * @return Process instance graph for this process instance encoded in
     *         {@link Base64}.
     */
    public static String getProcessGraph(String processIdVariable) throws Exception {
        return xsltHelper.get().getProcessGraph(processIdVariable);
    }

    /**
     * Add variable to internal storage.
     * 
     * @param name
     *            Variable name.
     * @param value
     *            Variable value.
     */
    public static void setNewVariable(String name, String value) {
        xsltHelper.get().setNewVariable(name, value);
    }

    /**
     * Get web service URL. Tries to create URL from settings (URL parameter).
     * If URL creation failed, then read variable with name from URL parameter
     * and create URL with variable value.
     * 
     * @param subject
     *            Current bot subject.
     * @param task
     *            Current task instance to be processed.
     * @return URL of web service to send requests.
     * @throws MalformedURLException
     */
    private URL getWebServiceUrl(User user, WfTask task) throws MalformedURLException {
        try {
            return new URL(settings.url);
        } catch (MalformedURLException e) {
            WfVariable variable = Delegates.getExecutionService().getVariable(user, task.getProcessId(),
                    settings.url);
            return new URL((variable != null && variable.getValue() != null) ? variable.getValue().toString() : "");
        }
    }

    /**
     * Prepare web request to send. Applies XSLT to replace WFE tags with actual
     * values.
     * 
     * @param task
     *            Current task instance to be processed.
     * @param interaction
     *            Current interaction with web service.
     * @return Prepared web service request.
     */
    private byte[] prepareRequest(WfTask task, Interaction interaction) throws Exception {
        ByteArrayOutputStream res2 = new ByteArrayOutputStream();
        Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(
                ClassLoaderUtil.getAsStreamNotNull("webServiceTaskHandlerRequest.xslt", getClass())));
        transformer.transform(
                new StreamSource(new ByteArrayInputStream(interaction.requestXML.getBytes(settings.encoding))),
                new StreamResult(res2));
        byte[] soapData = res2.toByteArray();
        if (settings.isLoggingEnable && log.isDebugEnabled()) {
            log.debug("Web service bot request for task " + task.getId() + " is:\n"
                    + new String(soapData, settings.encoding));
        }
        return soapData;
    }

    /**
     * Opens HTTP connection to web service and setup required connection
     * parameters. Send request to web service.
     * 
     * @param url
     *            URL to open connection.
     * @param length
     *            SOAP data length.
     * @return HTTP connection to communicate with web service.
     */
    private HttpURLConnection sendRequest(URL url, byte[] soapData) throws Exception {
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestProperty("Content-Type", "text/xml; charset=" + settings.encoding);
        if (settings.authBase != null) {
            String auth = Base64.encodeBase64String(settings.authBase.getBytes());
            connection.setRequestProperty("Authorization", "Basic " + auth);
        }
        String soapAction = settings.soapAction == null ? "" : settings.soapAction;
        connection.setRequestProperty("SOAPAction", "\"" + soapAction + "\"");
        if (settings.requestMethod != null) {
            connection.setRequestMethod(settings.requestMethod);
        }
        connection.setFixedLengthStreamingMode(soapData.length);
        connection.setDoOutput(true);
        OutputStream os = connection.getOutputStream();
        os.write(soapData);
        os.flush();
        return connection;
    }

    /**
     * Called to process response from web service with code not from [200; 299]
     * (This codes indicates error).
     * 
     * @param subject
     *            Current bot subject.
     * @param task
     *            Current task instance to be processed.
     * @param connection
     *            HTTP connection to communicate with web service.
     * @param interaction
     *            Current processing interaction.
     * @return true, if next interaction must be processed and false if required
     *         to stop interaction processing.
     */
    private boolean onErrorResponse(User user, WfTask task, HttpURLConnection connection, Interaction interaction)
            throws Exception {
        log.debug("Web service bot got error response with code " + connection.getResponseCode() + " for task "
                + task.getId());
        if (interaction.responseVariable != null) {
            xsltHelper.get().setNewVariable(interaction.responseVariable,
                    "Got error response: '" + connection.getResponseMessage() + "'");
        }
        ErrorResponseProcessingResult errorAction = (interaction.errorAction == null ? settings.errorAction
                : interaction.errorAction);
        if (errorAction == ErrorResponseProcessingResult.BREAK || errorAction == null) {
            throw new Exception("Interaction with we service failed with behavior BREAK.");
        }
        if (errorAction == ErrorResponseProcessingResult.IGNORE) {
            return true;
        }
        saveExecutionState(user, task, interaction);
        return false;
    }

    /**
     * Saves current bot execution state.
     */
    private void saveExecutionState(User user, WfTask task, Interaction interaction) {
        ExecutionService executionService = Delegates.getExecutionService();
        HashMap<String, Object> variables = new HashMap<String, Object>();
        xsltHelper.get().mergeVariablesIn(variables);
        variables.put("WS_ITERATION_" + task.getId(), settings.interactions.indexOf(interaction));
        executionService.updateVariables(user, task.getProcessId(), variables);
    }

    /**
     * Called to process response from web service with code from [200; 299]
     * (This codes indicates successful request processing).
     * 
     * @param task
     *            Current task instance to be processed.
     * @param connection
     *            HTTP connection to communicate with web service.
     * @param interaction
     *            Current processing interaction.
     */
    private void onResponse(WfTask task, HttpURLConnection connection, Interaction interaction) throws Exception {
        if (interaction.responseXSLT == null && interaction.responseVariable == null) {
            return;
        }
        String response = logResponseAndSetVariable(task, connection, interaction);
        InputStream inputStream = response == null ? connection.getInputStream()
                : new ByteArrayInputStream(response.getBytes(settings.encoding));
        if (interaction.responseXSLT != null) {
            Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(
                    new ByteArrayInputStream(interaction.responseXSLT.getBytes(settings.encoding))));
            transformer.transform(new StreamSource(inputStream), new StreamResult(ByteStreams.nullOutputStream()));
        }
    }

    /**
     * Reads response from connection, log it and set it into response variable.
     * 
     * @param task
     *            Current task instance to be processed.
     * @param connection
     *            HTTP connection to communicate with web service.
     * @param interaction
     *            Current processing interaction.
     * @return Response as string or null, if response wasn't read from stream.
     */
    private String logResponseAndSetVariable(WfTask task, HttpURLConnection connection, Interaction interaction)
            throws IOException {
        if (interaction.responseVariable == null && (!settings.isLoggingEnable || !log.isDebugEnabled())) {
            return null;
        }
        String response = readStringFromStream(connection.getInputStream(), settings.encoding,
                interaction.maxResponseLength);
        if (settings.isLoggingEnable) {
            log.debug("Web service bot got response for task " + task.getId() + ":\n" + response);
        }
        if (interaction.responseVariable != null) {
            xsltHelper.get().setNewVariable(interaction.responseVariable, response);
        }
        return response;
    }

    /**
     * Read string from input stream.
     * 
     * @param stream
     *            Stream with data, to read from.
     * @param encoding
     *            Characters encoding, used to convert bytes in stream to
     *            characters.
     * @param maxLength
     *            Maximum data length.
     * @return String, readed from stream.
     * @throws IllegalArgumentException
     *             if data length is exceeded maxLength value.
     */
    private String readStringFromStream(InputStream stream, String encoding, int maxLength) throws IOException {
        Writer stringWriter = new StringWriter(stream.available() > maxLength ? maxLength : stream.available());
        InputStreamReader inputStreamReader = new InputStreamReader(stream, encoding);
        Reader reader = new BufferedReader(inputStreamReader);
        try {
            char[] buffer = new char[4096];
            int totalReaded = 0;
            int readCount = 0;
            while (totalReaded != maxLength && (readCount = reader.read(buffer, 0,
                    maxLength - totalReaded > 4096 ? 4096 : maxLength - totalReaded)) != -1) {
                stringWriter.write(buffer, 0, readCount);
                totalReaded += readCount;
            }
            if (reader.read(buffer, 0, 1) != -1) {
                throw new IllegalArgumentException("WebServiceTaskHandler got to large response.");
            }
            return stringWriter.toString();
        } finally {
            reader.close();
            inputStreamReader.close();
        }
    }

    /**
     * Returns index of interaction, to start execution from.
     * 
     * @param subject
     *            Current bot subject.
     * @param task
     *            Current task instance to be processed.
     * @return index of interaction.
     */
    private int getStartInteraction(User user, WfTask task) {
        ExecutionService executionService = Delegates.getExecutionService();
        WfVariable variable = executionService.getVariable(user, task.getProcessId(),
                "WS_ITERATION_" + task.getId());
        return variable == null ? 0 : TypeConversionUtil.convertTo(int.class, variable.getValue());
    }
}