content: // 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.resource;
  
  import java.io.IOException;
  import java.io.StringReader;
  import java.io.StringWriter;
  import java.io.UnsupportedEncodingException;
  import java.net.URLDecoder;
  import java.net.URLEncoder;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import javax.naming.ConfigurationException;
  import javax.xml.transform.OutputKeys;
  import javax.xml.transform.Source;
  import javax.xml.transform.Transformer;
  import javax.xml.transform.TransformerFactory;
  import javax.xml.transform.dom.DOMSource;
  import javax.xml.transform.stream.StreamResult;
  import javax.xml.transform.stream.StreamSource;
  import javax.xml.xpath.XPath;
  import javax.xml.xpath.XPathConstants;
  import javax.xml.xpath.XPathExpression;
  import javax.xml.xpath.XPathExpressionException;
  import javax.xml.xpath.XPathFactory;
  
  import org.apache.cloudstack.utils.security.ParserUtils;
  import org.apache.http.NameValuePair;
  import org.apache.http.client.HttpClient;
  import org.apache.http.client.ResponseHandler;
  import org.apache.http.client.entity.UrlEncodedFormEntity;
  import org.apache.http.client.methods.HttpGet;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.impl.client.BasicResponseHandler;
  import org.apache.http.impl.client.DefaultHttpClient;
  import org.apache.http.message.BasicNameValuePair;
  import org.apache.http.protocol.HTTP;
  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.agent.IAgentControl;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.Command;
  import com.cloud.agent.api.ExternalNetworkResourceUsageAnswer;
  import com.cloud.agent.api.ExternalNetworkResourceUsageCommand;
  import com.cloud.agent.api.MaintainAnswer;
  import com.cloud.agent.api.MaintainCommand;
  import com.cloud.agent.api.PingCommand;
  import com.cloud.agent.api.ReadyAnswer;
  import com.cloud.agent.api.ReadyCommand;
  import com.cloud.agent.api.StartupCommand;
  import com.cloud.agent.api.StartupExternalFirewallCommand;
  import com.cloud.agent.api.routing.IpAssocAnswer;
  import com.cloud.agent.api.routing.IpAssocCommand;
  import com.cloud.agent.api.routing.NetworkElementCommand;
  import com.cloud.agent.api.routing.SetFirewallRulesCommand;
  import com.cloud.agent.api.routing.SetPortForwardingRulesCommand;
  import com.cloud.agent.api.routing.SetStaticNatRulesCommand;
  import com.cloud.agent.api.to.FirewallRuleTO;
  import com.cloud.agent.api.to.IpAddressTO;
  import com.cloud.agent.api.to.PortForwardingRuleTO;
  import com.cloud.agent.api.to.StaticNatRuleTO;
  import com.cloud.host.Host;
  import com.cloud.network.rules.FirewallRule;
  import com.cloud.network.utils.HttpClientWrapper;
  import com.cloud.resource.ServerResource;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.exception.ExecutionException;
  import com.cloud.utils.net.NetUtils;
  
  public class PaloAltoResource implements ServerResource {
  
      private String _name;
      private String _zoneId;
      private String _ip;
      private String _username;
      private String _password;
      private String _guid;
      private String _key;
      private Integer _numRetries;
      private Integer _timeoutInSeconds;
      private String _publicZone;
      private String _privateZone;
      private String _publicInterface;
      private String _privateInterface;
      private String _publicInterfaceType;
      private String _privateInterfaceType;
      private String _virtualRouter;
      private String _threatProfile;
      private String _logProfile;
      private String _pingManagementProfile;
      protected Logger logger = LogManager.getLogger(getClass());
  
      private static String s_apiUri = "/api";
      private static HttpClient s_httpclient;
  
      protected enum PaloAltoMethod {
          GET, POST;
      }
  
      private enum PaloAltoPrimative {
          CHECK_IF_EXISTS, ADD, DELETE;
      }
  
      private enum InterfaceType {
          AGGREGATE("aggregate-ethernet"), ETHERNET("ethernet");
  
          private final String type;
  
          private InterfaceType(String type) {
              this.type = type;
          }
  
          @Override
          public String toString() {
              return type;
          }
      }
  
      private enum Protocol {
          TCP("tcp"), UDP("udp"), ICMP("icmp"), ALL("all");
  
          private final String protocol;
  
          private Protocol(String protocol) {
              this.protocol = protocol;
          }
  
          @Override
          public String toString() {
              return protocol;
          }
      }
  
      private enum GuestNetworkType {
          SOURCE_NAT, INTERFACE_NAT;
      }
  
      @Override
      public Answer executeRequest(Command cmd) {
          if (cmd instanceof ReadyCommand) {
              return execute((ReadyCommand)cmd);
          } else if (cmd instanceof MaintainCommand) {
              return execute((MaintainCommand)cmd);
          } else if (cmd instanceof IpAssocCommand) {
              return execute((IpAssocCommand)cmd);
          } else if (cmd instanceof SetStaticNatRulesCommand) {
              return execute((SetStaticNatRulesCommand)cmd);
          } else if (cmd instanceof SetPortForwardingRulesCommand) {
              return execute((SetPortForwardingRulesCommand)cmd);
          } else if (cmd instanceof SetFirewallRulesCommand) {
              return execute((SetFirewallRulesCommand)cmd);
          } else if (cmd instanceof ExternalNetworkResourceUsageCommand) {
              return execute((ExternalNetworkResourceUsageCommand)cmd);
          } else {
              return Answer.createUnsupportedCommandAnswer(cmd);
          }
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          try {
              _name = (String)params.get("name");
              if (_name == null) {
                  throw new ConfigurationException("Unable to find name");
              }
  
              _zoneId = (String)params.get("zoneId");
              if (_zoneId == null) {
                  throw new ConfigurationException("Unable to find zone");
              }
  
              _ip = (String)params.get("ip");
              if (_ip == null) {
                  throw new ConfigurationException("Unable to find IP");
              }
  
              _username = (String)params.get("username");
              if (_username == null) {
                  throw new ConfigurationException("Unable to find username");
              }
  
              _password = (String)params.get("password");
              if (_password == null) {
                  throw new ConfigurationException("Unable to find password");
              }
  
              _publicInterface = (String)params.get("publicinterface");
              if (_publicInterface == null) {
                  throw new ConfigurationException("Unable to find public interface.");
              }
  
              _privateInterface = (String)params.get("privateinterface");
              if (_privateInterface == null) {
                  throw new ConfigurationException("Unable to find private interface.");
              }
  
              _publicZone = (String)params.get("publicnetwork");
              if (_publicZone == null) {
                  throw new ConfigurationException("Unable to find public zone");
              }
  
              _privateZone = (String)params.get("privatenetwork");
              if (_privateZone == null) {
                  throw new ConfigurationException("Unable to find private zone");
              }
  
              _virtualRouter = (String)params.get("pavr");
              if (_virtualRouter == null) {
                  throw new ConfigurationException("Unable to find virtual router");
              }
  
              _threatProfile = (String)params.get("patp");
              _logProfile = (String)params.get("palp");
  
              _guid = (String)params.get("guid");
              if (_guid == null) {
                  throw new ConfigurationException("Unable to find the guid");
              }
  
              _numRetries = NumbersUtil.parseInt((String)params.get("numretries"), 1);
              _timeoutInSeconds = NumbersUtil.parseInt((String)params.get("timeout"), 300);
  
              // Open a socket and login
              if (!refreshPaloAltoConnection()) {
                  throw new ConfigurationException("Unable to open a connection to the Palo Alto.");
              }
  
              // check that the threat profile exists if one was specified
              if (_threatProfile != null) {
                  try {
                      boolean has_profile = getThreatProfile(_threatProfile);
                      if (!has_profile) {
                          throw new ConfigurationException("The specified threat profile group does not exist.");
                      }
                  } catch (ExecutionException e) {
                      throw new ConfigurationException(e.getMessage());
                  }
              }
  
              // check that the log profile exists if one was specified
              if (_logProfile != null) {
                  try {
                      boolean has_profile = getLogProfile(_logProfile);
                      if (!has_profile) {
                          throw new ConfigurationException("The specified log profile does not exist.");
                      }
                  } catch (ExecutionException e) {
                      throw new ConfigurationException(e.getMessage());
                  }
              }
  
              // get public interface type
              try {
                  _publicInterfaceType = getInterfaceType(_publicInterface);
                  if (_publicInterfaceType.equals("")) {
                      throw new ConfigurationException("The specified public interface is not configured on the Palo Alto.");
                  }
              } catch (ExecutionException e) {
                  throw new ConfigurationException(e.getMessage());
              }
  
              // get private interface type
              try {
                  _privateInterfaceType = getInterfaceType(_privateInterface);
                  if (_privateInterfaceType.equals("")) {
                      throw new ConfigurationException("The specified private interface is not configured on the Palo Alto.");
                  }
              } catch (ExecutionException e) {
                  throw new ConfigurationException(e.getMessage());
              }
  
              _pingManagementProfile = "Ping";
              try {
                  ArrayList<IPaloAltoCommand> cmdList = new ArrayList<IPaloAltoCommand>();
                  managePingProfile(cmdList, PaloAltoPrimative.ADD);
                  boolean status = requestWithCommit(cmdList);
              } catch (ExecutionException e) {
                  throw new ConfigurationException(e.getMessage());
              }
  
              return true;
          } catch (Exception e) {
              throw new ConfigurationException(e.getMessage());
          }
  
      }
  
      @Override
      public StartupCommand[] initialize() {
          StartupExternalFirewallCommand cmd = new StartupExternalFirewallCommand();
          cmd.setName(_name);
          cmd.setDataCenter(_zoneId);
          cmd.setPod("");
          cmd.setPrivateIpAddress(_ip);
          cmd.setStorageIpAddress("");
          cmd.setVersion(PaloAltoResource.class.getPackage().getImplementationVersion());
          cmd.setGuid(_guid);
          return new StartupCommand[] {cmd};
      }
  
      @Override
      public Host.Type getType() {
          return Host.Type.ExternalFirewall;
      }
  
      @Override
      public String getName() {
          return _name;
      }
  
      @Override
      public boolean start() {
          return true;
      }
  
      @Override
      public boolean stop() {
          return true;
      }
  
      @Override
      public PingCommand getCurrentStatus(final long id) {
          return new PingCommand(Host.Type.ExternalFirewall, id);
      }
  
      @Override
      public void disconnected() {
          // nothing for now...
      }
  
      @Override
      public IAgentControl getAgentControl() {
          return null;
      }
  
      @Override
      public void setAgentControl(IAgentControl agentControl) {
          return;
      }
  
      /*
       * Login
       */
      private void openHttpConnection() {
          s_httpclient = new DefaultHttpClient();
  
          // Allows you to connect via SSL using unverified certs
          s_httpclient = HttpClientWrapper.wrapClient(s_httpclient);
      }
  
      private boolean refreshPaloAltoConnection() {
          if (s_httpclient == null) {
              openHttpConnection();
          }
  
          try {
              return login(_username, _password);
          } catch (ExecutionException e) {
              logger.error("Failed to login due to " + e.getMessage());
              return false;
          }
      }
  
      private boolean login(String username, String password) throws ExecutionException {
          Map<String, String> params = new HashMap<String, String>();
          params.put("type", "keygen");
          params.put("user", username);
          params.put("password", password);
  
          String keygenBody;
          try {
              keygenBody = request(PaloAltoMethod.GET, params);
          } catch (ExecutionException e) {
              return false;
          }
          Document keygen_doc = getDocument(keygenBody);
          XPath xpath = XPathFactory.newInstance().newXPath();
          try {
              XPathExpression expr = xpath.compile("/response[@status='success']/result/key/text()");
              _key = (String)expr.evaluate(keygen_doc, XPathConstants.STRING);
          } catch (XPathExpressionException e) {
              throw new ExecutionException(e.getCause().getMessage());
          }
          if (_key != null) {
              return true;
          }
          return false;
      }
  
      // ENTRY POINTS...
  
      private Answer execute(ReadyCommand cmd) {
          return new ReadyAnswer(cmd);
      }
  
      private Answer execute(MaintainCommand cmd) {
          return new MaintainAnswer(cmd);
      }
  
      private ExternalNetworkResourceUsageAnswer execute(ExternalNetworkResourceUsageCommand cmd) {
          return new ExternalNetworkResourceUsageAnswer(cmd);
      }
  
      /*
       * Guest networks
       */
  
      private synchronized Answer execute(IpAssocCommand cmd) {
          refreshPaloAltoConnection();
          return execute(cmd, _numRetries);
      }
  
      private Answer execute(IpAssocCommand cmd, int numRetries) {
          String[] results = new String[cmd.getIpAddresses().length];
          int i = 0;
          try {
              IpAddressTO ip;
              if (cmd.getIpAddresses().length != 1) {
                  throw new ExecutionException("Received an invalid number of guest IPs to associate.");
              } else {
                  ip = cmd.getIpAddresses()[0];
              }
  
              String sourceNatIpAddress = null;
              GuestNetworkType type = GuestNetworkType.INTERFACE_NAT;
  
              if (ip.isSourceNat()) {
                  type = GuestNetworkType.SOURCE_NAT;
  
                  if (ip.getPublicIp() == null) {
                      throw new ExecutionException("Source NAT IP address must not be null.");
                  } else {
                      sourceNatIpAddress = ip.getPublicIp();
                  }
              }
  
              long guestVlanTag = Long.parseLong(cmd.getAccessDetail(NetworkElementCommand.GUEST_VLAN_TAG));
              String guestVlanGateway = cmd.getAccessDetail(NetworkElementCommand.GUEST_NETWORK_GATEWAY);
              String cidr = cmd.getAccessDetail(NetworkElementCommand.GUEST_NETWORK_CIDR);
              long cidrSize = NetUtils.cidrToLong(cidr)[1];
              String guestVlanSubnet = NetUtils.getCidrSubNet(guestVlanGateway, cidrSize);
  
              Long publicVlanTag = null;
              if (ip.getBroadcastUri() != null) {
                  String parsedVlanTag = parsePublicVlanTag(ip.getBroadcastUri());
                  if (!parsedVlanTag.equals("untagged")) {
                      try {
                          publicVlanTag = Long.parseLong(parsedVlanTag);
                      } catch (Exception e) {
                          throw new ExecutionException("Could not parse public VLAN tag: " + parsedVlanTag);
                      }
                  }
              }
  
              ArrayList<IPaloAltoCommand> commandList = new ArrayList<IPaloAltoCommand>();
  
              if (ip.isAdd()) {
                  // Implement the guest network for this VLAN
                  implementGuestNetwork(commandList, type, publicVlanTag, sourceNatIpAddress, guestVlanTag, guestVlanGateway, guestVlanSubnet, cidrSize);
              } else {
                  // Remove the guest network:
                  shutdownGuestNetwork(commandList, type, publicVlanTag, sourceNatIpAddress, guestVlanTag, guestVlanGateway, guestVlanSubnet, cidrSize);
              }
  
              boolean status = requestWithCommit(commandList);
  
              results[i++] = ip.getPublicIp() + " - success";
          } catch (ExecutionException e) {
              logger.error(e);
  
              if (numRetries > 0 && refreshPaloAltoConnection()) {
                  int numRetriesRemaining = numRetries - 1;
                  logger.debug("Retrying IPAssocCommand. Number of retries remaining: " + numRetriesRemaining);
                  return execute(cmd, numRetriesRemaining);
              } else {
                  results[i++] = IpAssocAnswer.errorResult;
              }
          }
  
          return new IpAssocAnswer(cmd, results);
      }
  
      private void implementGuestNetwork(ArrayList<IPaloAltoCommand> cmdList, GuestNetworkType type, Long publicVlanTag, String publicIp, long privateVlanTag,
          String privateGateway, String privateSubnet, long privateCidrNumber) throws ExecutionException {
          privateSubnet = privateSubnet + "/" + privateCidrNumber;
  
          managePrivateInterface(cmdList, PaloAltoPrimative.ADD, privateVlanTag, privateGateway + "/" + privateCidrNumber);
  
          if (type.equals(GuestNetworkType.SOURCE_NAT)) {
              managePublicInterface(cmdList, PaloAltoPrimative.ADD, publicVlanTag, publicIp + "/32", privateVlanTag);
              manageSrcNatRule(cmdList, PaloAltoPrimative.ADD, type, publicVlanTag, publicIp + "/32", privateVlanTag, privateGateway + "/" + privateCidrNumber);
              manageNetworkIsolation(cmdList, PaloAltoPrimative.ADD, privateVlanTag, privateSubnet, privateGateway);
          }
  
          String msg =
              "Implemented guest network with type " + type + ". Guest VLAN tag: " + privateVlanTag + ", guest gateway: " + privateGateway + "/" + privateCidrNumber;
          msg += type.equals(GuestNetworkType.SOURCE_NAT) ? ", source NAT IP: " + publicIp : "";
          logger.debug(msg);
      }
  
      private void shutdownGuestNetwork(ArrayList<IPaloAltoCommand> cmdList, GuestNetworkType type, Long publicVlanTag, String sourceNatIpAddress, long privateVlanTag,
          String privateGateway, String privateSubnet, long privateCidrSize) throws ExecutionException {
          privateSubnet = privateSubnet + "/" + privateCidrSize;
  
          // remove any orphaned egress rules if they exist...
          removeOrphanedFirewallRules(cmdList, privateVlanTag);
  
          if (type.equals(GuestNetworkType.SOURCE_NAT)) {
              manageNetworkIsolation(cmdList, PaloAltoPrimative.DELETE, privateVlanTag, privateSubnet, privateGateway);
              manageSrcNatRule(cmdList, PaloAltoPrimative.DELETE, type, publicVlanTag, sourceNatIpAddress + "/32", privateVlanTag, privateGateway + "/" + privateCidrSize);
              managePublicInterface(cmdList, PaloAltoPrimative.DELETE, publicVlanTag, sourceNatIpAddress + "/32", privateVlanTag);
          }
  
          managePrivateInterface(cmdList, PaloAltoPrimative.DELETE, privateVlanTag, privateGateway + "/" + privateCidrSize);
  
          String msg = "Shut down guest network with type " + type + ". Guest VLAN tag: " + privateVlanTag + ", guest gateway: " + privateGateway + "/" + privateCidrSize;
          msg += type.equals(GuestNetworkType.SOURCE_NAT) ? ", source NAT IP: " + sourceNatIpAddress : "";
          logger.debug(msg);
      }
  
      /*
       * Firewall rule entry point
       */
      private synchronized Answer execute(SetFirewallRulesCommand cmd) {
          refreshPaloAltoConnection();
          return execute(cmd, _numRetries);
      }
  
      private Answer execute(SetFirewallRulesCommand cmd, int numRetries) {
          FirewallRuleTO[] rules = cmd.getRules();
          try {
              ArrayList<IPaloAltoCommand> commandList = new ArrayList<IPaloAltoCommand>();
  
              for (FirewallRuleTO rule : rules) {
                  if (!rule.revoked()) {
                      manageFirewallRule(commandList, PaloAltoPrimative.ADD, rule);
                  } else {
                      manageFirewallRule(commandList, PaloAltoPrimative.DELETE, rule);
                  }
              }
  
              boolean status = requestWithCommit(commandList);
  
              return new Answer(cmd);
          } catch (ExecutionException e) {
              logger.error(e);
  
              if (numRetries > 0 && refreshPaloAltoConnection()) {
                  int numRetriesRemaining = numRetries - 1;
                  logger.debug("Retrying SetFirewallRulesCommand. Number of retries remaining: " + numRetriesRemaining);
                  return execute(cmd, numRetriesRemaining);
              } else {
                  return new Answer(cmd, e);
              }
          }
      }
  
      /*
       * Static NAT rule entry point
       */
  
      private synchronized Answer execute(SetStaticNatRulesCommand cmd) {
          refreshPaloAltoConnection();
          return execute(cmd, _numRetries);
      }
  
      private Answer execute(SetStaticNatRulesCommand cmd, int numRetries) {
          StaticNatRuleTO[] rules = cmd.getRules();
  
          try {
              ArrayList<IPaloAltoCommand> commandList = new ArrayList<IPaloAltoCommand>();
  
              for (StaticNatRuleTO rule : rules) {
                  if (!rule.revoked()) {
                      manageStcNatRule(commandList, PaloAltoPrimative.ADD, rule);
                  } else {
                      manageStcNatRule(commandList, PaloAltoPrimative.DELETE, rule);
                  }
              }
  
              boolean status = requestWithCommit(commandList);
  
              return new Answer(cmd);
          } catch (ExecutionException e) {
              logger.error(e);
  
              if (numRetries > 0 && refreshPaloAltoConnection()) {
                  int numRetriesRemaining = numRetries - 1;
                  logger.debug("Retrying SetStaticNatRulesCommand. Number of retries remaining: " + numRetriesRemaining);
                  return execute(cmd, numRetriesRemaining);
              } else {
                  return new Answer(cmd, e);
              }
          }
      }
  
      /*
       * Destination NAT (Port Forwarding) entry point
       */
      private synchronized Answer execute(SetPortForwardingRulesCommand cmd) {
          refreshPaloAltoConnection();
          return execute(cmd, _numRetries);
      }
  
      private Answer execute(SetPortForwardingRulesCommand cmd, int numRetries) {
          PortForwardingRuleTO[] rules = cmd.getRules();
  
          try {
              ArrayList<IPaloAltoCommand> commandList = new ArrayList<IPaloAltoCommand>();
  
              for (PortForwardingRuleTO rule : rules) {
                  if (!rule.revoked()) {
                      manageDstNatRule(commandList, PaloAltoPrimative.ADD, rule);
                  } else {
                      manageDstNatRule(commandList, PaloAltoPrimative.DELETE, rule);
                  }
              }
  
              boolean status = requestWithCommit(commandList);
  
              return new Answer(cmd);
          } catch (ExecutionException e) {
              logger.error(e);
  
              if (numRetries > 0 && refreshPaloAltoConnection()) {
                  int numRetriesRemaining = numRetries - 1;
                  logger.debug("Retrying SetPortForwardingRulesCommand. Number of retries remaining: " + numRetriesRemaining);
                  return execute(cmd, numRetriesRemaining);
              } else {
                  return new Answer(cmd, e);
              }
          }
      }
  
      // IMPLEMENTATIONS...
  
      /*
       * Private interface implementation
       */
  
      private String genPrivateInterfaceName(long vlanTag) {
          return _privateInterface + "." + Long.toString(vlanTag);
      }
  
      public boolean managePrivateInterface(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, long privateVlanTag, String privateGateway)
          throws ExecutionException {
          String interfaceName = genPrivateInterfaceName(privateVlanTag);
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/network/interface/" + _privateInterfaceType + "/entry[@name='" + _privateInterface +
                      "']/layer3/units/entry[@name='" + interfaceName + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Private sub-interface exists: " + interfaceName + ", " + result);
                  return result;
  
              case ADD:
                  if (managePrivateInterface(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, privateVlanTag, privateGateway)) {
                      return true;
                  }
  
                  // add cmds
                  // add sub-interface
                  Map<String, String> a_sub_params = new HashMap<String, String>();
                  a_sub_params.put("type", "config");
                  a_sub_params.put("action", "set");
                  a_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _privateInterfaceType + "/entry[@name='" + _privateInterface +
                      "']/layer3/units/entry[@name='" + interfaceName + "']");
                  a_sub_params.put("element", "<tag>" + privateVlanTag + "</tag><ip><entry name='" + privateGateway + "'/></ip><interface-management-profile>" +
                      _pingManagementProfile + "</interface-management-profile>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_sub_params));
  
                  // add sub-interface to VR...
                  Map<String, String> a_vr_params = new HashMap<String, String>();
                  a_vr_params.put("type", "config");
                  a_vr_params.put("action", "set");
                  a_vr_params.put("xpath", "/config/devices/entry/network/virtual-router/entry[@name='" + _virtualRouter + "']/interface");
                  a_vr_params.put("element", "<member>" + interfaceName + "</member>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_vr_params));
  
                  // add sub-interface to vsys...
                  Map<String, String> a_vsys_params = new HashMap<String, String>();
                  a_vsys_params.put("type", "config");
                  a_vsys_params.put("action", "set");
                  a_vsys_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/import/network/interface");
                  a_vsys_params.put("element", "<member>" + interfaceName + "</member>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_vsys_params));
  
                  // add sub-interface to zone...
                  Map<String, String> a_zone_params = new HashMap<String, String>();
                  a_zone_params.put("type", "config");
                  a_zone_params.put("action", "set");
                  a_zone_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/zone/entry[@name='" + _privateZone + "']/network/layer3");
                  a_zone_params.put("element", "<member>" + interfaceName + "</member>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_zone_params));
  
                  return true;
  
              case DELETE:
                  if (!managePrivateInterface(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, privateVlanTag, privateGateway)) {
                      return true;
                  }
  
                  // add cmds to the list
                  // delete sub-interface from zone...
                  Map<String, String> d_zone_params = new HashMap<String, String>();
                  d_zone_params.put("type", "config");
                  d_zone_params.put("action", "delete");
                  d_zone_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/zone/entry[@name='" + _privateZone + "']/network/layer3/member[text()='" +
                      interfaceName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_zone_params));
  
                  // delete sub-interface from vsys...
                  Map<String, String> d_vsys_params = new HashMap<String, String>();
                  d_vsys_params.put("type", "config");
                  d_vsys_params.put("action", "delete");
                  d_vsys_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/import/network/interface/member[text()='" + interfaceName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_vsys_params));
  
                  // delete sub-interface from VR...
                  Map<String, String> d_vr_params = new HashMap<String, String>();
                  d_vr_params.put("type", "config");
                  d_vr_params.put("action", "delete");
                  d_vr_params.put("xpath", "/config/devices/entry/network/virtual-router/entry[@name='" + _virtualRouter + "']/interface/member[text()='" + interfaceName +
                      "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_vr_params));
  
                  // delete sub-interface...
                  Map<String, String> d_sub_params = new HashMap<String, String>();
                  d_sub_params.put("type", "config");
                  d_sub_params.put("action", "delete");
                  d_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _privateInterfaceType + "/entry[@name='" + _privateInterface +
                      "']/layer3/units/entry[@name='" + interfaceName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_sub_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      /*
       * Public Interface implementation
       */
  
      private String genPublicInterfaceName(Long id) {
          return _publicInterface + "." + Long.toString(id);
      }
  
      public boolean managePublicInterface(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, Long publicVlanTag, String publicIp, long privateVlanTag)
          throws ExecutionException {
          String interfaceName;
          if (publicVlanTag == null) {
              interfaceName = genPublicInterfaceName(new Long("9999"));
          } else {
              interfaceName = genPublicInterfaceName(publicVlanTag);
          }
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/network/interface/" + _publicInterfaceType + "/entry[@name='" + _publicInterface +
                      "']/layer3/units/entry[@name='" + interfaceName + "']/ip/entry[@name='" + publicIp + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Public sub-interface & IP exists: " + interfaceName + " : " + publicIp + ", " + result);
                  return result;
  
              case ADD:
                  if (managePublicInterface(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, publicVlanTag, publicIp, privateVlanTag)) {
                      return true;
                  }
  
                  // add IP to the sub-interface
                  Map<String, String> a_sub_params = new HashMap<String, String>();
                  a_sub_params.put("type", "config");
                  a_sub_params.put("action", "set");
                  a_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _publicInterfaceType + "/entry[@name='" + _publicInterface +
                      "']/layer3/units/entry[@name='" + interfaceName + "']/ip");
                  a_sub_params.put("element", "<entry name='" + publicIp + "'/>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_sub_params));
  
                  // add sub-interface to VR (does nothing if already done)...
                  Map<String, String> a_vr_params = new HashMap<String, String>();
                  a_vr_params.put("type", "config");
                  a_vr_params.put("action", "set");
                  a_vr_params.put("xpath", "/config/devices/entry/network/virtual-router/entry[@name='" + _virtualRouter + "']/interface");
                  a_vr_params.put("element", "<member>" + interfaceName + "</member>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_vr_params));
  
                  // add sub-interface to vsys (does nothing if already done)...
                  Map<String, String> a_vsys_params = new HashMap<String, String>();
                  a_vsys_params.put("type", "config");
                  a_vsys_params.put("action", "set");
                  a_vsys_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/import/network/interface");
                  a_vsys_params.put("element", "<member>" + interfaceName + "</member>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_vsys_params));
  
                  // add sub-interface to zone (does nothing if already done)...
                  Map<String, String> a_zone_params = new HashMap<String, String>();
                  a_zone_params.put("type", "config");
                  a_zone_params.put("action", "set");
                  a_zone_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/zone/entry[@name='" + _publicZone + "']/network/layer3");
                  a_zone_params.put("element", "<member>" + interfaceName + "</member>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_zone_params));
  
                  return true;
  
              case DELETE:
                  if (!managePublicInterface(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, publicVlanTag, publicIp, privateVlanTag)) {
                      return true;
                  }
  
                  // delete IP from sub-interface...
                  Map<String, String> d_sub_params = new HashMap<String, String>();
                  d_sub_params.put("type", "config");
                  d_sub_params.put("action", "delete");
                  d_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _publicInterfaceType + "/entry[@name='" + _publicInterface +
                      "']/layer3/units/entry[@name='" + interfaceName + "']/ip/entry[@name='" + publicIp + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_sub_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      /*
       * Source NAT rule implementation
       */
  
      private String genSrcNatRuleName(Long privateVlanTag) {
          return "src_nat." + Long.toString(privateVlanTag);
      }
  
      public boolean manageSrcNatRule(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, GuestNetworkType type, Long publicVlanTag, String publicIp,
          long privateVlanTag, String privateGateway) throws ExecutionException {
          String publicInterfaceName;
          if (publicVlanTag == null) {
              publicInterfaceName = genPublicInterfaceName(new Long("9999"));
          } else {
              publicInterfaceName = genPublicInterfaceName(publicVlanTag);
          }
          String srcNatName = genSrcNatRuleName(privateVlanTag);
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + srcNatName + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Source NAT exists: " + srcNatName + ", " + result);
                  return result;
  
              case ADD:
                  if (manageSrcNatRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, type, publicVlanTag, publicIp, privateVlanTag, privateGateway)) {
                      return true;
                  }
  
                  String xml = "";
                  xml += "<from><member>" + _privateZone + "</member></from>";
                  xml += "<to><member>" + _publicZone + "</member></to>";
                  xml += "<source><member>" + privateGateway + "</member></source>";
                  xml += "<destination><member>any</member></destination>";
                  xml += "<service>any</service>";
                  xml += "<nat-type>ipv4</nat-type>";
                  xml += "<to-interface>" + publicInterfaceName + "</to-interface>";
                  xml += "<source-translation><dynamic-ip-and-port><interface-address>";
                  xml += "<ip>" + publicIp + "</ip>";
                  xml += "<interface>" + publicInterfaceName + "</interface>";
                  xml += "</interface-address></dynamic-ip-and-port></source-translation>";
  
                  Map<String, String> a_params = new HashMap<String, String>();
                  a_params.put("type", "config");
                  a_params.put("action", "set");
                  a_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + srcNatName + "']");
                  a_params.put("element", xml);
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, a_params));
  
                  return true;
  
              case DELETE:
                  if (!manageSrcNatRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, type, publicVlanTag, publicIp, privateVlanTag, privateGateway)) {
                      return true;
                  }
  
                  Map<String, String> d_params = new HashMap<String, String>();
                  d_params.put("type", "config");
                  d_params.put("action", "delete");
                  d_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + srcNatName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, d_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      /*
       * Destination NAT rules (Port Forwarding) implementation
       */
      private String genDstNatRuleName(String publicIp, long id) {
          return "dst_nat." + genIpIdentifier(publicIp) + "_" + Long.toString(id);
      }
  
      public boolean manageDstNatRule(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, PortForwardingRuleTO rule) throws ExecutionException {
          String publicIp = rule.getSrcIp();
          String dstNatName = genDstNatRuleName(publicIp, rule.getId());
  
          String publicInterfaceName;
          String publicVlanTag;
          if (rule.getSrcVlanTag() == null) {
              publicInterfaceName = genPublicInterfaceName(new Long("9999"));
          } else {
              publicVlanTag = parsePublicVlanTag(rule.getSrcVlanTag());
              if (publicVlanTag.equals("untagged")) {
                  publicInterfaceName = genPublicInterfaceName(new Long("9999"));
              } else {
                  publicInterfaceName = genPublicInterfaceName(new Long(publicVlanTag));
              }
          }
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + dstNatName + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Destination NAT exists: " + dstNatName + ", " + result);
                  return result;
  
              case ADD:
                  if (manageDstNatRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, rule)) {
                      return true;
                  }
  
                  // build source service xml
                  String srcService;
                  String protocol = rule.getProtocol();
                  int[] srcPortRange = rule.getSrcPortRange();
                  if (srcPortRange != null) {
                      String portRange;
                      if (srcPortRange.length == 1 || srcPortRange[0] == srcPortRange[1]) {
                          portRange = String.valueOf(srcPortRange[0]);
                      } else {
                          portRange = String.valueOf(srcPortRange[0]) + "-" + String.valueOf(srcPortRange[1]);
                      }
                      manageService(cmdList, PaloAltoPrimative.ADD, protocol, portRange, null);
                      srcService = genServiceName(protocol, portRange, null);
                  } else {
                      // no equivalent config in PA, so allow all traffic...
                      srcService = "any";
                  }
  
                  // build destination port xml (single port limit in PA)
                  String dstPortXML = "";
                  int[] dstPortRange = rule.getDstPortRange();
                  if (dstPortRange != null) {
                      dstPortXML = "<translated-port>" + dstPortRange[0] + "</translated-port>";
                  }
  
                  // add public IP to the sub-interface
                  Map<String, String> a_sub_params = new HashMap<String, String>();
                  a_sub_params.put("type", "config");
                  a_sub_params.put("action", "set");
                  a_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _publicInterfaceType + "/entry[@name='" + _publicInterface +
                      "']/layer3/units/entry[@name='" + publicInterfaceName + "']/ip");
                  a_sub_params.put("element", "<entry name='" + publicIp + "/32'/>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_sub_params));
  
                  // add the destination nat rule for the public IP
                  String xml = "";
                  xml += "<from><member>" + _publicZone + "</member></from>";
                  xml += "<to><member>" + _publicZone + "</member></to>";
                  xml += "<source><member>any</member></source>";
                  xml += "<destination><member>" + publicIp + "</member></destination>";
                  xml += "<service>" + srcService + "</service>";
                  xml += "<nat-type>ipv4</nat-type>";
                  xml += "<to-interface>" + publicInterfaceName + "</to-interface>";
                  xml += "<destination-translation><translated-address>" + rule.getDstIp() + "</translated-address>" + dstPortXML + "</destination-translation>";
  
                  Map<String, String> a_params = new HashMap<String, String>();
                  a_params.put("type", "config");
                  a_params.put("action", "set");
                  a_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + dstNatName + "']");
                  a_params.put("element", xml);
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, a_params));
  
                  return true;
  
              case DELETE:
                  if (!manageDstNatRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, rule)) {
                      return true;
                  }
  
                  // determine if we need to delete the ip from the interface as well...
                  Map<String, String> c_params = new HashMap<String, String>();
                  c_params.put("type", "config");
                  c_params.put("action", "get");
                  c_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[destination/member[text()='" + publicIp + "']]");
                  String c_response = request(PaloAltoMethod.GET, c_params);
  
                  String count = "";
                  NodeList response_body;
                  Document doc = getDocument(c_response);
                  XPath xpath = XPathFactory.newInstance().newXPath();
                  try {
                      XPathExpression expr = xpath.compile("/response[@status='success']/result");
                      response_body = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
                  } catch (XPathExpressionException e) {
                      throw new ExecutionException(e.getCause().getMessage());
                  }
                  if (response_body.getLength() > 0 && response_body.item(0).getAttributes().getLength() > 0) {
                      count = response_body.item(0).getAttributes().getNamedItem("count").getTextContent();
                  }
  
                  // delete the dst nat rule
                  Map<String, String> d_params = new HashMap<String, String>();
                  d_params.put("type", "config");
                  d_params.put("action", "delete");
                  d_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + dstNatName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, d_params));
  
                  if (!count.equals("") && Integer.parseInt(count) == 1) { // this dst nat rule is the last, so remove the ip...
                      // delete IP from sub-interface...
                      Map<String, String> d_sub_params = new HashMap<String, String>();
                      d_sub_params.put("type", "config");
                      d_sub_params.put("action", "delete");
                      d_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _publicInterfaceType + "/entry[@name='" + _publicInterface +
                          "']/layer3/units/entry[@name='" + publicInterfaceName + "']/ip/entry[@name='" + publicIp + "/32']");
                      cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_sub_params));
                  }
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      /*
       * Static NAT rule implementation
       */
      private String genStcNatRuleName(String publicIp, long id) {
          return "stc_nat." + genIpIdentifier(publicIp) + "_" + Long.toString(id);
      }
  
      public boolean manageStcNatRule(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, StaticNatRuleTO rule) throws ExecutionException {
          String publicIp = rule.getSrcIp();
          String stcNatName = genStcNatRuleName(publicIp, rule.getId());
  
          String publicInterfaceName;
          String publicVlanTag;
          if (rule.getSrcVlanTag() == null) {
              publicInterfaceName = genPublicInterfaceName(new Long("9999"));
          } else {
              publicVlanTag = parsePublicVlanTag(rule.getSrcVlanTag());
              if (publicVlanTag.equals("untagged")) {
                  publicInterfaceName = genPublicInterfaceName(new Long("9999"));
              } else {
                  publicInterfaceName = genPublicInterfaceName(new Long(publicVlanTag));
              }
          }
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + stcNatName + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Static NAT exists: " + stcNatName + ", " + result);
                  return result;
  
              case ADD:
                  if (manageStcNatRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, rule)) {
                      return true;
                  }
  
                  // add public IP to the sub-interface
                  Map<String, String> a_sub_params = new HashMap<String, String>();
                  a_sub_params.put("type", "config");
                  a_sub_params.put("action", "set");
                  a_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _publicInterfaceType + "/entry[@name='" + _publicInterface +
                      "']/layer3/units/entry[@name='" + publicInterfaceName + "']/ip");
                  a_sub_params.put("element", "<entry name='" + publicIp + "/32'/>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_sub_params));
  
                  // add the static nat rule for the public IP
                  String xml = "";
                  xml += "<from><member>" + _publicZone + "</member></from>";
                  xml += "<to><member>" + _publicZone + "</member></to>";
                  xml += "<source><member>any</member></source>";
                  xml += "<destination><member>" + publicIp + "</member></destination>";
                  xml += "<service>any</service>";
                  xml += "<nat-type>ipv4</nat-type>";
                  xml += "<to-interface>" + publicInterfaceName + "</to-interface>";
                  xml += "<destination-translation><translated-address>" + rule.getDstIp() + "</translated-address></destination-translation>";
  
                  Map<String, String> a_params = new HashMap<String, String>();
                  a_params.put("type", "config");
                  a_params.put("action", "set");
                  a_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + stcNatName + "']");
                  a_params.put("element", xml);
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, a_params));
  
                  return true;
  
              case DELETE:
                  if (!manageStcNatRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, rule)) {
                      return true;
                  }
  
                  // delete the static nat rule
                  Map<String, String> d_params = new HashMap<String, String>();
                  d_params.put("type", "config");
                  d_params.put("action", "delete");
                  d_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/nat/rules/entry[@name='" + stcNatName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, d_params));
  
                  // delete IP from sub-interface...
                  Map<String, String> d_sub_params = new HashMap<String, String>();
                  d_sub_params.put("type", "config");
                  d_sub_params.put("action", "delete");
                  d_sub_params.put("xpath", "/config/devices/entry/network/interface/" + _publicInterfaceType + "/entry[@name='" + _publicInterface +
                      "']/layer3/units/entry[@name='" + publicInterfaceName + "']/ip/entry[@name='" + publicIp + "/32']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_sub_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      /*
       * Firewall rule implementation
       */
      private String genFirewallRuleName(long id) { // ingress
          return "policy_" + Long.toString(id);
      }
  
      private String genFirewallRuleName(long id, String vlan) { // egress
          return "policy_" + Long.toString(id) + "_" + vlan;
      }
  
      public boolean manageFirewallRule(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, FirewallRuleTO rule) throws ExecutionException {
          String ruleName;
          if (rule.getTrafficType() == FirewallRule.TrafficType.Egress) {
              ruleName = genFirewallRuleName(rule.getId(), rule.getSrcVlanTag());
          } else {
              ruleName = genFirewallRuleName(rule.getId());
          }
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='" + ruleName + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Firewall policy exists: " + ruleName + ", " + result);
                  return result;
  
              case ADD:
                  if (manageFirewallRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, rule)) {
                      return true;
                  }
  
                  String srcZone;
                  String dstZone;
                  String dstAddressXML;
                  String appXML;
                  String serviceXML;
  
                  String protocol = rule.getProtocol();
                  String action = "allow";
  
                  // Only ICMP will use an Application, so others will be any.
                  if (protocol.equals(Protocol.ICMP.toString())) {
                      appXML = "<member>icmp</member><member>ping</member><member>traceroute</member>"; // use the default icmp applications...
                  } else {
                      appXML = "<member>any</member>";
                  }
  
                  // Only TCP and UDP will use a Service, others will use any.
                  if (protocol.equals(Protocol.TCP.toString()) || protocol.equals(Protocol.UDP.toString())) {
                      String portRange;
                      if (rule.getSrcPortRange() != null) {
                          int startPort = rule.getSrcPortRange()[0];
                          int endPort = rule.getSrcPortRange()[1];
                          if (startPort == endPort) {
                              portRange = String.valueOf(startPort);
                          } else {
                              portRange = String.valueOf(startPort) + "-" + String.valueOf(endPort);
                          }
                          manageService(cmdList, PaloAltoPrimative.ADD, protocol, portRange, null);
                          serviceXML = "<member>" + genServiceName(protocol, portRange, null) + "</member>";
                      } else {
                          // no equivalent config in PA, so allow all traffic...
                          serviceXML = "<member>any</member>";
                      }
                  } else {
                      serviceXML = "<member>any</member>";
                  }
  
                  // handle different types of fire wall rules (egress | ingress)
                  if (rule.getTrafficType() == FirewallRule.TrafficType.Egress) { // Egress Rule
                      srcZone = _privateZone;
                      dstZone = _publicZone;
                      dstAddressXML = "<member>any</member>";
  
                      // defaults to 'allow', the deny rules are as follows
                      if (rule.getType() == FirewallRule.FirewallRuleType.System) {
                          if (!rule.isDefaultEgressPolicy()) { // default of deny && system rule, so deny
                              action = "deny";
                          }
                      } else {
                          if (rule.isDefaultEgressPolicy()) { // default is allow && user rule, so deny
                              action = "deny";
                          }
                      }
                  } else { // Ingress Rule
                      srcZone = _publicZone;
                      dstZone = _privateZone;
                      dstAddressXML = "<member>" + rule.getSrcIp() + "</member>";
                  }
  
                  // build the source cidr xml
                  String srcCidrXML = "";
                  List<String> ruleSrcCidrList = rule.getSourceCidrList();
                  if (ruleSrcCidrList.size() > 0) { // a cidr was entered, modify as needed...
                      for (int i = 0; i < ruleSrcCidrList.size(); i++) {
                          if (ruleSrcCidrList.get(i).trim().equals("0.0.0.0/0")) { // allow any
                              if (rule.getTrafficType() == FirewallRule.TrafficType.Egress) {
                                  srcCidrXML += "<member>" + getPrivateSubnet(rule.getSrcVlanTag()) + "</member>";
                              } else {
                                  srcCidrXML += "<member>any</member>";
                              }
                          } else {
                              srcCidrXML += "<member>" + ruleSrcCidrList.get(i).trim() + "</member>";
                          }
                      }
                  } else { // no cidr was entered, so allow ALL according to firewall rule type
                      if (rule.getTrafficType() == FirewallRule.TrafficType.Egress) {
                          srcCidrXML = "<member>" + getPrivateSubnet(rule.getSrcVlanTag()) + "</member>";
                      } else {
                          srcCidrXML = "<member>any</member>";
                      }
                  }
  
                  // build new rule xml
                  String xml = "";
                  xml += "<from><member>" + srcZone + "</member></from>";
                  xml += "<to><member>" + dstZone + "</member></to>";
                  xml += "<source>" + srcCidrXML + "</source>";
                  xml += "<destination>" + dstAddressXML + "</destination>";
                  xml += "<application>" + appXML + "</application>";
                  xml += "<service>" + serviceXML + "</service>";
                  xml += "<action>" + action + "</action>";
                  xml += "<negate-source>no</negate-source>";
                  xml += "<negate-destination>no</negate-destination>";
                  if (_threatProfile != null && action.equals("allow")) { // add the threat profile if it exists
                      xml += "<profile-setting><group><member>" + _threatProfile + "</member></group></profile-setting>";
                  }
                  if (_logProfile != null && action.equals("allow")) { // add the log profile if it exists
                      xml += "<log-setting>" + _logProfile + "</log-setting>";
                  }
  
                  boolean has_default = false;
                  String defaultEgressRule = "";
                  if (rule.getTrafficType() == FirewallRule.TrafficType.Egress) {
                      // check if a default egress rule exists because it always has to be after the other rules.
                      Map<String, String> e_params = new HashMap<String, String>();
                      e_params.put("type", "config");
                      e_params.put("action", "get");
                      e_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='policy_0_" + rule.getSrcVlanTag() + "']");
                      String e_response = request(PaloAltoMethod.GET, e_params);
                      has_default = (validResponse(e_response) && responseNotEmpty(e_response));
  
                      // there is an existing default rule, so we need to remove it and add it back after the new rule is added.
                      if (has_default) {
                          logger.debug("Moving the default egress rule after the new rule: " + ruleName);
                          NodeList response_body;
                          Document doc = getDocument(e_response);
                          XPath xpath = XPathFactory.newInstance().newXPath();
                          try {
                              XPathExpression expr = xpath.compile("/response[@status='success']/result/entry/node()");
                              response_body = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
                          } catch (XPathExpressionException e) {
                              throw new ExecutionException(e.getCause().getMessage());
                          }
                          for (int i = 0; i < response_body.getLength(); i++) {
                              Node n = response_body.item(i);
                              defaultEgressRule += nodeToString(n);
                          }
                          Map<String, String> dd_params = new HashMap<String, String>();
                          dd_params.put("type", "config");
                          dd_params.put("action", "delete");
                          dd_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='policy_0_" + rule.getSrcVlanTag() +
                              "']");
                          cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, dd_params));
                      }
                  }
  
                  // add the new rule...
                  Map<String, String> a_params = new HashMap<String, String>();
                  a_params.put("type", "config");
                  a_params.put("action", "set");
                  a_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='" + ruleName + "']");
                  a_params.put("element", xml);
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, a_params));
  
                  // add back the default rule
                  if (rule.getTrafficType() == FirewallRule.TrafficType.Egress && has_default) {
                      Map<String, String> da_params = new HashMap<String, String>();
                      da_params.put("type", "config");
                      da_params.put("action", "set");
                      da_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='policy_0_" + rule.getSrcVlanTag() + "']");
                      da_params.put("element", defaultEgressRule);
                      cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, da_params));
                      logger.debug("Completed move of the default egress rule after rule: " + ruleName);
                  }
  
                  return true;
  
              case DELETE:
                  if (!manageFirewallRule(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, rule)) {
                      return true;
                  }
  
                  Map<String, String> d_params = new HashMap<String, String>();
                  d_params.put("type", "config");
                  d_params.put("action", "delete");
                  d_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='" + ruleName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, d_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      // remove orphaned rules if they exist...
      public void removeOrphanedFirewallRules(ArrayList<IPaloAltoCommand> cmdList, long vlan) throws ExecutionException {
          Map<String, String> params = new HashMap<String, String>();
          params.put("type", "config");
          params.put("action", "get");
          params.put("xpath",
              "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[contains(@name, 'policy') and contains(@name, '" + Long.toString(vlan) + "')]");
          String response = request(PaloAltoMethod.GET, params);
          boolean has_orphans = (validResponse(response) && responseNotEmpty(response));
  
          if (has_orphans) {
              Map<String, String> d_params = new HashMap<String, String>();
              d_params.put("type", "config");
              d_params.put("action", "delete");
              d_params.put("xpath",
                  "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[contains(@name, 'policy') and contains(@name, '" + Long.toString(vlan) +
                      "')]");
              cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, d_params));
          }
      }
  
      /*
       * Usage
       */
  
      /*
       * Helper config functions
       */
  
      // ensure guest network isolation
      private String genNetworkIsolationName(long privateVlanTag) {
          return "isolate_" + Long.toString(privateVlanTag);
      }
  
      public boolean manageNetworkIsolation(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, long privateVlanTag, String privateSubnet, String privateGateway)
          throws ExecutionException {
          String ruleName = genNetworkIsolationName(privateVlanTag);
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='" + ruleName + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Firewall policy exists: " + ruleName + ", " + result);
                  return result;
  
              case ADD:
                  if (manageNetworkIsolation(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, privateVlanTag, privateSubnet, privateGateway)) {
                      return true;
                  }
  
                  String xml = "";
                  xml += "<from><member>" + _privateZone + "</member></from>";
                  xml += "<to><member>" + _privateZone + "</member></to>";
                  xml += "<source><member>" + privateSubnet + "</member></source>";
                  xml += "<destination><member>" + privateGateway + "</member></destination>";
                  xml += "<application><member>any</member></application>";
                  xml += "<service><member>any</member></service>";
                  xml += "<action>deny</action>";
                  xml += "<negate-source>no</negate-source>";
                  xml += "<negate-destination>yes</negate-destination>";
  
                  Map<String, String> a_params = new HashMap<String, String>();
                  a_params.put("type", "config");
                  a_params.put("action", "set");
                  a_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='" + ruleName + "']");
                  a_params.put("element", xml);
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, a_params));
  
                  return true;
  
              case DELETE:
                  if (!manageNetworkIsolation(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, privateVlanTag, privateSubnet, privateGateway)) {
                      return true;
                  }
  
                  Map<String, String> d_params = new HashMap<String, String>();
                  d_params.put("type", "config");
                  d_params.put("action", "delete");
                  d_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='" + ruleName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.POST, d_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      // make the interfaces pingable for basic network troubleshooting
      public boolean managePingProfile(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim) throws ExecutionException {
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/network/profiles/interface-management-profile/entry[@name='" + _pingManagementProfile + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Management profile exists: " + _pingManagementProfile + ", " + result);
                  return result;
  
              case ADD:
                  if (managePingProfile(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS)) {
                      return true;
                  }
  
                  // add ping profile...
                  Map<String, String> a_params = new HashMap<String, String>();
                  a_params.put("type", "config");
                  a_params.put("action", "set");
                  a_params.put("xpath", "/config/devices/entry/network/profiles/interface-management-profile/entry[@name='" + _pingManagementProfile + "']");
                  a_params.put("element", "<ping>yes</ping>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_params));
  
                  return true;
  
              case DELETE:
                  if (!managePingProfile(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS)) {
                      return true;
                  }
  
                  // delete ping profile...
                  Map<String, String> d_params = new HashMap<String, String>();
                  d_params.put("type", "config");
                  d_params.put("action", "delete");
                  d_params.put("xpath", "/config/devices/entry/network/profiles/interface-management-profile/entry[@name='" + _pingManagementProfile + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      private String genServiceName(String protocol, String dstPorts, String srcPorts) {
          String name;
          if (srcPorts == null) {
              name = "cs_" + protocol.toLowerCase() + "_" + dstPorts.replace(',', '.');
          } else {
              name = "cs_" + protocol.toLowerCase() + "_" + dstPorts.replace(',', '.') + "_" + srcPorts.replace(',', '.');
          }
          return name;
      }
  
      public boolean manageService(ArrayList<IPaloAltoCommand> cmdList, PaloAltoPrimative prim, String protocol, String dstPorts, String srcPorts)
          throws ExecutionException {
          String serviceName = genServiceName(protocol, dstPorts, srcPorts);
  
          switch (prim) {
  
              case CHECK_IF_EXISTS:
                  // check if one exists already
                  Map<String, String> params = new HashMap<String, String>();
                  params.put("type", "config");
                  params.put("action", "get");
                  params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/service/entry[@name='" + serviceName + "']");
                  String response = request(PaloAltoMethod.GET, params);
                  boolean result = (validResponse(response) && responseNotEmpty(response));
                  logger.debug("Service exists: " + serviceName + ", " + result);
                  return result;
  
              case ADD:
                  if (manageService(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, protocol, dstPorts, srcPorts)) {
                      return true;
                  }
  
                  String dstPortXML = "<port>" + dstPorts + "</port>";
                  String srcPortXML = "";
                  if (srcPorts != null) {
                      srcPortXML = "<source-port>" + srcPorts + "</source-port>";
                  }
  
                  // add ping profile...
                  Map<String, String> a_params = new HashMap<String, String>();
                  a_params.put("type", "config");
                  a_params.put("action", "set");
                  a_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/service/entry[@name='" + serviceName + "']");
                  a_params.put("element", "<protocol><" + protocol.toLowerCase() + ">" + dstPortXML + srcPortXML + "</" + protocol.toLowerCase() + "></protocol>");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, a_params));
  
                  return true;
  
              case DELETE:
                  if (!manageService(cmdList, PaloAltoPrimative.CHECK_IF_EXISTS, protocol, dstPorts, srcPorts)) {
                      return true;
                  }
  
                  // delete ping profile...
                  Map<String, String> d_params = new HashMap<String, String>();
                  d_params.put("type", "config");
                  d_params.put("action", "delete");
                  d_params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/service/entry[@name='" + serviceName + "']");
                  cmdList.add(new DefaultPaloAltoCommand(PaloAltoMethod.GET, d_params));
  
                  return true;
  
              default:
                  logger.debug("Unrecognized command.");
                  return false;
          }
      }
  
      private String getPrivateSubnet(String vlan) throws ExecutionException {
          String _interfaceName = genPrivateInterfaceName(Long.parseLong(vlan));
          Map<String, String> params = new HashMap<String, String>();
          params.put("type", "config");
          params.put("action", "get");
          params.put("xpath", "/config/devices/entry/network/interface/" + _privateInterfaceType + "/entry[@name='" + _privateInterface + "']/layer3/units/entry[@name='" +
              _interfaceName + "']/ip/entry");
          String response = request(PaloAltoMethod.GET, params);
          if (validResponse(response) && responseNotEmpty(response)) {
              NodeList response_body;
              Document doc = getDocument(response);
              XPath xpath = XPathFactory.newInstance().newXPath();
              try {
                  XPathExpression expr = xpath.compile("/response[@status='success']/result/entry");
                  response_body = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
              } catch (XPathExpressionException e) {
                  throw new ExecutionException(e.getCause().getMessage());
              }
              if (response_body.getLength() > 0) {
                  return response_body.item(0).getAttributes().getNamedItem("name").getTextContent();
              }
          }
          return null;
      }
  
      /*
       * XML API commands
       */
  
      /* Function to make calls to the Palo Alto API. */
      /* All API calls will end up going through this function. */
      protected String request(PaloAltoMethod method, Map<String, String> params) throws ExecutionException {
          if (method != PaloAltoMethod.GET && method != PaloAltoMethod.POST) {
              throw new ExecutionException("Invalid http method used to access the Palo Alto API.");
          }
  
          String responseBody = "";
          String debug_msg = "Palo Alto Request\n";
  
          // a GET method...
          if (method == PaloAltoMethod.GET) {
              String queryString = "?";
              for (String key : params.keySet()) {
                  if (!queryString.equals("?")) {
                      queryString = queryString + "&";
                  }
                  try {
                      queryString = queryString + key + "=" + URLEncoder.encode(params.get(key), "UTF-8");
                  } catch (UnsupportedEncodingException e) {
                      throw new ExecutionException(e.getMessage());
                  }
              }
              if (_key != null) {
                  queryString = queryString + "&key=" + _key;
              }
  
              try {
                  debug_msg = debug_msg + "GET request: https://" + _ip + s_apiUri + URLDecoder.decode(queryString, "UTF-8") + "\n";
              } catch (UnsupportedEncodingException e) {
                  debug_msg = debug_msg + "GET request: https://" + _ip + s_apiUri + queryString + "\n";
              }
  
              HttpGet get_request = new HttpGet("https://" + _ip + s_apiUri + queryString);
              ResponseHandler<String> responseHandler = new BasicResponseHandler();
              try {
                  responseBody = s_httpclient.execute(get_request, responseHandler);
              } catch (IOException e) {
                  throw new ExecutionException(e.getMessage());
              }
          }
  
          // a POST method...
          if (method == PaloAltoMethod.POST) {
              List<NameValuePair> nvps = new ArrayList<NameValuePair>();
              for (String key : params.keySet()) {
                  nvps.add(new BasicNameValuePair(key, params.get(key)));
              }
              if (_key != null) {
                  nvps.add(new BasicNameValuePair("key", _key));
              }
  
              debug_msg = debug_msg + "POST request: https://" + _ip + s_apiUri + "\n";
              for (NameValuePair nvp : nvps) {
                  debug_msg = debug_msg + "param: " + nvp.getName() + ", " + nvp.getValue() + "\n";
              }
  
              HttpPost post_request = new HttpPost("https://" + _ip + s_apiUri);
              try {
                  post_request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
              } catch (UnsupportedEncodingException e) {
                  throw new ExecutionException(e.getMessage());
              }
              ResponseHandler<String> responseHandler = new BasicResponseHandler();
              try {
                  responseBody = s_httpclient.execute(post_request, responseHandler);
              } catch (IOException e) {
                  throw new ExecutionException(e.getMessage());
              }
          }
  
          debug_msg = debug_msg + prettyFormat(responseBody);
          debug_msg = debug_msg + "\n" + responseBody.replace("\"", "\\\"") + "\n\n"; // test cases
          //logger.debug(debug_msg); // this can be commented if we don't want to show each request in the log.
  
          return responseBody;
      }
  
      /* Used for requests that require polling to get a result (eg: commit) */
      private String requestWithPolling(PaloAltoMethod method, Map<String, String> params) throws ExecutionException {
          String job_id;
          String job_response = request(method, params);
          Document doc = getDocument(job_response);
          XPath xpath = XPathFactory.newInstance().newXPath();
          try {
              XPathExpression expr = xpath.compile("/response[@status='success']/result/job/text()");
              job_id = (String)expr.evaluate(doc, XPathConstants.STRING);
          } catch (XPathExpressionException e) {
              throw new ExecutionException(e.getCause().getMessage());
          }
          if (job_id.length() > 0) {
              boolean finished = false;
              Map<String, String> job_params = new HashMap<String, String>();
              job_params.put("type", "op");
              job_params.put("cmd", "<show><jobs><id>" + job_id + "</id></jobs></show>");
  
              while (!finished) {
                  String job_status;
                  String response = request(PaloAltoMethod.GET, job_params);
                  Document job_doc = getDocument(response);
                  XPath job_xpath = XPathFactory.newInstance().newXPath();
                  try {
                      XPathExpression expr = job_xpath.compile("/response[@status='success']/result/job/status/text()");
                      job_status = (String)expr.evaluate(job_doc, XPathConstants.STRING);
                  } catch (XPathExpressionException e) {
                      throw new ExecutionException(e.getCause().getMessage());
                  }
                  if (job_status.equals("FIN")) {
                      finished = true;
                      String job_result;
                      try {
                          XPathExpression expr = job_xpath.compile("/response[@status='success']/result/job/result/text()");
                          job_result = (String)expr.evaluate(job_doc, XPathConstants.STRING);
                      } catch (XPathExpressionException e) {
                          throw new ExecutionException(e.getCause().getMessage());
                      }
                      if (!job_result.equals("OK")) {
                          NodeList job_details;
                          try {
                              XPathExpression expr = job_xpath.compile("/response[@status='success']/result/job/details/line");
                              job_details = (NodeList)expr.evaluate(job_doc, XPathConstants.NODESET);
                          } catch (XPathExpressionException e) {
                              throw new ExecutionException(e.getCause().getMessage());
                          }
                          String error = "";
                          for (int i = 0; i < job_details.getLength(); i++) {
                              error = error + job_details.item(i).getTextContent() + "\n";
                          }
                          throw new ExecutionException(error);
                      }
                      return response;
                  } else {
                      try {
                          Thread.sleep(2000); // poll periodically for the status of the async job...
                      } catch (InterruptedException e) { /* do nothing */
                      }
                  }
              }
          } else {
              return job_response;
          }
          return null;
      }
  
      /* Runs a sequence of commands and attempts to commit at the end. */
      /* Uses the Command pattern to enable overriding of the response handling if needed. */
      private synchronized boolean requestWithCommit(ArrayList<IPaloAltoCommand> commandList) throws ExecutionException {
          boolean result = true;
  
          if (commandList.size() > 0) {
              // CHECK IF THERE IS PENDING CHANGES THAT HAVE NOT BEEN COMMITTED...
              String pending_changes;
              Map<String, String> check_params = new HashMap<String, String>();
              check_params.put("type", "op");
              check_params.put("cmd", "<check><pending-changes></pending-changes></check>");
              String check_response = request(PaloAltoMethod.GET, check_params);
              Document check_doc = getDocument(check_response);
              XPath check_xpath = XPathFactory.newInstance().newXPath();
              try {
                  XPathExpression expr = check_xpath.compile("/response[@status='success']/result/text()");
                  pending_changes = (String)expr.evaluate(check_doc, XPathConstants.STRING);
              } catch (XPathExpressionException e) {
                  throw new ExecutionException(e.getCause().getMessage());
              }
              if (pending_changes.equals("yes")) {
                  throw new ExecutionException("The Palo Alto has uncommited changes, so no changes can be made.  Try again later or contact your administrator.");
              } else {
                  // ADD A CONFIG LOCK TO CAPTURE THE PALO ALTO RESOURCE
                  String add_lock_status;
                  Map<String, String> add_lock_params = new HashMap<String, String>();
                  add_lock_params.put("type", "op");
                  add_lock_params.put("cmd", "<request><config-lock><add></add></config-lock></request>");
                  String add_lock_response = request(PaloAltoMethod.GET, add_lock_params);
                  Document add_lock_doc = getDocument(add_lock_response);
                  XPath add_lock_xpath = XPathFactory.newInstance().newXPath();
                  try {
                      XPathExpression expr = add_lock_xpath.compile("/response[@status='success']/result/text()");
                      add_lock_status = (String)expr.evaluate(add_lock_doc, XPathConstants.STRING);
                  } catch (XPathExpressionException e) {
                      throw new ExecutionException(e.getCause().getMessage());
                  }
                  if (add_lock_status.length() == 0) {
                      throw new ExecutionException("The Palo Alto is locked, no changes can be made at this time.");
                  }
  
                  try {
                      // RUN THE SEQUENCE OF COMMANDS
                      for (IPaloAltoCommand command : commandList) {
                          result = (result && command.execute()); // run commands and modify result boolean
                      }
  
                      // COMMIT THE CHANGES (ALSO REMOVES CONFIG LOCK)
                      String commit_job_id;
                      Map<String, String> commit_params = new HashMap<String, String>();
                      commit_params.put("type", "commit");
                      commit_params.put("cmd", "<commit></commit>");
                      String commit_response = requestWithPolling(PaloAltoMethod.GET, commit_params);
                      Document commit_doc = getDocument(commit_response);
                      XPath commit_xpath = XPathFactory.newInstance().newXPath();
                      try {
                          XPathExpression expr = commit_xpath.compile("/response[@status='success']/result/job/id/text()");
                          commit_job_id = (String)expr.evaluate(commit_doc, XPathConstants.STRING);
                      } catch (XPathExpressionException e) {
                          throw new ExecutionException(e.getCause().getMessage());
                      }
                      if (commit_job_id.length() == 0) { // no commit was done, so release the lock...
                          // REMOVE THE CONFIG LOCK TO RELEASE THE PALO ALTO RESOURCE
                          String remove_lock_status;
                          Map<String, String> remove_lock_params = new HashMap<String, String>();
                          remove_lock_params.put("type", "op");
                          remove_lock_params.put("cmd", "<request><config-lock><remove></remove></config-lock></request>");
                          String remove_lock_response = request(PaloAltoMethod.GET, remove_lock_params);
                          Document remove_lock_doc = getDocument(remove_lock_response);
                          XPath remove_lock_xpath = XPathFactory.newInstance().newXPath();
                          try {
                              XPathExpression expr = remove_lock_xpath.compile("/response[@status='success']/result/text()");
                              remove_lock_status = (String)expr.evaluate(remove_lock_doc, XPathConstants.STRING);
                          } catch (XPathExpressionException e) {
                              throw new ExecutionException(e.getCause().getMessage());
                          }
                          if (remove_lock_status.length() == 0) {
                              throw new ExecutionException("Could not release the Palo Alto device.  Please notify an administrator!");
                          }
                      }
  
                  } catch (ExecutionException ex) {
                      // REVERT TO RUNNING
                      String revert_job_id;
                      Map<String, String> revert_params = new HashMap<String, String>();
                      revert_params.put("type", "op");
                      revert_params.put("cmd", "<load><config><from>running-config.xml</from></config></load>");
                      requestWithPolling(PaloAltoMethod.GET, revert_params);
  
                      // REMOVE THE CONFIG LOCK TO RELEASE THE PALO ALTO RESOURCE
                      String remove_lock_status;
                      Map<String, String> remove_lock_params = new HashMap<String, String>();
                      remove_lock_params.put("type", "op");
                      remove_lock_params.put("cmd", "<request><config-lock><remove></remove></config-lock></request>");
                      String remove_lock_response = request(PaloAltoMethod.GET, remove_lock_params);
                      Document remove_lock_doc = getDocument(remove_lock_response);
                      XPath remove_lock_xpath = XPathFactory.newInstance().newXPath();
                      try {
                          XPathExpression expr = remove_lock_xpath.compile("/response[@status='success']/result/text()");
                          remove_lock_status = (String)expr.evaluate(remove_lock_doc, XPathConstants.STRING);
                      } catch (XPathExpressionException e) {
                          throw new ExecutionException(e.getCause().getMessage());
                      }
                      if (remove_lock_status.length() == 0) {
                          throw new ExecutionException("Could not release the Palo Alto device.  Please notify an administrator!");
                      }
  
                      throw ex; // Bubble up the reason we reverted...
                  }
  
                  return result;
              }
          } else {
              return true; // nothing to do
          }
      }
  
      /* A default response handler to validate that the request was successful. */
      public boolean validResponse(String response) throws ExecutionException {
          NodeList response_body;
          Document doc = getDocument(response);
          XPath xpath = XPathFactory.newInstance().newXPath();
          try {
              XPathExpression expr = xpath.compile("/response[@status='success']");
              response_body = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
          } catch (XPathExpressionException e) {
              throw new ExecutionException(e.getCause().getMessage());
          }
  
          if (response_body.getLength() > 0) {
              return true;
          } else {
              NodeList error_details;
              try {
                  XPathExpression expr = xpath.compile("/response/msg/line/line");
                  error_details = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
              } catch (XPathExpressionException e) {
                  throw new ExecutionException(e.getCause().getMessage());
              }
              if (error_details.getLength() == 0) {
                  try {
                      XPathExpression expr = xpath.compile("/response/msg/line");
                      error_details = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
                  } catch (XPathExpressionException e) {
                      throw new ExecutionException(e.getCause().getMessage());
                  }
  
                  if (error_details.getLength() == 0) {
                      try {
                          XPathExpression expr = xpath.compile("/response/result/msg");
                          error_details = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
                      } catch (XPathExpressionException e) {
                          throw new ExecutionException(e.getCause().getMessage());
                      }
                  }
              }
              String error = "";
              for (int i = 0; i < error_details.getLength(); i++) {
                  error = error + error_details.item(i).getTextContent() + "\n";
              }
              throw new ExecutionException(error);
          }
      }
  
      /* Validate that the response is not empty. */
      public boolean responseNotEmpty(String response) throws ExecutionException {
          NodeList response_body;
          Document doc = getDocument(response);
          XPath xpath = XPathFactory.newInstance().newXPath();
          try {
              XPathExpression expr = xpath.compile("/response[@status='success']");
              response_body = (NodeList)expr.evaluate(doc, XPathConstants.NODESET);
          } catch (XPathExpressionException e) {
              throw new ExecutionException(e.getCause().getMessage());
          }
  
          if (response_body.getLength() > 0 &&
              (!response_body.item(0).getTextContent().equals("") || (response_body.item(0).hasChildNodes() && response_body.item(0).getFirstChild().hasChildNodes()))) {
              return true;
          } else {
              return false;
          }
      }
  
      /* Get the type of interface from the PA device. */
      private String getInterfaceType(String interfaceName) throws ExecutionException {
          String[] types = {InterfaceType.ETHERNET.toString(), InterfaceType.AGGREGATE.toString()};
          for (String type : types) {
              Map<String, String> params = new HashMap<String, String>();
              params.put("type", "config");
              params.put("action", "get");
              params.put("xpath", "/config/devices/entry/network/interface/" + type + "/entry[@name='" + interfaceName + "']");
              String ethernet_response = request(PaloAltoMethod.GET, params);
              if (validResponse(ethernet_response) && responseNotEmpty(ethernet_response)) {
                  return type;
              }
          }
          return "";
      }
  
      /* Get the threat profile from the server if it exists. */
      private boolean getThreatProfile(String profile) throws ExecutionException {
          Map<String, String> params = new HashMap<String, String>();
          params.put("type", "config");
          params.put("action", "get");
          params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/profile-group/entry[@name='" + profile + "']");
          String response = request(PaloAltoMethod.GET, params);
          return (validResponse(response) && responseNotEmpty(response));
      }
  
      /* Get the log profile from the server if it exists. */
      private boolean getLogProfile(String profile) throws ExecutionException {
          Map<String, String> params = new HashMap<String, String>();
          params.put("type", "config");
          params.put("action", "get");
          params.put("xpath", "/config/devices/entry/vsys/entry[@name='vsys1']/log-settings/profiles/entry[@name='" + profile + "']");
          String response = request(PaloAltoMethod.GET, params);
          return (validResponse(response) && responseNotEmpty(response));
      }
  
      /* Command Interface */
      public interface IPaloAltoCommand {
          public boolean execute() throws ExecutionException;
      }
  
      /* Command Abstract */
      private abstract class AbstractPaloAltoCommand implements IPaloAltoCommand {
          PaloAltoMethod method;
          Map<String, String> params;
  
          public AbstractPaloAltoCommand() {
          }
  
          public AbstractPaloAltoCommand(PaloAltoMethod method, Map<String, String> params) {
              this.method = method;
              this.params = params;
          }
  
          @Override
          public boolean execute() throws ExecutionException {
              String response = request(method, params);
              return validResponse(response);
          }
      }
  
      /* Implement the default functionality */
      private class DefaultPaloAltoCommand extends AbstractPaloAltoCommand {
          public DefaultPaloAltoCommand(PaloAltoMethod method, Map<String, String> params) {
              super(method, params);
          }
      }
  
      /*
       * Misc
       */
  
      private String genIpIdentifier(String ip) {
          return ip.replace('.', '-').replace('/', '-');
      }
  
      private String parsePublicVlanTag(String uri) {
          return uri.replace("vlan://", "");
      }
  
      private Protocol getProtocol(String protocolName) throws ExecutionException {
          protocolName = protocolName.toLowerCase();
  
          try {
              return Protocol.valueOf(protocolName);
          } catch (Exception e) {
              throw new ExecutionException("Invalid protocol: " + protocolName);
          }
      }
  
      private Document getDocument(String xml) throws ExecutionException {
          StringReader xmlReader = new StringReader(xml);
          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;
          }
      }
  
      // return an xml node as a string
      private String nodeToString(Node node) throws ExecutionException {
          StringWriter sw = new StringWriter();
          try {
              Transformer t = ParserUtils.getSaferTransformerFactory().newTransformer();
              t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
              t.transform(new DOMSource(node), new StreamResult(sw));
          } catch (Throwable t) {
              throw new ExecutionException("XML convert error when modifying PA config: " + t.getMessage());
          }
          return sw.toString();
      }
  
      // pretty printing of xml strings
      private String prettyFormat(String input) {
          int indent = 4;
          try {
              Source xmlInput = new StreamSource(new StringReader(input));
              StringWriter stringWriter = new StringWriter();
              StreamResult xmlOutput = new StreamResult(stringWriter);
              TransformerFactory transformerFactory = ParserUtils.getSaferTransformerFactory();
              transformerFactory.setAttribute("indent-number", indent);
              Transformer transformer = transformerFactory.newTransformer();
              transformer.setOutputProperty(OutputKeys.INDENT, "yes");
              transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
              transformer.transform(xmlInput, xmlOutput);
              return xmlOutput.getWriter().toString();
          } catch (Throwable e) {
              try {
                  Source xmlInput = new StreamSource(new StringReader(input));
                  StringWriter stringWriter = new StringWriter();
                  StreamResult xmlOutput = new StreamResult(stringWriter);
                  TransformerFactory transformerFactory = ParserUtils.getSaferTransformerFactory();
                  Transformer transformer = transformerFactory.newTransformer();
                  transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent));
                  transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
                  transformer.transform(xmlInput, xmlOutput);
                  return xmlOutput.getWriter().toString();
              } catch (Throwable t) {
                  return input;
              }
          }
      }
  
      //@Override
      @Override
      public void setName(String name) {
          // TODO Auto-generated method stub
      }
  
      //@Override
      @Override
      public void setConfigParams(Map<String, Object> params) {
          // TODO Auto-generated method stub
      }
  
      //@Override
      @Override
      public Map<String, Object> getConfigParams() {
          // TODO Auto-generated method stub
          return null;
      }
  
      //@Override
      @Override
      public int getRunLevel() {
          // TODO Auto-generated method stub
          return 0;
      }
  
      //@Override
      @Override
      public void setRunLevel(int level) {
          // TODO Auto-generated method stub
      }
  }
  