org.opentox.toxotis.client.http.PostHttpClient.java Source code

Java tutorial

Introduction

Here is the source code for org.opentox.toxotis.client.http.PostHttpClient.java

Source

/*
 *
 * ToxOtis
 *
 * ToxOtis is the Greek word for Sagittarius, that actually means archer. ToxOtis
 * is a Java interface to the predictive toxicology services of OpenTox. ToxOtis is
 * being developed to help both those who need a painless way to consume OpenTox
 * services and for ambitious service providers that dont want to spend half of
 * their time in RDF parsing and creation.
 *
 * Copyright (C) 2009-2010 Pantelis Sopasakis & Charalampos Chomenides
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact:
 * Pantelis Sopasakis
 * chvng@mail.ntua.gr
 * Address: Iroon Politechniou St. 9, Zografou, Athens Greece
 * tel. +30 210 7723236
 *
 */
package org.opentox.toxotis.client.http;

import java.io.InputStream;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.opentox.toxotis.client.IPostClient;
import com.hp.hpl.jena.ontology.OntModel;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.lang.StringUtils;
import org.opentox.toxotis.client.AbstractClient;
import org.opentox.toxotis.client.RequestHeaders;
import org.opentox.toxotis.client.VRI;
import org.opentox.toxotis.client.collection.Media;
import org.opentox.toxotis.core.IStAXWritable;
import org.opentox.toxotis.exceptions.impl.ConnectionException;
import org.opentox.toxotis.exceptions.impl.InternalServerError;
import org.opentox.toxotis.exceptions.impl.ServiceInvocationException;

/**
 * A client used to perform POST operations. It is used to perform POST requests in
 * a configurable way allowing users to specify the POSTed object and the various
 * header parameters.
 * 
 * @author Pantelis Sopasakis
 * @author Charalampos Chomenides
 */
public class PostHttpClient extends AbstractHttpClient implements IPostClient {

    //TODO: Add postable InputStream (What else?)
    /** Type of the posted content*/
    private String contentType = null;
    /** Parameters to be posted as application/x-www-form-urlencoded (if any) */
    private final Map<String, List<String>> postParameters = new LinkedHashMap<String, List<String>>();
    private OntModel model;
    /** Arbitrary object to be posted to the remote server s*/
    private File fileContentToPost = null;
    private InputStream inputStream = null;
    private String fileUploadFieldName = "upload";
    private String fileUploadFilename = "uploadedFile";
    /** A simple string to be posted to the remote service */
    private String stringToPost;
    private String bytesToPost;
    /** A StAX component that implements the interface {@link IStAXWritable }
    that will be posted to the remote server via the method {@link IStAXWritable#writeRdf(java.io.OutputStream)
    write(OutputStream)} that writes the component to an output stream pointing to the
    remote stream
     */
    private IStAXWritable staxComponent;
    private final WriteLock postLock = new ReentrantReadWriteLock().writeLock();

    public PostHttpClient() {
        super();
    }

    public PostHttpClient(VRI vri) {
        super();
        setUri(vri);
    }

    @Override
    public String getContentType() {
        return contentType;
    }

    @Override
    public PostHttpClient setContentType(String contentType) {
        this.contentType = contentType;
        return this;
    }

    @Override
    public PostHttpClient setContentType(Media media) {
        this.contentType = media.getMime();
        return this;
    }

    /**
     * Set an ontological data model which is to be posted to the remote location
     * as application/rdf+xml. Invocations of this method set automatically the content-type
     * to application/rdf+xml though it can be overridden afterwards.
     * @param model
     *      Ontological Model to be posted
     * @return
     *      The PostHttpClient with the updated Ontological Model.
     */
    @Override
    public PostHttpClient setPostable(OntModel model) {
        this.model = model;
        return this;
    }

    /**
     * Set a StAX-writeable component to be posted to the remote location
     * @param staxWritable
     *      A StAX component that implements the interface {@link IStAXWritable }
     *      that will be posted to the remote server via the method {@link IStAXWritable#writeRdf(java.io.OutputStream)
     *      write(OutputStream)} that writes the component to an outputstream pointing to the remote stream
     * @return
     *      The PostHttpClient with the updated writeable component.
     */
    @Override
    public PostHttpClient setPostable(IStAXWritable staxWritable) {
        this.staxComponent = staxWritable;
        return this;
    }

