org.apache.manifoldcf.crawler.connectors.meridio.CommonsHTTPSender.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.manifoldcf.crawler.connectors.meridio.CommonsHTTPSender.java

Source

/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed 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.
*
* $Id: CommonsHTTPSender.java 988245 2010-08-23 18:39:35Z kwright $
*/
package org.apache.manifoldcf.crawler.connectors.meridio;

import org.apache.manifoldcf.core.common.XThreadInputStream;

import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.components.net.CommonsHTTPClientProperties;
import org.apache.axis.components.net.CommonsHTTPClientPropertiesFactory;
import org.apache.axis.components.net.TransportClientProperties;
import org.apache.axis.components.net.TransportClientPropertiesFactory;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.handlers.BasicHandler;
import org.apache.axis.soap.SOAP12Constants;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.NetworkUtils;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.Header;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.ProtocolVersion;
import org.apache.http.util.EntityUtils;
import org.apache.http.message.BasicHeader;

import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.client.RedirectException;
import org.apache.http.client.CircularRedirectException;
import org.apache.http.NoHttpResponseException;
import org.apache.http.HttpException;

import org.apache.commons.logging.Log;

import javax.xml.soap.MimeHeader;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPException;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.Writer;
import java.io.StringWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.util.List;

/* Class to use httpcomponents to communicate with a SOAP server.
* I've replaced the original rather complicated class with a much simpler one that
* relies on having an HttpClient object passed into the invoke() method.  Since
* the object is already set up, not much needs to be done in here.
*/

public class CommonsHTTPSender extends BasicHandler {

    /** Field log           */
    protected static Log log = LogFactory.getLog(CommonsHTTPSender.class.getName());

    /** Properties */
    protected CommonsHTTPClientProperties clientProperties;

    public CommonsHTTPSender() {
        this.clientProperties = CommonsHTTPClientPropertiesFactory.create();
    }

