gov.va.med.imaging.proxy.ImageXChangeHttpCommonsSender.java Source code

Java tutorial

Introduction

Here is the source code for gov.va.med.imaging.proxy.ImageXChangeHttpCommonsSender.java

Source

package gov.va.med.imaging.proxy;

/*
 * 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.
 */

import gov.va.med.imaging.tomcat.vistarealm.VistaRealmPrincipal;
import gov.va.med.imaging.tomcat.vistarealm.VistaRealmSecurityContext;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.xml.soap.MimeHeader;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPException;

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.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.handlers.BasicHandler;
import org.apache.axis.soap.SOAP12Constants;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.NetworkUtils;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.log4j.Logger;

import gov.va.med.imaging.transactioncontext.TransactionContext;
import gov.va.med.imaging.transactioncontext.TransactionContextFactory;
import gov.va.med.imaging.transactioncontext.TransactionContextHttpHeaders;

/**
 * This class uses Jakarta Commons's HttpClient to call a SOAP server. This is
 * almost a direct copy of org.apache.axis.client.http.CommonsHTTPSender with a
 * couple of additions to allow us to manipulate the HTTP headers to pass
 * authentication information.
 * 
 * @author Davanum Srinivas (dims@yahoo.com)
 * @author Chris Beckey (c.beckey@comcast.net)
 */
public class ImageXChangeHttpCommonsSender extends BasicHandler {
    private static final long serialVersionUID = 5231525433064978860L;

    public final static String ENCODING_DEFLATE = "deflate";
    private static final String COMPRESSION_DEFLATE = "deflate";
    protected Logger log = Logger.getLogger(this.getClass().getName());

    protected HttpConnectionManager connectionManager;
    protected CommonsHTTPClientProperties clientProperties;
    boolean httpChunkStream = false; // Use HTTP chunking by default or not.

    public ImageXChangeHttpCommonsSender() {
        log.debug("<ctor>()");
        initialize();
        log.debug("<ctor>() - initialized");
    }

    protected void initialize() {
        MultiThreadedHttpConnectionManager cm = new MultiThreadedHttpConnectionManager();
        this.clientProperties = CommonsHTTPClientPropertiesFactory.create();
        cm.getParams().setDefaultMaxConnectionsPerHost(clientProperties.getMaximumConnectionsPerHost());
        cm.getParams().setMaxTotalConnections(clientProperties.getMaximumTotalConnections());

        // If defined, set the default timeouts
        // Can be overridden by the MessageContext
        if (this.clientProperties.getDefaultConnectionTimeout() > 0)
            cm.getParams().setConnectionTimeout(this.clientProperties.getDefaultConnectionTimeout());

        if (this.clientProperties.getDefaultSoTimeout() > 0)
            cm.getParams().setSoTimeout(this.clientProperties.getDefaultSoTimeout());

        this.connectionManager = cm;
    }

