org.bonitasoft.engine.api.HTTPServerAPI.java Source code

Java tutorial

Introduction

Here is the source code for org.bonitasoft.engine.api.HTTPServerAPI.java

Source

/**
 * Copyright (C) 2015 BonitaSoft S.A.
 * BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble
 * This library 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 library 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., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 **/
package org.bonitasoft.engine.api;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.UndeclaredThrowableException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.bonitasoft.engine.api.internal.ServerAPI;
import org.bonitasoft.engine.api.internal.ServerWrappedException;
import org.bonitasoft.engine.bpm.bar.BusinessArchive;
import org.bonitasoft.engine.exception.BonitaRuntimeException;
import org.bonitasoft.engine.exception.StackTraceTransformer;
import org.bonitasoft.engine.http.BonitaResponseHandler;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;

/**
 * @author Baptiste Mesta
 * @author Julien Mege
 * @author Elias Ricken de Medeiros
 * @author Celine Souchet
 */
public class HTTPServerAPI implements ServerAPI {

    private static final long serialVersionUID = -3375874140999200702L;

    private static final String UTF_8 = "UTF-8";

    private static final String CLASS_NAME_PARAMETERS = "classNameParameters";

    private static final String OPTIONS = "options";

    private static final String PARAMETERS_VALUES = "parametersValues";

    private static final String BINARY_PARAMETER = "binaryParameter";

    private static final String BYTE_ARRAY = "==ByteArray==";

    private static final char SLASH = '/';

    private static final String SERVER_API = "/serverAPI/";

    static final String SERVER_URL = "server.url";

    private static final String BASIC_AUTHENTICATION_ACTIVE = "basicAuthentication.active";

    private static final String BASIC_AUTHENTICATION_USERNAME = "basicAuthentication.username";

    private static final String BASIC_AUTHENTICATION_PASSWORD = "basicAuthentication.password";

    static final String APPLICATION_NAME = "application.name";

    private static final Logger LOGGER = Logger.getLogger(HTTPServerAPI.class.getName());

    private String serverUrl = null;

    private String applicationName = null;

    private boolean basicAuthenticationActive = false;

    private String basicAuthenticationUserName = null;

    private String basicAuthenticationPassword = null;

    private static DefaultHttpClient httpclient;

    private static final XStream XSTREAM;

    private static final Charset UTF8 = Charset.forName("UTF-8");

    private static final ResponseHandler<String> RESPONSE_HANDLER = new BonitaResponseHandler();

    static {
        XSTREAM = new XStream();
        XSTREAM.registerConverter(new BonitaStackTraceElementConverter(), XStream.PRIORITY_VERY_HIGH);
    }

    public HTTPServerAPI(final Map<String, String> parameters) {
        // initialize httpclient in the constructor to avoid incompatibility when running tests:
        // java.security.NoSuchAlgorithmException: class configured for SSLContext: sun.security.ssl.SSLContextImpl$TLS10Context not a SSLContext
        if (httpclient == null) {
            httpclient = new DefaultHttpClient(new PoolingClientConnectionManager());
        }
        serverUrl = parameters.get(SERVER_URL);
        applicationName = parameters.get(APPLICATION_NAME);
        basicAuthenticationActive = "true".equalsIgnoreCase(parameters.get(BASIC_AUTHENTICATION_ACTIVE));
        basicAuthenticationUserName = parameters.get(BASIC_AUTHENTICATION_USERNAME);
        basicAuthenticationPassword = parameters.get(BASIC_AUTHENTICATION_PASSWORD);
    }

