org.collectionspace.chain.csp.persistence.services.connection.ServicesConnection.java Source code

Java tutorial

Introduction

Here is the source code for org.collectionspace.chain.csp.persistence.services.connection.ServicesConnection.java

Source

/* Copyright 2010 University of Cambridge
 * Licensed under the Educational Community License (ECL), Version 2.0. You may not use this file except in 
 * compliance with this License.
 *
 * You may obtain a copy of the ECL 2.0 License at https://source.collectionspace.org/collection-space/LICENSE.txt
 */
package org.collectionspace.chain.csp.persistence.services.connection;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.io.input.TeeInputStream;
import org.collectionspace.chain.csp.persistence.services.ServicesStorageGenerator;
import org.collectionspace.csp.api.core.CSPRequestCache;
import org.collectionspace.csp.api.core.CSPRequestCredentials;
import org.dom4j.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** The actual REST calls are handled by ServicesConnection, which uses utility types ReturnedDocument and ReturnedURL
 * to return things along with status codes, etc. Less than ideal: answers on a postcard, please.
 * 
 */

// XXX Add useful info to ConnectionException on way out

public class ServicesConnection {
    public static final int MAX_SERVICES_CONNECTIONS = 20;

    private static final Logger log = LoggerFactory.getLogger(ServicesConnection.class);
    private static final Logger perflog = LoggerFactory.getLogger("org.collectionspace.perflog");
    private String base_url, ims_url;
    private MultiThreadedHttpConnectionManager manager;

    private void initClient() {
        if (manager != null)
            return;
        synchronized (getClass()) {
            if (manager != null)
                return;

            // We're only connecting to one host, so set the max connections per host to be
            // the same as the max total connections.

            HttpConnectionManagerParams params = new HttpConnectionManagerParams();
            params.setMaxTotalConnections(MAX_SERVICES_CONNECTIONS);
            params.setDefaultMaxConnectionsPerHost(MAX_SERVICES_CONNECTIONS);

            manager = new MultiThreadedHttpConnectionManager();
            manager.setParams(params);
        }
    }