    /**
    * 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 {
        HttpMethodBase method = null;
        log.debug(Messages.getMessage("enter00", "CommonsHttpSender::invoke"));
        try {
            URL targetURL = new URL(msgContext.getStrProp(MessageContext.TRANS_URL));

            // no need to retain these, as the cookies/credentials are
            // stored in the message context across multiple requests.
            // the underlying connection manager, however, is retained
            // so sockets get recycled when possible.
            HttpClient httpClient = new HttpClient(this.connectionManager);

            // the timeout value for allocation of connections from the pool
            httpClient.getParams().setConnectionManagerTimeout(this.clientProperties.getConnectionPoolTimeout());

            HostConfiguration hostConfiguration = getHostConfiguration(httpClient, msgContext, targetURL);

            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);
            }

            if (posting) {
                Message reqMessage = msgContext.getRequestMessage();
                method = new PostMethod(targetURL.toString());
                log.info("POST message created with target [" + targetURL.toString() + "]");
                TransactionContext transactionContext = TransactionContextFactory.get();
                transactionContext
                        .addDebugInformation("POST message created with target [" + targetURL.toString() + "]");

                // set false as default, addContetInfo can overwrite
                method.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, false);

                addContextInfo(method, httpClient, msgContext, targetURL);

                Credentials cred = httpClient.getState().getCredentials(AuthScope.ANY);
                if (cred instanceof UsernamePasswordCredentials) {
                    log.trace("POST message created on client with credentials ["
                            + ((UsernamePasswordCredentials) cred).getUserName() + ", "
                            + ((UsernamePasswordCredentials) cred).getPassword() + "].");
                }

                MessageRequestEntity requestEntity = null;
                if (msgContext.isPropertyTrue(HTTPConstants.MC_GZIP_REQUEST)) {
                    requestEntity = new GzipMessageRequestEntity(method, reqMessage, httpChunkStream);
                    log.info("HTTPCommonsSender - zipping request.");
                } else {
                    requestEntity = new MessageRequestEntity(method, reqMessage, httpChunkStream);
                    log.info("HTTPCommonsSender - not zipping request");
                }
                ((PostMethod) method).setRequestEntity(requestEntity);
            } else {
                method = new GetMethod(targetURL.toString());
                log.info("GET message created with target [" + targetURL.toString() + "]");
                addContextInfo(method, httpClient, msgContext, targetURL);
            }

            if (msgContext.isPropertyTrue(HTTPConstants.MC_ACCEPT_GZIP))
                log.info("HTTPCommonsSender - accepting GZIP");
            else
                log.info("HTTPCommonsSender - NOT accepting GZIP");

            String httpVersion = msgContext.getStrProp(MessageContext.HTTP_TRANSPORT_VERSION);
            if (httpVersion != null && httpVersion.equals(HTTPConstants.HEADER_PROTOCOL_V10))
                method.getParams().setVersion(HttpVersion.HTTP_1_0);

            // don't forget the cookies!
            // Cookies need to be set on HttpState, since HttpMethodBase
            // overwrites the cookies from HttpState
            if (msgContext.getMaintainSession()) {
                HttpState state = httpClient.getState();
                method.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
                String host = hostConfiguration.getHost();
                String path = targetURL.getPath();

                boolean secure = hostConfiguration.getProtocol().isSecure();
                fillHeaders(msgContext, state, HTTPConstants.HEADER_COOKIE, host, path, secure);
                fillHeaders(msgContext, state, HTTPConstants.HEADER_COOKIE2, host, path, secure);
                httpClient.setState(state);
            }

            // add HTTP header fields that the application thinks are "interesting"
            // the expectation is that these would be non-standard HTTP headers, 
            // by convention starting with "xxx-"
            VistaRealmPrincipal principal = VistaRealmSecurityContext.get();

            if (principal != null) {
                log.info("SecurityContext credentials for '" + principal.getAccessCode() + "' are available.");
                String duz = principal.getDuz();
                if (duz != null && duz.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderDuz, duz);

                String fullname = principal.getFullName();
                if (fullname != null && fullname.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderFullName, fullname);

                String sitename = principal.getSiteName();
                if (sitename != null && sitename.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderSiteName, sitename);

                String sitenumber = principal.getSiteNumber();
                if (sitenumber != null && sitenumber.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderSiteNumber, sitenumber);

                String ssn = principal.getSsn();
                if (ssn != null && ssn.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderSSN, ssn);

                String securityToken = principal.getSecurityToken();
                if (securityToken != null && securityToken.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderBrokerSecurityTokenId,
                            securityToken);

                String cacheLocationId = principal.getCacheLocationId();
                if (cacheLocationId != null && cacheLocationId.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderCacheLocationId,
                            cacheLocationId);

                String userDivision = principal.getUserDivision();
                if (userDivision != null && userDivision.length() > 0)
                    method.addRequestHeader(TransactionContextHttpHeaders.httpHeaderUserDivision, userDivision);
            } else
                log.debug("SecurityContext credentials are NOT available.");

            method.addRequestHeader(HTTPConstants.HEADER_CACHE_CONTROL, "no-cache,no-store");
            method.addRequestHeader(HTTPConstants.HEADER_PRAGMA, "no-cache");

            try {
                log.info("Executing method [" + method.getPath() + "] on target [" + hostConfiguration.getHostURL()
                        + "]");
            } catch (IllegalStateException isX) {
            }

            // send the HTTP request and wait for a response 
            int returnCode = httpClient.executeMethod(hostConfiguration, method, null);

            TransactionContext transactionContext = TransactionContextFactory.get();
            // don't set the response code here - this is not the response code we send out, but the resposne code we get back from the data source
            //transactionContext.setResponseCode (String.valueOf (returnCode));

            // How many bytes received?
            transactionContext.setDataSourceBytesReceived(method.getBytesReceived());

            // How long did it take to start getting a response coming back?
            Long timeSent = method.getTimeRequestSent();
            Long timeReceived = method.getTimeFirstByteReceived();
            if (timeSent != null && timeReceived != null) {
                long timeTook = timeReceived.longValue() - timeSent.longValue();
                transactionContext.setTimeToFirstByte(new Long(timeTook));
            }

            // Looks like it wasn't found in cache - is there a place to set this to true if it was?
            transactionContext.setItemCached(Boolean.FALSE);

            // extract the basic HTTP header fields for content type, location and length
            String contentType = getHeader(method, HTTPConstants.HEADER_CONTENT_TYPE);
            String contentLocation = getHeader(method, HTTPConstants.HEADER_CONTENT_LOCATION);
            String contentLength = getHeader(method, 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 = method.getStatusText();
                try {
                    log.warn("Method [" + method.getPath() + "] on target [" + hostConfiguration.getHostURL()
                            + "] failed - '" + statusMessage + "'.");
                } catch (IllegalStateException isX) {
                }

                AxisFault fault = new AxisFault("HTTP", "(" + returnCode + ")" + statusMessage, null, null);

                try {
                    fault.setFaultDetailString(
                            Messages.getMessage("return01", "" + returnCode, method.getResponseBodyAsString()));
                    fault.addFaultDetail(Constants.QNAME_FAULTDETAIL_HTTPERRORCODE, Integer.toString(returnCode));
                    throw fault;
                } finally {
                    method.releaseConnection(); // release connection back to
                    // pool.
                }
            }

            // wrap the response body stream so that close() also releases
            // the connection back to the pool.
            InputStream releaseConnectionOnCloseStream = createConnectionReleasingInputStream(method);

            Header contentEncoding = method.getResponseHeader(HTTPConstants.HEADER_CONTENT_ENCODING);
            log.info("HTTPCommonsSender - " + HTTPConstants.HEADER_CONTENT_ENCODING + "="
                    + (contentEncoding == null ? "null" : contentEncoding.getValue()));

            if (contentEncoding != null) {
                if (HTTPConstants.COMPRESSION_GZIP.equalsIgnoreCase(contentEncoding.getValue())) {
                    releaseConnectionOnCloseStream = new GZIPInputStream(releaseConnectionOnCloseStream);

                    log.debug("HTTPCommonsSender - receiving gzipped stream.");
                } else if (ENCODING_DEFLATE.equalsIgnoreCase(contentEncoding.getValue())) {
                    releaseConnectionOnCloseStream = new java.util.zip.InflaterInputStream(
                            releaseConnectionOnCloseStream);

                    log.debug("HTTPCommonsSender - receiving 'deflated' stream.");
                } else {
                    AxisFault fault = new AxisFault("HTTP",
                            "unsupported content-encoding of '" + contentEncoding.getValue() + "' found", null,
                            null);
                    log.warn(fault.getMessage());
                    throw fault;
                }

            }
            try {
                log.warn("Method [" + method.getPath() + "] on target [" + hostConfiguration.getHostURL()
                        + "] succeeded, parsing response.");
            } catch (IllegalStateException isX) {
            }

            Message outMsg = new Message(releaseConnectionOnCloseStream, false, contentType, contentLocation);
            // Transfer HTTP headers of HTTP message to MIME headers of SOAP
            // message
            Header[] responseHeaders = method.getResponseHeaders();
            MimeHeaders responseMimeHeaders = outMsg.getMimeHeaders();
            for (int i = 0; i < responseHeaders.length; i++) {
                Header responseHeader = responseHeaders[i];
                responseMimeHeaders.addHeader(responseHeader.getName(), responseHeader.getValue());
            }
            outMsg.setMessageType(Message.RESPONSE);
            msgContext.setResponseMessage(outMsg);
            if (log.isTraceEnabled()) {
                if (null == contentLength)
                    log.trace("\n" + Messages.getMessage("no00", "Content-Length"));
                log.trace("\n" + Messages.getMessage("xmlRecd00"));
                log.trace("-----------------------------------------------");
                log.trace(outMsg.getSOAPPartAsString());
            }

            // if we are maintaining session state,
            // handle cookies (if any)
            if (msgContext.getMaintainSession()) {
                Header[] headers = method.getResponseHeaders();

                for (int i = 0; i < headers.length; i++) {
                    if (headers[i].getName().equalsIgnoreCase(HTTPConstants.HEADER_SET_COOKIE)) {
                        handleCookie(HTTPConstants.HEADER_COOKIE, headers[i].getValue(), msgContext);
                    } else if (headers[i].getName().equalsIgnoreCase(HTTPConstants.HEADER_SET_COOKIE2)) {
                        handleCookie(HTTPConstants.HEADER_COOKIE2, headers[i].getValue(), msgContext);
                    }
                }
            }

            // always release the connection back to the pool if
            // it was one way invocation
            if (msgContext.isPropertyTrue("axis.one.way")) {
                method.releaseConnection();
            }

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

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

    // All properties that start with an application specific String are copied to the
    // transport (HTTP) header.
    public final static String defaultApplicationTransportHeaderPrefix = "xxx-";

    private String applicationTransportHeaderPrefix = defaultApplicationTransportHeaderPrefix;

    public String getApplicationTransportHeaderPrefix() {
        return this.applicationTransportHeaderPrefix;
    }

    public void setApplicationTransportHeaderPrefix(String applicationTransportHeaderPrefix) {
        this.applicationTransportHeaderPrefix = applicationTransportHeaderPrefix;
    }

    /**
     * Find all of the String properties in the message context that start with the value in applicationTransportHeaderPrefix
     * These are the application specific headers that the transport should put in some out-of-band comm channel.
     * In particular, this class puts them in HTTP headers.  
     * 
     * @param msgContext
     * @return a Map of property names and values or null if there are no properties of interest
     */
    protected Map<String, String> getPropertiesOfInterest(MessageContext msgContext) {
        Map<String, String> propertiesOfInterest = new HashMap<String, String>();
        String prefix = getApplicationTransportHeaderPrefix(); // the prefix that defines what an "interesting" property is
        Iterator propertyNameIter = msgContext.getPropertyNames();
        while (propertyNameIter.hasNext()) {
            try {
                String propertyName = (String) propertyNameIter.next();
                if (propertyName.startsWith(prefix)) {
                    String propertyValue = (String) msgContext.getProperty(propertyName);
                    propertiesOfInterest.put(propertyName, propertyValue);
                }
            } catch (ClassCastException ccX) {
                // eat the exception, it means that there is a non-String property
                // which we are, by definition, not interested in.
            }
        }

        // return null if there are no interesting properties
        return propertiesOfInterest.size() > 0 ? propertiesOfInterest : null;
    }

