/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Darren Marvin
//      Created date:           2004/04/30
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.transportmessaging.connection.https;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.*;
import javax.xml.soap.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.protocol.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.configuration.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.connection.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.connection.https.keystore.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.SOAPMessage;
import uk.ac.soton.itinnovation.gemss.transportmessaging.messaging.context.*;
import uk.ac.soton.itinnovation.gemss.utils.configuration.*;



public class HTTPSConnection implements Connection {

    private static final String USE_HTTPS_PROXY_CONFIG_NAME = "gemss.transport.messaging.use.https.proxy";
    private static final String HTTPS_PROXY_HOST_CONFIG_NAME = "gemss.transport.messaging.https.host";
    private static final String HTTPS_PROXY_PORT_CONFIG_NAME = "gemss.transport.messaging.https.port";
    private static final String HTTPS_PROXY_HOST_NAME = "https.proxyHost";
    private static final String HTTPS_PROXY_PORT_NAME = "https.proxyPort";
    private static final String HTTP_KEEP_ALIVE_NAME = "http.keepAlive";
    private static Logger mLogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.transportmessaging.connection.HTTPConnection");
    private URL mUrl;
    private SecurityContext mSecCtx;
    private TransportMessagingConfiguration mConfig;
    private boolean mUseProxy = false;
    private List openConnections = null;

    public HTTPSConnection(URL url,SecurityContext secCtx) throws ConnectionException {
        System.setProperty(HTTP_KEEP_ALIVE_NAME,"false");
        mLogger.log(Level.WARNING,"Creating https connection with no configuration and hence no proxy");
            mUrl = url;
            mSecCtx = secCtx;
            //check if proxy needed
            mUseProxy = false;
            //use the HTTPClient classes
            Protocol.registerProtocol("https",new Protocol("https", new DynamicKeystoreSSLProtocolSocketFactory(mSecCtx), 443));
    }

