net.sf.taverna.t2.activities.biomoby.ExecuteAsyncCgiService.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.taverna.t2.activities.biomoby.ExecuteAsyncCgiService.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package net.sf.taverna.t2.activities.biomoby;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.log4j.Logger;
import org.biomoby.shared.MobyException;
import org.biomoby.shared.MobyPrefixResolver;
import org.biomoby.shared.parser.MobyTags;
import org.biomoby.w3c.addressing.EndpointReference;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.omg.lsae.notifications.AnalysisEvent;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * This class contains one method that is used to execute asynchronous HTTP POST
 * services
 *
 * @author Edward Kawas
 *
 */
public class ExecuteAsyncCgiService {

    private static final String GET_MULTIPLE_RESOURCE_PROPERTIES_ACTION = "http://docs.oasis-open.org/wsrf/rpw-2/GetMultipleResourceProperties/GetMultipleResourcePropertiesRequest";
    private static final String DESTROY_RESOURCE_ACTION = "http://docs.oasis-open.org/wsrf/rlw-2/ImmediateResourceTermination/DestroyRequest";

    private static final String RESOURCE_PROPERTIES_NS = "http://docs.oasis-open.org/wsrf/rp-2";
    private static final String RESULT_PREFIX = "result_";
    private static final String STATUS_PREFIX = "status_";
    private static Logger logger = Logger.getLogger(ExecuteAsyncCgiService.class);

    /**
     *
     * @param url
     * @param serviceName
     * @param xml
     * @return
     */
    public static String executeMobyCgiAsyncService(String url, String serviceName, String xml)
            throws MobyException {

        // First, let's get the queryIds
        org.w3c.dom.Document message = null;

        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            dbf.setValidating(false);
            DocumentBuilder db = dbf.newDocumentBuilder();

            message = db.parse(new InputSource(new StringReader(xml)));
        } catch (Throwable t) {
            throw new MobyException("Error while parsing input query", t);
        }

        NodeList l_data = message.getElementsByTagNameNS(MobyPrefixResolver.MOBY_XML_NAMESPACE, MobyTags.MOBYDATA);
        if (l_data == null || l_data.getLength() == 0) {
            l_data = message.getElementsByTagNameNS(MobyPrefixResolver.MOBY_XML_NAMESPACE_INVALID,
                    MobyTags.MOBYDATA);
        }

        // Freeing resources
        message = null;

        if (l_data == null || l_data.getLength() == 0) {
            throw new MobyException("Empty asynchronous MOBY query!");
        }

        int nnode = l_data.getLength();
        String[] queryIds = new String[nnode];
        String[] tmpQueryIds = new String[nnode];
        String[] results = new String[nnode];
        for (int inode = 0; inode < nnode; inode++) {
            String queryId = null;

            org.w3c.dom.Element mdata = (org.w3c.dom.Element) l_data.item(inode);

            queryId = mdata.getAttribute(MobyTags.QUERYID);
            if (queryId == null || queryId.length() == 0)
                queryId = mdata.getAttributeNS(MobyPrefixResolver.MOBY_XML_NAMESPACE, MobyTags.QUERYID);
            if (queryId == null || queryId.length() == 0)
                queryId = mdata.getAttributeNS(MobyPrefixResolver.MOBY_XML_NAMESPACE_INVALID, MobyTags.QUERYID);

            if (queryId == null || queryId.length() == 0) {
                throw new MobyException("Unable to extract queryId for outgoing MOBY message");
            }

            tmpQueryIds[inode] = queryIds[inode] = queryId;
            results[inode] = null;
        }

        // Freeing resources
        l_data = null;

        // Second, let's launch
        EndpointReference epr = launchCgiAsyncService(url, xml);

