org.activebpel.rt.axis.bpel.handlers.AeHTTPSender.java Source code

Java tutorial

Introduction

Here is the source code for org.activebpel.rt.axis.bpel.handlers.AeHTTPSender.java

Source

//$Header: /Development/AEDevelopment/projects/org.activebpel.rt.axis.bpel/src/org/activebpel/rt/axis/bpel/handlers/AeHTTPSender.java,v 1.8 2007/12/11 22:31:07 jbik Exp $
/////////////////////////////////////////////////////////////////////////////
//PROPRIETARY RIGHTS STATEMENT
//The contents of this file represent confidential information that is the 
//proprietary property of Active Endpoints, Inc.  Viewing or use of 
//this information is prohibited without the express written consent of 
//Active Endpoints, Inc. Removal of this PROPRIETARY RIGHTS STATEMENT 
//is strictly forbidden. Copyright (c) 2002-2004 All rights reserved. 
/////////////////////////////////////////////////////////////////////////////
package org.activebpel.rt.axis.bpel.handlers;

/*
 * 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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.StringTokenizer;

import javax.wsdl.OperationType;
import javax.xml.soap.MimeHeader;
import javax.xml.soap.MimeHeaders;

import org.apache.axis.AxisFault;
import org.apache.axis.AxisProperties;
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.DefaultCommonsHTTPClientProperties;
import org.apache.axis.components.net.TransportClientProperties;
import org.apache.axis.components.net.TransportClientPropertiesFactory;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.handlers.BasicHandler;
import org.apache.axis.soap.SOAP12Constants;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.transport.http.CommonsHTTPSender;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.utils.Messages;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
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.util.IdleConnectionTimeoutThread;
import org.apache.commons.logging.Log;

/**
 * This class uses Jakarta Commons's HttpClient to call a SOAP server.
 *
 * @author Davanum Srinivas (dims@yahoo.com)
 * History: By Chandra Talluri
 * Modifications done for maintaining sessions. Cookies needed to be set on
 * HttpState not on MessageContext, since ttpMethodBase overwrites the cookies
 * from HttpState. Also we need to setCookiePolicy on HttpState to
 * CookiePolicy.COMPATIBILITY else it is defaulting to RFC2109Spec and adding
 * Version information to it and tomcat server not recognizing it
 */
public class AeHTTPSender extends BasicHandler {
    private static final String KEY_IDLE_TIMEOUT = "org.activebpel.httpsender.idle.timeout"; //$NON-NLS-1$
    private static final String KEY_IDLE_SWEEP_INTERVAL = "org.activebpel.httpsender.idle.sweep.interval"; //$NON-NLS-1$
    private static final int DEFAULT_IDLE_CONNECTION_TIMEOUT = 5000;
    private static final int DEFAULT_IDLE_CONNECTION_SWEEP_INTERVAL = 3000;

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

    /** default value for the number of open connections per host */
    protected static final int DEFAULT_MAX_CONNECTIONS_PER_HOST = 100;
    /** default value for the total number of open connections allowed */
    protected static final int DEFAULT_MAX_CONNECTIONS = 500;

    /** cached reference to the connection manager */
    private static MultiThreadedHttpConnectionManager sConnectionManager;
    /** cached reference to the client properties */
    private static CommonsHTTPClientProperties sClientProperties;
    /** timeout thread responsible for closing idle connections */
    private static IdleConnectionTimeoutThread sConnectionTimeoutThread;

    private MultiThreadedHttpConnectionManager connectionManager;
    private CommonsHTTPClientProperties clientProperties;

    /** @deprecated */
    public AeHTTPSender() {
        initialize();
    }

