// 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 com.cloud.hypervisor.vmware.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.xml.ws.soap.SOAPFaultException;

import org.apache.cloudstack.utils.security.SSLUtils;
import org.apache.cloudstack.utils.security.SecureSSLSocketFactory;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import com.cloud.hypervisor.vmware.mo.DatacenterMO;
import com.cloud.hypervisor.vmware.mo.DatastoreFile;
import com.cloud.utils.ActionDelegate;
import com.cloud.utils.StringUtils;
import com.vmware.pbm.PbmPortType;
import com.vmware.pbm.PbmServiceInstanceContent;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.ObjectContent;
import com.vmware.vim25.ObjectSpec;
import com.vmware.vim25.PropertyFilterSpec;
import com.vmware.vim25.PropertySpec;
import com.vmware.vim25.ServiceContent;
import com.vmware.vim25.TaskInfo;
import com.vmware.vim25.TraversalSpec;
import com.vmware.vim25.VimPortType;

public class VmwareContext {
    protected static Logger LOGGER = LogManager.getLogger(VmwareContext.class);

    private static final int MAX_CONNECT_RETRY = 5;
    private static final int CONNECT_RETRY_INTERVAL = 1000;

    private static final int ChunkSize = 1 * 1024 * 1024;        // 1M

    private final VmwareClient _vimClient;
    private final String _serverAddress;

    private final Map<String, Object> _stockMap = new HashMap<String, Object>();

    private VmwareContextPool _pool;
    private String _poolKey;

    private static volatile int s_outstandingCount = 0;

    static {
        try {
            javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
            javax.net.ssl.TrustManager tm = new TrustAllManager();
            trustAllCerts[0] = tm;
            javax.net.ssl.SSLContext sc = SSLUtils.getSSLContext();
            sc.init(null, trustAllCerts, null);
            javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(new SecureSSLSocketFactory(sc));

            HostnameVerifier hv = new HostnameVerifier() {
                @Override
                public boolean verify(String urlHostName, SSLSession session) {
                    return true;
                }
            };
            HttpsURLConnection.setDefaultHostnameVerifier(hv);
        } catch (Exception e) {
            LOGGER.error("Unexpected exception ", e);
        }
    }

    public VmwareContext(VmwareClient client, String address) {
        assert (client != null) : "Invalid parameter in constructing VmwareContext object";

        _vimClient = client;
        _serverAddress = address;

        registerOutstandingContext();
        if (LOGGER.isInfoEnabled())
            LOGGER.info("New VmwareContext object, current outstanding count: " + getOutstandingContextCount());
    }

    public boolean validate() {
        return _vimClient.validate();
    }

    public void registerStockObject(String name, Object obj) {
        synchronized (_stockMap) {
            _stockMap.put(name, obj);
        }
    }

    public void uregisterStockObject(String name) {
        synchronized (_stockMap) {
            _stockMap.remove(name);
        }
    }