    /**
     * Set a file whose contents are to be posted to the remote server specified
     * in the constructor of this class. If the file is not found under the specified
     * path, an IllegalArgumentException is thrown. Because the type of the file is
     * in general unknown and it is not considered to be a good practice to deduce the
     * file type from the file extension, it is up to the user to specify the content
     * type of the posted object using the method {@link PostHttpClient#setContentType(java.lang.String)
     * setContentType}. Since it is not possible to POST entities of different content
     * types to an HTTP server, any invocation to this method will override any previous
     * invocation of {@link PostHttpClient#setPostable(com.hp.hpl.jena.ontology.OntModel)
     * setPostable(OntModel) } and {@link PostHttpClient#setPostable(java.lang.String, boolean) 
     * setPostable(String)}.
     *
     * @param objectToPost
     *      File whose contents are to be posted.
     * @return
     *      This post client
     * @throws IllegalArgumentException
     *      In case the provided file does not exist
     */
    @Override
    public PostHttpClient setPostable(File objectToPost) {
        if (objectToPost != null && !objectToPost.exists()) {
            throw new IllegalArgumentException(
                    new FileNotFoundException("No file was found at the specified path!"));
        }
        this.fileContentToPost = objectToPost;
        return this;
    }

    @Override
    public PostHttpClient setPostable(String string, boolean binary) {
        if (binary) {
            this.bytesToPost = string;
        } else {
            this.stringToPost = string;
        }
        return this;
    }

    /**
     * Add a parameter which will be posted to the target URI. Once the parameter is
     * submitted to the PostHttpClient, it is stored as URL-encoded using the UTF-8 encoding.
     * @param paramName Parameter name
     * @param paramValue Parameter value
     * @return This object
     * @throws NullPointerException If paramName is <code>null</code>.
     */
    @Override
    public PostHttpClient addPostParameter(String paramName, String paramValue) {
        if (paramName == null) {
            throw new NullPointerException("paramName must be not null");
        }
        try {
            String encodedParamName = URLEncoder.encode(paramName, URL_ENCODING);
            String encodedParamvalue = paramValue != null ? URLEncoder.encode(paramValue, URL_ENCODING) : "";
            List<String> list = postParameters.get(encodedParamName); // check if param exists
            if (list != null) { // param exists
                list.add(encodedParamvalue);
            } else { // param does not exist
                list = new ArrayList<String>();
                list.add(encodedParamvalue);
                postParameters.put(encodedParamName, list);
            }
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException("Unsupported Encoding", ex);
        }
        return this;
    }

    @Override
    public AbstractClient addHeaderParameter(String paramName, String paramValue) {
        if (paramName == null) {
            throw new NullPointerException("ParamName is null");
        }
        if (paramValue == null) {
            throw new NullPointerException("ParamValue is null");
        }
        if (RequestHeaders.ACCEPT.equalsIgnoreCase(paramName)) {
            setMediaType(paramValue);
            return this;
        }
        if (RequestHeaders.CONTENT_TYPE.equalsIgnoreCase(paramName)) {
            setContentType(paramValue);
            return this;
        }
        return super.addHeaderParameter(paramName, paramValue);
    }

    String getParametersAsQuery() {
        if (postParameters.isEmpty()) {
            return "";
        }
        StringBuilder string = new StringBuilder();
        final int nParams = postParameters.size();

        if (nParams > 0) {
            for (Map.Entry<String, List<String>> e : postParameters.entrySet()) {
                List<String> values = e.getValue();
                for (String value : values) {
                    string.append(e.getKey());
                    string.append("=");
                    if (e.getValue() != null) {
                        string.append(value);
                    }
                    string.append("&");
                }
            }
        }
        string.deleteCharAt(string.length() - 1);
        return new String(string);
    }