    public HttpClient makeClient(CSPRequestCredentials creds, CSPRequestCache cache) {
        // Check request cache
        HttpClient client = (HttpClient) cache.getCached(getClass(), new String[] { "client" });
        if (client != null)
            return client;
        client = new HttpClient(manager);
        client.getState().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
                new UsernamePasswordCredentials((String) creds.getCredential(ServicesStorageGenerator.CRED_USERID),
                        (String) creds.getCredential(ServicesStorageGenerator.CRED_PASSWORD)));
        client.getParams().setAuthenticationPreemptive(true);
        cache.setCached(getClass(), new String[] { "client" }, client);
        return client;
    }

    public ServicesConnection(String base_url, String ims_url) {
        if (base_url.endsWith("/"))
            base_url = base_url.substring(0, base_url.length() - 1);
        this.base_url = base_url;
        this.ims_url = ims_url;
        initClient();
    }

    public String getBase() {
        return base_url;
    }

    public String getIMSBase() {
        return ims_url;
    }

    private String prepend_base(String uri) throws ConnectionException {
        if (uri == null)
            throw new ConnectionException("URI cannot be null");
        if (!uri.startsWith("/"))
            uri = "/" + uri;
        return base_url + uri;
    }

    private HttpMethod createMethod(RequestMethod method, String uri, InputStream data) throws ConnectionException {
        uri = prepend_base(uri);
        if (uri == null)
            throw new ConnectionException("URI must not be null");
        // Extract QP's
        int qp_start = uri.indexOf('?');
        String qps = null;
        if (qp_start != -1) {
            qps = uri.substring(qp_start + 1);
            uri = uri.substring(0, qp_start);
        }
        HttpMethod out = null;
        switch (method) {
        case POST: {
            out = new PostMethod(uri);
            if (data != null)
                ((PostMethod) out).setRequestEntity(new InputStreamRequestEntity(data));
            break;
        }
        case PUT: {
            out = new PutMethod(uri);
            if (data != null)
                ((PutMethod) out).setRequestEntity(new InputStreamRequestEntity(data));
            break;
        }
        case GET:
            out = new GetMethod(uri);
            break;
        case DELETE:
            out = new DeleteMethod(uri);
            break;
        default:
            throw new ConnectionException("Unsupported method " + method, 0, uri);
        }
        if (qps != null)
            out.setQueryString(qps);
        out.setDoAuthentication(true);
        return out;
    }

    private void closeStream(InputStream stream) throws ConnectionException {
        if (stream != null)
            try {
                stream.close();
            } catch (IOException e) {
                // Close failed: nothing we can do. Is a ByteArrayInputStream, anyway, should be impossible.
                throw new ConnectionException("Impossible exception raised during close of BAIS!?");
            }
    }

    // XXX eugh! error case control-flow nightmare
    private void doRequest(Returned out, RequestMethod method_type, String uri, RequestDataSource src,
            CSPRequestCredentials creds, CSPRequestCache cache) throws ConnectionException {
        InputStream body_data = null;
        if (src != null) {
            body_data = src.getStream();
        }
        try {
            HttpMethod method = createMethod(method_type, uri, body_data);
            if (body_data != null) {
                method.setRequestHeader("Content-Type", src.getMIMEType());
                // XXX Not sure if or when this ever actually writes to stderr?
                body_data = new TeeInputStream(body_data, System.err);
            }
            try {
                HttpClient client = makeClient(creds, cache);

                String requestContext = null;
                if (perflog.isDebugEnabled()) {
                    // TODO add more context, e.g. session id?
                    requestContext = "HttpClient@" + Integer.toHexString(client.hashCode());
                    requestContext += "/CSPRequestCache@" + Integer.toHexString(cache.hashCode()) + ",";
                    //String queryString = method.getQueryString();
                    perflog.debug(System.currentTimeMillis() + ",\"" + Thread.currentThread().getName()
                            + "\",app,svc," + requestContext + method.getName() + " " + method.getURI()
                    //+ (queryString!=null ? queryString : "")
                    );
                }

                int response = client.executeMethod(method);

                if (perflog.isDebugEnabled()) {
                    perflog.debug(System.currentTimeMillis() + ",\"" + Thread.currentThread().getName()
                            + "\",svc,app," + requestContext + "HttpClient.executeMethod done");
                }

                out.setResponse(method, response);
            } catch (ConnectionException e) {
                throw new ConnectionException(e.getMessage(), e.status, base_url + "/" + uri, e);
            } catch (Exception e) {
                throw new ConnectionException(e.getMessage(), 0, base_url + "/" + uri, e);
            } finally {
                method.releaseConnection();

                if (log.isWarnEnabled()) {
                    if (manager.getConnectionsInPool() >= MAX_SERVICES_CONNECTIONS) {
                        log.warn("reached max services connection limit of " + MAX_SERVICES_CONNECTIONS);

                        // Delete closed connections from the pool, so that the warning will cease
                        // once a connection becomes available.
                        manager.deleteClosedConnections();
                    }
                }
            }
        } finally {
            closeStream(body_data);
        }
    }

    private RequestDataSource makeStringSource(byte[] body, String type, String uploadname)
            throws ConnectionException {
        RequestDataSource src = null;
        if (body != null) {
            src = new StringRequestDataSource(body, type, uploadname);
        }
        return src;
    }

    private RequestDataSource makeDocumentSource(Document body) throws ConnectionException {
        RequestDataSource src = null;
        if (body != null) {
            src = new DocumentRequestDataSource(body);
        }
        return src;
    }

    private RequestDataSource makeUnknownSource(Document body) throws ConnectionException {
        RequestDataSource src = null;
        if (body != null) {
            src = new DocumentUnknownRequestDataSource(body);
        }
        return src;
    }

    private RequestDataSource makeMultipartSource(Map<String, Document> body) throws ConnectionException {
        RequestDataSource src = null;
        if (body != null) {
            src = new MultipartRequestDataSource(body);
        }
        return src;
    }

    public ReturnedDocument getXMLDocument(RequestMethod method_type, String uri, Document body,
            CSPRequestCredentials creds, CSPRequestCache cache) throws ConnectionException {
        ReturnedDocument out = new ReturnedDocument();
        doRequest(out, method_type, uri, makeDocumentSource(body), creds, cache);
        return out;
    }

    public ReturnUnknown getUnknownDocument(RequestMethod method_type, String uri, Document body,
            CSPRequestCredentials creds, CSPRequestCache cache) throws ConnectionException {
        ReturnUnknown out = new ReturnUnknown();
        doRequest(out, method_type, uri, makeUnknownSource(body), creds, cache);
        return out;
    }

    public ReturnUnknown getReportDocument(RequestMethod method_type, String uri, Document body,
            CSPRequestCredentials creds, CSPRequestCache cache) throws ConnectionException {
        ReturnUnknown out = new ReturnUnknown();
        doRequest(out, method_type, uri, makeDocumentSource(body), creds, cache);
        return out;
    }

    public ReturnedURL getPublishedReportDocumentURL(RequestMethod method_type, String uri, Document body,
            CSPRequestCredentials creds, CSPRequestCache cache) throws ConnectionException {
        ReturnedURL out = new ReturnedURL();
        doRequest(out, method_type, uri, makeDocumentSource(body), creds, cache);
        out.relativize(base_url); // Annoying, but we don't want to have factories etc. or too many args
        return out;
    }

    public ReturnedMultipartDocument getMultipartXMLDocument(RequestMethod method_type, String uri,
            Map<String, Document> body, CSPRequestCredentials creds, CSPRequestCache cache)
            throws ConnectionException {
        ReturnedMultipartDocument out = new ReturnedMultipartDocument();
        doRequest(out, method_type, uri, makeMultipartSource(body), creds, cache);
        return out;
    }

    public String getTextDocument(RequestMethod method_type, String uri, Document body, CSPRequestCredentials creds,
            CSPRequestCache cache) throws ConnectionException {
        ReturnedText out = new ReturnedText();
        doRequest(out, method_type, uri, makeDocumentSource(body), creds, cache);
        return out.getText();
    }

    public ReturnedURL getStringURL(RequestMethod method_type, String uri, byte[] body, String uploadname,
            String type, CSPRequestCredentials creds, CSPRequestCache cache) throws ConnectionException {
        ReturnedURL out = new ReturnedURL();
        doRequest(out, method_type, uri, makeStringSource(body, type, uploadname), creds, cache);
        out.relativize(base_url); // Annoying, but we don't want to have factories etc. or too many args
        return out;
    }

    public ReturnedURL getURL(RequestMethod method_type, String uri, Document body, CSPRequestCredentials creds,
            CSPRequestCache cache) throws ConnectionException {
        ReturnedURL out = new ReturnedURL();
        doRequest(out, method_type, uri, makeDocumentSource(body), creds, cache);
        out.relativize(base_url); // Annoying, but we don't want to have factories etc. or too many args
        return out;
    }

    public ReturnedURL getMultipartURL(RequestMethod method_type, String uri, Map<String, Document> body,
            CSPRequestCredentials creds, CSPRequestCache cache) throws ConnectionException {
        ReturnedURL out = new ReturnedURL();
        doRequest(out, method_type, uri, makeMultipartSource(body), creds, cache);
        out.relativize(base_url); // Annoying, but we don't want to have factories etc. or too many args
        return out;
    }

    public int getNone(RequestMethod method_type, String uri, Document body, CSPRequestCredentials creds,
            CSPRequestCache cache) throws ConnectionException {
        ReturnedNone out = new ReturnedNone();
        doRequest(out, method_type, uri, makeDocumentSource(body), creds, cache);
        return out.getStatus();
    }
}