    public void clearStockObjects() {
        synchronized (_stockMap) {
            _stockMap.clear();
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T getStockObject(String name) {
        synchronized (_stockMap) {
            return (T)_stockMap.get(name);
        }
    }

    public String getServerAddress() {
        return _serverAddress;
    }

    public VimPortType getService() {
        return _vimClient.getService();
    }

    public ServiceContent getServiceContent() {
        return _vimClient.getServiceContent();
    }

    public PbmPortType getPbmService() {
        return _vimClient.getPbmService();
    }

    public PbmServiceInstanceContent getPbmServiceContent() {
        return _vimClient.getPbmServiceContent();
    }

    public ManagedObjectReference getPropertyCollector() {
        return _vimClient.getPropCol();
    }

    public ManagedObjectReference getRootFolder() {
        return _vimClient.getRootFolder();
    }

    public VmwareClient getVimClient() {
        return _vimClient;
    }

    public void setPoolInfo(VmwareContextPool pool, String poolKey) {
        _pool = pool;
        _poolKey = poolKey;
    }

    public VmwareContextPool getPool() {
        return _pool;
    }

    public String getPoolKey() {
        return _poolKey;
    }

    public void idleCheck() throws Exception {
        getRootFolder();
    }

    public static int getOutstandingContextCount() {
        return s_outstandingCount;
    }

    public static void registerOutstandingContext() {
        s_outstandingCount++;
    }

    public static void unregisterOutstandingContext() {
        s_outstandingCount--;
    }

    public ManagedObjectReference getHostMorByPath(String inventoryPath) throws Exception {
        assert (inventoryPath != null);

        String[] tokens;
        if (inventoryPath.startsWith("/"))
            tokens = inventoryPath.substring(1).split("/");
        else
            tokens = inventoryPath.split("/");

        ManagedObjectReference mor = getRootFolder();
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
            List<ObjectContent> ocs;
            PropertySpec pSpec = null;
            ObjectSpec oSpec = null;
            if (mor.getType().equalsIgnoreCase("Datacenter")) {
                pSpec = new PropertySpec();
                pSpec.setAll(false);
                pSpec.setType("ManagedEntity");
                pSpec.getPathSet().add("name");

                TraversalSpec dcHostFolderTraversal = new TraversalSpec();
                dcHostFolderTraversal.setType("Datacenter");
                dcHostFolderTraversal.setPath("hostFolder");
                dcHostFolderTraversal.setName("dcHostFolderTraversal");

                oSpec = new ObjectSpec();
                oSpec.setObj(mor);
                oSpec.setSkip(Boolean.TRUE);
                oSpec.getSelectSet().add(dcHostFolderTraversal);

            } else if (mor.getType().equalsIgnoreCase("Folder")) {
                pSpec = new PropertySpec();
                pSpec.setAll(false);
                pSpec.setType("ManagedEntity");
                pSpec.getPathSet().add("name");

                TraversalSpec folderChildrenTraversal = new TraversalSpec();
                folderChildrenTraversal.setType("Folder");
                folderChildrenTraversal.setPath("childEntity");
                folderChildrenTraversal.setName("folderChildrenTraversal");

                oSpec = new ObjectSpec();
                oSpec.setObj(mor);
                oSpec.setSkip(Boolean.TRUE);
                oSpec.getSelectSet().add(folderChildrenTraversal);

            } else if (mor.getType().equalsIgnoreCase("ClusterComputeResource")) {
                pSpec = new PropertySpec();
                pSpec.setType("ManagedEntity");
                pSpec.getPathSet().add("name");

                TraversalSpec clusterHostTraversal = new TraversalSpec();
                clusterHostTraversal.setType("ClusterComputeResource");
                clusterHostTraversal.setPath("host");
                clusterHostTraversal.setName("folderChildrenTraversal");

                oSpec = new ObjectSpec();
                oSpec.setObj(mor);
                oSpec.setSkip(Boolean.TRUE);
                oSpec.getSelectSet().add(clusterHostTraversal);

            } else {
                LOGGER.error("Invalid inventory path, path element can only be datacenter and folder");
                return null;
            }

            PropertyFilterSpec pfSpec = new PropertyFilterSpec();
            pfSpec.getPropSet().add(pSpec);
            pfSpec.getObjectSet().add(oSpec);
            List<PropertyFilterSpec> pfSpecArr = new ArrayList<PropertyFilterSpec>();
            pfSpecArr.add(pfSpec);
            ocs = getService().retrieveProperties(getPropertyCollector(), pfSpecArr);

            if (ocs != null && ocs.size() > 0) {
                boolean found = false;
                for (ObjectContent oc : ocs) {
                    String name = oc.getPropSet().get(0).getVal().toString();
                    if (name.equalsIgnoreCase(token) || name.equalsIgnoreCase("host")) {
                        mor = oc.getObj();
                        found = true;
                        if (name.equalsIgnoreCase("host"))
                            i--;
                        break;
                    }
                }
                if (!found) {
                    LOGGER.error("Path element points to an un-existing inventory entity");
                    return null;
                }
            } else {
                LOGGER.error("Path element points to an un-existing inventory entity");
                return null;
            }
        }
        return mor;
    }

    // path in format of <datacenter name>/<datastore name>
    public ManagedObjectReference getDatastoreMorByPath(String inventoryPath) throws Exception {
        assert (inventoryPath != null);

        String[] tokens;
        if (inventoryPath.startsWith("/"))
            tokens = inventoryPath.substring(1).split("/");
        else
            tokens = inventoryPath.split("/");

        if (tokens == null || tokens.length != 2) {
            LOGGER.error("Invalid datastore inventory path. path: " + inventoryPath);
            return null;
        }

        DatacenterMO dcMo = new DatacenterMO(this, tokens[0]);
        if (dcMo.getMor() == null) {
            LOGGER.error("Unable to locate the datacenter specified in path: " + inventoryPath);
            return null;
        }

        return dcMo.findDatastore(tokens[1]);
    }

    // path in format of <datacenter name>/<datastore name>
    public String getDatastoreNameFromPath(String inventoryPath) throws Exception {
        assert (inventoryPath != null);

        String[] tokens;
        if (inventoryPath.startsWith("/"))
            tokens = inventoryPath.substring(1).split("/");
        else
            tokens = inventoryPath.split("/");

        if (tokens == null || tokens.length != 2) {
            LOGGER.error("Invalid datastore inventory path. path: " + inventoryPath);
            return null;
        }

        return tokens[1];
    }

    public void waitForTaskProgressDone(ManagedObjectReference morTask) throws Exception {
        while (true) {
            TaskInfo tinfo = (TaskInfo)_vimClient.getDynamicProperty(morTask, "info");
            Integer progress = tinfo.getProgress();
            if (progress == null)
                break;

            if (progress.intValue() >= 100)
                break;

            Thread.sleep(1000);
        }
    }

    public void getFile(String urlString, String localFileFullName) throws Exception {
        HttpURLConnection conn = getHTTPConnection(urlString);

        InputStream in = conn.getInputStream();
        OutputStream out = new FileOutputStream(new File(localFileFullName));
        byte[] buf = new byte[ChunkSize];
        int len = 0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
    }

    public void uploadFile(String httpMethod, String urlString, String localFileName) throws Exception {
        HttpURLConnection conn = getRawHTTPConnection(urlString);

        conn.setDoOutput(true);
        conn.setUseCaches(false);

        conn.setChunkedStreamingMode(ChunkSize);
        conn.setRequestMethod(httpMethod);
        conn.setRequestProperty("Connection", "Keep-Alive");
        String contentType = "application/octet-stream";
        conn.setRequestProperty("content-type", contentType);
        conn.setRequestProperty("content-length", Long.toString(new File(localFileName).length()));
        connectWithRetry(conn);
        OutputStream out = null;
        InputStream in = null;
        BufferedReader br = null;

        try {
            out = conn.getOutputStream();
            in = new FileInputStream(localFileName);
            byte[] buf = new byte[ChunkSize];
            int len = 0;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            out.flush();
        } finally {
            if (in != null)
                in.close();

            if (out != null)
                out.close();

            if (br != null)
                br.close();
            conn.disconnect();
        }
    }

    private Charset getCharSetFromConnection(HttpURLConnection conn) {
        String charsetName = conn.getContentEncoding();
        Charset charset;
        try {
            charset = Charset.forName(charsetName);
        } catch (IllegalArgumentException e) {
            LOGGER.warn("Illegal/unsupported/null charset name from connection. charsetname from connection is " + charsetName);
            charset = StringUtils.getPreferredCharset();
        }
        return charset;
    }

    public void uploadVmdkFile(String httpMethod, String urlString, String localFileName, long totalBytesUpdated, ActionDelegate<Long> progressUpdater) throws Exception {

        HttpURLConnection conn = getRawHTTPConnection(urlString);

        conn.setDoOutput(true);
        conn.setUseCaches(false);

        conn.setChunkedStreamingMode(ChunkSize);
        conn.setRequestMethod(httpMethod);
        if (urlString.endsWith(".iso")) {
            conn.setRequestProperty("Overwrite", "t");
        }
        conn.setRequestProperty("Connection", "Keep-Alive");
        String contentType = urlString.endsWith(".iso") ?
                "application/octet-stream" :
                "application/x-vnd.vmware-streamVmdk";
        conn.setRequestProperty("content-type", contentType);
        conn.setRequestProperty("content-length", Long.toString(new File(localFileName).length()));
        connectWithRetry(conn);

        BufferedOutputStream bos = null;
        BufferedInputStream is = null;
        try {
            bos = new BufferedOutputStream(conn.getOutputStream());
            is = new BufferedInputStream(new FileInputStream(localFileName));
            int bufferSize = ChunkSize;
            byte[] buffer = new byte[bufferSize];
            while (true) {
                int bytesRead = is.read(buffer, 0, bufferSize);
                if (bytesRead == -1) {
                    break;
                }
                bos.write(buffer, 0, bytesRead);
                totalBytesUpdated += bytesRead;
                bos.flush();
                if (progressUpdater != null)
                    progressUpdater.action(new Long(totalBytesUpdated));
            }
            bos.flush();
        } finally {
            if (is != null)
                is.close();
            if (bos != null)
                bos.close();

            conn.disconnect();
        }
    }

    public long downloadVmdkFile(String urlString, String localFileName, AtomicLong totalBytesDownloaded, ActionDelegate<Long> progressUpdater) throws Exception {
        HttpURLConnection conn = getRawHTTPConnection(urlString);

        String cookie = _vimClient.getServiceCookie();
        if (cookie == null) {
            LOGGER.error("No cookie is found in vwware web service request context!");
            throw new Exception("No cookie is found in vmware web service request context!");
        }
        conn.addRequestProperty("Cookie", cookie);
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setAllowUserInteraction(true);
        connectWithRetry(conn);

        long bytesWritten = 0;
        InputStream in = null;
        OutputStream out = null;
        try {
            in = conn.getInputStream();
            out = new FileOutputStream(new File(localFileName));

            byte[] buf = new byte[ChunkSize];
            int len = 0;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
                bytesWritten += len;
                totalBytesDownloaded.addAndGet(len);

                if (progressUpdater != null)
                    progressUpdater.action(new Long(totalBytesDownloaded.get()));
            }
        } finally {
            if (in != null)
                in.close();
            if (out != null)
                out.close();

            conn.disconnect();
        }
        return bytesWritten;
    }