    /** 
     * Initialize a connection to the target URI
     * 
     * @return 
     *  The HTTP connection.
     * 
     * @throws org.opentox.toxotis.exceptions.impl.InternalServerError 
     * @throws org.opentox.toxotis.exceptions.impl.ConnectionException 
     */
    @Override
    protected java.net.HttpURLConnection initializeConnection(final java.net.URI uri)
            throws InternalServerError, ConnectionException {
        try {
            java.net.HttpURLConnection.setFollowRedirects(true);
            java.net.URL target = uri.toURL();
            setConnection((java.net.HttpURLConnection) target.openConnection());
            getConnection().setRequestMethod(METHOD);
            getConnection().setAllowUserInteraction(false);
            getConnection().setDoInput(true);
            getConnection().setDoOutput(true); // allow data to be posted
            getConnection().setUseCaches(false);
            if (contentType != null) {
                getConnection().setRequestProperty(RequestHeaders.CONTENT_TYPE, contentType);
            }
            if (getMediaType() != null) {
                getConnection().setRequestProperty(RequestHeaders.ACCEPT, getMediaType());
            }
            if (!getHeaderValues().isEmpty()) {
                for (Map.Entry<String, String> e : getHeaderValues().entrySet()) {
                    getConnection().setRequestProperty(e.getKey(), e.getValue());// These are already URI-encoded!
                }
            }
            /* If there are some parameters to be posted, then the POST will
             * declare the posted data as application/x-form-urlencoded.
             */
            if (!postParameters.isEmpty()) {
                setContentType(Media.APPLICATION_FORM_URL_ENCODED);
                getConnection().setRequestProperty(RequestHeaders.CONTENT_TYPE, contentType);
                getConnection().setRequestProperty(RequestHeaders.CONTENT_LENGTH,
                        Integer.toString(getParametersAsQuery().getBytes().length));
            }
            return getConnection();
        } catch (final IOException ex) {
            throw new ConnectionException("Unable to connect to the remote service at '" + getUri() + "'", ex);
        } catch (final Exception unexpectedException) {
            throw new InternalServerError(
                    "Unexpected condition while attempting to " + "establish a connection to '" + uri + "'",
                    unexpectedException);
        }
    }

    /**
     * According to the the configuration of the PostHttpClient, performs a remote POST
     * request to the server identified by the URI provided in the constructor. First,
     * the protected method {@link PostHttpClient#initializeConnection(java.net.URI)
     * initializeConnection(URI)} is invoked and then a DataOutputStream opens to
     * transfer the data to the server.
     *
     * @throws ServiceInvocationException
     *      Encapsulates an IOException which might be thrown due to I/O errors
     *      during the data transaction.
     */
    @Override
    public void post() throws ServiceInvocationException {
        String query = getParametersAsQuery();
        if ((fileContentToPost != null || inputStream != null) && query != null && !query.isEmpty()) {
            postMultiPart();
        } else {
            connect(getUri().toURI());
            DataOutputStream wr;
            try {
                getPostLock().lock(); // LOCK
                wr = new DataOutputStream(getConnection().getOutputStream());

                if (query != null && !query.isEmpty()) {
                    wr.writeBytes(getParametersAsQuery());// POST the parameters
                } else if (model != null) {
                    model.write(wr);
                } else if (staxComponent != null) {
                    staxComponent.writeRdf(wr);
                } else if (stringToPost != null) {
                    wr.writeChars(stringToPost);
                } else if (bytesToPost != null) {
                    wr.writeBytes(bytesToPost);
                } else if (fileContentToPost != null || inputStream != null) {
                    // Post from file (binary data) or from other input stream
                    InputStream is = null;
                    InputStreamReader isr = null;
                    BufferedReader br = null;
                    boolean doCloseInputStream = false;
                    // choose input stream (used-defined or from file)
                    if (fileContentToPost != null) {
                        is = new FileInputStream(fileContentToPost);
                        doCloseInputStream = true;
                    } else {
                        is = inputStream;
                    }
                    try {
                        isr = new InputStreamReader(is);
                        br = new BufferedReader(isr);
                        String line;
                        while ((line = br.readLine()) != null) {
                            wr.writeBytes(line);
                            wr.writeChars("\n");
                        }
                    } catch (IOException ex) {
                        throw new ConnectionException("Unable to post data to the remote service at '" + getUri()
                                + "' - The connection dropped " + "unexpectidly while POSTing.", ex);
                    } finally {
                        Throwable thr = null;
                        if (br != null) {
                            try {
                                br.close();
                            } catch (final IOException ex) {
                                thr = ex;
                            }
                        }
                        if (isr != null) {
                            try {
                                isr.close();
                            } catch (final IOException ex) {
                                thr = ex;
                            }
                        }
                        if (doCloseInputStream) {
                            try {
                                is.close();
                            } catch (final IOException ex) {
                                thr = ex;
                            }
                        }
                        if (thr != null) {
                            ConnectionException connExc = new ConnectionException("Stream could not close", thr);
                            connExc.setActor(getUri() != null ? getUri().toString() : "N/A");
                            //TODO Why is the exception not thrown here?
                        }
                    }
                }
                wr.flush();
                wr.close();
            } catch (final IOException ex) {
                ConnectionException postException = new ConnectionException(
                        "Exception caught while posting the parameters to the " + "remote web service located at '"
                                + getUri() + "'",
                        ex);
                postException.setActor(getUri() != null ? getUri().toString() : "N/A");
                throw postException;
            } finally {
                getPostLock().unlock(); // UNLOCK
            }
        }
    }