    /**
     * little helper function for cookies. fills up the message context with a
     * string or an array of strings (if there are more than one Set-Cookie)
     * 
     * @param cookieName
     * @param setCookieName
     * @param cookie
     * @param msgContext
     */
    public void handleCookie(String cookieName, String cookie, MessageContext msgContext) {

        cookie = cleanupCookie(cookie);
        int keyIndex = cookie.indexOf("=");
        String key = (keyIndex != -1) ? cookie.substring(0, keyIndex) : cookie;

        ArrayList cookies = new ArrayList();
        Object oldCookies = msgContext.getProperty(cookieName);
        boolean alreadyExist = false;
        if (oldCookies != null) {
            if (oldCookies instanceof String[]) {
                String[] oldCookiesArray = (String[]) oldCookies;
                for (int i = 0; i < oldCookiesArray.length; i++) {
                    String anOldCookie = oldCookiesArray[i];
                    if (key != null && anOldCookie.indexOf(key) == 0) { // same
                        // cookie
                        // key
                        anOldCookie = cookie; // update to new one
                        alreadyExist = true;
                    }
                    cookies.add(anOldCookie);
                }
            } else {
                String oldCookie = (String) oldCookies;
                if (key != null && oldCookie.indexOf(key) == 0) { // same
                    // cookie
                    // key
                    oldCookie = cookie; // update to new one
                    alreadyExist = true;
                }
                cookies.add(oldCookie);
            }
        }

        if (!alreadyExist) {
            cookies.add(cookie);
        }

        if (cookies.size() == 1) {
            msgContext.setProperty(cookieName, cookies.get(0));
        } else if (cookies.size() > 1) {
            msgContext.setProperty(cookieName, cookies.toArray(new String[cookies.size()]));
        }
    }