    /**
    * invoke creates a socket connection, sends the request SOAP message and then
    * reads the response SOAP message back from the SOAP server
    *
    * @param msgContext the messsage context
    *
    * @throws AxisFault
    */
    public void invoke(MessageContext msgContext) throws AxisFault {
        if (log.isDebugEnabled()) {
            log.debug(Messages.getMessage("enter00", "CommonsHTTPSender::invoke"));
        }

        // Catch all exceptions and turn them into AxisFaults
        try {
            // Get the URL
            URL targetURL = new URL(msgContext.getStrProp(MessageContext.TRANS_URL));

            // Get the HttpClient
            HttpClient httpClient = (HttpClient) msgContext.getProperty(
                    org.apache.manifoldcf.crawler.connectors.meridio.meridiowrapper.MeridioWrapper.HTTPCLIENT_PROPERTY);

            boolean posting = true;
            // If we're SOAP 1.2, allow the web method to be set from the
            // MessageContext.
            if (msgContext.getSOAPConstants() == SOAPConstants.SOAP12_CONSTANTS) {
                String webMethod = msgContext.getStrProp(SOAP12Constants.PROP_WEBMETHOD);
                if (webMethod != null) {
                    posting = webMethod.equals(HTTPConstants.HEADER_POST);
                }
            }

            boolean http10 = false;
            String httpVersion = msgContext.getStrProp(MessageContext.HTTP_TRANSPORT_VERSION);
            if (httpVersion != null) {
                if (httpVersion.equals(HTTPConstants.HEADER_PROTOCOL_V10)) {
                    http10 = true;
                }
                // assume 1.1
            }

            HttpRequestBase method;

            if (posting) {
                HttpPost postMethod = new HttpPost(targetURL.toString());

                // set false as default, addContetInfo can overwrite
                HttpProtocolParams.setUseExpectContinue(postMethod.getParams(), false);

                Message reqMessage = msgContext.getRequestMessage();

                boolean httpChunkStream = addContextInfo(postMethod, msgContext);

                HttpEntity requestEntity = null;
                requestEntity = new MessageRequestEntity(reqMessage, httpChunkStream, http10 || !httpChunkStream);
                postMethod.setEntity(requestEntity);
                method = postMethod;
            } else {
                method = new HttpGet(targetURL.toString());
            }

            if (http10)
                HttpProtocolParams.setVersion(method.getParams(), new ProtocolVersion("HTTP", 1, 0));

            BackgroundHTTPThread methodThread = new BackgroundHTTPThread(httpClient, method);
            methodThread.start();
            try {
                int returnCode = methodThread.getResponseCode();

                String contentType = getHeader(methodThread, HTTPConstants.HEADER_CONTENT_TYPE);
                String contentLocation = getHeader(methodThread, HTTPConstants.HEADER_CONTENT_LOCATION);
                String contentLength = getHeader(methodThread, HTTPConstants.HEADER_CONTENT_LENGTH);

                if ((returnCode > 199) && (returnCode < 300)) {

                    // SOAP return is OK - so fall through
                } else if (msgContext.getSOAPConstants() == SOAPConstants.SOAP12_CONSTANTS) {
                    // For now, if we're SOAP 1.2, fall through, since the range of
                    // valid result codes is much greater
                } else if ((contentType != null) && !contentType.equals("text/html")
                        && ((returnCode > 499) && (returnCode < 600))) {

                    // SOAP Fault should be in here - so fall through
                } else {
                    String statusMessage = methodThread.getResponseStatus();
                    AxisFault fault = new AxisFault("HTTP", "(" + returnCode + ")" + statusMessage, null, null);

                    fault.setFaultDetailString(Messages.getMessage("return01", "" + returnCode,
                            getResponseBodyAsString(methodThread)));
                    fault.addFaultDetail(Constants.QNAME_FAULTDETAIL_HTTPERRORCODE, Integer.toString(returnCode));
                    throw fault;
                }

                String contentEncoding = methodThread.getFirstHeader(HTTPConstants.HEADER_CONTENT_ENCODING);
                if (contentEncoding != null) {
                    AxisFault fault = new AxisFault("HTTP",
                            "unsupported content-encoding of '" + contentEncoding + "' found", null, null);
                    throw fault;
                }

                Map<String, List<String>> responseHeaders = methodThread.getResponseHeaders();

                InputStream dataStream = methodThread.getSafeInputStream();

                Message outMsg = new Message(new BackgroundInputStream(methodThread, dataStream), false,
                        contentType, contentLocation);

                // Transfer HTTP headers of HTTP message to MIME headers of SOAP message
                MimeHeaders responseMimeHeaders = outMsg.getMimeHeaders();
                for (String name : responseHeaders.keySet()) {
                    List<String> values = responseHeaders.get(name);
                    for (String value : values) {
                        responseMimeHeaders.addHeader(name, value);
                    }
                }
                outMsg.setMessageType(Message.RESPONSE);

                // Put the message in the message context.
                msgContext.setResponseMessage(outMsg);

                // Pass off the method thread to the stream for closure
                methodThread = null;
            } finally {
                if (methodThread != null) {
                    methodThread.abort();
                    methodThread.finishUp();
                }
            }

        } catch (AxisFault af) {
            log.debug(af);
            throw af;
        } catch (Exception e) {
            log.debug(e);
            throw AxisFault.makeFault(e);
        }

        if (log.isDebugEnabled()) {
            log.debug(Messages.getMessage("exit00", "CommonsHTTPSender::invoke"));
        }
    }

