Sample Code for Working with the Limpopo Web Service

This code should work against the Limpopo webservice currently deployed at http://wwwdev.ebi.ac.uk/fgpt/limpopo. This is work in progress, and may be subject to change without notice - use at your own risk!

The following code has two required dependencies: the Apache httpclient library for sending GET and POST requests, and the Codehaus Jackson library for JSON processing. Otherwise, this code should run out of the box.

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A simple client that demonstrates some features of the REST API behind the Limpopo web service.
 *
 * Runs from the command line, and takes a single argument - the accession to retrieve from ArrayExpress.
 *
 * @author Tony Burdett
 * @date 12/07/11
 */
public class LimpopoRESTClient {
    private HttpClient client = new HttpClient();

    public static void main(String[] args) {
        try {
            LimpopoRESTClient client = new LimpopoRESTClient();
            client.runDemo(args[0]);
        }
        catch (Exception e) {
            System.err.print("Application failed: " + e.getMessage());
            System.exit(1);
        }
    }

    public void runDemo(String accession) {
        // pass accession to the service to fetch json
        String getJson = fetchMAGETAB(accession);
        System.out.println("We sent a GET request to the server for " + accession + " and got the following response:");
        System.out.println(getJson);
        System.out.println("\n\n\n============================\n\n\n");

        // now, just to prove we can roundtrip, parse the json into a simple magetab representation (two 2D String arrays)
        MAGETABSpreadsheet dataFromServer = parseLimpopoJSON(getJson);

        // now, post these 2D string arrays to the server and collect any error items
        String postJson = sendMAGETAB(dataFromServer.getIdf(), dataFromServer.getSdrf());
        System.out.println("We POSTed IDF and SDRF json objects to the server, and got the following response:");
        System.out.println(postJson);
        System.out.println("\n\n\n============================\n\n\n");

        // now, extract error items from the resulting JSON
        List<Map<String, Object>> errors = parseErrorItems(postJson);
        System.out.println("Our POST operation has returned " + errors.size() + " error items, these follow:");
        for (Map<String, Object> error : errors) {
            System.out.println("\t" +
                                       error.get("code") + ":\t" +
                                       error.get("comment") + "\t[line " +
                                       error.get("line") + ", column" +
                                       error.get("column") + "]");
        }
    }

    public String fetchMAGETAB(String accession) {
        // send a request to limpopo
        String getURL = "http://wwwdev.ebi.ac.uk/fgpt/limpopo/api/accessions/" + accession;
        GetMethod get = new GetMethod(getURL);
        get.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                                     new DefaultHttpMethodRetryHandler(3, false));