    /**
    * Add cookies from message context
    * 
    * @param msgContext
    * @param state
    * @param header
    * @param host
    * @param path
    * @param secure
    */
    private void fillHeaders(MessageContext msgContext, HttpState state, String header, String host, String path,
            boolean secure) {
        Object ck1 = msgContext.getProperty(header);
        if (ck1 != null) {
            if (ck1 instanceof String[]) {
                String[] cookies = (String[]) ck1;
                for (int i = 0; i < cookies.length; i++) {
                    addCookie(state, cookies[i], host, path, secure);
                }
            } else {
                addCookie(state, (String) ck1, host, path, secure);
            }
        }
    }

    /**
    * add cookie to state
    * 
    * @param state
    * @param cookie
    */
    private void addCookie(HttpState state, String cookie, String host, String path, boolean secure) {
        int index = cookie.indexOf('=');
        state.addCookie(
                new Cookie(host, cookie.substring(0, index), cookie.substring(index + 1), path, null, secure));
    }

    /**
    * cleanup the cookie value.
    * 
    * @param cookie
    *            initial cookie value
    * 
    * @return a cleaned up cookie value.
    */
    private String cleanupCookie(String cookie) {
        cookie = cookie.trim();
        // chop after first ; a la Apache SOAP (see HTTPUtils.java there)
        int index = cookie.indexOf(';');
        if (index != -1) {
            cookie = cookie.substring(0, index);
        }
        return cookie;
    }