    /**
    * Extracts info from message context.
    *
    * @param method Post or get method
    * @param msgContext the message context
    */
    private static boolean addContextInfo(HttpPost method, MessageContext msgContext) throws AxisFault {

        boolean httpChunkStream = false;

        // Get SOAPAction, default to ""
        String action = msgContext.useSOAPAction() ? msgContext.getSOAPActionURI() : "";

        if (action == null) {
            action = "";
        }

        Message msg = msgContext.getRequestMessage();

        if (msg != null) {

            // First, transfer MIME headers of SOAPMessage to HTTP headers.
            // Some of these might be overridden later.
            MimeHeaders mimeHeaders = msg.getMimeHeaders();
            if (mimeHeaders != null) {
                for (Iterator i = mimeHeaders.getAllHeaders(); i.hasNext();) {
                    MimeHeader mimeHeader = (MimeHeader) i.next();
                    method.addHeader(mimeHeader.getName(), mimeHeader.getValue());
                }
            }

            method.setHeader(new BasicHeader(HTTPConstants.HEADER_CONTENT_TYPE,
                    msg.getContentType(msgContext.getSOAPConstants())));
        }

        method.setHeader(new BasicHeader("Accept", "*/*"));

        method.setHeader(new BasicHeader(HTTPConstants.HEADER_SOAP_ACTION, "\"" + action + "\""));
        method.setHeader(new BasicHeader(HTTPConstants.HEADER_USER_AGENT, Messages.getMessage("axisUserAgent")));

        // process user defined headers for information.
        Hashtable userHeaderTable = (Hashtable) msgContext.getProperty(HTTPConstants.REQUEST_HEADERS);

        if (userHeaderTable != null) {
            for (Iterator e = userHeaderTable.entrySet().iterator(); e.hasNext();) {
                Map.Entry me = (Map.Entry) e.next();
                Object keyObj = me.getKey();

                if (null == keyObj) {
                    continue;
                }
                String key = keyObj.toString().trim();
                String value = me.getValue().toString().trim();

                if (key.equalsIgnoreCase(HTTPConstants.HEADER_EXPECT)
                        && value.equalsIgnoreCase(HTTPConstants.HEADER_EXPECT_100_Continue)) {
                    HttpProtocolParams.setUseExpectContinue(method.getParams(), true);
                } else if (key.equalsIgnoreCase(HTTPConstants.HEADER_TRANSFER_ENCODING_CHUNKED)) {
                    String val = me.getValue().toString();
                    if (null != val) {
                        httpChunkStream = JavaUtils.isTrue(val);
                    }
                } else {
                    method.addHeader(key, value);
                }
            }
        }

        return httpChunkStream;
    }

    private static String getHeader(BackgroundHTTPThread methodThread, String headerName)
            throws IOException, InterruptedException, HttpException {
        String header = methodThread.getFirstHeader(headerName);
        return (header == null) ? null : header.trim();
    }

    private static String getResponseBodyAsString(BackgroundHTTPThread methodThread)
            throws IOException, InterruptedException, HttpException {
        InputStream is = methodThread.getSafeInputStream();
        if (is != null) {
            try {
                String charSet = methodThread.getCharSet();
                if (charSet == null)
                    charSet = "utf-8";
                char[] buffer = new char[65536];
                Reader r = new InputStreamReader(is, charSet);
                Writer w = new StringWriter();
                try {
                    while (true) {
                        int amt = r.read(buffer);
                        if (amt == -1)
                            break;
                        w.write(buffer, 0, amt);
                    }
                } finally {
                    w.flush();
                }
                return w.toString();
            } finally {
                is.close();
            }
        }
        return "";
    }

    private static class MessageRequestEntity implements HttpEntity {

        private final Message message;
        private final boolean httpChunkStream; //Use HTTP chunking or not.
        private final boolean contentLengthNeeded;

        public MessageRequestEntity(Message message, boolean httpChunkStream, boolean contentLengthNeeded) {
            this.message = message;
            this.httpChunkStream = httpChunkStream;
            this.contentLengthNeeded = contentLengthNeeded;
        }

        @Override
        public boolean isChunked() {
            return httpChunkStream;
        }

        @Override
        public void consumeContent() throws IOException {
            EntityUtils.consume(this);
        }

        @Override
        public boolean isRepeatable() {
            return true;
        }

        @Override
        public boolean isStreaming() {
            return false;
        }

        @Override
        public InputStream getContent() throws IOException, IllegalStateException {
            // MHL
            return null;
        }

        @Override
        public void writeTo(OutputStream out) throws IOException {
            try {
                this.message.writeTo(out);
            } catch (SOAPException e) {
                throw new IOException(e.getMessage());
            }
        }

        @Override
        public long getContentLength() {
            if (contentLengthNeeded) {
                try {
                    return message.getContentLength();
                } catch (Exception e) {
                }
            }
            // Unknown (chunked) length
            return -1L;
        }

        @Override
        public Header getContentType() {
            return null; // a separate header is added
        }

        @Override
        public Header getContentEncoding() {
            return null;
        }
    }

    /** This input stream wraps a background http transaction thread, so that
    * the thread is ended when the stream is closed.
    */
    private static class BackgroundInputStream extends InputStream {

        private BackgroundHTTPThread methodThread = null;
        private InputStream xThreadInputStream = null;