        // execute the method method to retrieve json for 'accession'
        return executeGet(get);
    }

    public String sendMAGETAB(String[][] idf, String[][] sdrf) {
        // create post method for sending json back
        String postURL = "http://wwwdev.ebi.ac.uk/fgpt/limpopo/api/magetab";
        PostMethod post = new PostMethod(postURL);

        return executePost(post, idf, sdrf);
    }

    public String convert2DStringArrayToJSON(String[][] in) {
        try {
            JsonFactory jsonFactory = new JsonFactory();
            ObjectMapper mapper = new ObjectMapper(jsonFactory);

            StringWriter out = new StringWriter();
            JsonGenerator jg = jsonFactory.createJsonGenerator(out);

            mapper.writeValue(jg, in);
            return out.toString();
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public MAGETABSpreadsheet parseLimpopoJSON(String json) {
        try {
            // parse json
            JsonFactory jsonFactory = new JsonFactory();
            ObjectMapper mapper = new ObjectMapper(jsonFactory);
            TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {
            };

            HashMap<String, Object> requestResults = new HashMap<String, Object>();
            requestResults = mapper.readValue(json, typeRef);

            List<List<String>> idfObject = (List<List<String>>) requestResults.get("idf");
            List<List<String>> sdrfObject = (List<List<String>>) requestResults.get("sdrf");

            String[][] idf = new String[idfObject.size()][];
            int i = 0;
            for (List<String> row : idfObject) {
                String[] rowArr = new String[row.size()];
                idf[i] = row.toArray(rowArr);
                i++;
            }

            String[][] sdrf = new String[sdrfObject.size()][];
            int j = 0;
            for (List<String> row : sdrfObject) {
                String[] rowArr = new String[row.size()];
                sdrf[j] = row.toArray(rowArr);
                j++;
            }

            return new MAGETABSpreadsheet(idf, sdrf);
        }
        catch (JsonMappingException e) {
            throw new RuntimeException(e);
        }
        catch (JsonParseException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public List<Map<String, Object>> parseErrorItems(String json) {
        try {
            // parse json
            JsonFactory jsonFactory = new JsonFactory();
            ObjectMapper mapper = new ObjectMapper(jsonFactory);
            TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {
            };

            HashMap<String, Object> requestResults = new HashMap<String, Object>();
            requestResults = mapper.readValue(json, typeRef);

            return (List<Map<String, Object>>) requestResults.get("errors");
        }
        catch (JsonMappingException e) {
            throw new RuntimeException(e);
        }
        catch (JsonParseException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String executeGet(GetMethod get) {
        try {
            // Execute the method.
            int statusCode = client.executeMethod(get);
            if (statusCode != HttpStatus.SC_OK) {
                System.err.println("Method failed: " + get.getStatusLine());
            }
            else {
                System.out.println("Sent GET request to " + get.getURI() + ", response code " + statusCode);
            }

            // Read the response body.
            return parseResponse(get);
        }
        catch (HttpException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            // Release the connection.
            get.releaseConnection();
        }
    }

    public String executePost(PostMethod post, String[][] idf, String[][] sdrf) {
        // create json objects from the IDF and SDRF parts of the spreadsheet
        String idfJson = convert2DStringArrayToJSON(idf);
        String sdrfJson = convert2DStringArrayToJSON(sdrf);

        post.addParameter("idf", idfJson);
        post.addParameter("sdrf", sdrfJson);

        // bounce back to the magetab parsing hook
        try {
            client.executeMethod(post);

            if (post.getStatusCode() != HttpStatus.SC_OK) {
                System.err.println("Method failed: " + post.getStatusLine());
            }
            else {
                System.out.println("Sent POST request to " + post.getURI());
                System.out.println("The following parameters were posted along with this request:");
                System.out.println("IDF:\n" + idfJson);
                System.out.println("SDRF:\n" + sdrfJson);
            }
            return parseResponse(post);
        }
        catch (HttpException e) {
            throw new RuntimeException("Fatal protocol violation: " + e.getMessage(), e);
        }
        catch (IOException e) {
            throw new RuntimeException("Fatal transport error: " + e.getMessage(), e);
        }
        finally {
            // Release the connection.
            post.releaseConnection();
        }
    }

    public String parseResponse(HttpMethodBase method) {
        try {
            String charset = method.getResponseCharSet();
            for (Header header : method.getResponseHeaders()) {
                if (header.getName().contains("Content-Type")) {
                    // check we have a json response
                    if (header.getValue().contains("application/json")) {
                        // all ok, method response body and convert to json
                        System.out
                                .println("This request generated a valid response with application/json response type");

                        byte[] responseBody = method.getResponseBody();
                        return new String(responseBody, charset);
                    }
                    else {
                        throw new RuntimeException("Unexpected response type: should be application/json");
                    }
                }
            }
            throw new RuntimeException("No Content-Type header found");
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        catch (URIException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public class MAGETABSpreadsheet {
        private String[][] idf;
        private String[][] sdrf;

        public MAGETABSpreadsheet(String[][] idf, String[][] sdrf) {
            this.idf = idf;
            this.sdrf = sdrf;
        }

        public String[][] getIdf() {
            return idf;
        }

        public String[][] getSdrf() {
            return sdrf;
        }
    }
}