/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */

package org.apache.axis2.transport.http.impl.httpclient4;


import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.NamedValue;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.i18n.Messages;
import org.apache.axis2.transport.MessageFormatter;
import org.apache.axis2.transport.http.HTTPAuthenticator;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HTTPSender;
import org.apache.axis2.transport.http.HTTPTransportConstants;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.auth.params.AuthPNames;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.auth.NTLMSchemeFactory;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;


public class HTTPSenderImpl extends HTTPSender {

    private static final Log log = LogFactory.getLog(HTTPSenderImpl.class);

    /**
     * Used to send a request via HTTP Get method
     *
     * @param msgContext        - The MessageContext of the message
     * @param url               - The target URL
     * @param soapActionString - The soapAction string of the request
     * @throws org.apache.axis2.AxisFault - Thrown in case an exception occurs
     */
    protected void sendViaGet(MessageContext msgContext, URL url, String soapActionString)
            throws AxisFault {
        HttpGet httpGet = new HttpGet();
        AbstractHttpClient httpClient = getHttpClient(msgContext);
        MessageFormatter messageFormatter = populateCommonProperties(msgContext, url, httpGet,
                                                                     httpClient, soapActionString);

        // Need to have this here because we can have soap action when using the
        // soap response MEP
        String soapAction = messageFormatter
                .formatSOAPAction(msgContext, format, soapActionString);

        if (soapAction != null && !msgContext.isDoingREST()) {
            httpGet.setHeader(HTTPConstants.HEADER_SOAP_ACTION, soapAction);
        }

        /*
         * main execution takes place..
         */
        HttpResponse response = null;
        try {
            response = executeMethod(httpClient, msgContext, url, httpGet);
            handleResponse(msgContext, response);
        } catch (IOException e) {
            log.info("Unable to sendViaGet to url[" + url + "]", e);
            throw AxisFault.makeFault(e);
        } finally {
            cleanup(msgContext, response);
        }
    }