        /** Construct an http transaction stream.  The stream is driven by a background
        * thread, whose existence is tied to this class.  The sequence of activity that
        * this class expects is as follows:
        * (1) Construct the httpclient and request object and initialize them
        * (2) Construct a background method thread, and start it
        * (3) If the response calls for it, call this constructor, and put the resulting stream
        *    into the message response
        * (4) Otherwise, terminate the background method thread in the standard manner,
        *    being sure NOT
        */
        public BackgroundInputStream(BackgroundHTTPThread methodThread, InputStream xThreadInputStream) {
            this.methodThread = methodThread;
            this.xThreadInputStream = xThreadInputStream;
        }

        @Override
        public int available() throws IOException {
            if (xThreadInputStream != null)
                return xThreadInputStream.available();
            return super.available();
        }

        @Override
        public void close() throws IOException {
            try {
                if (xThreadInputStream != null) {
                    xThreadInputStream.close();
                    xThreadInputStream = null;
                }
            } finally {
                if (methodThread != null) {
                    methodThread.abort();
                    try {
                        methodThread.finishUp();
                    } catch (InterruptedException e) {
                        throw new InterruptedIOException(e.getMessage());
                    }
                    methodThread = null;
                }
            }
        }

        @Override
        public void mark(int readlimit) {
            if (xThreadInputStream != null)
                xThreadInputStream.mark(readlimit);
            else
                super.mark(readlimit);
        }

        @Override
        public void reset() throws IOException {
            if (xThreadInputStream != null)
                xThreadInputStream.reset();
            else
                super.reset();
        }

        @Override
        public boolean markSupported() {
            if (xThreadInputStream != null)
                return xThreadInputStream.markSupported();
            return super.markSupported();
        }

        @Override
        public long skip(long n) throws IOException {
            if (xThreadInputStream != null)
                return xThreadInputStream.skip(n);
            return super.skip(n);
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (xThreadInputStream != null)
                return xThreadInputStream.read(b, off, len);
            return super.read(b, off, len);
        }

        @Override
        public int read(byte[] b) throws IOException {
            if (xThreadInputStream != null)
                return xThreadInputStream.read(b);
            return super.read(b);
        }

        @Override
        public int read() throws IOException {
            if (xThreadInputStream != null)
                return xThreadInputStream.read();
            return -1;
        }

    }

    /** This thread does the actual socket communication with the server.
    * It's set up so that it can be abandoned at shutdown time.
    *
    * The way it works is as follows:
    * - it starts the transaction
    * - it receives the response, and saves that for the calling class to inspect
    * - it transfers the data part to an input stream provided to the calling class
    * - it shuts the connection down
    *
    * If there is an error, the sequence is aborted, and an exception is recorded
    * for the calling class to examine.
    *
    * The calling class basically accepts the sequence above.  It starts the
    * thread, and tries to get a response code.  If instead an exception is seen,
    * the exception is thrown up the stack.
    */
    protected static class BackgroundHTTPThread extends Thread {
        /** Client and method, all preconfigured */
        protected final HttpClient httpClient;
        protected final HttpRequestBase executeMethod;

        protected HttpResponse response = null;
        protected Throwable responseException = null;
        protected XThreadInputStream threadStream = null;
        protected InputStream bodyStream = null;
        protected String charSet = null;
        protected boolean streamCreated = false;
        protected Throwable streamException = null;
        protected boolean abortThread = false;

        protected Throwable shutdownException = null;

        protected Throwable generalException = null;

        public BackgroundHTTPThread(HttpClient httpClient, HttpRequestBase executeMethod) {
            super();
            setDaemon(true);
            this.httpClient = httpClient;
            this.executeMethod = executeMethod;
        }