    public HTTPSConnection(TransportMessagingConfiguration config,URL url,SecurityContext secCtx) throws ConnectionException {
        System.setProperty(HTTP_KEEP_ALIVE_NAME,"false");
        try{

            mConfig = config;
            mUrl = url;
            mSecCtx = secCtx;
            //check if proxy needed
            if(config.getConfigurationValue(USE_HTTPS_PROXY_CONFIG_NAME).equals("true")) {
               mLogger.log(Level.INFO,"Configuration requires use of HTTPS Proxy");
               mUseProxy = true;
           }
           else
                mLogger.log(Level.INFO,"Configuration does not require use of HTTPS Proxy");
             //use the HTTPClient classes
            Protocol.registerProtocol("https",new Protocol("https", new DynamicKeystoreSSLProtocolSocketFactory(mSecCtx), 443));

        }
        catch(ConfigurationException ex) {
            mLogger.log(Level.SEVERE,"Network connection failure when getting '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            throw new ConnectionException("Network connection failure when getting '" + mUrl.toExternalForm() +"'");
        }
    }

    /**
     * Send the supplied data within the input stream to the connection endpoint associated with
     * the concrete instance
     * @param in inputstream data source
     * @param packetLength size of the data source
     * @param properties name value pairs useful for the connection
     * @return response from the server
     * @throws ConnectionException
     */
    public ConnectionResponse send(Message message) throws ConnectionException {
      long packetLength = 0;
        PostMethod postHttp = null;
        try{
            if(message instanceof SOAPMessage) {

                SOAPMessage soapMsg = (SOAPMessage) message;
                soapMsg.getSOAPWireMessage().saveChanges();

                //use the HTTPClient classes
                HttpClient httpClient = new HttpClient();
                httpClient.setTimeout(0); //for now assume wait forever for data to arrive
                //httpClient.setConnectionTimeout(10000);//for now wait forever for 10 seconds
                postHttp = new PostMethod(mUrl.toExternalForm());
                mLogger.log(Level.INFO,"Invoking service at URI '" + mUrl.toExternalForm() +"'");

                postHttp.setRequestBody(soapMsg.getInputStream());

                packetLength = soapMsg.getByteSize();
                if (packetLength < Integer.MAX_VALUE) {
                    postHttp.setRequestContentLength((int) packetLength);
                }
                else if(packetLength == -1) {
                    //use chunking
                    postHttp.setRequestContentLength(EntityEnclosingMethod.CONTENT_LENGTH_CHUNKED);
                }
                else {
                    //use chunking
                    postHttp.setRequestContentLength(EntityEnclosingMethod.CONTENT_LENGTH_CHUNKED);
                }
                if(mUseProxy) {
                    postHttp.getHostConfiguration().setProxy(mConfig.getConfigurationValue(HTTPS_PROXY_HOST_CONFIG_NAME),Integer.parseInt(mConfig.getConfigurationValue(HTTPS_PROXY_PORT_CONFIG_NAME)));
                    mLogger.log(Level.INFO,"Using https proxy during invocation");
                }

                MimeHeaders mimeHeaders = soapMsg.getSOAPWireMessage().getMimeHeaders();

                Iterator it = mimeHeaders.getAllHeaders();
                boolean hasAuth = false; // true if we find explicit Auth header
                while (it.hasNext()) {
                    MimeHeader header = (MimeHeader) it.next();

                    String[] values = mimeHeaders.getHeader(header.getName());

                    if (values.length == 1)
                        postHttp.setRequestHeader(header.getName(),
                                header.getValue());
                    else {
                        StringBuffer concat = new StringBuffer();
                        int i = 0;
                        while (i < values.length) {
                            if (i != 0)
                                concat.append(',');
                            concat.append(values[i]);
                            i++;
                        }

                        postHttp.setRequestHeader(header.getName(),
                                concat.toString());
                    }

                }
                if(postHttp.getRequestHeader("SOAPAction")==null) {
                    postHttp.setRequestHeader("SOAPAction","\"\"");
                }
                if(postHttp.getRequestHeader("Accept")==null) {
                    postHttp.setRequestHeader("Accept","application/soap+xml, application/dime, multipart/related, text/*");
                }
                if(postHttp.getRequestHeader("Cache-Control")==null){
                    postHttp.setRequestHeader("Cache-Control","no-cache");
                }
                if(postHttp.getRequestHeader("Pragma")==null){
                    postHttp.setRequestHeader("Pragma","no-cache");
                }
                int statusCode = -1;
                int attempts = 0;
                while(attempts<3 && statusCode==-1) {
                    try{
                        attempts++;
                        statusCode = httpClient.executeMethod(postHttp);
                    }
                    catch (HttpRecoverableException ex) {
                        mLogger.log(Level.WARNING,"A recoverable http post exception occurred,retrying.",ex);
                    }
                }
                String responseMsg = postHttp.getStatusText();
                if(statusCode==-1) {
                    //ran out of retries and still failed
                    mLogger.log(Level.SEVERE,"HTTP connection failure described as '" + postHttp.getStatusText() + "'");
                    throw new ConnectionException("HTTP connection failure described as '" + postHttp.getStatusText() + "'");
                }
                if(statusCode>=400 && statusCode<500) {
                        //client side failure
                        if(statusCode==404) {
                            mLogger.log(Level.SEVERE,"Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'");
                            throw new ConnectionException("Service not accessible, please check it is available","Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'",statusCode,true);
                        }
                        if(statusCode==403) {
                            mLogger.log(Level.SEVERE,"Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'");
                            throw new ConnectionException("Access to service denied, please check it is available to you","Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'",statusCode,true);
                        }
                        mLogger.log(Level.SEVERE,"Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'");
                        throw new ConnectionException("Service not accessible, please check it is available to you","Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'",statusCode,true);
                }
                 if(statusCode>=500) {
                        //server side failure

                        if(statusCode==500) {
                            mLogger.log(Level.WARNING,"Service has suffered an internal failure, response message is '" + responseMsg + "', response code is '" + statusCode + "'");
                            //we don't get upset about 500 because this is used by some web service servers when throwing a soap fault, more interested
                            //in the the fault
                            //it could be that the response is an HTML page or SOAP message, best way to find out
                            //is to check the http headers
                            String contentType = postHttp.getResponseHeader("Content-Type").toExternalForm();
                            if(contentType!=null && contentType.equals("text/xml")) {
                              //not a parsable as a SOAP message so throw a wobbly
                              mLogger.log(Level.SEVERE,"Service has suffered an internal failure that has generated an HTML fault page");
                              throw new ConnectionException("Service has suffered an internal failure that has generated an HTML fault page, please check it is available to you","Service has suffered an internal failure that has generated an HTML fault page",statusCode,true);
                            }
                        }
                        else if(statusCode==501) {
                            mLogger.log(Level.SEVERE,"Service does not understand the request, check the endpoint is correct, response message is '" + responseMsg + "', response code is '" + statusCode + "'");
                            throw new ConnectionException("Service does not understand the request, check the endpoint is correct","Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'",statusCode,true);
                        }
                        else if(statusCode==503) {
                            mLogger.log(Level.SEVERE,"Service is unable to fulfil request, response message is '" + responseMsg + "', response code is '" + statusCode + "'");
                            throw new ConnectionException("Service is unable to fulfil request, please try again later","Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'",statusCode,true);
                        }
                        else {
                          mLogger.log(Level.SEVERE,"Service not accessible, response message is '" + responseMsg + "', response code is '" + statusCode + "'");
                          throw new ConnectionException("Service not accessible, please check it is available to you","Client-side HTTP error occured, response message is '" + responseMsg + "', response code is '" + statusCode + "'",statusCode,true);
                        }
                    }


                //pull off the response headers, headers can have multiple values, e.g. cookies
                Header[] headers = postHttp.getResponseHeaders();
                ConnectionProperties respProps = new ConnectionProperties();
                for(int i=0;i<headers.length;i++) {
                    //add a property for each header element.
                    //this is slightly wrong I think since
                    //some HTTP headers (such as the set-cookie header) have values that can be decomposed into multiple elements
                    //see the javadoc for HeaderElement
                    HeaderElement[] hes = headers[i].getValues();
                    for(int j=0;j<hes.length;j++) {
                        respProps.addProperty(headers[i].getName(),hes[j].getName());
                    }
                }
                if(openConnections==null)
                    openConnections = new ArrayList();
                openConnections.add(postHttp);
                mLogger.log(Level.INFO,"Completed invocation on service at URI '" + postHttp.getHostConfiguration().getHost());

                return new ConnectionResponse(postHttp.getResponseBodyAsStream(),respProps);
            }
            else {
                mLogger.log(Level.SEVERE,"Trying to send an unsupported message of class name " + message.getClass().getName());
                throw new ConnectionException("Trying to send an unsupported message, logs will contain more information");
            }
        }
        catch(javax.xml.soap.SOAPException ex) {

          if(postHttp!=null)
               postHttp.releaseConnection();
           mLogger.log(Level.SEVERE,"Messaging failure when sending to '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
          mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
           throw new ConnectionException("Messaging failure when sending to '" + mUrl.toExternalForm() +"'");
        }
        catch(HttpException ex) {
          //signifies an underlying protocol problem that cannot be recovered from
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from, logs may hold further information.",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ProtocolException("Network protocol failure when getting '" + mUrl.toExternalForm() +"'","Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from");
        }
        catch(java.net.ConnectException ex) {
          //signifies that we could not even obtain a socket connection implying that the host / port combination is
            //not exposed
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Unable to connect to '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from, logs may hold further information.",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ConnectException("Unable to connect to '" + mUrl.toExternalForm() +"',this usually means the service is unavailable","Unable to connect to '" + mUrl.toExternalForm() +"',this usually means the service is unavailable");
        }
        catch(java.net.SocketException ex) {
          //signifies an underlying protocol problem that cannot be recovered from
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from, logs may hold further information.",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ProtocolException("Network protocol failure when getting '" + mUrl.toExternalForm() +"'","Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from");
        }
        catch(java.net.SocketTimeoutException ex) {
          //signifies that a socket has timed out in either reading or writing, retrying may actually succeed
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network timeout when connected to '" + mUrl.toExternalForm() +"', is possible to try again, logs may hold further information.",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ConnectionInterruptedException("Network timeout when connected to '" + mUrl.toExternalForm() +"', is possible to try again","Network timeout when connected to '" + mUrl.toExternalForm() +"', is possible to try again");
        }
        catch(java.io.InterruptedIOException ex) {
            //signifies that a thread was interrupted. This might be recoverable if try again
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network connection interrupted when connected to '" + mUrl.toExternalForm() +"', possible to try again",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ConnectionInterruptedException("Network connection interrupted when connected to '" + mUrl.toExternalForm() +"', possible to try again","Network connection interrupted when connected to '" + mUrl.toExternalForm() +"', possible to try again");
        }
        catch(java.io.IOException ex) {
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network connection failure when getting '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new ConnectionException("Network connection failure when getting '" + mUrl.toExternalForm() +"'");
        }
        catch(MessagingException ex) {
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Messaging failure when sending to '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new ConnectionException("Messaging failure when sending to '" + mUrl.toExternalForm() +"'");
        }
        catch(ConfigurationException ex) {
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network connection failure when getting '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            mLogger.log(Level.INFO,"Packet length for network transmission is '" + packetLength +"' bytes");
            throw new ConnectionException("Network connection failure when getting '" + mUrl.toExternalForm() +"'");
        }
        catch(Exception ex) {
            if(postHttp!=null)
                postHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Unrecognised failure when getting '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            throw new ConnectionException("Unrecognised connection failure when getting '" + mUrl.toExternalForm() +"'");
        }

    }

    /**
    * Retrieve the inputstream for the resource pointed to by this connection
    * @return inputstream to resource
    * @throws ConnectionException
     */
    public InputStream getResourceInputStream() throws ConnectionException {
        GetMethod getHttp = null;
        try{
            //Use the commons HTTPClient classes
            HttpClient httpClient = new HttpClient();
            getHttp = new GetMethod(mUrl.toExternalForm());
            if(mUseProxy) {
                getHttp.getHostConfiguration().setProxy(mConfig.getConfigurationValue(HTTPS_PROXY_HOST_CONFIG_NAME),Integer.parseInt(mConfig.getConfigurationValue(HTTPS_PROXY_PORT_CONFIG_NAME)));
            }
            int statusCode = -1;
            int attempts = 0;
            while(attempts<3 && statusCode==-1) {
                try{
                    attempts++;
                    statusCode = httpClient.executeMethod(getHttp);
                }
                catch (HttpRecoverableException ex) {
                    mLogger.log(Level.WARNING,"A recoverable http get exception occurred,retrying.",ex);
                }
            }
            if(statusCode==-1) {
                //ran out of retries and still failed
                mLogger.log(Level.SEVERE,"HTTP connection failure described as '" + getHttp.getStatusText() + "'");
                throw new ConnectionException("HTTP connection failure described as '" + getHttp.getStatusText() + "'");
            }
            if(statusCode>=400) {
              mLogger.log(Level.SEVERE,"Could not obtain the resource at '" + mUrl.toExternalForm() + "', please check the resource is available");
                throw new ConnectionException("Could not obtain the resource at '" + mUrl.toExternalForm() + "', please check the resource is available");
            }
            //might be an issue over releasing this connection. Need dedicated release method on connection.
            InputStream inStream = getHttp.getResponseBodyAsStream();
            if(openConnections==null)
                openConnections = new ArrayList();
            openConnections.add(getHttp);
            return inStream;

        }
        catch(HttpException ex) {
            //signifies an underlying protocol problem that cannot be recovered from
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from, logs may hold further information.",ex);
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ProtocolException("Network protocol failure when getting '" + mUrl.toExternalForm() +"'","Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from");
        }
        catch(java.net.ConnectException ex) {
            //signifies that we could not even obtain a socket connection implying that the host / port combination is
            //not exposed
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Unable to connect to '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from, logs may hold further information.",ex);
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ConnectException("Unable to connect to '" + mUrl.toExternalForm() +"',this usually means the service is unavailable","Unable to connect to '" + mUrl.toExternalForm() +"',this usually means the service is unavailable");
        }
        catch(java.net.SocketException ex) {
            //signifies an underlying protocol problem that cannot be recovered from
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from, logs may hold further information.",ex);
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ProtocolException("Network protocol failure when getting '" + mUrl.toExternalForm() +"'","Underlying transport protocol failure when getting '" + mUrl.toExternalForm() +"', unlikely that this can be recovered from");
        }
        catch(java.net.SocketTimeoutException ex) {
            //signifies that a socket has timed out in either reading or writing, retrying may actually succeed
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network timeout when connected to '" + mUrl.toExternalForm() +"', is possible to try again, logs may hold further information.",ex);
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ConnectionInterruptedException("Network timeout when connected to '" + mUrl.toExternalForm() +"', is possible to try again","Network timeout when connected to '" + mUrl.toExternalForm() +"', is possible to try again");
        }
        catch(java.io.InterruptedIOException ex) {
            //signifies that a thread was interrupted. This might be recoverable if try again
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network connection interrupted when connected to '" + mUrl.toExternalForm() +"', possible to try again",ex);
            throw new uk.ac.soton.itinnovation.gemss.transportmessaging.connection.ConnectionInterruptedException("Network connection interrupted when connected to '" + mUrl.toExternalForm() +"', possible to try again","Network connection interrupted when connected to '" + mUrl.toExternalForm() +"', possible to try again");
        }
        catch(java.io.IOException ex) {
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network connection failure when getting '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            throw new ConnectionException("Network connection failure when getting '" + mUrl.toExternalForm() +"'");
        }
        catch(ConfigurationException ex) {
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Network connection failure when getting '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            throw new ConnectionException("Network connection failure when getting '" + mUrl.toExternalForm() +"'");
        }
        catch(Exception ex) {
            if(getHttp!=null)
                getHttp.releaseConnection();
            mLogger.log(Level.SEVERE,"Unrecognised failure when getting '" + mUrl.toExternalForm() +"', logs may hold further information.",ex);
            throw new ConnectionException("Unrecognised failure when getting '" + mUrl.toExternalForm() +"'");
        }

    }

    /**
     * Release the connection and any associated resources.
     */
    public void releaseConnection() {
        try{
            Iterator iterator = openConnections.iterator();
            while(iterator.hasNext()) {
                HttpMethod method = (HttpMethod) iterator.next();
                method.releaseConnection();
            }
        }
        catch(Exception ex) {
            //tried our best
        }
    }


}