        // Third, waiting for the results
        try {
            // FIXME - add appropriate values here
            long pollingInterval = 1000L;
            double backoff = 1.0;

            // Max: one minute pollings
            long maxPollingInterval = 60000L;

            // Min: one second
            if (pollingInterval <= 0L)
                pollingInterval = 1000L;

            // Backoff: must be bigger than 1.0
            if (backoff <= 1.0)
                backoff = 1.5;

            do {
                try {
                    Thread.sleep(pollingInterval);
                } catch (InterruptedException ie) {
                    // DoNothing(R)
                }

                if (pollingInterval != maxPollingInterval) {
                    pollingInterval = (long) ((double) pollingInterval * backoff);
                    if (pollingInterval > maxPollingInterval) {
                        pollingInterval = maxPollingInterval;
                    }
                }
            } while (pollAsyncCgiService(serviceName, url, epr, tmpQueryIds, results));
        } finally {

            // Call destroy on this service ....
            freeCgiAsyncResources(url, epr);

        }

        // Fourth, assembling back the results

        // Results array already contains mobyData
        Element[] mobydatas = new Element[results.length];
        for (int x = 0; x < results.length; x++) {
            // TODO remove the extra wrapping from our result
            try {
                Element inputElement = XMLUtilities.getDOMDocument(results[x]).getRootElement();
                if (inputElement.getName().indexOf("GetMultipleResourcePropertiesResponse") >= 0)
                    if (inputElement.getChildren().size() > 0)
                        inputElement = (Element) inputElement.getChildren().get(0);
                if (inputElement.getName().indexOf("result_") >= 0)
                    if (inputElement.getChildren().size() > 0)
                        inputElement = (Element) inputElement.getChildren().get(0);
                // replace results[x]
                mobydatas[x] = inputElement;
            } catch (MobyException e) {
                // TODO what should i do?
            }
        }
        Element e = null;
        try {
            e = XMLUtilities.createMultipleInvokations(mobydatas);
        } catch (Exception ex) {
            logger.error("There was a problem creating our XML message ...", ex);
        }
        // Fifth, returning results
        return e == null ? "" : new XMLOutputter(Format.getPrettyFormat()).outputString(e);
    }

    private static void freeCgiAsyncResources(String endpoint, EndpointReference epr) throws MobyException {
        // construct the Httpclient
        HttpClient client = new HttpClient();
        client.getParams().setParameter("http.useragent", "jMoby/Taverna2");
        // create the post method
        PostMethod method = new PostMethod(endpoint + "/destroy");

        // put our data in the request
        RequestEntity entity;
        try {
            entity = new StringRequestEntity("<Destroy xmlns=\"http://docs.oasis-open.org/wsrf/rl-2\"/>",
                    "text/xml", null);
        } catch (UnsupportedEncodingException e) {
            throw new MobyException("Problem posting data to webservice", e);
        }
        method.setRequestEntity(entity);

        // set the header
        StringBuffer httpheader = new StringBuffer();
        httpheader.append("<moby-wsrf>");
        httpheader.append("<wsa:Action xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">"
                + DESTROY_RESOURCE_ACTION + "</wsa:Action>");
        httpheader.append(
                "<wsa:To xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" wsu:Id=\"To\">"
                        + endpoint + "</wsa:To>");
        httpheader.append(
                "<mobyws:ServiceInvocationId xmlns:mobyws=\"http://biomoby.org/\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" wsa:IsReferenceParameter=\"true\">"
                        + epr.getServiceInvocationId() + "</mobyws:ServiceInvocationId>");
        httpheader.append("</moby-wsrf>");
        method.addRequestHeader("moby-wsrf", httpheader.toString().replaceAll("\r\n", ""));
        // retry up to 10 times
        client.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(10, true));

        // call the method
        try {
            int result = client.executeMethod(method);
            if (result != HttpStatus.SC_OK)
                throw new MobyException(
                        "Async HTTP POST service returned code: " + result + "\n" + method.getStatusLine());
        } catch (IOException e) {
            throw new MobyException("Problem reading response from webservice", e);
        } finally {
            // Release current connection to the connection pool once you are
            // done
            method.releaseConnection();
        }
    }

    /**
     *
     * @param endpoint
     *            the url to the service to call
     * @param xml
     *            the BioMOBY input message
     * @return EndpointReference the EPR returned by the service
     * @throws MobyException
     */
    private static EndpointReference launchCgiAsyncService(String endpoint, String xml) throws MobyException {
        // construct the Httpclient
        HttpClient client = new HttpClient();
        client.getParams().setParameter("http.useragent", "jMoby/Taverna2");
        // create the post method
        PostMethod method = new PostMethod(endpoint);

        // put our data in the request
        RequestEntity entity;
        try {
            entity = new StringRequestEntity(xml, "text/xml", null);
        } catch (UnsupportedEncodingException e) {
            throw new MobyException("Problem posting data to webservice", e);
        }
        method.setRequestEntity(entity);

        // retry up to 10 times
        client.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(10, true));

        // call the method
        try {
            int result = client.executeMethod(method);
            if (result != HttpStatus.SC_OK)
                throw new MobyException(
                        "Async HTTP POST service returned code: " + result + "\n" + method.getStatusLine());
            return EndpointReference.createFromXML(method.getResponseHeader("moby-wsrf").getValue());
        } catch (IOException e) {
            throw new MobyException("Problem reading response from webservice", e);
        } finally {
            // Release current connection to the connection pool once you are
            // done
            method.releaseConnection();
        }
    }

    private static boolean pollAsyncCgiService(String msName, String url, EndpointReference epr, String[] queryIds,
            String[] result) throws MobyException {
        // Needed to remap results
        HashMap<String, Integer> queryMap = new HashMap<String, Integer>();
        for (int qi = 0; qi < queryIds.length; qi++) {
            String queryId = queryIds[qi];
            if (queryId != null)
                queryMap.put(queryId, new Integer(qi));
        }

        if (queryMap.size() == 0)
            return false;

        // construct the GetMultipleResourceProperties XML
        StringBuffer xml = new StringBuffer();
        xml.append("<wsrf-rp:GetMultipleResourceProperties xmlns:wsrf-rp='" + RESOURCE_PROPERTIES_NS
                + "' xmlns:mobyws='http://biomoby.org/'>");
        for (String q : queryMap.keySet())
            xml.append("<wsrf-rp:ResourceProperty>mobyws:" + STATUS_PREFIX + q + "</wsrf-rp:ResourceProperty>");
        xml.append("</wsrf-rp:GetMultipleResourceProperties>");

        StringBuffer httpheader = new StringBuffer();
        httpheader.append("<moby-wsrf>");
        httpheader.append("<wsa:Action xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">"
                + GET_MULTIPLE_RESOURCE_PROPERTIES_ACTION + "</wsa:Action>");
        httpheader.append(
                "<wsa:To xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" wsu:Id=\"To\">"
                        + url + "</wsa:To>");
        httpheader.append(
                "<mobyws:ServiceInvocationId xmlns:mobyws=\"http://biomoby.org/\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" wsa:IsReferenceParameter=\"true\">"
                        + epr.getServiceInvocationId() + "</mobyws:ServiceInvocationId>");
        httpheader.append("</moby-wsrf>");

        AnalysisEvent[] l_ae = null;
        // First, status from queries
        String response = "";
        // construct the Httpclient
        HttpClient client = new HttpClient();
        client.getParams().setParameter("http.useragent", "jMoby/Taverna2");
        // create the post method
        PostMethod method = new PostMethod(url + "/status");
        // add the moby-wsrf header (with no newlines)
        method.addRequestHeader("moby-wsrf", httpheader.toString().replaceAll("\r\n", ""));

        // put our data in the request
        RequestEntity entity;
        try {
            entity = new StringRequestEntity(xml.toString(), "text/xml", null);
        } catch (UnsupportedEncodingException e) {
            throw new MobyException("Problem posting data to webservice", e);
        }
        method.setRequestEntity(entity);

        // retry up to 10 times
        client.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(10, true));

        // call the method
        try {
            if (client.executeMethod(method) != HttpStatus.SC_OK)
                throw new MobyException("Async HTTP POST service returned code: " + method.getStatusCode() + "\n"
                        + method.getStatusLine() + "\nduring our polling request");
            response = stream2String(method.getResponseBodyAsStream());
        } catch (IOException e) {
            throw new MobyException("Problem reading response from webservice", e);
        } finally {
            // Release current connection to the connection pool once you
            // are
            // done
            method.releaseConnection();
        }

        if (response != null) {
            l_ae = AnalysisEvent.createFromXML(response);
        }

        if (l_ae == null || l_ae.length == 0) {
            new MobyException("Troubles while checking asynchronous MOBY job status from service " + msName);
        }

        ArrayList<String> finishedQueries = new ArrayList<String>();
        // Second, gather those finished queries
        for (int iae = 0; iae < l_ae.length; iae++) {
            AnalysisEvent ae = l_ae[iae];
            if (ae.isCompleted()) {
                String queryId = ae.getQueryId();
                if (!queryMap.containsKey(queryId)) {
                    throw new MobyException(
                            "Invalid result queryId on asynchronous MOBY job status fetched from " + msName);
                }
                finishedQueries.add(queryId);
            }
        }

        // Third, let's fetch the results from the finished queries
        if (finishedQueries.size() > 0) {
            String[] resQueryIds = finishedQueries.toArray(new String[0]);
            for (int x = 0; x < resQueryIds.length; x++) {
                // construct the GetMultipleResourceProperties XML
                xml = new StringBuffer();
                xml.append("<wsrf-rp:GetMultipleResourceProperties xmlns:wsrf-rp='" + RESOURCE_PROPERTIES_NS
                        + "' xmlns:mobyws='http://biomoby.org/'>");
                for (String q : resQueryIds)
                    xml.append("<wsrf-rp:ResourceProperty>mobyws:" + RESULT_PREFIX + q
                            + "</wsrf-rp:ResourceProperty>");
                xml.append("</wsrf-rp:GetMultipleResourceProperties>");

                httpheader = new StringBuffer();
                httpheader.append("<moby-wsrf>");
                httpheader.append("<wsa:Action xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">"
                        + GET_MULTIPLE_RESOURCE_PROPERTIES_ACTION + "</wsa:Action>");
                httpheader.append(
                        "<wsa:To xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" wsu:Id=\"To\">"
                                + url + "</wsa:To>");
                httpheader.append(
                        "<mobyws:ServiceInvocationId xmlns:mobyws=\"http://biomoby.org/\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" wsa:IsReferenceParameter=\"true\">"
                                + epr.getServiceInvocationId() + "</mobyws:ServiceInvocationId>");
                httpheader.append("</moby-wsrf>");
                client = new HttpClient();
                client.getParams().setParameter("http.useragent", "jMoby/Taverna2");
                // create the post method
                method = new PostMethod(url + "/results");
                // add the moby-wsrf header (with no newlines)
                method.addRequestHeader("moby-wsrf", httpheader.toString().replaceAll("\r\n", ""));

                // put our data in the request
                entity = null;
                try {
                    entity = new StringRequestEntity(xml.toString(), "text/xml", null);
                } catch (UnsupportedEncodingException e) {
                    throw new MobyException("Problem posting data to webservice", e);
                }
                method.setRequestEntity(entity);

                // retry up to 10 times
                client.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                        new DefaultHttpMethodRetryHandler(10, true));

                // call the method
                try {
                    if (client.executeMethod(method) != HttpStatus.SC_OK)
                        throw new MobyException("Async HTTP POST service returned code: " + method.getStatusCode()
                                + "\n" + method.getStatusLine() + "\nduring our polling request");
                    // place the result in the array
                    result[x] = stream2String(method.getResponseBodyAsStream());
                    // Marking as null
                    queryIds[x] = null;
                } catch (IOException e) {
                    logger.warn("Problem getting result from webservice\n" + e.getMessage());
                } finally {
                    // Release current connection
                    method.releaseConnection();
                }
            }

        }
        return finishedQueries.size() != queryMap.size();
    }

    private static String stream2String(InputStream is) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line = null;
        String newline = System.getProperty("line.separator");
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line + newline);
            }
        } catch (IOException e) {
            logger.warn("Exception reading input stream ...", e);
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                logger.warn("Exception closing input stream ...", e);
            }
        }
        return sb.toString();
    }
}