    @Override
    protected void cleanup(MessageContext msgContext, Object httpResponse) {
        HttpResponse response;
        if (httpResponse instanceof HttpResponse) {
            response = (HttpResponse) httpResponse;
        } else {
            log.trace("HttpResponse expected, but found - " + httpResponse);
            return;
        }
        if (msgContext.isPropertyTrue(HTTPConstants.CLEANUP_RESPONSE)) {
            log.trace("Cleaning response : " + response);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                try {
                    EntityUtils.consume(entity);
                } catch (IOException e) {
                    log.error("Error while cleaning response : " + response, e);
                }
            }
        }
    }

    /**
     * Used to send a request via HTTP Delete Method
     *
     * @param msgContext        - The MessageContext of the message
     * @param url               - The target URL
     * @param soapActionString - The soapAction string of the request
     * @throws org.apache.axis2.AxisFault - Thrown in case an exception occurs
     */
    protected void sendViaDelete(MessageContext msgContext, URL url, String soapActionString)
            throws AxisFault {

        HttpDelete deleteMethod = new HttpDelete();
        AbstractHttpClient httpClient = getHttpClient(msgContext);
        populateCommonProperties(msgContext, url, deleteMethod, httpClient, soapActionString);

        /*
         * main execution takes place..
         */
        HttpResponse response = null;
        try {
            response = executeMethod(httpClient, msgContext, url, deleteMethod);
            handleResponse(msgContext, response);
        } catch (IOException e) {
            log.info("Unable to sendViaDelete to url[" + url + "]", e);
            throw AxisFault.makeFault(e);
        } finally {
            cleanup(msgContext, response);
        }
    }

    /**
     * Used to send a request via HTTP Post Method
     *
     * @param msgContext       - The MessageContext of the message
     * @param url              - The target URL
     * @param soapActionString - The soapAction string of the request
     * @throws org.apache.axis2.AxisFault - Thrown in case an exception occurs
     */
    protected void sendViaPost(MessageContext msgContext, URL url, String soapActionString)
            throws AxisFault {

        AbstractHttpClient httpClient = getHttpClient(msgContext);

        /*
         * What's up with this, it never gets used anywhere?? --Glen String
         * charEncoding = (String)
         * msgContext.getProperty(Constants.Configuration
         * .CHARACTER_SET_ENCODING);
         *
         * if (charEncoding == null) { charEncoding =
         * MessageContext.DEFAULT_CHAR_SET_ENCODING; }
         */

        HttpPost postMethod = new HttpPost();
        if (log.isTraceEnabled()) {
            log.trace(Thread.currentThread() + " PostMethod " + postMethod + " / " + httpClient);
        }
        MessageFormatter messageFormatter = populateCommonProperties(msgContext, url, postMethod,
                                                                     httpClient, soapActionString);
        AxisRequestEntityImpl requestEntity =
                new AxisRequestEntityImpl(messageFormatter, msgContext, format,
                                          soapActionString, chunked, isAllowedRetry);
        postMethod.setEntity(requestEntity);

        if (!httpVersion.equals(HTTPConstants.HEADER_PROTOCOL_10) && chunked) {
            requestEntity.setChunked(chunked);
        }

        String soapAction = messageFormatter.formatSOAPAction(msgContext, format, soapActionString);

        if (soapAction != null && !msgContext.isDoingREST()) {
            postMethod.setHeader(HTTPConstants.HEADER_SOAP_ACTION, soapAction);
        }

        /*
         * main execution takes place..
         */
        HttpResponse response = null;
        try {
            response = executeMethod(httpClient, msgContext, url, postMethod);
            handleResponse(msgContext, response);
        } catch (IOException e) {
            log.info("Unable to sendViaPost to url[" + url + "]", e);
            throw AxisFault.makeFault(e);
        } finally {
            cleanup(msgContext, response);
        }
    }

    /**
     * Used to send a request via HTTP Put Method
     *
     * @param msgContext       - The MessageContext of the message
     * @param url              - The target URL
     * @param soapActionString - The soapAction string of the request
     * @throws org.apache.axis2.AxisFault - Thrown in case an exception occurs
     */
    protected void sendViaPut(MessageContext msgContext, URL url, String soapActionString)
            throws AxisFault {

        AbstractHttpClient httpClient = getHttpClient(msgContext);

        /*
         * Same deal - this value never gets used, why is it here? --Glen String
         * charEncoding = (String)
         * msgContext.getProperty(Constants.Configuration
         * .CHARACTER_SET_ENCODING);
         *
         * if (charEncoding == null) { charEncoding =
         * MessageContext.DEFAULT_CHAR_SET_ENCODING; }
         */

        HttpPut putMethod = new HttpPut();
        MessageFormatter messageFormatter = populateCommonProperties(msgContext, url, putMethod,
                                                                     httpClient, soapActionString);
        AxisRequestEntityImpl requestEntity =
                new AxisRequestEntityImpl(messageFormatter, msgContext, format,
                                          soapActionString, chunked, isAllowedRetry);
        putMethod.setEntity(requestEntity);

        if (!httpVersion.equals(HTTPConstants.HEADER_PROTOCOL_10) && chunked) {
            requestEntity.setChunked(chunked);
        }

        String soapAction = messageFormatter.formatSOAPAction(msgContext, format, soapActionString);
        if (soapAction != null && !msgContext.isDoingREST()) {
            putMethod.setHeader(HTTPConstants.HEADER_SOAP_ACTION, soapAction);
        }

        /*
         * main execution takes place..
         */
        HttpResponse response = null;
        try {
            response = executeMethod(httpClient, msgContext, url, putMethod);
            handleResponse(msgContext, response);
        } catch (IOException e) {
            log.info("Unable to sendViaPut to url[" + url + "]", e);
            throw AxisFault.makeFault(e);
        } finally {
            cleanup(msgContext, response);
        }
    }

    /**
     * Used to handle the HTTP Response
     *
     * @param msgContext - The MessageContext of the message
     * @param -          The HTTP method used
     * @throws java.io.IOException - Thrown in case an exception occurs
     */
    protected void handleResponse(MessageContext msgContext, Object httpResponse)
            throws IOException {
        HttpResponse response;
        if (httpResponse instanceof HttpResponse) {
            response = (HttpResponse) httpResponse;
        } else {
            log.trace("HttpResponse expected, but found - " + httpResponse);
            return;
        }
        int statusCode = response.getStatusLine().getStatusCode();
        HTTPStatusCodeFamily family = getHTTPStatusCodeFamily(statusCode);
        log.trace("Handling response - " + statusCode);
        if (statusCode == HttpStatus.SC_ACCEPTED) {
            msgContext.setProperty(HTTPConstants.CLEANUP_RESPONSE, Boolean.TRUE);
            /*
            * When an HTTP 202 Accepted code has been received, this will be
            * the case of an execution of an in-only operation. In such a
            * scenario, the HTTP response headers should be returned, i.e.
            * session cookies.
            */
            obtainHTTPHeaderInformation(response, msgContext);

        } else if (HTTPStatusCodeFamily.SUCCESSFUL.equals(family)) {
            // We don't clean the response here because the response will be used afterwards
            msgContext.setProperty(HTTPConstants.CLEANUP_RESPONSE, Boolean.FALSE);
            processResponse(response, msgContext);

        } else if (statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
                   || statusCode == HttpStatus.SC_BAD_REQUEST) {
            msgContext.setProperty(HTTPConstants.CLEANUP_RESPONSE, Boolean.TRUE);
            Header contentTypeHeader = response.getFirstHeader(HTTPConstants.HEADER_CONTENT_TYPE);
            String value = null;
            if (contentTypeHeader != null) {
                value = contentTypeHeader.getValue();
            }
            OperationContext opContext = msgContext.getOperationContext();
            if (opContext != null) {
                MessageContext inMessageContext = opContext
                        .getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
                if (inMessageContext != null) {
                    inMessageContext.setProcessingFault(true);
                }
            }
            if (value != null) {
                msgContext.setProperty(HTTPConstants.CLEANUP_RESPONSE, Boolean.FALSE);
                processResponse(response, msgContext);
            }

            if (org.apache.axis2.util.Utils.isClientThreadNonBlockingPropertySet(msgContext)) {
                throw new AxisFault(Messages.
                        getMessage("transportError",
                                   String.valueOf(statusCode),
                                   response.getStatusLine().toString()));
            }
        } else {
            msgContext.setProperty(HTTPConstants.CLEANUP_RESPONSE, Boolean.TRUE);
            throw new AxisFault(Messages.getMessage("transportError", String.valueOf(statusCode),
                                                    response.getStatusLine().toString()));
        }
    }

    /**
     * Collect the HTTP header information and set them in the message context
     *
     * @param httpResponse which holds the header information
     * @param msgContext the MessageContext in which to place the information... OR
     *                   NOT!
     * @throws AxisFault if problems occur
     */
    protected void obtainHTTPHeaderInformation(Object httpResponse, MessageContext msgContext)
            throws AxisFault {
        HttpResponse response;
        if (httpResponse instanceof HttpResponse) {
            response = (HttpResponse) httpResponse;
        } else {
            return;
        }
        // Set RESPONSE properties onto the REQUEST message context. They will
        // need to be copied off the request context onto
        // the response context elsewhere, for example in the
        // OutInOperationClient.
        Map transportHeaders = new HTTPTransportHeaders(response.getAllHeaders());
        msgContext.setProperty(MessageContext.TRANSPORT_HEADERS, transportHeaders);
        msgContext.setProperty(HTTPConstants.MC_HTTP_STATUS_CODE,
                               new Integer(response.getStatusLine().getStatusCode()));
        Header header = response.getFirstHeader(HTTPConstants.HEADER_CONTENT_TYPE);

        if (header != null) {
            HeaderElement[] headers = header.getElements();
            MessageContext inMessageContext = msgContext.getOperationContext().getMessageContext(
                    WSDLConstants.MESSAGE_LABEL_IN_VALUE);

            Object contentType = header.getValue();
            Object charSetEnc = null;

            for (int i = 0; i < headers.length; i++) {
                NameValuePair charsetEnc = headers[i]
                        .getParameterByName(HTTPConstants.CHAR_SET_ENCODING);
                if (charsetEnc != null) {
                    charSetEnc = charsetEnc.getValue();
                }
            }

            if (inMessageContext != null) {
                inMessageContext.setProperty(Constants.Configuration.CONTENT_TYPE, contentType);
                inMessageContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING,
                                             charSetEnc);
            } else {

                // Transport details will be stored in a HashMap so that anybody
                // interested can
                // retrieve them
                HashMap transportInfoMap = new HashMap();
                transportInfoMap.put(Constants.Configuration.CONTENT_TYPE, contentType);
                transportInfoMap.put(Constants.Configuration.CHARACTER_SET_ENCODING, charSetEnc);

                // the HashMap is stored in the outgoing message.
                msgContext
                        .setProperty(Constants.Configuration.TRANSPORT_INFO_MAP, transportInfoMap);
            }
        }

        String sessionCookie = null;
        // Process old style headers first
        Header[] cookieHeaders = response.getHeaders(HTTPConstants.HEADER_SET_COOKIE);
        String customCoookiId = (String) msgContext.getProperty(Constants.CUSTOM_COOKIE_ID);
        for (int i = 0; i < cookieHeaders.length; i++) {
            HeaderElement[] elements = cookieHeaders[i].getElements();
            for (int e = 0; e < elements.length; e++) {
                HeaderElement element = elements[e];
                if (Constants.SESSION_COOKIE.equalsIgnoreCase(element.getName())
                    || Constants.SESSION_COOKIE_JSESSIONID.equalsIgnoreCase(element.getName())) {
                    sessionCookie = processCookieHeader(element);
                }
                if (customCoookiId != null && customCoookiId.equalsIgnoreCase(element.getName())) {
                    sessionCookie = processCookieHeader(element);
                }
            }
        }
        // Overwrite old style cookies with new style ones if present
        cookieHeaders = response.getHeaders(HTTPConstants.HEADER_SET_COOKIE2);
        for (int i = 0; i < cookieHeaders.length; i++) {
            HeaderElement[] elements = cookieHeaders[i].getElements();
            for (int e = 0; e < elements.length; e++) {
                HeaderElement element = elements[e];
                if (Constants.SESSION_COOKIE.equalsIgnoreCase(element.getName())
                    || Constants.SESSION_COOKIE_JSESSIONID.equalsIgnoreCase(element.getName())) {
                    sessionCookie = processCookieHeader(element);
                }
                if (customCoookiId != null && customCoookiId.equalsIgnoreCase(element.getName())) {
                    sessionCookie = processCookieHeader(element);
                }
            }
        }

        if (sessionCookie != null) {
            msgContext.getServiceContext().setProperty(HTTPConstants.COOKIE_STRING, sessionCookie);
        }
    }

    private String processCookieHeader(HeaderElement element) {
        String cookie = element.getName() + "=" + element.getValue();
        NameValuePair[] parameters = element.getParameters();
        for (int j = 0; parameters != null && j < parameters.length; j++) {
            NameValuePair parameter = parameters[j];
            cookie = cookie + "; " + parameter.getName() + "=" + parameter.getValue();
        }
        return cookie;
    }

    protected void processResponse(HttpResponse response, MessageContext msgContext)
            throws IOException {
        obtainHTTPHeaderInformation(response, msgContext);

        HttpEntity httpEntity = response.getEntity();
        InputStream in = httpEntity.getContent();
        if (in == null) {
            throw new AxisFault(Messages.getMessage("canNotBeNull", "InputStream"));
        }
        Header contentEncoding = httpEntity.getContentEncoding();
        if (contentEncoding != null) {
            if (contentEncoding.getValue().equalsIgnoreCase(HTTPConstants.COMPRESSION_GZIP)) {
                in = new GZIPInputStream(in);
                // If the content-encoding is identity we can basically ignore
                // it.
            } else if (!"identity".equalsIgnoreCase(contentEncoding.getValue())) {
                throw new AxisFault("HTTP :" + "unsupported content-encoding of '"
                                    + contentEncoding.getValue() + "' found");
            }
        }

        OperationContext opContext = msgContext.getOperationContext();
        if (opContext != null) {
            opContext.setProperty(MessageContext.TRANSPORT_IN, in);
        }
    }

    /**
     * getting host configuration to support standard http/s, proxy and NTLM
     * support
     *
     * @param client    active HttpClient
     * @param msgCtx    active MessageContext
     * @param targetURL the target URL
     * @return a HostConfiguration set up with proxy information
     * @throws org.apache.axis2.AxisFault if problems occur
     */
    protected HttpHost getHostConfiguration(AbstractHttpClient client, MessageContext msgCtx,
                                            URL targetURL) throws AxisFault {

        boolean isAuthenticationEnabled = isAuthenticationEnabled(msgCtx);
        int port = targetURL.getPort();

        String protocol = targetURL.getProtocol();
        if (port == -1) {
            if (HTTPTransportConstants.PROTOCOL_HTTP.equals(protocol)) {
                port = 80;
            } else if (HTTPTransportConstants.PROTOCOL_HTTPS.equals(protocol)) {
                port = 443;
            }
        }
        // to see the host is a proxy and in the proxy list - available in
        // axis2.xml
        HttpHost hostConfig = new HttpHost(targetURL.getHost(), port, targetURL.getProtocol());

        // TODO : one might need to set his own socket factory. We have to allow that case as well.

        if (isAuthenticationEnabled) {
            // Basic, Digest, NTLM and custom authentications.
            this.setAuthenticationInfo(client, msgCtx);
        }
        // proxy configuration

        if (HTTPProxyConfigurator.isProxyEnabled(msgCtx, targetURL)) {
            if (log.isDebugEnabled()) {
                log.debug("Configuring HTTP proxy.");
            }
            HTTPProxyConfigurator.configure(msgCtx, client);
        }

        return hostConfig;
    }

    protected boolean isAuthenticationEnabled(MessageContext msgCtx) {
        return (msgCtx.getProperty(HTTPConstants.AUTHENTICATE) != null);
    }

    /*
     * This will handle server Authentication, It could be either NTLM, Digest
     * or Basic Authentication. Apart from that user can change the priory or
     * add a custom authentication scheme.
     */
    protected void setAuthenticationInfo(AbstractHttpClient agent, MessageContext msgCtx)
            throws AxisFault {
        HTTPAuthenticator authenticator;
        Object obj = msgCtx.getProperty(HTTPConstants.AUTHENTICATE);
        if (obj != null) {
            if (obj instanceof HTTPAuthenticator) {
                authenticator = (HTTPAuthenticator) obj;

                String username = authenticator.getUsername();
                String password = authenticator.getPassword();
                String host = authenticator.getHost();
                String domain = authenticator.getDomain();

                int port = authenticator.getPort();
                String realm = authenticator.getRealm();

                /* If retrying is available set it first */
                isAllowedRetry = authenticator.isAllowedRetry();

                Credentials creds;

                // TODO : Set preemptive authentication, but its not recommended in HC 4

                if (host != null) {
                    if (domain != null) {
                        /* Credentials for NTLM Authentication */
                        agent.getAuthSchemes().register("ntlm",new NTLMSchemeFactory());
                        creds = new NTCredentials(username, password, host, domain);
                    } else {
                        /* Credentials for Digest and Basic Authentication */
                        creds = new UsernamePasswordCredentials(username, password);
                    }
                    agent.getCredentialsProvider().
                            setCredentials(new AuthScope(host, port, realm), creds);
                } else {
                    if (domain != null) {
                        /*
                         * Credentials for NTLM Authentication when host is
                         * ANY_HOST
                         */
                        agent.getAuthSchemes().register("ntlm",new NTLMSchemeFactory());
                        creds = new NTCredentials(username, password, AuthScope.ANY_HOST, domain);
                        agent.getCredentialsProvider().
                                setCredentials(new AuthScope(AuthScope.ANY_HOST, port, realm),
                                               creds);
                    } else {
                        /* Credentials only for Digest and Basic Authentication */
                        creds = new UsernamePasswordCredentials(username, password);
                        agent.getCredentialsProvider().
                                setCredentials(new AuthScope(AuthScope.ANY), creds);
                    }
                }
                /* Customizing the priority Order */
                List schemes = authenticator.getAuthSchemes();
                if (schemes != null && schemes.size() > 0) {
                    List authPrefs = new ArrayList(3);
                    for (int i = 0; i < schemes.size(); i++) {
                        if (schemes.get(i) instanceof AuthPolicy) {
                            authPrefs.add(schemes.get(i));
                            continue;
                        }
                        String scheme = (String) schemes.get(i);
                        authPrefs.add(authenticator.getAuthPolicyPref(scheme));

                    }
                    agent.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, authPrefs);
                }

            } else {
                throw new AxisFault("HttpTransportProperties.Authenticator class cast exception");
            }
        }

    }

    /**
     * Method used to copy all the common properties
     *
     * @param msgContext       - The messageContext of the request message
     * @param url              - The target URL
     * @param httpMethod       - The http method used to send the request
     * @param httpClient       - The httpclient used to send the request
     * @param soapActionString - The soap action atring of the request message
     * @return MessageFormatter - The messageFormatter for the relavent request
     *         message
     * @throws org.apache.axis2.AxisFault - Thrown in case an exception occurs
     */
    protected MessageFormatter populateCommonProperties(MessageContext msgContext, URL url,
                                                        HttpRequestBase httpMethod,
                                                        AbstractHttpClient httpClient,
                                                        String soapActionString)
            throws AxisFault {

        if (isAuthenticationEnabled(msgContext)) {
            httpMethod.getParams().setBooleanParameter(ClientPNames.HANDLE_AUTHENTICATION, true);
        }

        MessageFormatter messageFormatter = MessageProcessorSelector.getMessageFormatter(msgContext);

        url = messageFormatter.getTargetAddress(msgContext, format, url);

        try {
            httpMethod.setURI(url.toURI());
        } catch (URISyntaxException e) {
            log.error("Error in URI : " + url, e);
        }

        httpMethod.setHeader(HTTPConstants.HEADER_CONTENT_TYPE,
                             messageFormatter.getContentType(msgContext, format, soapActionString));

        httpMethod.setHeader(HTTPConstants.HEADER_HOST, url.getHost());

        if (msgContext.getOptions() != null && msgContext.getOptions().isManageSession()) {
            // setting the cookie in the out path
            Object cookieString = msgContext.getProperty(HTTPConstants.COOKIE_STRING);

            if (cookieString != null) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(cookieString);
                httpMethod.setHeader(HTTPConstants.HEADER_COOKIE, buffer.toString());
            }
        }

        if (httpVersion.equals(HTTPConstants.HEADER_PROTOCOL_10)) {
            httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
                                                HttpVersion.HTTP_1_0);
        }
        return messageFormatter;
    }

    /**
     * This is used to get the dynamically set time out values from the message
     * context. If the values are not available or invalid then the default
     * values or the values set by the configuration will be used
     *
     * @param msgContext the active MessageContext
     * @param httpClient
     */
    protected void initializeTimeouts(MessageContext msgContext, AbstractHttpClient httpClient) {
        // If the SO_TIMEOUT of CONNECTION_TIMEOUT is set by dynamically the
        // override the static config
        Integer tempSoTimeoutProperty = (Integer) msgContext.getProperty(HTTPConstants.SO_TIMEOUT);
        Integer tempConnTimeoutProperty = (Integer) msgContext
                .getProperty(HTTPConstants.CONNECTION_TIMEOUT);
        long timeout = msgContext.getOptions().getTimeOutInMilliSeconds();

        if (tempConnTimeoutProperty != null) {
            int connectionTimeout = tempConnTimeoutProperty.intValue();
            // timeout for initial connection
            httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
                                                connectionTimeout);
        } else {
            // set timeout in client
            if (timeout > 0) {
                httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
                                                    (int) timeout);
            }
        }

        if (tempSoTimeoutProperty != null) {
            int soTimeout = tempSoTimeoutProperty.intValue();
            // SO_TIMEOUT -- timeout for blocking reads
            httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, soTimeout);
        } else {
            // set timeout in client
            if (timeout > 0) {
                httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, (int) timeout);
            }
        }
    }

    /**
     * This is used to get the dynamically set time out values from the message
     * context. If the values are not available or invalid then the default
     * values or the values set by the configuration will be used
     *
     * @param msgContext the active MessageContext
     * @param httpMethod method
     */
    protected void setTimeouts(MessageContext msgContext, HttpRequestBase httpMethod) {
        // If the SO_TIMEOUT of CONNECTION_TIMEOUT is set by dynamically the
        // override the static config
        Integer tempSoTimeoutProperty = (Integer) msgContext.getProperty(HTTPConstants.SO_TIMEOUT);
        Integer tempConnTimeoutProperty = (Integer) msgContext
                .getProperty(HTTPConstants.CONNECTION_TIMEOUT);
        long timeout = msgContext.getOptions().getTimeOutInMilliSeconds();

        if (tempConnTimeoutProperty != null) {
            // timeout for initial connection
            httpMethod.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
                                                tempConnTimeoutProperty);
        }

        if (tempSoTimeoutProperty != null) {
            // SO_TIMEOUT -- timeout for blocking reads
            httpMethod.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
                                                tempSoTimeoutProperty);
        } else {
            // set timeout in client
            if (timeout > 0) {
                httpMethod.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, (int) timeout);
            }
        }
    }

    protected AbstractHttpClient getHttpClient(MessageContext msgContext) {
        ConfigurationContext configContext = msgContext.getConfigurationContext();

        AbstractHttpClient httpClient = (AbstractHttpClient) msgContext
                .getProperty(HTTPConstants.CACHED_HTTP_CLIENT);

        if (httpClient == null) {
            httpClient = (AbstractHttpClient) configContext.
                    getProperty(HTTPConstants.CACHED_HTTP_CLIENT);
        }

        if (httpClient != null) {
            return httpClient;
        }

        synchronized (this) {
            httpClient = (AbstractHttpClient) msgContext.
                    getProperty(HTTPConstants.CACHED_HTTP_CLIENT);

            if (httpClient == null) {
                httpClient = (AbstractHttpClient) configContext
                        .getProperty(HTTPConstants.CACHED_HTTP_CLIENT);
            }

            if (httpClient != null) {
                return httpClient;
            }

            ClientConnectionManager connManager = (ClientConnectionManager) msgContext
                    .getProperty(HTTPConstants.MULTITHREAD_HTTP_CONNECTION_MANAGER);
            if (connManager == null) {
                connManager = (ClientConnectionManager) msgContext
                        .getProperty(HTTPConstants.MULTITHREAD_HTTP_CONNECTION_MANAGER);
            }
            if (connManager == null) {
                // reuse HttpConnectionManager
                synchronized (configContext) {
                    connManager = (ClientConnectionManager) configContext
                            .getProperty(HTTPConstants.MULTITHREAD_HTTP_CONNECTION_MANAGER);
                    if (connManager == null) {
                        log.trace("Making new ConnectionManager");
                        SchemeRegistry schemeRegistry = new SchemeRegistry();
                        schemeRegistry.register(
                                new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
                        schemeRegistry.register(
                                new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));

                        connManager = new PoolingClientConnectionManager(schemeRegistry);
                        ((PoolingClientConnectionManager)connManager).setMaxTotal(200);
                        ((PoolingClientConnectionManager)connManager).setDefaultMaxPerRoute(200);
                        configContext.setProperty(
                                HTTPConstants.MULTITHREAD_HTTP_CONNECTION_MANAGER, connManager);
                    }
                }
            }
            /*
             * Create a new instance of HttpClient since the way it is used here
             * it's not fully thread-safe.
             */
            HttpParams clientParams = new BasicHttpParams();
            clientParams.setParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET, "UTF-8");
            httpClient = new DefaultHttpClient(connManager, clientParams);

            //We don't need to set timeout for connection manager, since we are doing it below
            // and its enough

            // Get the timeout values set in the runtime
            initializeTimeouts(msgContext, httpClient);

            return httpClient;
        }
    }

    protected HttpResponse executeMethod(AbstractHttpClient httpClient, MessageContext msgContext,
                                         URL url,
                                         HttpRequestBase method) throws IOException {
        HttpHost httpHost = this.getHostConfiguration(httpClient, msgContext, url);

        // set the custom headers, if available
        addCustomHeaders(method, msgContext);

        // add compression headers if needed
        if (msgContext.isPropertyTrue(HTTPConstants.MC_ACCEPT_GZIP)) {
            method.addHeader(HTTPConstants.HEADER_ACCEPT_ENCODING,
                             HTTPConstants.COMPRESSION_GZIP);
        }

        if (msgContext.isPropertyTrue(HTTPConstants.MC_GZIP_REQUEST)) {
            method.addHeader(HTTPConstants.HEADER_CONTENT_ENCODING,
                             HTTPConstants.COMPRESSION_GZIP);
        }

        if (msgContext.getProperty(HTTPConstants.HTTP_METHOD_PARAMS) != null) {
            HttpParams params = (HttpParams) msgContext
                    .getProperty(HTTPConstants.HTTP_METHOD_PARAMS);
            method.setParams(params);
        }

        String cookiePolicy = (String) msgContext.getProperty(HTTPConstants.COOKIE_POLICY);
        if (cookiePolicy != null) {
            method.getParams().setParameter(ClientPNames.COOKIE_POLICY, cookiePolicy);
        }

        setTimeouts(msgContext, method);
        HttpContext localContext = new BasicHttpContext();
        // Why do we have add context here
        return httpClient.execute(httpHost, method, localContext);
    }

    public void addCustomHeaders(HttpRequestBase method, MessageContext msgContext) {

        boolean isCustomUserAgentSet = false;
        // set the custom headers, if available
        Object httpHeadersObj = msgContext.getProperty(HTTPConstants.HTTP_HEADERS);
        if (httpHeadersObj != null) {
            if (httpHeadersObj instanceof List) {
                List httpHeaders = (List) httpHeadersObj;
                for (int i = 0; i < httpHeaders.size(); i++) {
                    NamedValue nv = (NamedValue) httpHeaders.get(i);
                    if (nv != null) {
                        Header header = new BasicHeader(nv.getName(), nv.getValue());
                        if (HTTPConstants.HEADER_USER_AGENT.equals(header.getName())) {
                            isCustomUserAgentSet = true;
                        }
                        method.addHeader(header);
                    }
                }

            }
            if (httpHeadersObj instanceof Map) {
                Map httpHeaders = (Map) httpHeadersObj;
                for (Iterator iterator = httpHeaders.entrySet().iterator(); iterator.hasNext(); ) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    String key = (String) entry.getKey();
                    String value = (String) entry.getValue();
                    if (HTTPConstants.HEADER_USER_AGENT.equals(key)) {
                        isCustomUserAgentSet = true;
                    }
                    method.addHeader(key, value);
                }
            }
        }

        // we have to consider the TRANSPORT_HEADERS map as well
        Map transportHeaders = (Map) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS);
        if (transportHeaders != null) {
            removeUnwantedHeaders(msgContext);

            Set headerEntries = transportHeaders.entrySet();

            for (Object headerEntry : headerEntries) {
                if (headerEntry instanceof Map.Entry) {
                    Header[] headers = method.getAllHeaders();

                    boolean headerAdded = false;
                    for (Header header : headers) {
                        if (header.getName() != null
                            && header.getName().equals(((Map.Entry) headerEntry).getKey())) {
                            headerAdded = true;
                            break;
                        }
                    }

                    if (!headerAdded) {
                        method.addHeader(((Map.Entry) headerEntry).getKey().toString(),
                                         ((Map.Entry) headerEntry).getValue().toString());
                    }
                }
            }
        }

        if (!isCustomUserAgentSet) {
            String userAgentString = getUserAgent(msgContext);
            method.setHeader(HTTPConstants.HEADER_USER_AGENT, userAgentString);
        }

    }

    /**
     * Remove unwanted headers from the transport headers map of outgoing
     * request. These are headers which should be dictated by the transport and
     * not the user. We remove these as these may get copied from the request
     * messages
     *
     * @param msgContext the Axis2 Message context from which these headers should be
     *                   removed
     */
    private void removeUnwantedHeaders(MessageContext msgContext) {
        Map headers = (Map) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS);

        if (headers == null || headers.isEmpty()) {
            return;
        }

        Iterator iter = headers.keySet().iterator();
        while (iter.hasNext()) {
            String headerName = (String) iter.next();
            if (HTTP.CONN_DIRECTIVE.equalsIgnoreCase(headerName)
                || HTTP.TRANSFER_ENCODING.equalsIgnoreCase(headerName)
                || HTTP.DATE_HEADER.equalsIgnoreCase(headerName)
                || HTTP.CONTENT_TYPE.equalsIgnoreCase(headerName)
                || HTTP.CONTENT_LEN.equalsIgnoreCase(headerName)) {
                iter.remove();
            }
        }
    }

    private String getUserAgent(MessageContext messageContext) {
        String userAgentString = "Axis2";
        boolean locked = false;
        if (messageContext.getParameter(HTTPConstants.USER_AGENT) != null) {
            OMElement userAgentElement = messageContext.getParameter(HTTPConstants.USER_AGENT)
                    .getParameterElement();
            userAgentString = userAgentElement.getText().trim();
            OMAttribute lockedAttribute = userAgentElement.getAttribute(new QName("locked"));
            if (lockedAttribute != null) {
                if (lockedAttribute.getAttributeValue().equalsIgnoreCase("true")) {
                    locked = true;
                }
            }
        }
        // Runtime overing part
        if (!locked) {
            if (messageContext.getProperty(HTTPConstants.USER_AGENT) != null) {
                userAgentString = (String) messageContext.getProperty(HTTPConstants.USER_AGENT);
            }
        }

        return userAgentString;
    }

}