    protected HostConfiguration getHostConfiguration(HttpClient client, MessageContext context, URL targetURL) {
        TransportClientProperties tcp = TransportClientPropertiesFactory.create(targetURL.getProtocol()); // http
        // or
        // https
        int port = targetURL.getPort();
        boolean hostInNonProxyList = isHostInNonProxyList(targetURL.getHost(), tcp.getNonProxyHosts());

        HostConfiguration config = new HostConfiguration();

        if (port == -1) {
            if (targetURL.getProtocol().equalsIgnoreCase("https")) {
                port = 443; // default port for https being 443
            } else { // it must be http
                port = 80; // default port for http being 80
            }
        }

        if (hostInNonProxyList) {
            config.setHost(targetURL.getHost(), port, targetURL.getProtocol());
        } else {
            if (tcp.getProxyHost().length() == 0 || tcp.getProxyPort().length() == 0) {
                config.setHost(targetURL.getHost(), port, targetURL.getProtocol());
            } else {
                if (tcp.getProxyUser().length() != 0) {
                    Credentials proxyCred = new UsernamePasswordCredentials(tcp.getProxyUser(),
                            tcp.getProxyPassword());
                    // if the username is in the form "user\domain"
                    // then use NTCredentials instead.
                    int domainIndex = tcp.getProxyUser().indexOf("\\");
                    if (domainIndex > 0) {
                        String domain = tcp.getProxyUser().substring(0, domainIndex);
                        if (tcp.getProxyUser().length() > domainIndex + 1) {
                            String user = tcp.getProxyUser().substring(domainIndex + 1);
                            proxyCred = new NTCredentials(user, tcp.getProxyPassword(), tcp.getProxyHost(), domain);
                        }
                    }
                    client.getState().setProxyCredentials(AuthScope.ANY, proxyCred);
                }
                int proxyPort = new Integer(tcp.getProxyPort()).intValue();
                config.setProxy(tcp.getProxyHost(), proxyPort);
            }
        }
        return config;
    }