    /** @deprecated */
    protected void initialize() {
        if (sConnectionManager == null) {
            synchronized (AeHTTPSender.class) {
                if (sConnectionManager == null) {
                    // initialize the connection manager w/ its properties
                    MultiThreadedHttpConnectionManager cm = new MultiThreadedHttpConnectionManager();
                    sClientProperties = CommonsHTTPClientPropertiesFactory.create();

                    // max connections per host, defaults to constant above
                    int maxConnectionsPerHost = getIntegerOption(
                            DefaultCommonsHTTPClientProperties.MAXIMUM_CONNECTIONS_PER_HOST_PROPERTY_KEY,
                            DEFAULT_MAX_CONNECTIONS_PER_HOST);

                    cm.setMaxConnectionsPerHost(maxConnectionsPerHost);

                    // max connections, defaults to constant above
                    int maxConnections = getIntegerOption(
                            DefaultCommonsHTTPClientProperties.MAXIMUM_TOTAL_CONNECTIONS_PROPERTY_KEY,
                            DEFAULT_MAX_CONNECTIONS);
                    // as per apache-commons problem report #36882 (max connections per host setting does not work)
                    // cm.setMaxTotalConnections(maxConnections);
                    cm.getParams().setMaxTotalConnections(maxConnections);

                    // save the connection manager for future calls
                    sConnectionManager = cm;

                    sConnectionTimeoutThread = new IdleConnectionTimeoutThread();
                    sConnectionTimeoutThread.addConnectionManager(sConnectionManager);

                    int sweepInterval = getIntegerOption(KEY_IDLE_SWEEP_INTERVAL,
                            DEFAULT_IDLE_CONNECTION_SWEEP_INTERVAL);
                    sConnectionTimeoutThread.setTimeoutInterval(sweepInterval);

                    int idleTimeout = getIntegerOption(KEY_IDLE_TIMEOUT, DEFAULT_IDLE_CONNECTION_TIMEOUT);
                    sConnectionTimeoutThread.setConnectionTimeout(idleTimeout);
                    sConnectionTimeoutThread.start();
                }
            }
        }
        connectionManager = sConnectionManager;
        clientProperties = sClientProperties;
    }

    /**
     * Gets the option from our handler or returns the default value if not specified.
     * @param aKey
     * @param aDefaultValue
     */
    protected int getIntegerOption(String aKey, int aDefaultValue) {
        int value = aDefaultValue;
        try {
            Object obj = AxisProperties.getProperty(aKey, null);
            if (obj != null) {
                value = Integer.parseInt(obj.toString());
            }
        } catch (Exception e) {
            // eat any exception and reply w/ default value below
        }
        return value;
    }

    /**
     * Shuts down the connection manager and clears the reference. 
     */
    public static synchronized void shutdown() {
        if (sConnectionManager != null) {
            sConnectionManager.shutdown();
            sConnectionManager = null;
        }

        if (sConnectionTimeoutThread != null) {
            sConnectionTimeoutThread.shutdown();
            sConnectionTimeoutThread.interrupt();
            sConnectionTimeoutThread = null;
        }
    }

    /**
     * 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 message context
     *
     * @throws AxisFault
     * @deprecated
     */
    public void invoke(MessageContext msgContext) throws AxisFault {
        HttpMethodBase method = null;
        if (log.isDebugEnabled()) {
            log.debug(Messages.getMessage("enter00", //$NON-NLS-1$
                    "CommonsHTTPSender::invoke")); //$NON-NLS-1$
        }
        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(connectionManager);
            // the timeout value for allocation of connections from the pool
            httpClient.setHttpConnectionFactoryTimeout(clientProperties.getConnectionPoolTimeout());

            HostConfiguration hostConfiguration = getHostConfiguration(httpClient, targetURL);
            httpClient.setHostConfiguration(hostConfiguration);

            // look for option to send credentials preemptively (w/out challenge)
            // Control of Preemptive is controlled via policy on a per call basis.
            String preemptive = (String) msgContext.getProperty("HTTPPreemptive"); //$NON-NLS-1$
            if ("true".equals(preemptive)) //$NON-NLS-1$
            {
                httpClient.getParams().setAuthenticationPreemptive(true);
            }

            String webMethod = null;
            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) {
                webMethod = msgContext.getStrProp(SOAP12Constants.PROP_WEBMETHOD);
                if (webMethod != null) {
                    posting = webMethod.equals(HTTPConstants.HEADER_POST);
                }
            }

