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.resourceicon;
  
  import java.util.Collection;
  import java.util.Date;
  import java.util.List;
  import java.util.Map;
  import java.util.function.Function;
  import java.util.stream.Collectors;
  
  import javax.inject.Inject;
  import javax.persistence.EntityExistsException;
  
  import org.apache.cloudstack.api.ApiCommandResourceType;
  import org.apache.cloudstack.context.CallContext;
  
  import com.cloud.domain.PartOf;
  import com.cloud.event.ActionEvent;
  import com.cloud.event.EventTypes;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.network.security.SecurityGroupRuleVO;
  import com.cloud.network.security.SecurityGroupVO;
  import com.cloud.network.vpc.NetworkACLItemVO;
  import com.cloud.network.vpc.NetworkACLVO;
  import com.cloud.network.vpc.VpcVO;
  import com.cloud.projects.ProjectVO;
  import com.cloud.resource.icon.ResourceIconVO;
  import com.cloud.resource.icon.dao.ResourceIconDao;
  import com.cloud.server.ResourceIcon;
  import com.cloud.server.ResourceIconManager;
  import com.cloud.server.ResourceManagerUtil;
  import com.cloud.server.ResourceTag;
  import com.cloud.storage.SnapshotPolicyVO;
  import com.cloud.storage.VolumeVO;
  import com.cloud.tags.ResourceManagerUtilImpl;
  import com.cloud.user.Account;
  import com.cloud.user.AccountService;
  import com.cloud.user.AccountVO;
  import com.cloud.user.OwnedBy;
  import com.cloud.user.dao.AccountDao;
  import com.cloud.utils.Pair;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.EntityManager;
  import com.cloud.utils.db.SearchBuilder;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallbackNoReturn;
  import com.cloud.utils.db.TransactionStatus;
  import com.cloud.utils.exception.CloudRuntimeException;
  
  public class ResourceIconManagerImpl extends ManagerBase implements ResourceIconManager {
  
      @Inject
      AccountService accountService;
      @Inject
      ResourceManagerUtil resourceManagerUtil;
      @Inject
      ResourceIconDao resourceIconDao;
      @Inject
      EntityManager entityMgr;
      @Inject
      AccountDao accountDao;
  
      private Pair<Long, Long> getAccountDomain(long resourceId, ResourceTag.ResourceObjectType resourceType) {
          Class<?> clazz = ResourceManagerUtilImpl.s_typeMap.get(resourceType);
  
          Object entity = entityMgr.findById(clazz, resourceId);
          Long accountId = null;
          Long domainId = null;
  
          // if the resource type is a security group rule, get the accountId and domainId from the security group itself
          if (resourceType == ResourceTag.ResourceObjectType.SecurityGroupRule) {
              SecurityGroupRuleVO rule = (SecurityGroupRuleVO)entity;
              Object SecurityGroup = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.SecurityGroup), rule.getSecurityGroupId());
  
              accountId = ((SecurityGroupVO)SecurityGroup).getAccountId();
              domainId = ((SecurityGroupVO)SecurityGroup).getDomainId();
          }
  
          if (resourceType == ResourceTag.ResourceObjectType.Account) {
              AccountVO account = (AccountVO)entity;
              accountId = account.getId();
              domainId = account.getDomainId();
          }
  
          // if the resource type is network acl, get the accountId and domainId from VPC following: NetworkACLItem -> NetworkACL -> VPC
          if (resourceType == ResourceTag.ResourceObjectType.NetworkACL) {
              NetworkACLItemVO aclItem = (NetworkACLItemVO)entity;
              Object networkACL = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.NetworkACLList), aclItem.getAclId());
              Long vpcId = ((NetworkACLVO)networkACL).getVpcId();
  
              if (vpcId != null && vpcId != 0) {
                  Object vpc = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.Vpc), vpcId);
  
                  accountId = ((VpcVO)vpc).getAccountId();
                  domainId = ((VpcVO)vpc).getDomainId();
              }
          }
  
          if (resourceType == ResourceTag.ResourceObjectType.Project) {
              accountId = ((ProjectVO)entity).getProjectAccountId();
          }
  
          if (resourceType == ResourceTag.ResourceObjectType.SnapshotPolicy) {
              accountId = entityMgr.findById(VolumeVO.class, ((SnapshotPolicyVO)entity).getVolumeId()).getAccountId();
          }
  
          if (entity instanceof OwnedBy) {
              accountId = ((OwnedBy)entity).getAccountId();
          }
  
          if (entity instanceof PartOf) {
              domainId = ((PartOf)entity).getDomainId();
          }
  
          if (accountId == null) {
              accountId = Account.ACCOUNT_ID_SYSTEM;
          }
  
          if ((domainId == null) || ((accountId != null) && (domainId.longValue() == -1))) {
              domainId = accountDao.getDomainIdForGivenAccountId(accountId);
          }
          return new Pair<>(accountId, domainId);
      }
  
      private void updateResourceDetailsInContext(Long resourceId, ResourceTag.ResourceObjectType resourceType) {
          Class<?> clazz = ResourceManagerUtilImpl.s_typeMap.get(resourceType);
          ApiCommandResourceType type = ApiCommandResourceType.valueFromAssociatedClass(clazz);
          int depth = 5;
          while (type == null && depth > 0) {
              Class<?>[] clazzes = clazz.getInterfaces();
              if (clazzes.length == 0) {
                  break;
              }
              depth--;
              clazz = clazzes[0];
              type = ApiCommandResourceType.valueFromAssociatedClass(clazz);
          }
          CallContext.current().setEventResourceId(resourceId);
          if (!ApiCommandResourceType.None.equals(type)) {
              CallContext.current().setEventResourceType(type);
          }
      }
  
      @Override
      @DB
      @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_ICON_UPLOAD, eventDescription = "uploading resource icon")
      public boolean uploadResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType, String base64Image) {
          final Account caller = CallContext.current().getCallingAccount();
  
          Transaction.execute(new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
                  for (String resourceId : resourceIds) {
                      if (!resourceType.resourceIconSupport()) {
                          throw new InvalidParameterValueException("The resource type " + resourceType + " doesn't support resource icons");
                      }
  
                      if (base64Image == null) {
                          throw new InvalidParameterValueException("No icon provided to be uploaded for resource: " + resourceId);
                      }
  
                      long id = resourceManagerUtil.getResourceId(resourceId, resourceType);
                      String resourceUuid = resourceManagerUtil.getUuid(resourceId, resourceType);
                      updateResourceDetailsInContext(id, resourceType);
                      ResourceIconVO existingResourceIcon = resourceIconDao.findByResourceUuid(resourceUuid, resourceType);
                      ResourceIconVO resourceIcon = null;
                      Pair<Long, Long> accountDomainPair = getAccountDomain(id, resourceType);
                      Long domainId = accountDomainPair.second();
                      Long accountId = accountDomainPair.first();
                      resourceManagerUtil.checkResourceAccessible(accountId, domainId, String.format("Account ' %s ' doesn't have permissions to upload icon for resource [id: %s, uuid: %s] ", caller, id, resourceUuid));
  
                      if (existingResourceIcon == null) {
                          resourceIcon = new ResourceIconVO(id, resourceType, resourceUuid, base64Image);
                      } else {
                          resourceIcon = existingResourceIcon;
                          resourceIcon.setIcon(base64Image);
                          resourceIcon.setUpdated(new Date());
                      }
                      try {
                          resourceIconDao.persist(resourceIcon);
                      } catch (EntityExistsException e) {
                          throw new CloudRuntimeException(String.format("Image already uploaded for resource type: %s with id %s",  resourceType.toString(), resourceId),e);
                      }
                  }
              }
          });
  
          return true;
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_ICON_DELETE, eventDescription = "deleting resource icon")
      public boolean deleteResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType) {
          Account caller = CallContext.current().getCallingAccount();
          List<? extends ResourceIcon> resourceIcons = searchResourceIcons(resourceIds, resourceType);
          if (resourceIcons.isEmpty()) {
              logger.debug("No resource Icon(s) uploaded for the specified resources");
              return false;
          }
          Transaction.execute(new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
                  for (ResourceIcon resourceIcon : resourceIcons) {
                      String resourceId = resourceIcon.getResourceUuid();
                      long id = resourceManagerUtil.getResourceId(resourceId, resourceType);
                      updateResourceDetailsInContext(id, resourceType);
                      Pair<Long, Long> accountDomainPair = getAccountDomain(id, resourceType);
                      Long domainId = accountDomainPair.second();
                      Long accountId = accountDomainPair.first();
                      resourceManagerUtil.checkResourceAccessible(accountId, domainId, String.format("Account ' %s ' doesn't have permissions to upload icon for resource [id: %s, uuid: %s]", caller, id, resourceId));
                      resourceIconDao.remove(resourceIcon.getId());
                      logger.debug("Removed icon for resources (" +
                              String.join(", ", resourceIds) + ")");
                  }
              }
          });
          return true;
      }
  
      @Override
      public ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId) {
          return resourceIconDao.findByResourceUuid(resourceId, type);
      }
  
      private List<? extends ResourceIcon> searchResourceIcons(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType) {
          List<String> resourceUuids = resourceIds.stream().map(resourceId -> resourceManagerUtil.getUuid(resourceId, resourceType)).collect(Collectors.toList());
          SearchBuilder<ResourceIconVO> sb = resourceIconDao.createSearchBuilder();
          sb.and("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.IN);
          sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
  
          SearchCriteria<ResourceIconVO> sc = sb.create();
          sc.setParameters("resourceUuid", resourceUuids.toArray());
          sc.setParameters("resourceType", resourceType);
          return resourceIconDao.search(sc, null);
      }
  
  
      @Override
      public Map<Long, ResourceIcon> getByResourceTypeAndIds(ResourceTag.ResourceObjectType resourceType, Collection<Long> resourceIds) {
          List<ResourceIconVO> icons = resourceIconDao.listByResourceTypeAndIds(resourceType, resourceIds);
          return icons.stream().collect(Collectors.toMap(ResourceIconVO::getResourceId, Function.identity()));
      }
  
  
      @Override
      public Map<String, ResourceIcon> getByResourceTypeAndUuids(ResourceTag.ResourceObjectType resourceType, Collection<String> resourceUuids) {
          List<ResourceIconVO> icons = resourceIconDao.listByResourceTypeAndUuids(resourceType, resourceUuids);
          return icons.stream().collect(Collectors.toMap(ResourceIconVO::getResourceUuid, Function.identity()));
      }
  }
  