// 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.network.cisco;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.cloudstack.utils.security.ParserUtils;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.cloud.utils.exception.ExecutionException;
import com.cloud.utils.script.Script;

public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection {

    private final String _ip;
    private final String _username;
    private final String _password;
    private String _cookie;

    protected static Logger LOGGER = LogManager.getLogger(CiscoVnmcConnectionImpl.class);

    private enum VnmcXml {
        LOGIN("login.xml", "mgmt-controller"),

        CREATE_TENANT("create-tenant.xml", "service-reg"),
        DELETE_TENANT("delete-tenant.xml", "service-reg"),
        CREATE_VDC("create-vdc.xml", "service-reg"),
        DELETE_VDC("delete-vdc.xml", "service-reg"),

        CREATE_EDGE_DEVICE_PROFILE("create-edge-device-profile.xml", "policy-mgr"),
        CREATE_EDGE_ROUTE_POLICY("create-edge-device-route-policy.xml", "policy-mgr"),
        CREATE_EDGE_ROUTE("create-edge-device-route.xml", "policy-mgr"),
        RESOLVE_EDGE_ROUTE_POLICY("associate-route-policy.xml", "policy-mgr"),

        CREATE_DHCP_POLICY("create-dhcp-policy.xml", "policy-mgr"),
        RESOLVE_EDGE_DHCP_POLICY("associate-dhcp-policy.xml", "policy-mgr"),
        RESOLVE_EDGE_DHCP_SERVER_POLICY("associate-dhcp-server.xml", "policy-mgr"),

        CREATE_EDGE_SECURITY_PROFILE("create-edge-security-profile.xml", "policy-mgr"),
        DELETE_EDGE_SECURITY_PROFILE("delete-edge-security-profile.xml", "policy-mgr"),

        CREATE_NAT_POLICY_SET("create-nat-policy-set.xml", "policy-mgr"),
        DELETE_NAT_POLICY_SET("delete-nat-policy-set.xml", "policy-mgr"),
        RESOLVE_NAT_POLICY_SET("associate-nat-policy-set.xml", "policy-mgr"),
        CREATE_NAT_POLICY("create-nat-policy.xml", "policy-mgr"),
        DELETE_NAT_POLICY("delete-nat-policy.xml", "policy-mgr"),
        LIST_NAT_POLICIES("list-nat-policies.xml", "policy-mgr"),
        CREATE_NAT_POLICY_REF("create-nat-policy-ref.xml", "policy-mgr"),
        CREATE_PORT_POOL("create-port-pool.xml", "policy-mgr"),
        CREATE_IP_POOL("create-ip-pool.xml", "policy-mgr"),

        CREATE_PF_RULE("create-pf-rule.xml", "policy-mgr"),
        CREATE_ACL_RULE_FOR_PF("create-acl-rule-for-pf.xml", "policy-mgr"),
        CREATE_DNAT_RULE("create-dnat-rule.xml", "policy-mgr"),
        CREATE_ACL_RULE_FOR_DNAT("create-acl-rule-for-dnat.xml", "policy-mgr"),
        CREATE_SOURCE_NAT_RULE("create-source-nat-rule.xml", "policy-mgr"),

        CREATE_ACL_POLICY_SET("create-acl-policy-set.xml", "policy-mgr"),
        DELETE_ACL_POLICY_SET("delete-acl-policy-set.xml", "policy-mgr"),
        RESOLVE_ACL_POLICY_SET("associate-acl-policy-set.xml", "policy-mgr"),
        CREATE_ACL_POLICY("create-acl-policy.xml", "policy-mgr"),
        DELETE_ACL_POLICY("delete-acl-policy.xml", "policy-mgr"),
        LIST_ACL_POLICIES("list-acl-policies.xml", "policy-mgr"),
        CREATE_ACL_POLICY_REF("create-acl-policy-ref.xml", "policy-mgr"),
        CREATE_INGRESS_ACL_RULE("create-ingress-acl-rule.xml", "policy-mgr"),
        CREATE_EGRESS_ACL_RULE("create-egress-acl-rule.xml", "policy-mgr"),
        CREATE_GENERIC_INGRESS_ACL_RULE("create-generic-ingress-acl-rule.xml", "policy-mgr"),
        CREATE_GENERIC_EGRESS_ACL_RULE("create-generic-egress-acl-rule.xml", "policy-mgr"),
        CREATE_GENERIC_EGRESS_ACL_NO_PROTOCOL_RULE("create-generic-egress-acl-no-protocol-rule.xml", "policy-mgr"),

        DELETE_RULE("delete-rule.xml", "policy-mgr"),

        LIST_CHILDREN("list-children.xml", "policy-mgr"),

        CREATE_EDGE_FIREWALL("create-edge-firewall.xml", "resource-mgr"),
        DELETE_EDGE_FIREWALL("delete-edge-firewall.xml", "resource-mgr"),

        LIST_UNASSOC_ASA1000V("list-unassigned-asa1000v.xml", "resource-mgr"),
        ASSIGN_ASA1000V("assoc-asa1000v.xml", "resource-mgr"),
        UNASSIGN_ASA1000V("disassoc-asa1000v.xml", "resource-mgr");

        private final String scriptsDir = "scripts/network/cisco";
        private String xml;
        private String service;

        private VnmcXml(String filename, String service) {
            xml = getXml(filename);
            this.service = service;
        }

        public String getXml() {
            return xml;
        }

        private String getXml(String filename) {
            try {
                String xmlFilePath = Script.findScript(scriptsDir, filename);

                if (xmlFilePath == null) {
                    throw new Exception("Failed to find Cisco VNMC XML file: " + filename);
                }

                InputStreamReader fr = new InputStreamReader(new FileInputStream(xmlFilePath),"UTF-8");
                BufferedReader br = new BufferedReader(fr);

                String xml = "";
                String line;
                while ((line = br.readLine()) != null) {
                    //xml += line.replaceAll("\n"," ");
                    xml += line;
                }

                return xml;
            } catch (Exception e) {
                LOGGER.debug(e);
                return null;
            }
        }

        public String getService() {
            return service;
        }
    }

    public CiscoVnmcConnectionImpl(String hostIp, String userName, String password) {
        _ip = hostIp;
        _username = userName;
        _password = password;

    }

    public boolean login() throws ExecutionException {
        String xml = VnmcXml.LOGIN.getXml();
        String service = VnmcXml.LOGIN.getService();
        xml = replaceXmlValue(xml, "username", _username);
        xml = replaceXmlValue(xml, "password", _password);
        String response = sendRequest(service, xml);
        Map<String, String> checked = checkResponse(response, "outCookie", "errorCode", "response");

        if (checked.get("errorCode") != null)
            return false;
        _cookie = checked.get("outCookie");
        if (_cookie == null) {
            return false;
        }
        return true;
    }

    private String getDnForTenant(String tenantName) {
        return "org-root/org-" + tenantName;
    }

    private String getDnForTenantVDC(String tenantName) {
        return getDnForTenant(tenantName) + "/org-VDC-" + tenantName;
    }

    private String getDnForTenantVDCEdgeDeviceProfile(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/edsp-" + getNameForEdgeDeviceServiceProfile(tenantName);
    }

    private String getDnForTenantVDCEdgeSecurityProfile(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/vnep-" + getNameForEdgeDeviceSecurityProfile(tenantName);
    }

    private String getDnForEdgeDeviceRoutingPolicy(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/routing-policy-" + getNameForEdgeDeviceRoutePolicy(tenantName);
        //FIXME: any other construct is unreliable. why?
    }

    private String getDnForDhcpPolicy(String tenantName, String intfName) {
        return getDnForTenantVDCEdgeDeviceProfile(tenantName) + "/dhcp-" + intfName;
    }

    private String getNameForDhcpPolicy(String tenantName) {
        return tenantName + "-Dhcp-Policy";
    }

    private String getNameForDhcpServer(String tenantName) {
        return tenantName + "-Dhcp-Server";
    }

    private String getDnForDhcpServerPolicy(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/dhcp-server-" + getNameForDhcpPolicy(tenantName);
    }

    private String getNameForIpRange() {
        return "iprange";
    }

    private String getDnForDhcpIpRange(String tenantName) {
        return getDnForDhcpServerPolicy(tenantName) + "/ip-range-" + getNameForIpRange();
    }

    private String getNameForDNSService(String tenantName) {
        return tenantName + "-DNS";
    }

    private String getDnForDnsService(String tenantName) {
        return getDnForDhcpServerPolicy(tenantName) + "/dns-svc-" + getNameForDNSService(tenantName);
    }

    private String getDnForDnsServer(String tenantName, String dnsip) {
        return getDnForDnsService(tenantName) + "/dns-" + dnsip;
    }

    private String getNameForTenantVDC(String tenantName) {
        return "VDC-" + tenantName;
    }

    private String getNameForEdgeDeviceServiceProfile(String tenantName) {
        return "EDSP-" + tenantName;
    }

    private String getNameForEdgeDeviceSecurityProfile(String tenantName) {
        return "ESP-" + tenantName;
    }

    private String getNameForEdgeDeviceRoutePolicy(String tenantName) {
        return "EDSP-" + tenantName + "-Routes";
    }

    @Override
    public boolean createTenant(String tenantName) throws ExecutionException {
        String xml = VnmcXml.CREATE_TENANT.getXml();
        String service = VnmcXml.CREATE_TENANT.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "Tenant for account " + tenantName);
        xml = replaceXmlValue(xml, "name", tenantName);
        xml = replaceXmlValue(xml, "dn", getDnForTenant(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenant(String tenantName) throws ExecutionException {
        String xml = VnmcXml.DELETE_TENANT.getXml();
        String service = VnmcXml.DELETE_TENANT.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "name", tenantName);
        xml = replaceXmlValue(xml, "dn", getDnForTenant(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDC(String tenantName) throws ExecutionException {
        String xml = VnmcXml.CREATE_VDC.getXml();
        String service = VnmcXml.CREATE_VDC.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "VDC for Tenant " + tenantName);
        xml = replaceXmlValue(xml, "name", getNameForTenantVDC(tenantName));
        xml = replaceXmlValue(xml, "dn", getDnForTenantVDC(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDC(String tenantName) throws ExecutionException {
        String xml = VnmcXml.DELETE_VDC.getXml();
        String service = VnmcXml.DELETE_VDC.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "name", getNameForTenantVDC(tenantName));
        xml = replaceXmlValue(xml, "dn", getDnForTenantVDC(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCEdgeDeviceProfile(String tenantName) throws ExecutionException {
        String xml = VnmcXml.CREATE_EDGE_DEVICE_PROFILE.getXml();
        String service = VnmcXml.CREATE_EDGE_DEVICE_PROFILE.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "Edge Device Profile for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "name", getNameForEdgeDeviceServiceProfile(tenantName));
        xml = replaceXmlValue(xml, "dn", getDnForTenantVDCEdgeDeviceProfile(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCEdgeStaticRoutePolicy(String tenantName) throws ExecutionException {
        String xml = VnmcXml.CREATE_EDGE_ROUTE_POLICY.getXml();
        String service = VnmcXml.CREATE_EDGE_ROUTE_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "name", getNameForEdgeDeviceRoutePolicy(tenantName));
        xml = replaceXmlValue(xml, "routepolicydn", getDnForEdgeDeviceRoutingPolicy(tenantName));
        xml = replaceXmlValue(xml, "descr", "Routing Policy for Edge Device for Tenant " + tenantName);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCEdgeStaticRoute(String tenantName, String nextHopIp, String destination, String netmask) throws ExecutionException {
        String xml = VnmcXml.CREATE_EDGE_ROUTE.getXml();
        String service = VnmcXml.CREATE_EDGE_ROUTE.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "routepolicydn", getDnForEdgeDeviceRoutingPolicy(tenantName));
        xml = replaceXmlValue(xml, "nexthop", nextHopIp);
        xml = replaceXmlValue(xml, "nexthopintf", getNameForEdgeOutsideIntf(tenantName));
        xml = replaceXmlValue(xml, "destination", destination);
        xml = replaceXmlValue(xml, "netmask", netmask);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean associateTenantVDCEdgeStaticRoutePolicy(String tenantName) throws ExecutionException {
        String xml = VnmcXml.RESOLVE_EDGE_ROUTE_POLICY.getXml();
        String service = VnmcXml.RESOLVE_EDGE_ROUTE_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "name", getNameForEdgeDeviceServiceProfile(tenantName));
        xml = replaceXmlValue(xml, "dn", getDnForTenantVDCEdgeDeviceProfile(tenantName));
        xml = replaceXmlValue(xml, "routepolicyname", getNameForEdgeDeviceRoutePolicy(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean associateTenantVDCEdgeDhcpPolicy(String tenantName, String intfName) throws ExecutionException {
        String xml = VnmcXml.RESOLVE_EDGE_DHCP_POLICY.getXml();
        String service = VnmcXml.RESOLVE_EDGE_DHCP_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "dhcpdn", getDnForDhcpPolicy(tenantName, intfName));
        xml = replaceXmlValue(xml, "insideintf", intfName);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCEdgeDhcpPolicy(String tenantName, String startIp, String endIp, String subnet, String nameServerIp, String domain)
        throws ExecutionException {
        String xml = VnmcXml.CREATE_DHCP_POLICY.getXml();
        String service = VnmcXml.CREATE_DHCP_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "dhcpserverdn", getDnForDhcpServerPolicy(tenantName));
        xml = replaceXmlValue(xml, "dhcpserverdescr", "DHCP server for " + tenantName);
        xml = replaceXmlValue(xml, "dhcpservername", getNameForDhcpPolicy(tenantName));
        xml = replaceXmlValue(xml, "iprangedn", getDnForDhcpIpRange(tenantName));
        xml = replaceXmlValue(xml, "startip", startIp);
        xml = replaceXmlValue(xml, "endip", endIp);
        xml = replaceXmlValue(xml, "subnet", subnet);
        xml = replaceXmlValue(xml, "domain", domain);
        xml = replaceXmlValue(xml, "dnsservicedn", getDnForDnsService(tenantName));
        xml = replaceXmlValue(xml, "dnsservicename", getNameForDNSService(tenantName));
        xml = replaceXmlValue(xml, "nameserverip", nameServerIp);
        xml = replaceXmlValue(xml, "nameserverdn", getDnForDnsServer(tenantName, nameServerIp));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean associateTenantVDCEdgeDhcpServerPolicy(String tenantName, String intfName) throws ExecutionException {
        String xml = VnmcXml.RESOLVE_EDGE_DHCP_SERVER_POLICY.getXml();
        String service = VnmcXml.RESOLVE_EDGE_DHCP_SERVER_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "dhcpdn", getDnForDhcpPolicy(tenantName, intfName));
        xml = replaceXmlValue(xml, "insideintf", intfName);
        xml = replaceXmlValue(xml, "dhcpserverpolicyname", getNameForDhcpServer(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCEdgeSecurityProfile(String tenantName) throws ExecutionException {
        String xml = VnmcXml.CREATE_EDGE_SECURITY_PROFILE.getXml();
        String service = VnmcXml.CREATE_EDGE_SECURITY_PROFILE.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "Edge Security Profile for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "name", getNameForEdgeDeviceSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "espdn", getDnForTenantVDCEdgeSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "egressref", "default-egress");
        xml = replaceXmlValue(xml, "ingressref", "default-ingress"); //FIXME: allows everything

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDCEdgeSecurityProfile(String tenantName) throws ExecutionException {
        String xml = VnmcXml.DELETE_EDGE_SECURITY_PROFILE.getXml();
        String service = VnmcXml.DELETE_EDGE_SECURITY_PROFILE.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "name", getNameForEdgeDeviceSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "espdn", getDnForTenantVDCEdgeSecurityProfile(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private String getNameForSourceNatIpPool(String tenantName) {
        return "SNATIp-" + tenantName;
    }

    private String getDnForSourceNatPool(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/objgrp-" + getNameForSourceNatIpPool(tenantName);
    }

    @Override
    public boolean createTenantVDCSourceNatIpPool(String tenantName, String identifier, String publicIp) throws ExecutionException {
        return createTenantVDCIpPool(getDnForSourceNatPool(tenantName), getNameForSourceNatIpPool(tenantName), "Source NAT ip pool for Tenant VDC " + tenantName,
            publicIp);
    }

    private String getNameForSourceNatPolicy(String tenantName) {
        return "SNAT-Policy-" + tenantName;
    }

    private String getDnForSourceNatPolicy(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/natpol-" + getNameForSourceNatPolicy(tenantName);
    }

    private String getNameForSourceNatRule(String tenantName) {
        return "SNAT-Rule-" + tenantName;
    }

    private String getDnForSourceNatRule(String tenantName) {
        return getDnForSourceNatPolicy(tenantName) + "/rule-" + getNameForSourceNatRule(tenantName);
    }

    private String getNameForNatPolicySet(String tenantName) {
        return "NAT-PolicySet-" + tenantName;
    }

    private String getDnForNatPolicySet(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/natpset-" + getNameForNatPolicySet(tenantName);
    }

    private String getDnForSourceNatPolicyRef(String tenantName) {
        return getDnForNatPolicySet(tenantName) + "/polref-" + getNameForSourceNatPolicy(tenantName);
    }

    @Override
    public boolean createTenantVDCSourceNatRule(String tenantName, String identifier, String startSourceIp, String endSourceIp) throws ExecutionException {

        String xml = VnmcXml.CREATE_SOURCE_NAT_RULE.getXml();
        String service = VnmcXml.CREATE_SOURCE_NAT_RULE.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "natruledn", getDnForSourceNatRule(tenantName));
        xml = replaceXmlValue(xml, "natrulename", getNameForSourceNatRule(tenantName));
        xml = replaceXmlValue(xml, "descr", "Source NAT rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "srcstartip", startSourceIp);
        xml = replaceXmlValue(xml, "srcendip", endSourceIp);
        xml = replaceXmlValue(xml, "ippoolname", getNameForSourceNatIpPool(tenantName));

        long order = 100;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCSourceNatPolicyRef(String tenantName, String identifier) throws ExecutionException {
        return createTenantVDCNatPolicyRef(getDnForSourceNatPolicyRef(tenantName), getNameForSourceNatPolicy(tenantName), tenantName, true);
    }

    @Override
    public boolean createTenantVDCSourceNatPolicy(String tenantName, String identifier) throws ExecutionException {
        return createTenantVDCNatPolicy(getDnForSourceNatPolicy(tenantName), getNameForSourceNatPolicy(tenantName));
    }

    @Override
    public boolean createTenantVDCNatPolicySet(String tenantName) throws ExecutionException {
        String xml = VnmcXml.CREATE_NAT_POLICY_SET.getXml();
        String service = VnmcXml.CREATE_NAT_POLICY_SET.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "NAT policy set for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "natpolicysetname", getNameForNatPolicySet(tenantName));
        xml = replaceXmlValue(xml, "natpolicysetdn", getDnForNatPolicySet(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDCNatPolicySet(String tenantName) throws ExecutionException {
        String xml = VnmcXml.DELETE_NAT_POLICY_SET.getXml();
        String service = VnmcXml.DELETE_NAT_POLICY_SET.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "natpolicysetname", getNameForNatPolicySet(tenantName));
        xml = replaceXmlValue(xml, "natpolicysetdn", getDnForNatPolicySet(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean associateNatPolicySet(String tenantName) throws ExecutionException {
        String xml = VnmcXml.RESOLVE_NAT_POLICY_SET.getXml();
        String service = VnmcXml.RESOLVE_NAT_POLICY_SET.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "Edge Security Profile for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "name", getNameForEdgeDeviceSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "espdn", getDnForTenantVDCEdgeSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "natpolicysetname", getNameForNatPolicySet(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private String getNameForAclPolicySet(String tenantName, boolean ingress) {
        return (ingress ? "Ingress-" : "Egress-") + "ACL-PolicySet-" + tenantName;
    }

    private String getDnForAclPolicySet(String tenantName, boolean ingress) {
        return getDnForTenantVDC(tenantName) + "/pset-" + getNameForAclPolicySet(tenantName, ingress);
    }

    private String getNameForAclPolicy(String tenantName, String identifier) {
        return "ACL-" + tenantName + "-" + identifier;
    }

    private String getDnForAclPolicy(String tenantName, String identifier) {
        return getDnForTenantVDC(tenantName) + "/pol-" + getNameForAclPolicy(tenantName, identifier);
    }

    private String getDnForAclPolicyRef(String tenantName, String identifier, boolean ingress) {
        return getDnForAclPolicySet(tenantName, ingress) + "/polref-" + getNameForAclPolicy(tenantName, identifier);
    }

    private String getNameForAclRule(String tenantName, String identifier) {
        return "Rule-" + tenantName + "-" + identifier;
    }

    private String getDnForAclRule(String tenantName, String identifier, String policyIdentifier) {
        return getDnForAclPolicy(tenantName, policyIdentifier) + "/rule-" + getNameForAclRule(tenantName, identifier);
    }

    @Override
    public boolean createTenantVDCAclPolicy(String tenantName, String identifier) throws ExecutionException {
        String xml = VnmcXml.CREATE_ACL_POLICY.getXml();
        String service = VnmcXml.CREATE_ACL_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclpolicyname", getNameForAclPolicy(tenantName, identifier));
        xml = replaceXmlValue(xml, "aclpolicydn", getDnForAclPolicy(tenantName, identifier));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDCAclPolicy(String tenantName, String identifier) throws ExecutionException {
        String xml = VnmcXml.DELETE_ACL_POLICY.getXml();
        String service = VnmcXml.DELETE_ACL_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclpolicyname", getNameForAclPolicy(tenantName, identifier));
        xml = replaceXmlValue(xml, "aclpolicydn", getDnForAclPolicy(tenantName, identifier));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCAclPolicyRef(String tenantName, String identifier, boolean ingress) throws ExecutionException {
        String xml = VnmcXml.CREATE_ACL_POLICY_REF.getXml();
        String service = VnmcXml.CREATE_ACL_POLICY_REF.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclpolicyname", getNameForAclPolicy(tenantName, identifier));
        xml = replaceXmlValue(xml, "aclpolicydn", getDnForAclPolicy(tenantName, identifier));
        xml = replaceXmlValue(xml, "aclpolicyrefdn", getDnForAclPolicyRef(tenantName, identifier, ingress));

        List<String> policies = listAclPolicies(tenantName);
        int order = 100;
        if (policies != null) {
            order += policies.size();
        }
        xml = replaceXmlValue(xml, "order", Integer.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCAclPolicySet(String tenantName, boolean ingress) throws ExecutionException {
        String xml = VnmcXml.CREATE_ACL_POLICY_SET.getXml();
        String service = VnmcXml.CREATE_ACL_POLICY_SET.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "ACL policy set for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "aclpolicysetname", getNameForAclPolicySet(tenantName, ingress));
        xml = replaceXmlValue(xml, "aclpolicysetdn", getDnForAclPolicySet(tenantName, ingress));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDCAclPolicySet(String tenantName, boolean ingress) throws ExecutionException {
        String xml = VnmcXml.DELETE_ACL_POLICY_SET.getXml();
        String service = VnmcXml.DELETE_ACL_POLICY_SET.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclpolicysetname", getNameForAclPolicySet(tenantName, ingress));
        xml = replaceXmlValue(xml, "aclpolicysetdn", getDnForAclPolicySet(tenantName, ingress));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean associateAclPolicySet(String tenantName) throws ExecutionException {
        String xml = VnmcXml.RESOLVE_ACL_POLICY_SET.getXml();
        String service = VnmcXml.RESOLVE_ACL_POLICY_SET.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "descr", "Edge Security Profile for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "name", getNameForEdgeDeviceSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "espdn", getDnForTenantVDCEdgeSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "egresspolicysetname", getNameForAclPolicySet(tenantName, false));
        xml = replaceXmlValue(xml, "ingresspolicysetname", getNameForAclPolicySet(tenantName, true));
        xml = replaceXmlValue(xml, "natpolicysetname", getNameForNatPolicySet(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCIngressAclRule(String tenantName, long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp,
        String destStartPort, String destEndPort) throws ExecutionException {
        String xml = VnmcXml.CREATE_INGRESS_ACL_RULE.getXml();
        String service = VnmcXml.CREATE_INGRESS_ACL_RULE.getService();

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "Ingress ACL rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "actiontype", "permit");
        xml = replaceXmlValue(xml, "protocolvalue", protocol);
        xml = replaceXmlValue(xml, "sourcestartip", sourceStartIp);
        xml = replaceXmlValue(xml, "sourceendip", sourceEndIp);
        xml = replaceXmlValue(xml, "deststartport", destStartPort);
        xml = replaceXmlValue(xml, "destendport", destEndPort);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCIngressAclRule(String tenantName, long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp)
        throws ExecutionException {
        String xml = VnmcXml.CREATE_GENERIC_INGRESS_ACL_RULE.getXml();
        String service = VnmcXml.CREATE_GENERIC_INGRESS_ACL_RULE.getService();

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "Ingress ACL rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "actiontype", "permit");
        xml = replaceXmlValue(xml, "protocolvalue", protocol);
        xml = replaceXmlValue(xml, "sourcestartip", sourceStartIp);
        xml = replaceXmlValue(xml, "sourceendip", sourceEndIp);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCEgressAclRule(String tenantName, long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp,
        String destStartPort, String destEndPort) throws ExecutionException {
        String xml = VnmcXml.CREATE_EGRESS_ACL_RULE.getXml();
        String service = VnmcXml.CREATE_EGRESS_ACL_RULE.getService();

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "Egress ACL rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "actiontype", "permit");
        xml = replaceXmlValue(xml, "protocolvalue", protocol);
        xml = replaceXmlValue(xml, "sourcestartip", sourceStartIp);
        xml = replaceXmlValue(xml, "sourceendip", sourceEndIp);
        xml = replaceXmlValue(xml, "deststartport", destStartPort);
        xml = replaceXmlValue(xml, "destendport", destEndPort);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCEgressAclRule(String tenantName, long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp)
        throws ExecutionException {
        String xml = VnmcXml.CREATE_GENERIC_EGRESS_ACL_RULE.getXml();
        String service = VnmcXml.CREATE_GENERIC_EGRESS_ACL_RULE.getService();
        if (protocol.equalsIgnoreCase("all")) { // any protocol
            xml = VnmcXml.CREATE_GENERIC_EGRESS_ACL_NO_PROTOCOL_RULE.getXml();
            service = VnmcXml.CREATE_GENERIC_EGRESS_ACL_NO_PROTOCOL_RULE.getService();
        } else { // specific protocol
            xml = replaceXmlValue(xml, "protocolvalue", protocol);
        }

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "Egress ACL rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "actiontype", "permit");
        xml = replaceXmlValue(xml, "sourcestartip", sourceStartIp);
        xml = replaceXmlValue(xml, "sourceendip", sourceEndIp);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDCAclRule(String tenantName, long ruleId, String policyIdentifier) throws ExecutionException {
        String identifier = Long.toString(ruleId);
        return deleteTenantVDCRule(getDnForAclRule(tenantName, identifier, policyIdentifier), getNameForAclRule(tenantName, identifier));
    }

    private String getNameForPFPortPool(String tenantName, String identifier) {
        return "PortPool-" + tenantName + "-" + identifier;
    }

    private String getDnForPFPortPool(String tenantName, String identifier) {
        return getDnForTenantVDC(tenantName) + "/objgrp-" + getNameForPFPortPool(tenantName, identifier);
    }

    private String getNameForPFIpPool(String tenantName, String identifier) {
        return "IpPool-" + tenantName + "-" + identifier;
    }

    private String getDnForPFIpPool(String tenantName, String identifier) {
        return getDnForTenantVDC(tenantName) + "/objgrp-" + getNameForPFIpPool(tenantName, identifier);
    }

    private boolean createTenantVDCPortPool(String poolDn, String name, String description, String startPort, String endPort) throws ExecutionException {
        String xml = VnmcXml.CREATE_PORT_POOL.getXml();
        String service = VnmcXml.CREATE_PORT_POOL.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "portpooldn", poolDn);
        xml = replaceXmlValue(xml, "portpoolname", name);
        xml = replaceXmlValue(xml, "descr", description);
        xml = replaceXmlValue(xml, "startport", startPort);
        xml = replaceXmlValue(xml, "endport", endPort);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private boolean createTenantVDCIpPool(String poolDn, String name, String description, String ipAddress) throws ExecutionException {
        String xml = VnmcXml.CREATE_IP_POOL.getXml();
        String service = VnmcXml.CREATE_IP_POOL.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "ippooldn", poolDn);
        xml = replaceXmlValue(xml, "ippoolname", name);
        xml = replaceXmlValue(xml, "descr", description);
        xml = replaceXmlValue(xml, "ipvalue", ipAddress);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private boolean createTenantVDCNatPolicyRef(String policyRefDn, String name, String tenantName, boolean isSourceNat) throws ExecutionException {
        String xml = VnmcXml.CREATE_NAT_POLICY_REF.getXml();
        String service = VnmcXml.CREATE_NAT_POLICY_REF.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "natpolicyrefdn", policyRefDn);
        xml = replaceXmlValue(xml, "natpolicyname", name);

        // PF and static NAT policies need to come before source NAT, so leaving buffer
        // and creating source NAT with a high order value.
        // Initially tried setting MAX_INT as the order but VNMC complains about it
        int order = 10000; // TODO: For now value should be sufficient, if required may need to increase
        if (!isSourceNat) {
            List<String> policies = listNatPolicies(tenantName);
            order = 100; // order starts at 100
            if (policies != null) {
                order += policies.size();
            }
        }
        xml = replaceXmlValue(xml, "order", Integer.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private boolean createTenantVDCNatPolicy(String policyDn, String name) throws ExecutionException {
        String xml = VnmcXml.CREATE_NAT_POLICY.getXml();
        String service = VnmcXml.CREATE_NAT_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "natpolicydn", policyDn);
        xml = replaceXmlValue(xml, "natpolicyname", name);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private boolean deleteTenantVDCNatPolicy(String policyDn, String name) throws ExecutionException {
        String xml = VnmcXml.DELETE_NAT_POLICY.getXml();
        String service = VnmcXml.DELETE_NAT_POLICY.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "natpolicydn", policyDn);
        xml = replaceXmlValue(xml, "natpolicyname", name);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private boolean deleteTenantVDCRule(String ruledn, String ruleName) throws ExecutionException {
        String xml = VnmcXml.DELETE_RULE.getXml();
        String service = VnmcXml.DELETE_RULE.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "ruledn", ruledn);
        xml = replaceXmlValue(xml, "rulename", ruleName);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private List<String> listNatPolicies(String tenantName) throws ExecutionException {

        String xml = VnmcXml.LIST_NAT_POLICIES.getXml();
        String service = VnmcXml.LIST_NAT_POLICIES.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "vdcdn", getDnForTenantVDC(tenantName));

        String response = sendRequest(service, xml);

        List<String> result = new ArrayList<String>();
        Document xmlDoc = getDocument(response);
        xmlDoc.normalize();
        NodeList policyList = xmlDoc.getElementsByTagName("pair");
        for (int i = 0; i < policyList.getLength(); i++) {
            Node policyNode = policyList.item(i);
            result.add(policyNode.getAttributes().getNamedItem("key").getNodeValue());
        }

        return result;
    }

    private List<String> listAclPolicies(String tenantName) throws ExecutionException {

        String xml = VnmcXml.LIST_ACL_POLICIES.getXml();
        String service = VnmcXml.LIST_ACL_POLICIES.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "vdcdn", getDnForTenantVDC(tenantName));

        String response = sendRequest(service, xml);

        List<String> result = new ArrayList<String>();
        Document xmlDoc = getDocument(response);
        xmlDoc.normalize();
        NodeList policyList = xmlDoc.getElementsByTagName("pair");
        for (int i = 0; i < policyList.getLength(); i++) {
            Node policyNode = policyList.item(i);
            result.add(policyNode.getAttributes().getNamedItem("key").getNodeValue());
        }

        return result;
    }

    private List<String> listChildren(String dn) throws ExecutionException {

        String xml = VnmcXml.LIST_CHILDREN.getXml();
        String service = VnmcXml.LIST_CHILDREN.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "dn", dn);

        String response = sendRequest(service, xml);

        List<String> result = new ArrayList<String>();
        Document xmlDoc = getDocument(response);
        xmlDoc.normalize();
        NodeList policyList = xmlDoc.getElementsByTagName("policyRule");
        for (int i = 0; i < policyList.getLength(); i++) {
            Node policyNode = policyList.item(i);
            result.add(policyNode.getAttributes().getNamedItem("name").getNodeValue());
        }

        return result;
    }

    @Override
    public boolean createTenantVDCPFPortPool(String tenantName, String identifier, String startPort, String endPort) throws ExecutionException {
        return createTenantVDCPortPool(getDnForPFPortPool(tenantName, identifier), getNameForPFPortPool(tenantName, identifier), "PF port pool for " +
            getNameForPFPortPool(tenantName, identifier), startPort, endPort);
    }

    @Override
    public boolean createTenantVDCPFIpPool(String tenantName, String identifier, String ipAddress) throws ExecutionException {
        return createTenantVDCIpPool(getDnForPFIpPool(tenantName, identifier), getNameForPFIpPool(tenantName, identifier),
            "PF ip pool for " + getNameForPFIpPool(tenantName, identifier), ipAddress);
    }

    private String getNameForPFPolicy(String tenantName, String identifier) {
        return "PF-" + tenantName + "-" + identifier;
    }

    private String getDnForPFPolicy(String tenantName, String identifier) {
        return getDnForTenantVDC(tenantName) + "/natpol-" + getNameForPFPolicy(tenantName, identifier);
    }

    private String getDnForPFPolicyRef(String tenantName, String identifier) {
        return getDnForNatPolicySet(tenantName) + "/polref-" + getNameForPFPolicy(tenantName, identifier);
    }

    private String getNameForPFRule(String tenantName, String identifier) {
        return "Rule-" + tenantName + "-" + identifier;
    }

    private String getDnForPFRule(String tenantName, String identifier, String policyIdentifier) {
        return getDnForPFPolicy(tenantName, policyIdentifier) + "/rule-" + getNameForPFRule(tenantName, identifier);
    }

    @Override
    public boolean createTenantVDCPFRule(String tenantName, long ruleId, String policyIdentifier, String protocol, String publicIp, String startPort, String endPort)
        throws ExecutionException {
        String xml = VnmcXml.CREATE_PF_RULE.getXml();
        String service = VnmcXml.CREATE_PF_RULE.getService();

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "natruledn", getDnForPFRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "natrulename", getNameForPFRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "PF rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "ippoolname", getNameForPFIpPool(tenantName, identifier));
        xml = replaceXmlValue(xml, "portpoolname", getNameForPFPortPool(tenantName, identifier));
        xml = replaceXmlValue(xml, "ip", publicIp);
        xml = replaceXmlValue(xml, "startport", startPort);
        xml = replaceXmlValue(xml, "endport", endPort);
        xml = replaceXmlValue(xml, "protocolvalue", protocol);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDCPFRule(String tenantName, long ruleId, String policyIdentifier) throws ExecutionException {
        String identifier = Long.toString(ruleId);
        return deleteTenantVDCRule(getDnForPFRule(tenantName, identifier, policyIdentifier), getNameForPFRule(tenantName, identifier));
    }

    @Override
    public boolean createTenantVDCAclRuleForPF(String tenantName, long ruleId, String policyIdentifier, String protocol, String ipAddress, String startPort,
        String endPort) throws ExecutionException {
        String xml = VnmcXml.CREATE_ACL_RULE_FOR_PF.getXml();
        String service = VnmcXml.CREATE_ACL_RULE_FOR_PF.getService();

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "ACL rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "actiontype", "permit");
        xml = replaceXmlValue(xml, "protocolvalue", protocol);
        xml = replaceXmlValue(xml, "ip", ipAddress);
        xml = replaceXmlValue(xml, "startport", startPort);
        xml = replaceXmlValue(xml, "endport", endPort);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCPFPolicyRef(String tenantName, String identifier) throws ExecutionException {
        return createTenantVDCNatPolicyRef(getDnForPFPolicyRef(tenantName, identifier), getNameForPFPolicy(tenantName, identifier), tenantName, false);
    }

    @Override
    public boolean createTenantVDCPFPolicy(String tenantName, String identifier) throws ExecutionException {
        return createTenantVDCNatPolicy(getDnForPFPolicy(tenantName, identifier), getNameForPFPolicy(tenantName, identifier));
    }

    @Override
    public boolean deleteTenantVDCPFPolicy(String tenantName, String identifier) throws ExecutionException {
        return deleteTenantVDCNatPolicy(getDnForPFPolicy(tenantName, identifier), getNameForPFPolicy(tenantName, identifier));
    }

    private String getNameForDNatIpPool(String tenantName, String identifier) {
        return "IpPool-" + tenantName + "-" + identifier + "n";
    }

    private String getDnForDNatIpPool(String tenantName, String identifier) {
        return getDnForTenantVDC(tenantName) + "/objgrp-" + getNameForDNatIpPool(tenantName, identifier);
    }

    @Override
    public boolean createTenantVDCDNatIpPool(String tenantName, String identifier, String ipAddress) throws ExecutionException {
        return createTenantVDCIpPool(getDnForDNatIpPool(tenantName, identifier), getNameForDNatIpPool(tenantName, identifier), "DNAT ip pool for " +
            getNameForDNatIpPool(tenantName, identifier), ipAddress);
    }

    private String getNameForDNatRule(String tenantName, String identifier) {
        return "Rule-" + tenantName + "-" + identifier;
    }

    private String getDnForDNatRule(String tenantName, String identifier, String policyIdentifier) {
        return getDnForDNatPolicy(tenantName, policyIdentifier) + "/rule-" + getNameForDNatRule(tenantName, identifier);
    }

    private String getNameForDNatPolicy(String tenantName, String identifier) {
        return "DNAT-" + tenantName + "-" + identifier;
    }

    private String getDnForDNatPolicy(String tenantName, String identifier) {
        return getDnForTenantVDC(tenantName) + "/natpol-" + getNameForDNatPolicy(tenantName, identifier);
    }

    private String getDnForDNatPolicyRef(String tenantName, String identifier) {
        return getDnForNatPolicySet(tenantName) + "/polref-" + getNameForDNatPolicy(tenantName, identifier);
    }

    @Override
    public boolean createTenantVDCDNatRule(String tenantName, long ruleId, String policyIdentifier, String publicIp) throws ExecutionException {
        String xml = VnmcXml.CREATE_DNAT_RULE.getXml();
        String service = VnmcXml.CREATE_DNAT_RULE.getService();

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "natruledn", getDnForDNatRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "natrulename", getNameForDNatRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "DNAT rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "ippoolname", getNameForDNatIpPool(tenantName, identifier));
        xml = replaceXmlValue(xml, "ip", publicIp);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteTenantVDCDNatRule(String tenantName, long ruleId, String policyIdentifier) throws ExecutionException {
        String identifier = Long.toString(ruleId);
        return deleteTenantVDCRule(getDnForDNatRule(tenantName, identifier, policyIdentifier), getNameForDNatRule(tenantName, identifier));
    }

    @Override
    public boolean createTenantVDCAclRuleForDNat(String tenantName, long ruleId, String policyIdentifier, String ipAddress) throws ExecutionException {
        String xml = VnmcXml.CREATE_ACL_RULE_FOR_DNAT.getXml();
        String service = VnmcXml.CREATE_ACL_RULE_FOR_DNAT.getService();

        String identifier = Long.toString(ruleId);
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier));
        xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier));
        xml = replaceXmlValue(xml, "descr", "ACL rule for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "actiontype", "permit");
        xml = replaceXmlValue(xml, "ip", ipAddress);

        long order = 100 + ruleId;
        xml = replaceXmlValue(xml, "order", Long.toString(order));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean createTenantVDCDNatPolicyRef(String tenantName, String identifier) throws ExecutionException {
        return createTenantVDCNatPolicyRef(getDnForDNatPolicyRef(tenantName, identifier), getNameForDNatPolicy(tenantName, identifier), tenantName, false);
    }

    @Override
    public boolean createTenantVDCDNatPolicy(String tenantName, String identifier) throws ExecutionException {
        return createTenantVDCNatPolicy(getDnForDNatPolicy(tenantName, identifier), getNameForDNatPolicy(tenantName, identifier));
    }

    @Override
    public boolean deleteTenantVDCDNatPolicy(String tenantName, String identifier) throws ExecutionException {
        return deleteTenantVDCNatPolicy(getDnForDNatPolicy(tenantName, identifier), getNameForDNatPolicy(tenantName, identifier));
    }

    private String getNameForEdgeFirewall(String tenantName) {
        return "ASA-1000v-" + tenantName;
    }

    private String getDnForEdgeFirewall(String tenantName) {
        return getDnForTenantVDC(tenantName) + "/efw-" + getNameForEdgeFirewall(tenantName);
    }

    private String getNameForEdgeInsideIntf(String tenantName) {
        return "Edge_Inside"; //TODO: make this configurable
    }

    private String getNameForEdgeOutsideIntf(String tenantName) {
        return "Edge_Outside"; //TODO: make this configurable
    }

    private String getDnForOutsideIntf(String tenantName) {
        return getDnForEdgeFirewall(tenantName) + "/interface-" + getNameForEdgeOutsideIntf(tenantName);
    }

    private String getDnForInsideIntf(String tenantName) {
        return getDnForEdgeFirewall(tenantName) + "/interface-" + getNameForEdgeInsideIntf(tenantName);
    }

    @Override
    public boolean createEdgeFirewall(String tenantName, String publicIp, String insideIp, String publicSubnet, String insideSubnet) throws ExecutionException {
        String xml = VnmcXml.CREATE_EDGE_FIREWALL.getXml();
        String service = VnmcXml.CREATE_EDGE_FIREWALL.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "edgefwdescr", "Edge Firewall for Tenant VDC " + tenantName);
        xml = replaceXmlValue(xml, "edgefwname", getNameForEdgeFirewall(tenantName));
        xml = replaceXmlValue(xml, "edgefwdn", getDnForEdgeFirewall(tenantName));
        xml = replaceXmlValue(xml, "insideintfname", getNameForEdgeInsideIntf(tenantName));
        xml = replaceXmlValue(xml, "outsideintfname", getNameForEdgeOutsideIntf(tenantName));

        xml = replaceXmlValue(xml, "insideintfdn", getDnForInsideIntf(tenantName));
        xml = replaceXmlValue(xml, "outsideintfdn", getDnForOutsideIntf(tenantName));

        xml = replaceXmlValue(xml, "deviceserviceprofiledn", getDnForEdgeFirewall(tenantName) + "/device-service-profile");
        xml = replaceXmlValue(xml, "outsideintfsp", getDnForOutsideIntf(tenantName) + "/interface-service-profile");

        xml = replaceXmlValue(xml, "secprofileref", getNameForEdgeDeviceSecurityProfile(tenantName));
        xml = replaceXmlValue(xml, "deviceserviceprofile", getNameForEdgeDeviceServiceProfile(tenantName));

        xml = replaceXmlValue(xml, "insideip", insideIp);
        xml = replaceXmlValue(xml, "publicip", publicIp);
        xml = replaceXmlValue(xml, "insidesubnet", insideSubnet);
        xml = replaceXmlValue(xml, "outsidesubnet", publicSubnet);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean deleteEdgeFirewall(String tenantName) throws ExecutionException {
        String xml = VnmcXml.DELETE_EDGE_FIREWALL.getXml();
        String service = VnmcXml.DELETE_EDGE_FIREWALL.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "edgefwname", getNameForEdgeFirewall(tenantName));
        xml = replaceXmlValue(xml, "edgefwdn", getDnForEdgeFirewall(tenantName));

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public Map<String, String> listUnAssocAsa1000v() throws ExecutionException {
        String xml = VnmcXml.LIST_UNASSOC_ASA1000V.getXml();
        String service = VnmcXml.LIST_UNASSOC_ASA1000V.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);

        String response = sendRequest(service, xml);

        Map<String, String> result = new HashMap<String, String>();
        Document xmlDoc = getDocument(response);
        xmlDoc.normalize();
        NodeList fwList = xmlDoc.getElementsByTagName("fwInstance");
        for (int j = 0; j < fwList.getLength(); j++) {
            Node fwNode = fwList.item(j);
            result.put(fwNode.getAttributes().getNamedItem("mgmtIp").getNodeValue(), fwNode.getAttributes().getNamedItem("dn").getNodeValue());
        }

        return result;
    }

    @Override
    public boolean assignAsa1000v(String tenantName, String firewallDn) throws ExecutionException {
        String xml = VnmcXml.ASSIGN_ASA1000V.getXml();
        String service = VnmcXml.ASSIGN_ASA1000V.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "binddn", getDnForEdgeFirewall(tenantName) + "/binding");
        xml = replaceXmlValue(xml, "fwdn", firewallDn);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    @Override
    public boolean unassignAsa1000v(String tenantName, String firewallDn) throws ExecutionException {
        String xml = VnmcXml.UNASSIGN_ASA1000V.getXml();
        String service = VnmcXml.UNASSIGN_ASA1000V.getService();
        xml = replaceXmlValue(xml, "cookie", _cookie);
        xml = replaceXmlValue(xml, "binddn", getDnForEdgeFirewall(tenantName) + "/binding");
        xml = replaceXmlValue(xml, "fwdn", firewallDn);

        String response = sendRequest(service, xml);
        return verifySuccess(response);
    }

    private String sendRequest(String service, String xmlRequest) throws ExecutionException {
        HttpClient client = new HttpClient();
        String response = null;
        PostMethod method = new PostMethod("/xmlIM/" + service);
        method.setRequestBody(xmlRequest);

        try {
            org.apache.commons.httpclient.protocol.Protocol myhttps = new org.apache.commons.httpclient.protocol.Protocol("https", new EasySSLProtocolSocketFactory(), 443);
            client.getHostConfiguration().setHost(_ip, 443, myhttps);
            int statusCode = client.executeMethod(method);

            if (statusCode != HttpStatus.SC_OK) {
                throw new Exception("Error code : " + statusCode);
            }
            response = method.getResponseBodyAsString();
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw new ExecutionException(e.getMessage());
        }
        System.out.println(response);
        return response;
    }

    private Map<String, String> checkResponse(String xmlResponse, String... keys) throws ExecutionException {
        Document xmlDoc = getDocument(xmlResponse);
        Map<String, String> result = new HashMap<String, String>();
        Node topElement = xmlDoc.getChildNodes().item(0);
        if (topElement != null) {
            for (String key : keys) {
                Node valueNode = topElement.getAttributes().getNamedItem(key);
                result.put(key, valueNode == null ? null : valueNode.getNodeValue());
            }
        }
        return result;
    }

    private boolean verifySuccess(String xmlResponse) throws ExecutionException {
        Map<String, String> checked = checkResponse(xmlResponse, "errorCode", "errorDescr");

        if (checked.get("errorCode") != null) {
            String errorCode = checked.get("errorCode");
            if (errorCode.equals("103")) {
                //tenant already exists
                return true;
            }
            String errorDescr = checked.get("errorDescr");
            throw new ExecutionException(errorDescr);
        }
        return true;
    }

    /*
     * XML utils
     */

    private Document getDocument(String xml) throws ExecutionException {
        StringReader xmlReader = new StringReader("<?xml version=\"1.0\"?> \n" + xml.trim());
        InputSource xmlSource = new InputSource(xmlReader);
        Document doc = null;

        try {
            doc = ParserUtils.getSaferDocumentBuilderFactory().newDocumentBuilder().parse(xmlSource);

        } catch (Exception e) {
            LOGGER.error(e);
            throw new ExecutionException(e.getMessage());
        }

        if (doc == null) {
            throw new ExecutionException("Failed to parse xml " + xml);
        } else {
            return doc;
        }
    }

    private String replaceXmlTag(String xml, String oldTag, String newTag) {
        return xml.replaceAll(oldTag, newTag);
    }

    private String replaceXmlValue(String xml, String marker, String value) {
        marker = "\\s*%" + marker + "%\\s*";

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

        return xml.replaceAll(marker, value);
    }

    private String extractXml(String xml, String marker) {
        String startMarker = "<" + marker + ">";
        String endMarker = "</" + marker + ">";
        if (xml.contains(startMarker) && xml.contains(endMarker)) {
            return xml.substring(xml.indexOf(startMarker) + startMarker.length(), xml.indexOf(endMarker));
        } else {
            return null;
        }

    }

}