    @Override
    public Object invokeMethod(final Map<String, Serializable> options, final String apiInterfaceName,
            final String methodName, final List<String> classNameParameters, final Object[] parametersValues)
            throws ServerWrappedException {
        String response = null;
        try {
            response = executeHttpPost(options, apiInterfaceName, methodName, classNameParameters, parametersValues,
                    XSTREAM);
            return checkInvokeMethodReturn(response, XSTREAM);
        } catch (final UndeclaredThrowableException e) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
            }
            throw new ServerWrappedException(e);
        } catch (final Throwable e) {
            final StackTraceElement[] stackTrace = new Exception().getStackTrace();
            StackTraceTransformer.addStackTo(e, stackTrace);
            throw new ServerWrappedException(e.getMessage() + "response= " + response, e);
        }
    }

    private Object checkInvokeMethodReturn(final String response, final XStream xstream) throws Throwable {
        Object invokeMethodReturn = null;
        if (response != null && !response.isEmpty() && !"null".equals(response)) {
            invokeMethodReturn = fromXML(response, xstream);
            if (invokeMethodReturn instanceof Throwable) {
                throw (Throwable) invokeMethodReturn;
            }
        }
        return invokeMethodReturn;
    }

    private String executeHttpPost(final Map<String, Serializable> options, final String apiInterfaceName,
            final String methodName, final List<String> classNameParameters, final Object[] parametersValues,
            final XStream xstream) throws UnsupportedEncodingException, IOException, ClientProtocolException {
        final HttpPost httpost = createHttpPost(options, apiInterfaceName, methodName, classNameParameters,
                parametersValues, xstream);
        try {
            return httpclient.execute(httpost, RESPONSE_HANDLER);
        } catch (final ClientProtocolException e) {
            if (LOGGER.isLoggable(Level.SEVERE)) {
                LOGGER.log(Level.SEVERE,
                        e.getMessage() + System.getProperty("line.separator") + "httpost = <" + httpost + ">");
            }
            throw e;
        }
    }

    private final HttpPost createHttpPost(final Map<String, Serializable> options, final String apiInterfaceName,
            final String methodName, final List<String> classNameParameters, final Object[] parametersValues,
            final XStream xstream) throws UnsupportedEncodingException, IOException {
        final HttpEntity httpEntity = buildEntity(options, classNameParameters, parametersValues, xstream);
        final StringBuilder sBuilder = new StringBuilder(serverUrl);
        sBuilder.append(SLASH).append(applicationName).append(SERVER_API).append(apiInterfaceName).append(SLASH)
                .append(methodName);
        final HttpPost httpost = new HttpPost(sBuilder.toString());
        httpost.setEntity(httpEntity);

        // Basic authentication
        if (basicAuthenticationActive) {
            final StringBuilder credentials = new StringBuilder();
            credentials.append(basicAuthenticationUserName).append(":").append(basicAuthenticationPassword);
            final Base64 encoder = new Base64();
            final String encodedCredentials = encoder.encodeAsString(credentials.toString().getBytes());
            httpost.setHeader("Authorization", "Basic " + encodedCredentials);
        }

        return httpost;
    }

    final HttpEntity buildEntity(final Map<String, Serializable> options, final List<String> classNameParameters,
            final Object[] parametersValues, final XStream xstream)
            throws UnsupportedEncodingException, IOException {
        final HttpEntity httpEntity;
        /*
         * if we have a business archive we use multipart to have the business archive attached as a binary content (it can be big)
         */
        if (classNameParameters.contains(BusinessArchive.class.getName())
                || classNameParameters.contains(byte[].class.getName())) {
            final List<Object> bytearrayParameters = new ArrayList<Object>();
            final MultipartEntity entity = new MultipartEntity(null, null, UTF8);
            entity.addPart(OPTIONS, new StringBody(toXML(options, xstream), UTF8));
            entity.addPart(CLASS_NAME_PARAMETERS, new StringBody(toXML(classNameParameters, xstream), UTF8));
            for (int i = 0; i < parametersValues.length; i++) {
                final Object parameterValue = parametersValues[i];
                if (parameterValue instanceof BusinessArchive || parameterValue instanceof byte[]) {
                    parametersValues[i] = BYTE_ARRAY;
                    bytearrayParameters.add(parameterValue);
                }
            }
            entity.addPart(PARAMETERS_VALUES, new StringBody(toXML(parametersValues, xstream), UTF8));
            int i = 0;
            for (final Object object : bytearrayParameters) {
                entity.addPart(BINARY_PARAMETER + i, new ByteArrayBody(serialize(object), BINARY_PARAMETER + i));
                i++;
            }
            httpEntity = entity;
        } else {
            final List<NameValuePair> nvps = new ArrayList<NameValuePair>();
            nvps.add(new BasicNameValuePair(OPTIONS, toXML(options, xstream)));
            nvps.add(new BasicNameValuePair(CLASS_NAME_PARAMETERS, toXML(classNameParameters, xstream)));
            nvps.add(new BasicNameValuePair(PARAMETERS_VALUES, toXML(parametersValues, xstream)));
            httpEntity = new UrlEncodedFormEntity(nvps, UTF_8);
        }
        return httpEntity;
    }

    public byte[] serialize(final Object obj) throws IOException {
        final ByteArrayOutputStream b = new ByteArrayOutputStream();
        final ObjectOutputStream o = new ObjectOutputStream(b);
        o.writeObject(obj);
        return b.toByteArray();
    }

    public Object deserialize(final byte[] bytes) throws IOException, ClassNotFoundException {
        final ByteArrayInputStream b = new ByteArrayInputStream(bytes);
        final ObjectInputStream o = new ObjectInputStream(b);
        return o.readObject();
    }

    private String toXML(final Object options, final XStream xstream) throws IOException {
        final StringWriter stringWriter = new StringWriter();
        final ObjectOutputStream out = xstream.createObjectOutputStream(stringWriter);
        out.writeObject(options);
        out.close();
        return stringWriter.toString();
    }

    @SuppressWarnings("unchecked")
    private <T> T fromXML(final String object, final XStream xstream) {
        final StringReader xmlReader = new StringReader(object);
        ObjectInputStream in = null;
        try {
            in = xstream.createObjectInputStream(xmlReader);
            try {
                return (T) in.readObject();
            } catch (final IOException e) {
                throw new BonitaRuntimeException("unable to deserialize object " + object, e);
            } catch (final ClassNotFoundException e) {
                throw new BonitaRuntimeException("unable to deserialize object " + object, e);
            } catch (final CannotResolveClassException e) {
                throw new BonitaRuntimeException("unable to deserialize object " + object, e);
            } finally {
                in.close();
                xmlReader.close();
            }
        } catch (final IOException e) {
            throw new BonitaRuntimeException("unable to deserialize object " + object, e);
        }
    }

}