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.vpc;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Objects;
  
  import javax.inject.Inject;
  
  import com.cloud.dc.DataCenter;
  import com.cloud.exception.PermissionDeniedException;
  import com.cloud.network.dao.NetrisProviderDao;
  import com.cloud.network.dao.NsxProviderDao;
  import com.cloud.network.element.NetrisProviderVO;
  import com.cloud.network.element.NsxProviderVO;
  import org.apache.cloudstack.api.ApiErrorCode;
  import org.apache.cloudstack.api.ServerApiException;
  import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
  import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd;
  import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
  import org.apache.cloudstack.api.command.user.network.MoveNetworkAclItemCmd;
  import org.apache.cloudstack.api.command.user.network.UpdateNetworkACLItemCmd;
  import org.apache.cloudstack.api.command.user.network.UpdateNetworkACLListCmd;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.commons.codec.digest.DigestUtils;
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.lang3.ObjectUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.springframework.stereotype.Component;
  
  import com.cloud.event.ActionEvent;
  import com.cloud.event.EventTypes;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.network.Network;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.Networks;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.vpc.NetworkACLItem.Action;
  import com.cloud.network.vpc.NetworkACLItem.TrafficType;
  import com.cloud.network.vpc.dao.NetworkACLDao;
  import com.cloud.network.vpc.dao.VpcDao;
  import com.cloud.network.vpc.dao.VpcGatewayDao;
  import com.cloud.projects.Project.ListProjectResourcesCriteria;
  import com.cloud.server.ResourceTag.ResourceObjectType;
  import com.cloud.tags.ResourceTagVO;
  import com.cloud.tags.dao.ResourceTagDao;
  import com.cloud.user.Account;
  import com.cloud.user.AccountManager;
  import com.cloud.user.User;
  import com.cloud.utils.Pair;
  import com.cloud.utils.Ternary;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.db.EntityManager;
  import com.cloud.utils.db.Filter;
  import com.cloud.utils.db.JoinBuilder;
  import com.cloud.utils.db.SearchBuilder;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.utils.db.SearchCriteria.Op;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.net.NetUtils;
  
  @Component
  public class NetworkACLServiceImpl extends ManagerBase implements NetworkACLService {
  
      @Inject
      private AccountManager _accountMgr;
      @Inject
      private ResourceTagDao _resourceTagDao;
      @Inject
      private NetworkACLDao _networkACLDao;
      @Inject
      private NetworkACLItemDao _networkACLItemDao;
      @Inject
      private NetworkModel networkModel;
      @Inject
      private NetworkDao _networkDao;
      @Inject
      private NetworkACLManager _networkAclMgr;
      @Inject
      private VpcGatewayDao _vpcGatewayDao;
      @Inject
      private EntityManager _entityMgr;
      @Inject
      private VpcDao _vpcDao;
      @Inject
      private VpcService _vpcSvc;
      @Inject
      private NsxProviderDao nsxProviderDao;
      @Inject
      private NetrisProviderDao netrisProviderDao;
  
      private String supportedProtocolsForAclRules = "tcp,udp,icmp,all";
  
      @Override
      public NetworkACL createNetworkACL(final String name, final String description, final long vpcId, final Boolean forDisplay) {
          if (vpcId != 0) {
              final Account caller = CallContext.current().getCallingAccount();
              final Vpc vpc = _vpcDao.findById(vpcId);
              if (vpc == null) {
                  throw new InvalidParameterValueException(String.format("Unable to find VPC with ID [%s].", vpcId));
              }
              _accountMgr.checkAccess(caller, null, true, vpc);
  
          }
          return _networkAclMgr.createNetworkACL(name, description, vpcId, forDisplay);
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_CREATE, eventDescription = "creating network acl list", async = true)
      public NetworkACL getNetworkACL(final long id) {
          return _networkAclMgr.getNetworkACL(id);
      }
  
      @Override
      public Pair<List<? extends NetworkACL>, Integer> listNetworkACLs(final ListNetworkACLListsCmd cmd) {
          final Long id = cmd.getId();
          final String name = cmd.getName();
          final Long networkId = cmd.getNetworkId();
          final Long vpcId = cmd.getVpcId();
          final String keyword = cmd.getKeyword();
          final Boolean display = cmd.getDisplay();
  
          final SearchBuilder<NetworkACLVO> sb = _networkACLDao.createSearchBuilder();
          sb.and("id", sb.entity().getId(), Op.EQ);
          sb.and("name", sb.entity().getName(), Op.EQ);
          sb.and("vpcId", sb.entity().getVpcId(), Op.IN);
          sb.and("display", sb.entity().isDisplay(), Op.EQ);
  
          final Account caller = CallContext.current().getCallingAccount();
  
          if (networkId != null) {
              final SearchBuilder<NetworkVO> network = _networkDao.createSearchBuilder();
              network.and("networkId", network.entity().getId(), Op.EQ);
              sb.join("networkJoin", network, sb.entity().getId(), network.entity().getNetworkACLId(), JoinBuilder.JoinType.INNER);
          }
  
          SearchCriteria<NetworkACLVO> sc = sb.create();
  
          if (keyword != null) {
              final SearchCriteria<NetworkACLVO> ssc = _networkACLDao.createSearchCriteria();
              ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
              ssc.addOr("description", SearchCriteria.Op.LIKE, "%" + keyword + "%");
              sc.addAnd("name", SearchCriteria.Op.SC, ssc);
          }
  
          if (display != null) {
              sc.setParameters("display", display);
          }
  
          if (id != null) {
              sc.setParameters("id", id);
          }
  
          if (name != null) {
              sc.setParameters("name", name);
          }
  
          if (vpcId != null) {
              final Vpc vpc = _entityMgr.findById(Vpc.class, vpcId);
              if (vpc == null) {
                  throw new InvalidParameterValueException("Unable to find VPC");
              }
              _accountMgr.checkAccess(caller, null, true, vpc);
              //Include vpcId 0 to list default ACLs
              sc.setParameters("vpcId", vpcId, 0);
          } else {
              //ToDo: Add accountId to network_acl table for permission check
  
              // VpcId is not specified. Find permitted VPCs for the caller
              // and list ACLs belonging to the permitted VPCs
              final List<Long> permittedAccounts = new ArrayList<Long>();
              Long domainId = cmd.getDomainId();
              boolean isRecursive = cmd.isRecursive();
              final String accountName = cmd.getAccountName();
              final Long projectId = cmd.getProjectId();
              final boolean listAll = cmd.listAll();
              final Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, isRecursive, null);
              _accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccounts, domainIdRecursiveListProject, listAll, false);
              domainId = domainIdRecursiveListProject.first();
              isRecursive = domainIdRecursiveListProject.second();
              final ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
              final SearchBuilder<VpcVO> sbVpc = _vpcDao.createSearchBuilder();
              _accountMgr.buildACLSearchBuilder(sbVpc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
              final SearchCriteria<VpcVO> scVpc = sbVpc.create();
              _accountMgr.buildACLSearchCriteria(scVpc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
              final List<VpcVO> vpcs = _vpcDao.search(scVpc, null);
              final List<Long> vpcIds = new ArrayList<Long>();
              for (final VpcVO vpc : vpcs) {
                  vpcIds.add(vpc.getId());
              }
              //Add vpc_id 0 to list default ACLs
              vpcIds.add(0L);
              sc.setParameters("vpcId", vpcIds.toArray());
          }
  
          if (networkId != null) {
              sc.setJoinParameters("networkJoin", "networkId", networkId);
          }
  
          final Filter filter = new Filter(NetworkACLVO.class, "id", false, cmd.getStartIndex(), cmd.getPageSizeVal());
          final Pair<List<NetworkACLVO>, Integer> acls = _networkACLDao.searchAndCount(sc, filter);
          return new Pair<List<? extends NetworkACL>, Integer>(acls.first(), acls.second());
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_DELETE, eventDescription = "Deleting Network ACL List", async = true)
      public boolean deleteNetworkACL(final long id) {
          final NetworkACL acl = _networkACLDao.findById(id);
          Account account = CallContext.current().getCallingAccount();
          if (acl == null) {
              throw new InvalidParameterValueException("Unable to find specified ACL");
          }
  
          if (isDefaultAcl(acl.getId())) {
              throw new InvalidParameterValueException("Default ACL cannot be removed");
          }
  
          validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admin can delete global ACLs.");
          return _networkAclMgr.deleteNetworkACL(acl);
      }
  
      @Override
      public boolean replaceNetworkACLonPrivateGw(final long aclId, final long privateGatewayId) throws ResourceUnavailableException {
          final Account caller = CallContext.current().getCallingAccount();
          final VpcGateway gateway = _vpcGatewayDao.findById(privateGatewayId);
          if (gateway == null) {
              throw new InvalidParameterValueException("Unable to find specified private gateway");
          }
  
          final VpcGatewayVO vo = _vpcGatewayDao.findById(privateGatewayId);
          if (vo.getState() != VpcGateway.State.Ready) {
              throw new InvalidParameterValueException("Gateway is not in Ready state");
          }
  
          final NetworkACL acl = _networkACLDao.findById(aclId);
          if (acl == null) {
              throw new InvalidParameterValueException("Unable to find specified NetworkACL");
          }
  
          if (gateway.getVpcId() == null) {
              throw new InvalidParameterValueException("Unable to find specified vpc id");
          }
  
          if (!isDefaultAcl(aclId)) {
              final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
              if (vpc == null) {
                  throw new InvalidParameterValueException("Unable to find Vpc associated with the NetworkACL");
              }
              _accountMgr.checkAccess(caller, null, true, vpc);
              if (!gateway.getVpcId().equals(acl.getVpcId())) {
                  throw new InvalidParameterValueException(String.format("private gateway: %s and ACL: %s do not belong to the same VPC", vo, acl));
              }
          }
  
          final PrivateGateway privateGateway = _vpcSvc.getVpcPrivateGateway(gateway.getId());
          _accountMgr.checkAccess(caller, null, true, privateGateway);
  
          return _networkAclMgr.replaceNetworkACLForPrivateGw(acl, privateGateway);
  
      }
  
      @Override
      public boolean replaceNetworkACL(final long aclId, final long networkId) throws ResourceUnavailableException {
          final Account caller = CallContext.current().getCallingAccount();
  
          final NetworkVO network = _networkDao.findById(networkId);
          if (network == null) {
              throw new InvalidParameterValueException("Unable to find specified Network");
          }
  
          final NetworkACL acl = _networkACLDao.findById(aclId);
          if (acl == null) {
              throw new InvalidParameterValueException("Unable to find specified NetworkACL");
          }
  
          if (network.getVpcId() == null) {
              throw new InvalidParameterValueException("Network is not part of a VPC: " + network.getUuid());
          }
  
          if (network.getTrafficType() != Networks.TrafficType.Guest) {
              throw new InvalidParameterValueException("Network ACL can be created just for networks of type " + Networks.TrafficType.Guest);
          }
  
          if (!isDefaultAcl(aclId) && !isGlobalAcl(acl.getVpcId())) {
              validateAclAssociatedToVpc(acl.getVpcId(), caller, acl.getUuid());
  
              if (!network.getVpcId().equals(acl.getVpcId())) {
                  throw new InvalidParameterValueException(String.format("Network: %s and ACL: %s do not belong to the same VPC", network, acl));
              }
          }
  
          return _networkAclMgr.replaceNetworkACL(acl, network);
      }
  
      /**
       * Creates and persists a network ACL rule. The network ACL rule is persisted as a {@link NetworkACLItemVO}.
       * If no ACL list ID is informed, we will create one. To check these details, please refer to {@link #createAclListIfNeeded(CreateNetworkACLCmd)}.
       * All of the attributes will be validated accordingly using the following methods:
       * <ul>
       * <li> {@link #validateAclRuleNumber(CreateNetworkACLCmd, Long, NetworkACL)} to validate the provided ACL rule number;
       * <li> {@link #validateNetworkAclList(Long, NetworkACL)} to check if the user has access to the informed ACL list ID and respective VPC;
       * <li> {@link #validateAndCreateNetworkAclRuleAction(String)} to validate the ACL rule action;
       * <li> {@link #validateNetworkACLItem(NetworkACLItemVO)} to validate general configurations relating to protocol, ports, and ICMP codes and types.
       * </ul>
       *
       * Moreover, if not ACL rule number is provided we generate one based on the last ACL number used. We will increment +1 in the last ACL rule number used. After all of the validation the ACL rule is persisted using the method {@link NetworkACLManagerImpl#createNetworkACLItem(NetworkACLItemVO)}.
       */
      @Override
      public NetworkACLItem createNetworkACLItem(CreateNetworkACLCmd createNetworkACLCmd) {
          Long aclId = createAclListIfNeeded(createNetworkACLCmd);
  
          Integer sourcePortStart = createNetworkACLCmd.getPublicStartPort();
          Integer sourcePortEnd = createNetworkACLCmd.getPublicEndPort();
          String protocol = createNetworkACLCmd.getProtocol();
          List<String> sourceCidrList = createNetworkACLCmd.getSourceCidrList();
          Integer icmpCode = createNetworkACLCmd.getIcmpCode();
          Integer icmpType = createNetworkACLCmd.getIcmpType();
          TrafficType trafficType = createNetworkACLCmd.getTrafficType();
          String reason = createNetworkACLCmd.getReason();
          String action = createNetworkACLCmd.getAction();
  
          NetworkACL acl = _networkAclMgr.getNetworkACL(aclId);
  
          validateNetworkAcl(acl);
          Account caller = CallContext.current().getCallingAccount();
  
          if (isGlobalAcl(acl.getVpcId()) && !Account.Type.ADMIN.equals(caller.getType())) {
              throw new PermissionDeniedException("Only Root Admins can create rules for a global ACL.");
          }
          validateNsxConstraints(acl.getVpcId(), protocol, icmpType, icmpCode, sourcePortStart, sourcePortEnd);
          validateAclRuleNumber(createNetworkACLCmd, acl);
  
          NetworkACLItem.Action ruleAction = validateAndCreateNetworkAclRuleAction(action);
          Integer number = createNetworkACLCmd.getNumber();
          if (number == null) {
              number = _networkACLItemDao.getMaxNumberByACL(aclId) + 1;
          }
          NetworkACLItemVO networkACLItemVO = new NetworkACLItemVO(sourcePortStart, sourcePortEnd, protocol, aclId, sourceCidrList, icmpCode, icmpType, trafficType, ruleAction, number, reason);
          networkACLItemVO.setDisplay(createNetworkACLCmd.isDisplay());
  
          validateNetworkACLItem(networkACLItemVO);
          return _networkAclMgr.createNetworkACLItem(networkACLItemVO);
      }
  
      /**
       *  We first validate the given ACL action as a string using {@link #validateNetworkAclRuleAction(String)}.
       *  Afterwards, we convert this ACL to an object of {@link NetworkACLItem.Action}.
       *  If the action as String matches the word 'deny' (ignoring case), we return an instance of {@link NetworkACLItem.Action#Deny}.
       *  Otherwise, we return {@link NetworkACLItem.Action#Allow}.
       */
      protected NetworkACLItem.Action validateAndCreateNetworkAclRuleAction(String action) {
          validateNetworkAclRuleAction(action);
          NetworkACLItem.Action ruleAction = NetworkACLItem.Action.Allow;
          if ("deny".equalsIgnoreCase(action)) {
              ruleAction = NetworkACLItem.Action.Deny;
          }
          return ruleAction;
      }
  
      /**
       * Validates the network ACL rule action given as a {@link String}.
       * If the parameter is null, we do not perform any validations. Otherwise, we check if the parameter is equal to 'Allow' or 'Deny' (ignoring the case).
       * If the parameter is an invalid action, we throw an {@link InvalidParameterValueException}.
       */
      protected void validateNetworkAclRuleAction(String action) {
          if (action != null) {
              if (!("Allow".equalsIgnoreCase(action) || "Deny".equalsIgnoreCase(action))) {
                  throw new InvalidParameterValueException(String.format("Invalid action [%s]. Permitted actions are Allow and Deny", action));
              }
          }
      }
  
      /**
       * Validates the ACL rule number field. If the field is null, then we do not have anything to check here.
       * If the number is not null, we perform the following checks:
       * <ul>
       *  <li>If number is less than one, than we throw an {@link InvalidParameterValueException};
       *  <li>if there is already an ACL configured with the given number for the network, we also throw an {@link InvalidParameterValueException}. The check is performed using {@link NetworkACLItemDao#findByAclAndNumber(long, int)} method.
       * </ul>
       *
       * At the end, if not exception is thrown, the number of the ACL rule is valid.
       */
      protected void validateAclRuleNumber(CreateNetworkACLCmd createNetworkAclCmd, NetworkACL acl) {
          Integer number = createNetworkAclCmd.getNumber();
          if (number != null) {
              if (number < 1) {
                  throw new InvalidParameterValueException(String.format("Invalid number [%d]. Number cannot be < 1", number));
              }
              if (_networkACLItemDao.findByAclAndNumber(acl.getId(), createNetworkAclCmd.getNumber()) != null) {
                  throw new InvalidParameterValueException("ACL item with number " + number + " already exists in ACL: " + acl.getUuid());
              }
          }
      }
  
      /**
       * Validates a given {@link NetworkACL}. The validations are the following:
       * <ul>
       *  <li>If the parameter is null, we return an  {@link InvalidParameterValueException};
       *  <li>Default ACLs {@link NetworkACL#DEFAULT_ALLOW} and {@link NetworkACL#DEFAULT_DENY} cannot be modified. Therefore, if any of them is provided we throw a {@link InvalidParameterValueException};
       *  <li>If no VPC is given, then it is a global ACL and there is no need to check any VPC ID. However, if a VPC is given and it does not exist, throws an
       *  {@link InvalidParameterValueException}.
       * </ul>
       *
       * After all validations, we check if the user has access to the given network ACL using {@link AccountManager#checkAccess(Account, org.apache.cloudstack.acl.SecurityChecker.AccessType, boolean, org.apache.cloudstack.acl.ControlledEntity...)}.
       */
      protected void validateNetworkAcl(NetworkACL acl) {
          if (acl == null) {
              throw new InvalidParameterValueException("Unable to find specified ACL.");
          }
  
          if (isDefaultAcl(acl.getId())) {
              throw new InvalidParameterValueException("Default ACL cannot be modified");
          }
  
          Long aclVpcId = acl.getVpcId();
          if (!isGlobalAcl(aclVpcId)) {
              validateAclAssociatedToVpc(aclVpcId, CallContext.current().getCallingAccount(), acl.getUuid());
          }
      }
  
      private void validateNsxConstraints(long vpcId, String protocol, Integer icmpType,
                                          Integer icmpCode, Integer sourcePortStart, Integer sourcePortEnd) {
          VpcVO vpc = _vpcDao.findById(vpcId);
          if (Objects.isNull(vpc)) {
              return;
          }
          final DataCenter dc = _entityMgr.findById(DataCenter.class, vpc.getZoneId());
          final NsxProviderVO nsxProvider = nsxProviderDao.findByZoneId(dc.getId());
          if (Objects.isNull(nsxProvider)) {
              return;
          }
  
          if (NetUtils.ICMP_PROTO.equals(protocol.toLowerCase(Locale.ROOT)) && (icmpType == -1 || icmpCode == -1)) {
              String errorMsg = "Passing -1 for ICMP type is not supported for NSX enabled zones";
              logger.error(errorMsg);
              throw new InvalidParameterValueException(errorMsg);
          }
  
          if (List.of(NetUtils.TCP_PROTO, NetUtils.UDP_PROTO).contains(protocol.toLowerCase(Locale.ROOT)) &&
                  (Objects.isNull(sourcePortStart) || Objects.isNull(sourcePortEnd))) {
              String errorMsg = "Source start and end ports are required to be passed";
              logger.error(errorMsg);
              throw new InvalidParameterValueException(errorMsg);
          }
      }
  
      /**
       * This methods will simply return the ACL rule list ID if it has been provided by the parameter 'createNetworkACLCmd'.
       * If no ACL rule List ID has been provided the method behave as follows:
       * <ul>
       *  <li> If it has not been provided either, we will throw an {@link InvalidParameterValueException};
       *  <li> if the network ID has been provided, we will check if the network has a VPC; if it does not have, we will throw an {@link InvalidParameterValueException};
       *  <ul>
       *      <li> If the VPC already has an ACL rule list, we will return it;
       *      <li> otherwise, we will create one using {@link #createAclListForNetworkAndReturnAclListId(CreateNetworkACLCmd, Network)} method. This behavior is a legacy thing that has been maintained so far.
       *  </ul>
       * </ul>
       *
       * @return The network ACL list ID
       */
      protected Long createAclListIfNeeded(CreateNetworkACLCmd createNetworkACLCmd) {
          Long aclId = createNetworkACLCmd.getACLId();
          if (aclId != null) {
              return aclId;
          }
          if (createNetworkACLCmd.getNetworkId() == null) {
              throw new InvalidParameterValueException("Cannot create Network ACL Item. ACL Id or network Id is required");
          }
          Network network = networkModel.getNetwork(createNetworkACLCmd.getNetworkId());
          if (network.getVpcId() == null) {
              throw new InvalidParameterValueException("Network: " + network.getUuid() + " does not belong to VPC");
          }
          aclId = network.getNetworkACLId();
  
          if (aclId == null) {
              aclId = createAclListForNetworkAndReturnAclListId(createNetworkACLCmd, network);
          }
          return aclId;
      }
  
      /**
       * This method will created a network ACL for the provided network. This method will behave as follows:
       * <ul>
       *  <li> If the network offering does not support ACLs ( {@link NetworkModel#areServicesSupportedByNetworkOffering(long, com.cloud.network.Network.Service...)} ), then it throws an {@link InvalidParameterValueException};
       *  <li> If the network does not have any VPC, it throws an {@link InvalidParameterValueException};
       *  <li> If everything is OK so far, we try to create the ACL using {@link NetworkACLManagerImpl#createNetworkACL(String, String, long, Boolean)} method.
       *  <ul>
       *      <li> If the ACL is not created we throw a {@link CloudRuntimeException};
       *      <li> otherwise, the workflow continues.
       *  </ul>
       *  <li> With the ACL in our hands, we try to apply it. If it does not work we throw a {@link CloudRuntimeException}.
       * </ul>
       *
       * @return the Id of the network ACL that is created.
       */
      protected Long createAclListForNetworkAndReturnAclListId(CreateNetworkACLCmd aclItemCmd, Network network) {
          logger.debug("Network {} is not associated with any ACL. Creating an ACL before adding acl item", network);
  
          if (!networkModel.areServicesSupportedByNetworkOffering(network.getNetworkOfferingId(), Network.Service.NetworkACL)) {
              throw new InvalidParameterValueException("Network Offering does not support NetworkACL service");
          }
  
          Vpc vpc = _entityMgr.findById(Vpc.class, network.getVpcId());
          if (vpc == null) {
              throw new InvalidParameterValueException("Unable to find Vpc associated with the Network");
          }
  
          String aclName = "VPC_" + vpc.getName() + "_Tier_" + network.getName() + "_ACL_" + network.getUuid();
          String description = "ACL for " + aclName;
          NetworkACL acl = _networkAclMgr.createNetworkACL(aclName, description, network.getVpcId(), aclItemCmd.isDisplay());
          if (acl == null) {
              throw new CloudRuntimeException(String.format("Error while create ACL before adding ACL Item for network %s", network));
          }
          logger.debug("Created ACL: {} for network {}", aclName, network);
          Long aclId = acl.getId();
          //Apply acl to network
          try {
              if (!_networkAclMgr.replaceNetworkACL(acl, (NetworkVO)network)) {
                  throw new CloudRuntimeException(String.format("Unable to apply auto created ACL to network %s", network));
              }
              logger.debug("Created ACL is applied to network {}", network);
          } catch (ResourceUnavailableException e) {
              throw new CloudRuntimeException(String.format("Unable to apply auto created ACL to network %s", network), e);
          }
          return aclId;
      }
  
      /**
       *  Performs all of the validations for the {@link NetworkACLItem}.
       *  First we validate the sources start and end ports using {@link #validateSourceStartAndEndPorts(NetworkACLItemVO)};
       *  then, we validate the source CIDR list using {@link #validateSourceCidrList(NetworkACLItemVO)};
       *  afterwards, it is validated the protocol entered in the {@link NetworkACLItemVO} using {@link #validateProtocol(NetworkACLItemVO)}.
       */
      protected void validateNetworkACLItem(NetworkACLItemVO networkACLItemVO) {
          validateSourceStartAndEndPorts(networkACLItemVO);
          validateSourceCidrList(networkACLItemVO);
          validateProtocol(networkACLItemVO);
      }
  
      /**
       * Validated ICMP type and code of {@link NetworkACLItemVO}. The behavior of this method is the following:
       * <ul>
       *  <li>If no ICMP type is provided, we do not perform validations;
       *  <li>If the ICMP type is not '-1', we validate it using {@link NetUtils#validateIcmpType(long)};
       *  <li>If the ICMP code is null, we do not perform validations;
       *  <li>If the ICMP code is not '-1', we validate it using {@link NetUtils#validateIcmpCode(long)};
       * </ul>
       * Failing to meet the above conditions, we throw an {@link InvalidParameterValueException}.
       */
      protected void validateIcmpTypeAndCode(NetworkACLItemVO networkACLItemVO) {
          Integer icmpType = networkACLItemVO.getIcmpType();
          Integer icmpCode = networkACLItemVO.getIcmpCode();
          if (icmpType == null) {
              return;
          }
          if (icmpType.longValue() != -1 && !NetUtils.validateIcmpType(icmpType.longValue())) {
              throw new InvalidParameterValueException(String.format("Invalid icmp type [%d]. It should belong to [0-255] range", icmpType));
          }
          if (icmpCode != null) {
              if (icmpCode.longValue() != -1 && !NetUtils.validateIcmpCode(icmpCode.longValue())) {
                  throw new InvalidParameterValueException(String.format("Invalid icmp code [%d]. It should belong to [0-16] range and can be defined when icmpType belongs to [0-40] range", icmpCode));
              }
          }
      }
  
      /**
       *   Validates the {@link NetworkACLItemVO} protocol. If the protocol is blank, we do not execute any validations. Otherwise, we perform the following checks:
       *   <ul>
       *      <li>If it is a numeric value, the protocol must be bigger or equal to 0 and smaller or equal to 255;
       *      <li>if it is a {@link String}, it must be one of the following: {@link #supportedProtocolsForAclRules};
       *   </ul>
       *    Whenever the conditions enumerated above are not met, we throw an {@link InvalidParameterValueException}.
       *
       *    If the parameter passes the protocol type validations, we check the following:
       *    <ul>
       *      <li>If it is not an ICMP type protocol, it cannot have any value in {@link NetworkACLItemVO#getIcmpCode()} and {@link NetworkACLItemVO#getIcmpType()};
       *      <li>If it is an ICMP type protocol, it cannot have any value in {@link NetworkACLItemVO#getSourcePortStart()} and {@link NetworkACLItemVO#getSourcePortEnd()}.
       *    </ul>
       *    Failing to meet the above conditions, we throw an {@link InvalidParameterValueException}.
       *
       *    The last check is performed via {@link #validateIcmpTypeAndCode(NetworkACLItemVO)} method.
       */
      protected void validateProtocol(NetworkACLItemVO networkACLItemVO) {
          String protocol = networkACLItemVO.getProtocol();
          if (StringUtils.isBlank(protocol)) {
              return;
          }
          if (StringUtils.isNumeric(protocol)) {
              int protoNumber = Integer.parseInt(protocol);
              if (protoNumber < 0 || protoNumber > 255) {
                  throw new InvalidParameterValueException("Invalid protocol number: " + protoNumber);
              }
          } else {
              if (!supportedProtocolsForAclRules.contains(protocol.toLowerCase())) {
                  throw new InvalidParameterValueException(String.format("Invalid protocol [%s]. Expected one of: [%s]", protocol, supportedProtocolsForAclRules));
              }
          }
  
          Integer icmpCode = networkACLItemVO.getIcmpCode();
          Integer icmpType = networkACLItemVO.getIcmpType();
          // icmp code and icmp type can't be passed in for any other protocol rather than icmp
          boolean isIcmpProtocol = protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) || protocol.equalsIgnoreCase(String.valueOf(NetUtils.ICMP_PROTO_NUMBER));
          if (!isIcmpProtocol && (icmpCode != null || icmpType != null)) {
              throw new InvalidParameterValueException("Can specify icmpCode and icmpType for ICMP protocol only");
          }
  
          Integer sourcePortStart = networkACLItemVO.getSourcePortStart();
          Integer sourcePortEnd = networkACLItemVO.getSourcePortEnd();
          if (isIcmpProtocol && (sourcePortStart != null || sourcePortEnd != null)) {
              throw new InvalidParameterValueException("Can't specify start/end port when protocol is ICMP");
          }
  
          validateIcmpTypeAndCode(networkACLItemVO);
      }
  
      /**
       *  Validates all of the CIDRs in the {@link NetworkACLItemVO#getSourceCidrList()}.
       *  If the list is empty we do not execute any validation. Otherwise, all of the CIDRs are validated using {@link NetUtils#isValidIp4Cidr(String)}.
       */
      protected void validateSourceCidrList(NetworkACLItemVO networkACLItemVO) {
          List<String> sourceCidrList = networkACLItemVO.getSourceCidrList();
          if (CollectionUtils.isNotEmpty(sourceCidrList)) {
              for (String cidr : sourceCidrList) {
                  if (!NetUtils.isValidIp4Cidr(cidr) && !NetUtils.isValidIp6Cidr(cidr)) {
                      throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Source cidrs formatting error " + cidr);
                  }
              }
          }
      }
  
      /**
       * Validates the source start and end ports for the given network ACL rule.
       * If both ports (start and end) are null, we do not execute validations. Otherwise, we check the following:
       * <ul>
       *  <li> Check if start port is valid using {@link NetUtils#isValidPort(int)};
       *  <li> Check if end port is valid using {@link NetUtils#isValidPort(int)};
       *  <li> Check if start port is bigger than end port;
       *  <li> Check if start and end ports were used with protocol 'all'
       *  </ul>
       *  All of the above cases will generate an {@link InvalidParameterValueException}.
       */
      protected void validateSourceStartAndEndPorts(NetworkACLItemVO networkACLItemVO) {
          Integer sourcePortStart = networkACLItemVO.getSourcePortStart();
          Integer sourcePortEnd = networkACLItemVO.getSourcePortEnd();
          if (sourcePortStart == null && sourcePortEnd == null) {
              return;
          }
  
          if (!NetUtils.isValidPort(sourcePortStart)) {
              throw new InvalidParameterValueException("Start public port is an invalid value: " + sourcePortStart);
          }
  
          if (!NetUtils.isValidPort(sourcePortEnd)) {
              throw new InvalidParameterValueException("End public port is an invalid value: " + sourcePortEnd);
          }
          if (sourcePortStart > sourcePortEnd) {
              throw new InvalidParameterValueException(String.format("Start port can't be bigger than end port [startport=%d,endport=%d]", sourcePortStart, sourcePortEnd));
          }
          String protocol = networkACLItemVO.getProtocol();
          if ("all".equalsIgnoreCase(protocol)) {
              throw new InvalidParameterValueException("start port and end port must be null if protocol = 'all'");
          }
      }
  
      @Override
      public NetworkACLItem getNetworkACLItem(final long ruleId) {
          return _networkAclMgr.getNetworkACLItem(ruleId);
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_ITEM_CREATE, eventDescription = "Applying Network ACL Item", async = true)
      public boolean applyNetworkACL(final long aclId) throws ResourceUnavailableException {
          return _networkAclMgr.applyNetworkACL(aclId);
      }
  
      @Override
      public Pair<List<? extends NetworkACLItem>, Integer> listNetworkACLItems(final ListNetworkACLsCmd cmd) {
          final Long networkId = cmd.getNetworkId();
          final Long id = cmd.getId();
          Long aclId = cmd.getAclId();
          final String trafficType = cmd.getTrafficType();
          final String protocol = cmd.getProtocol();
          final String action = cmd.getAction();
          final String keyword = cmd.getKeyword();
          final Map<String, String> tags = cmd.getTags();
          final Account caller = CallContext.current().getCallingAccount();
  
          final Filter filter = new Filter(NetworkACLItemVO.class, "id", false, cmd.getStartIndex(), cmd.getPageSizeVal());
          final SearchBuilder<NetworkACLItemVO> sb = _networkACLItemDao.createSearchBuilder();
  
          sb.and("id", sb.entity().getId(), Op.EQ);
          sb.and("aclId", sb.entity().getAclId(), Op.EQ);
          sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ);
          sb.and("protocol", sb.entity().getProtocol(), Op.EQ);
          sb.and("action", sb.entity().getAction(), Op.EQ);
          sb.and("reason", sb.entity().getReason(), Op.EQ);
  
          if (tags != null && !tags.isEmpty()) {
              final SearchBuilder<ResourceTagVO> tagSearch = _resourceTagDao.createSearchBuilder();
              for (int count = 0; count < tags.size(); count++) {
                  tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), Op.EQ);
                  tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), Op.EQ);
                  tagSearch.cp();
              }
              tagSearch.and("resourceType", tagSearch.entity().getResourceType(), Op.EQ);
              sb.groupBy(sb.entity().getId());
              sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER);
          }
  
          if (aclId == null) {
              //Join with network_acl table when aclId is not specified to list acl_items within permitted VPCs
              final SearchBuilder<NetworkACLVO> vpcSearch = _networkACLDao.createSearchBuilder();
              vpcSearch.and("vpcId", vpcSearch.entity().getVpcId(), Op.IN);
              sb.join("vpcSearch", vpcSearch, sb.entity().getAclId(), vpcSearch.entity().getId(), JoinBuilder.JoinType.INNER);
          }
  
          final SearchCriteria<NetworkACLItemVO> sc = sb.create();
  
          if (StringUtils.isNotBlank(keyword)) {
              final SearchCriteria<NetworkACLItemVO> ssc = _networkACLItemDao.createSearchCriteria();
              ssc.addOr("protocol", SearchCriteria.Op.LIKE, "%" + keyword + "%");
              ssc.addOr("reason", SearchCriteria.Op.LIKE, "%" + keyword + "%");
              sc.addAnd("acl_id", SearchCriteria.Op.SC, ssc);
          }
          if (id != null) {
              sc.setParameters("id", id);
          }
  
          if (networkId != null) {
              final Network network = _networkDao.findById(networkId);
              aclId = network.getNetworkACLId();
              if (aclId == null) {
                  // No aclId associated with the network.
                  //Return empty list
                  return new Pair(new ArrayList<NetworkACLItem>(), 0);
              }
          }
  
          if (trafficType != null) {
              sc.setParameters("trafficType", trafficType);
          }
          if (aclId != null) {
              // Get VPC and check access
              final NetworkACL acl = _networkACLDao.findById(aclId);
              if (acl.getVpcId() != 0) {
                  final Vpc vpc = _vpcDao.findById(acl.getVpcId());
                  if (vpc == null) {
                      throw new InvalidParameterValueException("Unable to find VPC associated with acl");
                  }
                  _accountMgr.checkAccess(caller, null, true, vpc);
              }
              sc.setParameters("aclId", aclId);
          } else {
              //ToDo: Add accountId to network_acl_item table for permission check
  
              // aclId is not specified
              // List permitted VPCs and filter aclItems
              final List<Long> permittedAccounts = new ArrayList<>();
              Long domainId = cmd.getDomainId();
              boolean isRecursive = cmd.isRecursive();
              final String accountName = cmd.getAccountName();
              final Long projectId = cmd.getProjectId();
              final boolean listAll = cmd.listAll();
              final Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, isRecursive, null);
              _accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccounts, domainIdRecursiveListProject, listAll, false);
              domainId = domainIdRecursiveListProject.first();
              isRecursive = domainIdRecursiveListProject.second();
              final ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
              final SearchBuilder<VpcVO> sbVpc = _vpcDao.createSearchBuilder();
              _accountMgr.buildACLSearchBuilder(sbVpc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
              final SearchCriteria<VpcVO> scVpc = sbVpc.create();
              _accountMgr.buildACLSearchCriteria(scVpc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
              final List<VpcVO> vpcs = _vpcDao.search(scVpc, null);
              final List<Long> vpcIds = new ArrayList<>();
              for (final VpcVO vpc : vpcs) {
                  vpcIds.add(vpc.getId());
              }
              //Add vpc_id 0 to list acl_items in default ACL
              vpcIds.add(0L);
              sc.setJoinParameters("vpcSearch", "vpcId", vpcIds.toArray());
          }
  
          if (protocol != null) {
              sc.setParameters("protocol", protocol);
          }
  
          if (action != null) {
              sc.setParameters("action", action);
          }
  
          if (tags != null && !tags.isEmpty()) {
              int count = 0;
              sc.setJoinParameters("tagSearch", "resourceType", ResourceObjectType.NetworkACL.toString());
              for (final String key : tags.keySet()) {
                  sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key);
                  sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key));
                  count++;
              }
          }
  
          final Pair<List<NetworkACLItemVO>, Integer> result = _networkACLItemDao.searchAndCount(sc, filter);
          final List<NetworkACLItemVO> aclItemVOs = result.first();
          for (final NetworkACLItemVO item : aclItemVOs) {
              _networkACLItemDao.loadCidrs(item);
          }
          return new Pair<List<? extends NetworkACLItem>, Integer>(aclItemVOs, result.second());
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_ITEM_DELETE, eventDescription = "Deleting Network ACL Item", async = true)
      public boolean revokeNetworkACLItem(final long ruleId) {
          final NetworkACLItemVO aclItem = _networkACLItemDao.findById(ruleId);
          if (aclItem != null) {
              final NetworkACL acl = _networkAclMgr.getNetworkACL(aclItem.getAclId());
              final Account account = CallContext.current().getCallingAccount();
  
              if (isDefaultAcl(aclItem.getAclId())) {
                  throw new InvalidParameterValueException("ACL Items in default ACL cannot be deleted");
              }
              validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admin can delete global ACL rules.");
          }
          return _networkAclMgr.revokeNetworkACLItem(ruleId);
      }
  
      /**
       * Updates a network ACL with the given values found in the {@link UpdateNetworkACLItemCmd} parameter.
       * First we will validate the network ACL rule provided in the command using {@link #validateNetworkAclRuleIdAndRetrieveIt(UpdateNetworkACLItemCmd)}.
       * Then, we validate the ACL itself using {@link #validateNetworkAcl(NetworkACL)}. If all of the validation is ok, we do the following.
       * <ul>
       *  <li>Transfer new data to {@link NetworkACLItemVO} that is intended to be updated;
       *  <li>Validate the ACL rule being updated using {@link #validateNetworkACLItem(NetworkACLItemVO)}.
       * </ul>
       *
       * After the validations and updating the POJO we execute the update in the database using {@link NetworkACLManagerImpl#updateNetworkACLItem(NetworkACLItemVO)}.
       *
       */
      @Override
      public NetworkACLItem updateNetworkACLItem(UpdateNetworkACLItemCmd updateNetworkACLItemCmd) throws ResourceUnavailableException {
          NetworkACLItemVO networkACLItemVo = validateNetworkAclRuleIdAndRetrieveIt(updateNetworkACLItemCmd);
  
          NetworkACL acl = _networkAclMgr.getNetworkACL(networkACLItemVo.getAclId());
          validateNetworkAcl(acl);
          validateNsxConstraints(acl.getVpcId(), networkACLItemVo.getProtocol(), networkACLItemVo.getIcmpType(),
                  networkACLItemVo.getIcmpCode(), networkACLItemVo.getSourcePortStart(), networkACLItemVo.getSourcePortEnd());
          Account account = CallContext.current().getCallingAccount();
          validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admins can update global ACLs.");
  
          transferDataToNetworkAclRulePojo(updateNetworkACLItemCmd, networkACLItemVo, acl);
          validateNetworkACLItem(networkACLItemVo);
          return _networkAclMgr.updateNetworkACLItem(networkACLItemVo);
      }
  
      /**
       *  We transfer the update information form {@link UpdateNetworkACLItemCmd} to the {@link NetworkACLItemVO} POJO passed as parameter.
       *  There is one validation performed here, which is regarding the number of the ACL. We will check if there is already an ACL rule with that number, and if this is the case an {@link InvalidParameterValueException} is thrown.
       *  All of the parameters in {@link UpdateNetworkACLItemCmd} that are not null will be set to their corresponding fields in {@link NetworkACLItemVO}.
       *  If the parameter {@link UpdateNetworkACLItemCmd#isPartialUpgrade()} returns false, we will use null parameters, which will allow us to completely update the ACL rule.
       *  However, the number and custom Uuid will never be set to null. Therefore, if it is not a partial upgrade, these values will remain the same.
       *
       *  We use {@link #validateAndCreateNetworkAclRuleAction(String)} when converting an action as {@link String} to its Enum corresponding value.
       */
      protected void transferDataToNetworkAclRulePojo(UpdateNetworkACLItemCmd updateNetworkACLItemCmd, NetworkACLItemVO networkACLItemVo, NetworkACL acl) {
          Integer number = updateNetworkACLItemCmd.getNumber();
          if (number != null) {
              NetworkACLItemVO aclNumber = _networkACLItemDao.findByAclAndNumber(acl.getId(), number);
              if (aclNumber != null && aclNumber.getId() != networkACLItemVo.getId()) {
                  throw new InvalidParameterValueException("ACL item with number " + number + " already exists in ACL: " + acl.getUuid());
              }
              networkACLItemVo.setNumber(number);
          }
          boolean isPartialUpgrade = updateNetworkACLItemCmd.isPartialUpgrade();
  
          Integer sourcePortStart = updateNetworkACLItemCmd.getSourcePortStart();
          if (!isPartialUpgrade || sourcePortStart != null) {
              networkACLItemVo.setSourcePortStart(sourcePortStart);
          }
          Integer sourcePortEnd = updateNetworkACLItemCmd.getSourcePortEnd();
          if (!isPartialUpgrade || sourcePortEnd != null) {
              networkACLItemVo.setSourcePortEnd(sourcePortEnd);
          }
          List<String> sourceCidrList = updateNetworkACLItemCmd.getSourceCidrList();
          if (!isPartialUpgrade || CollectionUtils.isNotEmpty(sourceCidrList)) {
              networkACLItemVo.setSourceCidrList(sourceCidrList);
          }
          String protocol = updateNetworkACLItemCmd.getProtocol();
          if (!isPartialUpgrade || StringUtils.isNotBlank(protocol)) {
              networkACLItemVo.setProtocol(protocol);
          }
          updateIcmpCodeAndType(isPartialUpgrade, updateNetworkACLItemCmd, networkACLItemVo);
          String action = updateNetworkACLItemCmd.getAction();
          if (!isPartialUpgrade || StringUtils.isNotBlank(action)) {
              Action aclRuleAction = validateAndCreateNetworkAclRuleAction(action);
              networkACLItemVo.setAction(aclRuleAction);
          }
          TrafficType trafficType = updateNetworkACLItemCmd.getTrafficType();
          if (!isPartialUpgrade || trafficType != null) {
              networkACLItemVo.setTrafficType(trafficType);
          }
          String customId = updateNetworkACLItemCmd.getCustomId();
          if (StringUtils.isNotBlank(customId)) {
              networkACLItemVo.setUuid(customId);
          }
          boolean display = updateNetworkACLItemCmd.isDisplay();
          if (!isPartialUpgrade || display != networkACLItemVo.isDisplay()) {
              networkACLItemVo.setDisplay(display);
          }
          String reason = updateNetworkACLItemCmd.getReason();
          if (!isPartialUpgrade || StringUtils.isNotBlank(reason)) {
              networkACLItemVo.setReason(reason);
          }
      }
  
      protected void updateIcmpCodeAndType (boolean isPartialUpgrade, UpdateNetworkACLItemCmd updateNetworkACLItemCmd, NetworkACLItemVO networkACLItemVo) {
          Integer icmpCode = updateNetworkACLItemCmd.getIcmpCode();
          Integer icmpType = updateNetworkACLItemCmd.getIcmpType();
  
          if (!isPartialUpgrade) {
              updateIcmpCodeAndTypeFullUpgrade(icmpCode, icmpType, networkACLItemVo);
              return;
          }
          if (icmpCode != null) {
              networkACLItemVo.setIcmpCode(icmpCode);
          }
          if (icmpType != null) {
              networkACLItemVo.setIcmpType(icmpType);
          }
      }
  
      private void updateIcmpCodeAndTypeFullUpgrade (Integer icmpCode, Integer icmpType, NetworkACLItemVO networkACLItemVo) {
          if (networkACLItemVo.getProtocol().equalsIgnoreCase(NetUtils.ICMP_PROTO)) {
              networkACLItemVo.setIcmpCode(icmpCode != null ? icmpCode : -1);
              networkACLItemVo.setIcmpType(icmpType != null ? icmpType : -1);
          } else {
              networkACLItemVo.setIcmpCode(null);
              networkACLItemVo.setIcmpType(null);
          }
      }
  
      /**
       * We validate the network ACL rule ID provided. If not ACL rule is found with the given Id an {@link InvalidParameterValueException} is thrown.
       * If an ACL rule is found, we return the clone of the rule to avoid messing up with CGlib enhanced objects that might be linked to database entries.
       */
      protected NetworkACLItemVO validateNetworkAclRuleIdAndRetrieveIt(UpdateNetworkACLItemCmd updateNetworkACLItemCmd) {
          Long id = updateNetworkACLItemCmd.getId();
          NetworkACLItemVO networkACLItemVoFromDatabase = _networkACLItemDao.findById(id);
          if (networkACLItemVoFromDatabase == null) {
              throw new InvalidParameterValueException(String.format("Unable to find ACL rule with ID [%s]", id));
          }
          return networkACLItemVoFromDatabase.clone();
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_UPDATE, eventDescription = "Updating Network ACL", async = true)
      public NetworkACL updateNetworkACL(UpdateNetworkACLListCmd updateNetworkACLListCmd) {
          Long id = updateNetworkACLListCmd.getId();
          NetworkACLVO acl = _networkACLDao.findById(id);
          Account account = CallContext.current().getCallingAccount();
  
          validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Must be Root Admin to update a global ACL.");
  
          String name = updateNetworkACLListCmd.getName();
          if (StringUtils.isNotBlank(name)) {
              acl.setName(name);
          }
          String description = updateNetworkACLListCmd.getDescription();
          if (StringUtils.isNotBlank(description)) {
              acl.setDescription(description);
          }
          String customId = updateNetworkACLListCmd.getCustomId();
          if (StringUtils.isNotBlank(customId)) {
              acl.setUuid(customId);
          }
          Boolean forDisplay = updateNetworkACLListCmd.getDisplay();
          if (forDisplay != null) {
              acl.setDisplay(forDisplay);
          }
          _networkACLDao.update(id, acl);
          return _networkACLDao.findById(id);
      }
  
      @Override
      public NetworkACLItem moveNetworkAclRuleToNewPosition(MoveNetworkAclItemCmd moveNetworkAclItemCmd) {
          String uuidRuleBeingMoved = moveNetworkAclItemCmd.getUuidRuleBeingMoved();
          String nextAclRuleUuid = moveNetworkAclItemCmd.getNextAclRuleUuid();
          String previousAclRuleUuid = moveNetworkAclItemCmd.getPreviousAclRuleUuid();
  
          if (StringUtils.isAllBlank(previousAclRuleUuid, nextAclRuleUuid)) {
              throw new InvalidParameterValueException("Both previous and next ACL rule IDs cannot be blank.");
          }
  
          NetworkACLItemVO ruleBeingMoved = _networkACLItemDao.findByUuid(uuidRuleBeingMoved);
          if (ruleBeingMoved == null) {
              throw new InvalidParameterValueException(String.format("Could not find a rule with ID[%s]", uuidRuleBeingMoved));
          }
          NetworkACLItemVO previousRule = retrieveAndValidateAclRule(previousAclRuleUuid);
          NetworkACLItemVO nextRule = retrieveAndValidateAclRule(nextAclRuleUuid);
  
          validateMoveAclRulesData(ruleBeingMoved, previousRule, nextRule);
  
          try {
              NetworkACLVO lockedAcl = _networkACLDao.acquireInLockTable(ruleBeingMoved.getAclId());
              List<NetworkACLItemVO> allAclRules = getAllAclRulesSortedByNumber(lockedAcl.getId());
              validateAclConsistency(moveNetworkAclItemCmd, lockedAcl, allAclRules);
              NetworkACLItem networkACLItem = null;
              if (previousRule == null) {
                  networkACLItem = moveRuleToTheTop(ruleBeingMoved, allAclRules);
              } else if (nextRule == null) {
                  networkACLItem = moveRuleToTheBottom(ruleBeingMoved, allAclRules);
              } else {
                  networkACLItem = moveRuleBetweenAclRules(ruleBeingMoved, allAclRules, previousRule, nextRule);
              }
              VpcVO vpc = _vpcDao.findById(lockedAcl.getVpcId());
              if (Objects.isNull(vpc)) {
                  return networkACLItem;
              }
              final DataCenter dc = _entityMgr.findById(DataCenter.class, vpc.getZoneId());
              final NsxProviderVO nsxProvider = nsxProviderDao.findByZoneId(dc.getId());
              final NetrisProviderVO netrisProvider = netrisProviderDao.findByZoneId(dc.getId());
              List<NetworkVO> networks = _networkDao.listByAclId(lockedAcl.getId());
              if (ObjectUtils.anyNotNull(nsxProvider, netrisProvider) && !networks.isEmpty()) {
                  allAclRules = getAllAclRulesSortedByNumber(lockedAcl.getId());
                  Network.Provider networkProvider = nsxProvider != null ? Network.Provider.Nsx : Network.Provider.Netris;
                  _networkAclMgr.reorderAclRules(vpc, networks, allAclRules, networkProvider);
              }
              return networkACLItem;
          } finally {
              _networkACLDao.releaseFromLockTable(ruleBeingMoved.getAclId());
          }
      }
  
      @Override
      public NetworkACLItem moveRuleToTheTopInACLList(NetworkACLItem ruleBeingMoved) {
          List<NetworkACLItemVO> allRules = getAllAclRulesSortedByNumber(ruleBeingMoved.getAclId());
          if (allRules.size() == 1) {
              return ruleBeingMoved;
          }
          return moveRuleToTheTop(ruleBeingMoved, allRules);
      }
  
      /**
       * Validates the consistency of the ACL; the validation process is the following.
       * <ul>
       *  <li> If the ACL does not have rules yet, we do not have any validation to perform;
       *  <li> we will check first if the user provided a consistency hash; if not, we will log a warning message informing administrators that the user is performing the call is assuming the risks of applying ACL replacement without a consistency check;
       *  <li> if the ACL consistency hash is entered by the user, we check if it is the same as we currently have in the database. If it is different we throw an exception.
       * </ul>
       *
       * If the consistency hash sent by the user is the same as the one we get with the database data we should be safe to proceed.
       */
      protected void validateAclConsistency(MoveNetworkAclItemCmd moveNetworkAclItemCmd, NetworkACLVO lockedAcl, List<NetworkACLItemVO> allAclRules) {
          if (CollectionUtils.isEmpty(allAclRules)) {
              logger.debug("No ACL rules for {}. Therefore, there is no need for consistency validation.", lockedAcl);
              return;
          }
          String aclConsistencyHash = moveNetworkAclItemCmd.getAclConsistencyHash();
          if (StringUtils.isBlank(aclConsistencyHash)) {
              User callingUser = CallContext.current().getCallingUser();
              Account callingAccount = CallContext.current().getCallingAccount();
  
              logger.warn(String.format(
                      "User [id=%s, name=%s] from Account [id=%s, name=%s] has not entered an ACL consistency hash to execute the replacement of an ACL rule. Therefore, she/he is assuming all of the risks of procedding without this validation.",
                      callingUser.getUuid(), callingUser.getUsername(), callingAccount.getUuid(), callingAccount.getAccountName()));
              return;
          }
          String aclRulesUuids = StringUtils.EMPTY;
          for (NetworkACLItemVO rule : allAclRules) {
              aclRulesUuids += rule.getUuid();
          }
          String md5UuidsSortedByNumber = DigestUtils.md5Hex(aclRulesUuids);
          if (!md5UuidsSortedByNumber.equals(aclConsistencyHash)) {
              throw new InvalidParameterValueException("It seems that the access control list in the database is not in the state that you used to apply the changed. Could you try it again?");
          }
      }
  
      /**
       * Loads all ACL rules from given network ACL list. Then, the ACL rules will be sorted according to the 'number' field in ascending order.
       */
      protected List<NetworkACLItemVO> getAllAclRulesSortedByNumber(long aclId) {
          List<NetworkACLItemVO> allAclRules = _networkACLItemDao.listByACL(aclId);
          Collections.sort(allAclRules, new Comparator<NetworkACLItemVO>() {
              @Override
              public int compare(NetworkACLItemVO o1, NetworkACLItemVO o2) {
                  return o1.number - o2.number;
              }
          });
          return allAclRules;
      }
  
      /**
       * Moves an ACL to the space between to other rules. If there is already enough room to accommodate the ACL rule being moved, we simply get the 'number' field from the previous ACL rule and add one, and then define this new value as the 'number' value for the ACL rule being moved.
       * Otherwise, we will need to make room. This process is executed via {@link #updateAclRuleToNewPositionAndExecuteShiftIfNecessary(NetworkACLItem, int, List, int)}, which will create the space between ACL rules if necessary. This involves shifting ACL rules to accommodate the rule being moved.
       */
      protected NetworkACLItem moveRuleBetweenAclRules(NetworkACLItemVO ruleBeingMoved, List<NetworkACLItemVO> allAclRules, NetworkACLItemVO previousRule, NetworkACLItemVO nextRule) {
          if (previousRule.getNumber() + 1 != nextRule.getNumber()) {
              int newNumberFieldValue = previousRule.getNumber() + 1;
              for (NetworkACLItemVO networkACLItemVO : allAclRules) {
                  if (networkACLItemVO.getNumber() == newNumberFieldValue) {
                      throw new InvalidParameterValueException("There are some inconsistencies with the data you sent. The new position calculated already has a ACL rule on it.");
                  }
              }
              ruleBeingMoved.setNumber(newNumberFieldValue);
              _networkACLItemDao.updateNumberFieldNetworkItem(ruleBeingMoved.getId(), newNumberFieldValue);
              return _networkACLItemDao.findById(ruleBeingMoved.getId());
          }
          int positionToStartProcessing = 0;
          for (int i = 0; i < allAclRules.size(); i++) {
              if (allAclRules.get(i).getId() == previousRule.getId()) {
                  positionToStartProcessing = i + 1;
                  break;
              }
          }
          return updateAclRuleToNewPositionAndExecuteShiftIfNecessary(ruleBeingMoved, previousRule.getNumber() + 1, allAclRules, positionToStartProcessing);
      }
  
      /**
       *  Moves a network ACL rule to the bottom of the list. This is executed by getting the 'number' field of the last ACL rule from the ACL list, and incrementing one.
       *  This new value is assigned to the network ACL being moved and updated in the database using {@link NetworkACLItemDao#updateNumberFieldNetworkItem(long, int)}.
       */
      protected NetworkACLItem moveRuleToTheBottom(NetworkACLItemVO ruleBeingMoved, List<NetworkACLItemVO> allAclRules) {
          NetworkACLItemVO lastAclRule = allAclRules.get(allAclRules.size() - 1);
  
          int newNumberFieldValue = lastAclRule.getNumber() + 1;
          ruleBeingMoved.setNumber(newNumberFieldValue);
  
          _networkACLItemDao.updateNumberFieldNetworkItem(ruleBeingMoved.getId(), newNumberFieldValue);
          return _networkACLItemDao.findById(ruleBeingMoved.getId());
      }
  
      /**
       *  Move the rule to the top of the ACL rule list. This means that the ACL rule being moved will receive the position '1'.
       *  Also, if necessary other ACL rules will have their 'number' field updated to create room for the new top rule.
       */
      protected NetworkACLItem moveRuleToTheTop(NetworkACLItem ruleBeingMoved, List<NetworkACLItemVO> allAclRules) {
          return updateAclRuleToNewPositionAndExecuteShiftIfNecessary(ruleBeingMoved, 1, allAclRules, 0);
      }
  
      /**
       * Updates the ACL rule number executing the shift on subsequent ACL rules if necessary.
       * For example, if we have the following ACL rules:
       * <ul>
       *  <li> ACL A - number 1
       *  <li> ACL B - number 2
       *  <li> ACL C - number 3
       *  <li> ACL D - number 12
       * </ul>
       * If we move 'ACL D' to a place  between 'ACL A' and 'ACL B', this method will execute the shift needded to create the space for 'ACL D'.
       * After applying this method, we will have the following condition.
       * <ul>
       *  <li> ACL A - number 1
       *  <li> ACL D - number 2
       *  <li> ACL B - number 3
       *  <li> ACL C - number 4
       * </ul>
       */
      protected NetworkACLItem updateAclRuleToNewPositionAndExecuteShiftIfNecessary(NetworkACLItem ruleBeingMoved, int newNumberFieldValue, List<NetworkACLItemVO> allAclRules,
              int indexToStartProcessing) {
          for (int i = indexToStartProcessing; i < allAclRules.size(); i++) {
              NetworkACLItemVO networkACLItemVO = allAclRules.get(i);
              if (networkACLItemVO.getId() == ruleBeingMoved.getId()) {
                  continue;
              }
              if (newNumberFieldValue != networkACLItemVO.getNumber()) {
                  break;
              }
              int newNumberFieldValueNextAclRule = newNumberFieldValue + 1;
              updateAclRuleToNewPositionAndExecuteShiftIfNecessary(networkACLItemVO, newNumberFieldValueNextAclRule, allAclRules, i);
          }
          _networkACLItemDao.updateNumberFieldNetworkItem(ruleBeingMoved.getId(), newNumberFieldValue);
          return _networkACLItemDao.findById(ruleBeingMoved.getId());
      }
  
      /**
       * Searches in the database for an ACL rule by its UUID.
       * An {@link InvalidParameterValueException} is thrown if no ACL rule is found with the given UUID.
       */
      protected NetworkACLItemVO retrieveAndValidateAclRule(String aclRuleUuid) {
          if (StringUtils.isBlank(aclRuleUuid)) {
              return null;
          }
          NetworkACLItemVO aclRule = _networkACLItemDao.findByUuid(aclRuleUuid);
          if (aclRule == null) {
              throw new InvalidParameterValueException(String.format("Could not find rule with ID [%s]", aclRuleUuid));
          }
          return aclRule;
      }
  
      /**
       *  Validates if the data provided to move the ACL rule is supported by this implementation. The user needs to provide a valid ACL UUID, and at least one of the previous or the next ACL rule.
       *  The validation is as follows:
       *  <ul>
       *      <li> If both ACL rules 'previous' and 'next' are invalid, we throw an {@link InvalidParameterValueException};
       *      <li> informed previous and next ACL rules must have the same ACL ID as the rule being moved; otherwise, an {@link InvalidParameterValueException} is thrown;
       *      <li> then we check if the user trying to move ACL rules has access to the VPC, where the ACL rules are being applied.
       *  </ul>
       */
      protected void validateMoveAclRulesData(NetworkACLItemVO ruleBeingMoved, NetworkACLItemVO previousRule, NetworkACLItemVO nextRule) {
          if (nextRule == null && previousRule == null) {
              throw new InvalidParameterValueException("Both previous and next ACL rule IDs cannot be invalid.");
          }
          long aclId = ruleBeingMoved.getAclId();
  
          if ((nextRule != null && nextRule.getAclId() != aclId) || (previousRule != null && previousRule.getAclId() != aclId)) {
              throw new InvalidParameterValueException("Cannot use ACL rules from differentiating ACLs. Rule being moved.");
          }
          NetworkACLVO acl = _networkACLDao.findById(aclId);
          Account account = CallContext.current().getCallingAccount();
  
          if (isDefaultAcl(aclId)) {
              throw new InvalidParameterValueException("Default ACL rules cannot be moved.");
          }
  
          validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account,"Must be Root Admin to move global ACL rules.");
      }
  
      /**
       * Checks if the given ACL is a global ACL. If it is, then checks if the account is Root Admin, and throws an exception according to {@code exceptionMessage} param if it
       * does not have permission.
       */
      protected void checkGlobalAclPermission(Long aclVpcId, Account account, String exceptionMessage) {
          if (isGlobalAcl(aclVpcId) && !Account.Type.ADMIN.equals(account.getType())) {
              throw new PermissionDeniedException(exceptionMessage);
          }
      }
  
      protected void validateAclAssociatedToVpc(Long aclVpcId, Account account, String aclUuid) {
          final Vpc vpc = _vpcDao.findById(aclVpcId);
          if (vpc == null) {
              throw new InvalidParameterValueException(String.format("Unable to find specified VPC [%s] associated with the ACL [%s].", aclVpcId, aclUuid));
          }
          _accountMgr.checkAccess(account, null, true, vpc);
      }
  
      /**
       * It performs two different verifications depending on if the ACL is global or not.
       * <ul>
       *     <li> If the given ACL is a Global ACL, i.e. has VPC ID = 0, then checks if the account is Root Admin, and throws an exception if it isn't.
       *     <li> If the given ACL is associated to a VPC, i.e. VPC ID != 0, then calls {@link #validateAclAssociatedToVpc(Long, Account, String)} and checks if the given {@code
       *     aclVpcId} is from a valid VPC.
       * </ul>
       */
      protected void validateGlobalAclPermissionAndAclAssociatedToVpc(NetworkACL acl, Account account, String exception){
          if (isGlobalAcl(acl.getVpcId())) {
              logger.info(String.format("Checking if account [%s] has permission to manipulate global ACL [%s].", account, acl));
              checkGlobalAclPermission(acl.getVpcId(), account, exception);
          } else {
              logger.info(String.format("Validating ACL [%s] associated to VPC [%s] with account [%s].", acl, acl.getVpcId(), account));
              validateAclAssociatedToVpc(acl.getVpcId(), account, acl.getUuid());
          }
      }
  
      protected boolean isGlobalAcl(Long aclVpcId) {
          return aclVpcId != null && aclVpcId == 0;
      }
  
      protected boolean isDefaultAcl(long aclId) {
          return aclId == NetworkACL.DEFAULT_ALLOW || aclId == NetworkACL.DEFAULT_DENY;
      }
  }
  