    private void postMultiPart() throws ServiceInvocationException {
        String charset = "UTF-8";
        String LINE_FEED = "\r\n";
        InputStream is;

        try {
            getPostLock().lock(); // LOCK
            if (fileContentToPost != null) {
                is = new FileInputStream(fileContentToPost);
            } else {
                is = inputStream;
            }

            // creates a unique boundary based on time stamp
            long boundaryTs = System.currentTimeMillis();
            String boundary = "===" + boundaryTs + "===";

            HttpURLConnection httpConn = connect(getUri().toURI());
            httpConn.setUseCaches(false);
            httpConn.setDoOutput(true); // indicates POST method
            httpConn.setDoInput(true);
            httpConn.setRequestProperty("Content-Type", "multipart/form-data;boundary=\"" + boundary + "\"");
            httpConn.setRequestProperty("User-Agent", "CodeJava Agent");
            httpConn.setRequestProperty("Test", "Bonjour");

            OutputStream outputStream = httpConn.getOutputStream();
            PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true);

            final int nParams = postParameters.size();

            if (nParams > 0) {
                for (Map.Entry<String, List<String>> e : postParameters.entrySet()) {
                    List<String> values = e.getValue();
                    for (String value : values) {

                        writer.append("--" + boundary).append(LINE_FEED);
                        writer.append("Content-Disposition: form-data; name=\"" + e.getKey() + "\"")
                                .append(LINE_FEED);
                        writer.append("Content-Type: text/plain; charset=" + charset).append(LINE_FEED);
                        writer.append(LINE_FEED);
                        writer.append(value).append(LINE_FEED);
                        writer.flush();
                    }
                }
            }

            if (is.read() > 0) {
                is.reset();
                writer.append("--" + boundary).append(LINE_FEED);
                writer.append("Content-Disposition: form-data; name=\"" + fileUploadFieldName + "\"; filename=\""
                        + fileUploadFilename + "\"").append(LINE_FEED);
                writer.append(
                        "Content-Type: " + java.net.URLConnection.guessContentTypeFromName(fileUploadFilename))
                        .append(LINE_FEED);
                writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
                writer.append(LINE_FEED);
                writer.flush();

                byte[] buffer = new byte[4096];
                int bytesRead = -1;
                while ((bytesRead = is.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.flush();
                is.close();

                writer.append(LINE_FEED);
                writer.flush();
                writer.append(LINE_FEED).flush();
            }

            writer.append("--" + boundary + "--").append(LINE_FEED);
            writer.close();
        } catch (final IOException ex) {
            ConnectionException postException = new ConnectionException(
                    "Exception caught while posting the parameters to the " + "remote web service located at '"
                            + getUri() + "'",
                    ex);
            postException.setActor(getUri() != null ? getUri().toString() : "N/A");
            throw postException;
        } finally {
            getPostLock().unlock(); // UNLOCK
        }
    }

    @Override
    public WriteLock getPostLock() {
        return postLock;
    }

    @Override
    public IPostClient setPostable(InputStream inputStream) {
        this.inputStream = inputStream;
        return this;
    }

    public void setPostableFilename(String fieldName, String filename) {
        this.fileUploadFilename = (StringUtils.isEmpty(filename)) ? this.fileUploadFilename : filename;
        this.fileUploadFieldName = (StringUtils.isEmpty(fieldName)) ? this.fileUploadFieldName : fieldName;
    }
}