    /**
    * Extracts info from message context.
    * 
    * @param method
    *            Post method
    * @param httpClient
    *            The client used for posting
    * @param msgContext
    *            the message context
    * @param tmpURL
    *            the url to post to.
    * 
    * @throws Exception
    */
    private void addContextInfo(HttpMethodBase method, HttpClient httpClient, MessageContext msgContext, URL tmpURL)
            throws Exception {

        // optionally set a timeout for the request
        if (msgContext.getTimeout() != 0) {
            /*
            * ISSUE: these are not the same, but MessageContext has only one
            * definition of timeout
            */
            // SO_TIMEOUT -- timeout for blocking reads
            httpClient.getHttpConnectionManager().getParams().setSoTimeout(msgContext.getTimeout());
            // timeout for initial connection
            httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(msgContext.getTimeout());
        }

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

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

        Message msg = msgContext.getRequestMessage();
        if (msg != null) {
            method.setRequestHeader(new Header(HTTPConstants.HEADER_CONTENT_TYPE,
                    msg.getContentType(msgContext.getSOAPConstants())));
        }
        method.setRequestHeader(new Header(HTTPConstants.HEADER_SOAP_ACTION, "\"" + action + "\""));
        method.setRequestHeader(new Header(HTTPConstants.HEADER_USER_AGENT, Messages.getMessage("axisUserAgent")));
        //method.setRequestHeader(
        //   new Header(HTTPConstants.HEADER_USER_AGENT, "Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.5072)")
        //);

        String userID = msgContext.getUsername();
        String passwd = msgContext.getPassword();
        //System.out.println("ImageXChangeHttpCommonsSender setting credentials = '" + userID + ". " + passwd + "'");

        // if UserID is not part of the context, but is in the URL, use
        // the one in the URL.
        if ((userID == null) && (tmpURL.getUserInfo() != null)) {
            String info = tmpURL.getUserInfo();
            int sep = info.indexOf(':');

            if ((sep >= 0) && (sep + 1 < info.length())) {
                userID = info.substring(0, sep);
                passwd = info.substring(sep + 1);
            } else {
                userID = info;
            }
        }
        if (userID != null) {
            Credentials proxyCred = new UsernamePasswordCredentials(userID, passwd);
            // if the username is in the form "user\domain"
            // then use NTCredentials instead.
            int domainIndex = userID.indexOf("\\");
            if (domainIndex > 0) {
                String domain = userID.substring(0, domainIndex);
                if (userID.length() > domainIndex + 1) {
                    String user = userID.substring(domainIndex + 1);
                    proxyCred = new NTCredentials(user, passwd, NetworkUtils.getLocalHostname(), domain);
                }
            }
            httpClient.getState().setCredentials(AuthScope.ANY, proxyCred);
            //System.out.println("ImageXChangeHttpCommonsSender setting credentials = '" + userID + ". " + passwd + "'");
        }

        // add compression headers if needed
        // if we accept GZIP then add the accept-encoding header
        if (msgContext.isPropertyTrue(HTTPConstants.MC_ACCEPT_GZIP)) {
            // accept both gzip and deflate if the gzip property is set
            method.addRequestHeader(HTTPConstants.HEADER_ACCEPT_ENCODING,
                    HTTPConstants.COMPRESSION_GZIP + "," + COMPRESSION_DEFLATE);
            //method.addRequestHeader(HTTPConstants.HEADER_ACCEPT_ENCODING, COMPRESSION_DEFLATE);
        }

        // if we will gzip the request then add the content-encoding header
        if (msgContext.isPropertyTrue(HTTPConstants.MC_GZIP_REQUEST)) {
            method.addRequestHeader(HTTPConstants.HEADER_CONTENT_ENCODING, HTTPConstants.COMPRESSION_GZIP);
        }

        // Transfer MIME headers of SOAPMessage to HTTP headers.
        MimeHeaders mimeHeaders = msg.getMimeHeaders();
        if (mimeHeaders != null) {
            for (Iterator i = mimeHeaders.getAllHeaders(); i.hasNext();) {
                MimeHeader mimeHeader = (MimeHeader) i.next();
                // HEADER_CONTENT_TYPE and HEADER_SOAP_ACTION are already set.
                // Let's not duplicate them.
                String headerName = mimeHeader.getName();
                if (headerName.equals(HTTPConstants.HEADER_CONTENT_TYPE)
                        || headerName.equals(HTTPConstants.HEADER_SOAP_ACTION))
                    continue;
                method.addRequestHeader(mimeHeader.getName(), mimeHeader.getValue());
            }
        }

        // 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)) {
                    method.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, true);
                } else if (key.equalsIgnoreCase(HTTPConstants.HEADER_TRANSFER_ENCODING_CHUNKED)) {
                    String val = me.getValue().toString();
                    if (null != val) {
                        httpChunkStream = JavaUtils.isTrue(val);
                    }
                } else {
                    method.addRequestHeader(key, value);
                }
            }
        }
    }

    /**
    * Check if the specified host is in the list of non proxy hosts.
    * 
    * @param host
    *            host name
    * @param nonProxyHosts
    *            string containing the list of non proxy hosts
    * 
    * @return true/false
    */
    protected boolean isHostInNonProxyList(String host, String nonProxyHosts) {

        if ((nonProxyHosts == null) || (host == null)) {
            return false;
        }

        /*
        * The http.nonProxyHosts system property is a list enclosed in double
        * quotes with items separated by a vertical bar.
        */
        StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|\"");

        while (tokenizer.hasMoreTokens()) {
            String pattern = tokenizer.nextToken();

            if (log.isDebugEnabled()) {
                log.debug(Messages.getMessage("match00", new String[] { "HTTPSender", host, pattern }));
            }
            if (match(pattern, host, false)) {
                return true;
            }
        }
        return false;
    }

    /**
    * Matches a string against a pattern. The pattern contains two special
    * characters: '*' which means zero or more characters,
    * 
    * @param pattern
    *            the (non-null) pattern to match against
    * @param str
    *            the (non-null) string that must be matched against the pattern
    * @param isCaseSensitive
    * 
    * @return <code>true</code> when the string matches against the pattern,
    *         <code>false</code> otherwise.
    */
    protected static boolean match(String pattern, String str, boolean isCaseSensitive) {

        char[] patArr = pattern.toCharArray();
        char[] strArr = str.toCharArray();
        int patIdxStart = 0;
        int patIdxEnd = patArr.length - 1;
        int strIdxStart = 0;
        int strIdxEnd = strArr.length - 1;
        char ch;
        boolean containsStar = false;

        for (int i = 0; i < patArr.length; i++) {
            if (patArr[i] == '*') {
                containsStar = true;
                break;
            }
        }
        if (!containsStar) {

            // No '*'s, so we make a shortcut
            if (patIdxEnd != strIdxEnd) {
                return false; // Pattern and string do not have the
                // same size
            }
            for (int i = 0; i <= patIdxEnd; i++) {
                ch = patArr[i];
                if (isCaseSensitive && (ch != strArr[i])) {
                    return false; // Character mismatch
                }
                if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[i]))) {
                    return false; // Character mismatch
                }
            }
            return true; // String matches against pattern
        }
        if (patIdxEnd == 0) {
            return true; // Pattern contains only '*', which matches anything
        }

        // Process characters before first star
        while ((ch = patArr[patIdxStart]) != '*' && (strIdxStart <= strIdxEnd)) {
            if (isCaseSensitive && (ch != strArr[strIdxStart])) {
                return false; // Character mismatch
            }
            if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart]))) {
                return false; // Character mismatch
            }
            patIdxStart++;
            strIdxStart++;
        }
        if (strIdxStart > strIdxEnd) {

            // All characters in the string are used. Check if only '*'s are
            // left in the pattern. If so, we succeeded. Otherwise failure.
            for (int i = patIdxStart; i <= patIdxEnd; i++) {
                if (patArr[i] != '*') {
                    return false;
                }
            }
            return true;
        }

        // Process characters after last star
        while ((ch = patArr[patIdxEnd]) != '*' && (strIdxStart <= strIdxEnd)) {
            if (isCaseSensitive && (ch != strArr[strIdxEnd])) {
                return false; // Character mismatch
            }
            if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxEnd]))) {
                return false; // Character mismatch
            }
            patIdxEnd--;
            strIdxEnd--;
        }
        if (strIdxStart > strIdxEnd) {

            // All characters in the string are used. Check if only '*'s are
            // left in the pattern. If so, we succeeded. Otherwise failure.
            for (int i = patIdxStart; i <= patIdxEnd; i++) {
                if (patArr[i] != '*') {
                    return false;
                }
            }
            return true;
        }

        // process pattern between stars. padIdxStart and patIdxEnd point
        // always to a '*'.
        while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd)) {
            int patIdxTmp = -1;

            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
                if (patArr[i] == '*') {
                    patIdxTmp = i;
                    break;
                }
            }
            if (patIdxTmp == patIdxStart + 1) {

                // Two stars next to each other, skip the first one.
                patIdxStart++;
                continue;
            }

            // Find the pattern between padIdxStart & padIdxTmp in str between
            // strIdxStart & strIdxEnd
            int patLength = (patIdxTmp - patIdxStart - 1);
            int strLength = (strIdxEnd - strIdxStart + 1);
            int foundIdx = -1;

            strLoop: for (int i = 0; i <= strLength - patLength; i++) {
                for (int j = 0; j < patLength; j++) {
                    ch = patArr[patIdxStart + j + 1];
                    if (isCaseSensitive && (ch != strArr[strIdxStart + i + j])) {
                        continue strLoop;
                    }
                    if (!isCaseSensitive
                            && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart + i + j]))) {
                        continue strLoop;
                    }
                }
                foundIdx = strIdxStart + i;
                break;
            }
            if (foundIdx == -1) {
                return false;
            }
            patIdxStart = patIdxTmp;
            strIdxStart = foundIdx + patLength;
        }

        // All characters in the string are used. Check if only '*'s are left
        // in the pattern. If so, we succeeded. Otherwise failure.
        for (int i = patIdxStart; i <= patIdxEnd; i++) {
            if (patArr[i] != '*') {
                return false;
            }
        }
        return true;
    }

    private static String getHeader(HttpMethodBase method, String headerName) {
        Header header = method.getResponseHeader(headerName);
        return (header == null) ? null : header.getValue().trim();
    }

    private InputStream createConnectionReleasingInputStream(final HttpMethodBase method) throws IOException {
        return new FilterInputStream(method.getResponseBodyAsStream()) {
            public void close() throws IOException {
                try {
                    super.close();
                } finally {
                    method.releaseConnection();
                }
            }
        };
    }

    private static class MessageRequestEntity implements RequestEntity {

        private HttpMethodBase method;
        private Message message;
        boolean httpChunkStream = true; // Use HTTP chunking or not.

        public MessageRequestEntity(HttpMethodBase method, Message message) {
            this.message = message;
            this.method = method;
        }

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

        public boolean isRepeatable() {
            return true;
        }

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

        protected boolean isContentLengthNeeded() {
            return this.method.getParams().getVersion() == HttpVersion.HTTP_1_0 || !httpChunkStream;
        }

        public long getContentLength() {
            if (isContentLengthNeeded()) {
                try {
                    return message.getContentLength();
                } catch (Exception e) {
                }
            }
            return -1; /* -1 for chunked */
        }

        public String getContentType() {
            return null; // a separate header is added
        }

    }

    private static class GzipMessageRequestEntity extends MessageRequestEntity {

        public GzipMessageRequestEntity(HttpMethodBase method, Message message) {
            super(method, message);
        }

        public GzipMessageRequestEntity(HttpMethodBase method, Message message, boolean httpChunkStream) {
            super(method, message, httpChunkStream);
        }

        public void writeRequest(OutputStream out) throws IOException {
            if (cachedStream != null) {
                cachedStream.writeTo(out);
            } else {
                GZIPOutputStream gzStream = new GZIPOutputStream(out);
                super.writeRequest(gzStream);
                gzStream.finish();
            }
        }

        public long getContentLength() {
            if (isContentLengthNeeded()) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    writeRequest(baos);
                    cachedStream = baos;
                    return baos.size();
                } catch (IOException e) {
                    // fall through to doing chunked.
                }
            }
            return -1; // do chunked
        }

        private ByteArrayOutputStream cachedStream;
    }
}