            Message reqMessage = msgContext.getRequestMessage();
            if (posting) {
                method = new PostMethod(targetURL.toString());
                addContextInfo(method, httpClient, msgContext, targetURL);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                reqMessage.writeTo(baos);
                ((PostMethod) method).setRequestBody(new ByteArrayInputStream(baos.toByteArray()));
                ((PostMethod) method).setUseExpectHeader(false); // workaround for
            } else {
                method = new GetMethod(targetURL.toString());
                addContextInfo(method, httpClient, msgContext, targetURL);
            }
            // 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();
                state.setCookiePolicy(CookiePolicy.COMPATIBILITY);
                String host = hostConfiguration.getHost();
                String path = targetURL.getPath();
                boolean secure = hostConfiguration.getProtocol().isSecure();
                String ck1 = (String) msgContext.getProperty(HTTPConstants.HEADER_COOKIE);

                String ck2 = (String) msgContext.getProperty(HTTPConstants.HEADER_COOKIE2);
                if (ck1 != null) {
                    int index = ck1.indexOf('=');
                    state.addCookie(new Cookie(host, ck1.substring(0, index), ck1.substring(index + 1), path, null,
                            secure));
                }
                if (ck2 != null) {
                    int index = ck2.indexOf('=');
                    state.addCookie(new Cookie(host, ck2.substring(0, index), ck2.substring(index + 1), path, null,
                            secure));
                }
                httpClient.setState(state);
            }
            boolean hasSoapFault = false;
            int returnCode = httpClient.executeMethod(method);
            String contentType = null;
            String contentLocation = null;
            String contentLength = null;
            if (method.getResponseHeader(HTTPConstants.HEADER_CONTENT_TYPE) != null) {
                contentType = method.getResponseHeader(HTTPConstants.HEADER_CONTENT_TYPE).getValue();
            }
            if (method.getResponseHeader(HTTPConstants.HEADER_CONTENT_LOCATION) != null) {
                contentLocation = method.getResponseHeader(HTTPConstants.HEADER_CONTENT_LOCATION).getValue();
            }
            if (method.getResponseHeader(HTTPConstants.HEADER_CONTENT_LENGTH) != null) {
                contentLength = method.getResponseHeader(HTTPConstants.HEADER_CONTENT_LENGTH).getValue();
            }
            contentType = (null == contentType) ? null : contentType.trim();
            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") //$NON-NLS-1$
                    && ((returnCode > 499) && (returnCode < 600))) {

                // SOAP Fault should be in here - so fall through
                hasSoapFault = true;
            } else {
                String statusMessage = method.getStatusText();
                AxisFault fault = new AxisFault("HTTP", //$NON-NLS-1$
                        "(" + returnCode + ")" //$NON-NLS-1$ //$NON-NLS-2$
                                + statusMessage,
                        null, null);

                try {
                    fault.setFaultDetailString(Messages.getMessage("return01", //$NON-NLS-1$
                            "" + returnCode, method.getResponseBodyAsString())); //$NON-NLS-1$
                    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);

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

            OperationDesc operation = msgContext.getOperation();
            if (hasSoapFault || operation.getMep().equals(OperationType.REQUEST_RESPONSE)) {
                msgContext.setResponseMessage(outMsg);
            } else {
                // Change #1
                //
                // If the operation is a one-way, then don't set the response
                // on the msg context. Doing so will cause Axis to attempt to
                // read from a non-existent SOAP message which causes errors.
                //
                // Note: also checking to see if the return type is our "VOID"
                // QName from the AeInvokeHandler since that's our workaround
                // for avoiding Axis's Thread creation in Call.invokeOneWay()
                //
                // Since the message context won't have a chance to consume the
                // response stream (which closes the connection), close the
                // connection here.
                method.releaseConnection();
            }

            if (log.isDebugEnabled()) {
                if (null == contentLength) {
                    log.debug("\n" //$NON-NLS-1$
                            + Messages.getMessage("no00", "Content-Length")); //$NON-NLS-1$ //$NON-NLS-2$
                }
                log.debug("\n" + Messages.getMessage("xmlRecd00")); //$NON-NLS-1$ //$NON-NLS-2$
                log.debug("-----------------------------------------------"); //$NON-NLS-1$
                log.debug(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))
                        msgContext.setProperty(HTTPConstants.HEADER_COOKIE, cleanupCookie(headers[i].getValue()));
                    else if (headers[i].getName().equalsIgnoreCase(HTTPConstants.HEADER_SET_COOKIE2))
                        msgContext.setProperty(HTTPConstants.HEADER_COOKIE2, cleanupCookie(headers[i].getValue()));
                }

            }

        } catch (Throwable t) {
            log.debug(t);

            if (method != null) {
                method.releaseConnection();
            }

            // We can call Axis.makeFault() if it's an exception; otherwise
            // construct the AxisFault directly.
            throw (t instanceof Exception) ? AxisFault.makeFault((Exception) t)
                    : new AxisFault(t.getLocalizedMessage(), t);
        }

        if (log.isDebugEnabled()) {
            log.debug(Messages.getMessage("exit00", //$NON-NLS-1$
                    "CommonsHTTPSender::invoke")); //$NON-NLS-1$
        }
    }

    /**
     * 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;
    }

    /**
     * @deprecated
     */
    private HostConfiguration getHostConfiguration(HttpClient client, 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) {
            port = 80; // even for https
        }

        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());
                    client.getState().setProxyCredentials(null, null, 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
     * @deprecated
     */
    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.setTimeout(msgContext.getTimeout());
            // timeout for initial connection
            httpClient.setConnectionTimeout(msgContext.getTimeout());
        }

        // Get SOAPAction, default to ""
        String action = msgContext.useSOAPAction() ? msgContext.getSOAPActionURI() : ""; //$NON-NLS-1$

        if (action == null) {
            action = ""; //$NON-NLS-1$
        }
        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 + "\"")); //$NON-NLS-1$ //$NON-NLS-2$
        String userID = msgContext.getUsername();
        String passwd = msgContext.getPassword();

        // 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 cred = new UsernamePasswordCredentials(userID, passwd);
            httpClient.getState().setCredentials(null, null, cred);

            // Change #2
            //
            // Comment out the lines below since they force all authentication
            // to be Basic. This is a problem if the web service you're invoking 
            // is expecting Digest.

            // The following 3 lines should NOT be required. But Our SimpleAxisServer fails
            // during all-tests if this is missing.
            //            StringBuffer tmpBuf = new StringBuffer();
            //            tmpBuf.append(userID).append(":").append((passwd == null) ? "" : passwd);
            //            method.addRequestHeader(HTTPConstants.HEADER_AUTHORIZATION, "Basic " + Base64.encode(tmpBuf.toString().getBytes()));
        }

        // 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();
                method.addRequestHeader(mimeHeader.getName(), mimeHeader.getValue());
            }
        }

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

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

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

                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, "|\""); //$NON-NLS-1$

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

            if (log.isDebugEnabled()) {
                log.debug(Messages.getMessage("match00", //$NON-NLS-1$
                        new String[] { "HTTPSender", //$NON-NLS-1$
                                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 InputStream createConnectionReleasingInputStream(final HttpMethodBase method) throws IOException {
        return new FilterInputStream(method.getResponseBodyAsStream()) {
            public void close() throws IOException {
                try {
                    super.close();
                } finally {
                    method.releaseConnection();
                }
            }
        };
    }
}