        public void run() {
            try {
                try {
                    // Call the execute method appropriately
                    synchronized (this) {
                        if (!abortThread) {
                            try {
                                response = httpClient.execute(executeMethod);
                            } catch (java.net.SocketTimeoutException e) {
                                responseException = e;
                            } catch (ConnectTimeoutException e) {
                                responseException = e;
                            } catch (InterruptedIOException e) {
                                throw e;
                            } catch (Throwable e) {
                                responseException = e;
                            }
                            this.notifyAll();
                        }
                    }

                    // Start the transfer of the content
                    if (responseException == null) {
                        synchronized (this) {
                            if (!abortThread) {
                                try {
                                    HttpEntity entity = response.getEntity();
                                    bodyStream = entity.getContent();
                                    if (bodyStream != null) {
                                        threadStream = new XThreadInputStream(bodyStream);
                                        charSet = EntityUtils.getContentCharSet(entity);
                                    }
                                    streamCreated = true;
                                } catch (java.net.SocketTimeoutException e) {
                                    streamException = e;
                                } catch (ConnectTimeoutException e) {
                                    streamException = e;
                                } catch (InterruptedIOException e) {
                                    throw e;
                                } catch (Throwable e) {
                                    streamException = e;
                                }
                                this.notifyAll();
                            }
                        }
                    }

                    if (responseException == null && streamException == null) {
                        if (threadStream != null) {
                            // Stuff the content until we are done
                            threadStream.stuffQueue();
                        }
                    }

                } finally {
                    if (bodyStream != null) {
                        try {
                            bodyStream.close();
                        } catch (IOException e) {
                        }
                        bodyStream = null;
                    }
                    synchronized (this) {
                        try {
                            executeMethod.abort();
                        } catch (Throwable e) {
                            shutdownException = e;
                        }
                        this.notifyAll();
                    }
                }
            } catch (Throwable e) {
                // We catch exceptions here that should ONLY be InterruptedExceptions, as a result of the thread being aborted.
                this.generalException = e;
            }
        }

        public int getResponseCode() throws InterruptedException, IOException, HttpException {
            // Must wait until the response object is there
            while (true) {
                synchronized (this) {
                    checkException(responseException);
                    if (response != null)
                        return response.getStatusLine().getStatusCode();
                    wait();
                }
            }
        }

        public String getResponseStatus() throws InterruptedException, IOException, HttpException {
            // Must wait until the response object is there
            while (true) {
                synchronized (this) {
                    checkException(responseException);
                    if (response != null)
                        return response.getStatusLine().toString();
                    wait();
                }
            }
        }

        public Map<String, List<String>> getResponseHeaders()
                throws InterruptedException, IOException, HttpException {
            // Must wait for the response object to appear
            while (true) {
                synchronized (this) {
                    checkException(responseException);
                    if (response != null) {
                        Header[] headers = response.getAllHeaders();
                        Map<String, List<String>> rval = new HashMap<String, List<String>>();
                        int i = 0;
                        while (i < headers.length) {
                            Header h = headers[i++];
                            String name = h.getName();
                            String value = h.getValue();
                            List<String> values = rval.get(name);
                            if (values == null) {
                                values = new ArrayList<String>();
                                rval.put(name, values);
                            }
                            values.add(value);
                        }
                        return rval;
                    }
                    wait();
                }
            }

        }

        public String getFirstHeader(String headerName) throws InterruptedException, IOException, HttpException {
            // Must wait for the response object to appear
            while (true) {
                synchronized (this) {
                    checkException(responseException);
                    if (response != null) {
                        Header h = response.getFirstHeader(headerName);
                        if (h == null)
                            return null;
                        return h.getValue();
                    }
                    wait();
                }
            }
        }

        public InputStream getSafeInputStream() throws InterruptedException, IOException, HttpException {
            // Must wait until stream is created, or until we note an exception was thrown.
            while (true) {
                synchronized (this) {
                    if (responseException != null)
                        throw new IllegalStateException("Check for response before getting stream");
                    checkException(streamException);
                    if (streamCreated)
                        return threadStream;
                    wait();
                }
            }
        }

        public String getCharSet() throws InterruptedException, IOException, HttpException {
            while (true) {
                synchronized (this) {
                    if (responseException != null)
                        throw new IllegalStateException("Check for response before getting charset");
                    checkException(streamException);
                    if (streamCreated)
                        return charSet;
                    wait();
                }
            }
        }

        public void abort() {
            // This will be called during the finally
            // block in the case where all is well (and
            // the stream completed) and in the case where
            // there were exceptions.
            synchronized (this) {
                if (streamCreated) {
                    if (threadStream != null)
                        threadStream.abort();
                }
                abortThread = true;
            }
        }

        public void finishUp() throws InterruptedException {
            join();
        }

        protected synchronized void checkException(Throwable exception) throws IOException, HttpException {
            if (exception != null) {
                Throwable e = exception;
                if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof HttpException)
                    throw (HttpException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else if (e instanceof Error)
                    throw (Error) e;
                else
                    throw new RuntimeException("Unhandled exception of type: " + e.getClass().getName(), e);
            }
        }

    }

}