    public byte[] getResourceContent(String urlString) throws Exception {
        HttpURLConnection conn = getHTTPConnection(urlString);
        InputStream in = conn.getInputStream();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buf = new byte[ChunkSize];
        int len = 0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
        return out.toByteArray();
    }

    public void uploadResourceContent(String urlString, byte[] content) throws Exception {
        // vSphere does not support POST
        HttpURLConnection conn = getHTTPConnection(urlString, "PUT");

        OutputStream out = conn.getOutputStream();
        out.write(content);
        out.flush();

        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), getCharSetFromConnection(conn)));
        String line;
        while ((in.ready()) && (line = in.readLine()) != null) {
            if (LOGGER.isTraceEnabled())
                LOGGER.trace("Upload " + urlString + " response: " + line);
        }
        out.close();
        in.close();
    }

    /*
     * Sample content returned by query a datastore directory
     *
     * Url for the query
     *     https://vsphere-1.lab.vmops.com/folder/Fedora-clone-test?dcPath=cupertino&dsName=NFS+datastore
     *
     * Returned content from vSphere
     *
        <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
          <head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
            <title>Index of Fedora-clone-test on datastore NFS datastore in datacenter cupertino</title></head>
          <body>
        <h1>Index of Fedora-clone-test on datastore NFS datastore in datacenter cupertino</h1>
        <table>
            <tr><th>Name</th><th>Last modified</th><th>Size</th></tr><tr><th colspan="3"><hr></th></tr>
            <tr><td><a href="/folder?dcPath=cupertino&amp;dsName=NFS%20datastore">Parent Directory</a></td><td>&nbsp;</td><td align="right">  - </td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2da2013465%2ehlog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test-a2013465.hlog</a></td><td align="right">15-Aug-2010 00:13</td><td align="right">1</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2da2013465%2evswp?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test-a2013465.vswp</a></td><td align="right">14-Aug-2010 23:01</td><td align="right">402653184</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2dflat%2evmdk?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test-flat.vmdk</a></td><td align="right">26-Aug-2010 18:43</td><td align="right">17179869184</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2envram?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test.nvram</a></td><td align="right">15-Aug-2010 00:13</td><td align="right">8684</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2evmdk?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test.vmdk</a></td><td align="right">15-Aug-2010 00:13</td><td align="right">479</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2evmsd?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test.vmsd</a></td><td align="right">14-Aug-2010 16:59</td><td align="right">0</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2evmx?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test.vmx</a></td><td align="right">15-Aug-2010 00:13</td><td align="right">3500</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/Fedora%2dclone%2dtest%2evmxf?dcPath=cupertino&amp;dsName=NFS%20datastore">
            Fedora-clone-test.vmxf</a></td><td align="right">15-Aug-2010 00:13</td><td align="right">272</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/test%2etxt?dcPath=cupertino&amp;dsName=NFS%20datastore">test.txt</a></td>
            <td align="right">24-Aug-2010 01:03</td><td align="right">12</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/vmware%2d2%2elog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            vmware-2.log</a></td><td align="right">14-Aug-2010 16:51</td><td align="right">80782</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/vmware%2d3%2elog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            vmware-3.log</a></td><td align="right">14-Aug-2010 19:07</td><td align="right">58573</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/vmware%2d4%2elog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            vmware-4.log</a></td><td align="right">14-Aug-2010 23:00</td><td align="right">49751</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/vmware%2d5%2elog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            vmware-5.log</a></td><td align="right">15-Aug-2010 00:04</td><td align="right">64024</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/vmware%2d6%2elog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            vmware-6.log</a></td><td align="right">15-Aug-2010 00:11</td><td align="right">59742</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/vmware%2d7%2elog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            vmware-7.log</a></td><td align="right">15-Aug-2010 00:13</td><td align="right">59859</td></tr>
            <tr><td><a href="/folder/Fedora%2dclone%2dtest/vmware%2elog?dcPath=cupertino&amp;dsName=NFS%20datastore">
            vmware.log</a></td><td align="right">15-Aug-2010 00:23</td><td align="right">47157</td></tr>
            <tr><th colspan="5"><hr></th></tr>
        </table>
          </body>
        </html>
     */
    public String[] listDatastoreDirContent(String urlString) throws Exception {
        List<String> fileList = new ArrayList<String>();
        String content = new String(getResourceContent(urlString),"UTF-8");
        String marker = "</a></td><td ";
        int parsePos = -1;
        do {
            parsePos = content.indexOf(marker, parsePos < 0 ? 0 : parsePos);
            if (parsePos > 0) {
                int beginPos = content.lastIndexOf('>', parsePos - 1);
                if (beginPos < 0)
                    beginPos = 0;

                fileList.add((content.substring(beginPos + 1, parsePos)));
                parsePos += marker.length();
            } else {
                break;
            }
        } while (parsePos > 0);
        return fileList.toArray(new String[0]);
    }

    public String composeDatastoreBrowseUrl(String dcName, String fullPath) {
        DatastoreFile dsFile = new DatastoreFile(fullPath);
        return composeDatastoreBrowseUrl(dcName, dsFile.getDatastoreName(), dsFile.getRelativePath());
    }

    public String composeDatastoreBrowseUrl(String dcName, String datastoreName, String relativePath) {
        assert (relativePath != null);
        assert (datastoreName != null);

        StringBuffer sb = new StringBuffer();
        sb.append("https://");
        sb.append(_serverAddress);
        sb.append("/folder/");
        try {
            sb.append(URLEncoder.encode(relativePath, "UTF-8"));
            sb.append("?dcPath=").append(URLEncoder.encode(dcName, "UTF-8"));
            sb.append("&dsName=").append(URLEncoder.encode(datastoreName, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            LOGGER.error(String.format("Unable to encode URL. relativePath : %s, dcPath : %s, dsName : %s", relativePath, dcName, datastoreName), e);
        }
        return sb.toString();
    }

    public HttpURLConnection getHTTPConnection(String urlString) throws Exception {
        return getHTTPConnection(urlString, "GET");
    }

    public HttpURLConnection getHTTPConnection(String urlString, String httpMethod) throws Exception {
        String cookie = _vimClient.getServiceCookie();
        if (cookie == null) {
            LOGGER.error("No cookie is found in vmware web service request context!");
            throw new Exception("No cookie is found in vmware web service request context!");
        }
        URL url = new URL(urlString);
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();

        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setAllowUserInteraction(true);
        conn.addRequestProperty("Cookie", cookie);
        conn.setRequestMethod(httpMethod);
        connectWithRetry(conn);
        return conn;
    }

    public HttpURLConnection getRawHTTPConnection(String urlString) throws Exception {
        URL url = new URL(urlString);
        return (HttpURLConnection)url.openConnection();
    }

    private static void connectWithRetry(HttpURLConnection conn) throws Exception {
        boolean connected = false;
        for (int i = 0; i < MAX_CONNECT_RETRY && !connected; i++) {
            try {
                conn.connect();
                connected = true;
                LOGGER.info("Connected, conn: " + conn.toString() + ", retry: " + i);
            } catch (Exception e) {
                LOGGER.warn("Unable to connect, conn: " + conn.toString() + ", message: " + e.toString() + ", retry: " + i);

                try {
                    Thread.sleep(CONNECT_RETRY_INTERVAL);
                } catch (InterruptedException ex) {
                    LOGGER.debug("[ignored] interrupted while connecting.");
                }
            }
        }

        if (!connected)
            throw new Exception("Unable to connect to " + conn.toString());
    }

    public void close() {
        clearStockObjects();
        try {
            LOGGER.info("Disconnecting VMware session");
            _vimClient.disconnect();
        } catch(SOAPFaultException sfe) {
            LOGGER.debug("Tried to disconnect a session that is no longer valid");
        } catch (Exception e) {
            LOGGER.warn("Unexpected exception: ", e);
        } finally {
            if (_pool != null) {
                _pool.unregisterContext(this);
            }
            unregisterOutstandingContext();
        }
    }

    public static class TrustAllManager implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
            return true;
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {
            return;
        }

        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {
            return;
        }
    }
}
