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 org.apache.cloudstack.annotation;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Collectors;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import com.cloud.cluster.ManagementServerHostVO;
  import com.cloud.user.dao.UserDataDao;
  import org.apache.cloudstack.acl.ControlledEntity;
  import org.apache.cloudstack.acl.Role;
  import org.apache.cloudstack.acl.RoleService;
  import org.apache.cloudstack.acl.RoleType;
  import org.apache.cloudstack.annotation.dao.AnnotationDao;
  import org.apache.cloudstack.api.ApiCommandResourceType;
  import org.apache.cloudstack.api.InternalIdentity;
  import org.apache.cloudstack.api.command.admin.annotation.AddAnnotationCmd;
  import org.apache.cloudstack.api.command.admin.annotation.ListAnnotationsCmd;
  import org.apache.cloudstack.api.command.admin.annotation.RemoveAnnotationCmd;
  import org.apache.cloudstack.api.command.admin.annotation.UpdateAnnotationVisibilityCmd;
  import org.apache.cloudstack.api.response.AnnotationResponse;
  import org.apache.cloudstack.api.response.ListResponse;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.cloudstack.framework.config.ConfigKey;
  import org.apache.cloudstack.framework.config.Configurable;
  import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
  import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
  import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
  import org.apache.commons.lang3.StringUtils;
  
  import com.cloud.cluster.dao.ManagementServerHostDao;
  import com.cloud.dc.ClusterVO;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.HostPodVO;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.dc.dao.HostPodDao;
  import com.cloud.domain.DomainVO;
  import com.cloud.domain.dao.DomainDao;
  import com.cloud.event.ActionEvent;
  import com.cloud.event.EventTypes;
  import com.cloud.exception.PermissionDeniedException;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.kubernetes.cluster.KubernetesServiceHelper;
  import com.cloud.network.as.dao.AutoScaleVmGroupDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.dao.Site2SiteCustomerGatewayDao;
  import com.cloud.network.vpc.dao.VpcDao;
  import com.cloud.offerings.NetworkOfferingVO;
  import com.cloud.offerings.dao.NetworkOfferingDao;
  import com.cloud.service.ServiceOfferingVO;
  import com.cloud.service.dao.ServiceOfferingDao;
  import com.cloud.storage.DiskOfferingVO;
  import com.cloud.storage.dao.DiskOfferingDao;
  import com.cloud.storage.dao.SnapshotDao;
  import com.cloud.storage.dao.VMTemplateDao;
  import com.cloud.storage.dao.VolumeDao;
  import com.cloud.user.AccountService;
  import com.cloud.user.AccountVO;
  import com.cloud.user.UserVO;
  import com.cloud.user.dao.AccountDao;
  import com.cloud.user.dao.SSHKeyPairDao;
  import com.cloud.user.dao.UserDao;
  import com.cloud.utils.Pair;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.component.PluggableService;
  import com.cloud.utils.db.EntityManager;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.dao.InstanceGroupDao;
  import com.cloud.vm.dao.VMInstanceDao;
  import com.cloud.vm.snapshot.dao.VMSnapshotDao;
  
  /**
   * @since 4.11
   */
  public final class AnnotationManagerImpl extends ManagerBase implements AnnotationService, Configurable, PluggableService {
  
      @Inject
      private AnnotationDao annotationDao;
      @Inject
      private UserDao userDao;
      @Inject
      private AccountDao accountDao;
      @Inject
      private RoleService roleService;
      @Inject
      private AccountService accountService;
      @Inject
      private VMInstanceDao vmInstanceDao;
      @Inject
      private VolumeDao volumeDao;
      @Inject
      private SnapshotDao snapshotDao;
      @Inject
      private VMSnapshotDao vmSnapshotDao;
      @Inject
      private InstanceGroupDao instanceGroupDao;
      @Inject
      private SSHKeyPairDao sshKeyPairDao;
      @Inject
      private NetworkDao networkDao;
      @Inject
      private VpcDao vpcDao;
      @Inject
      private IPAddressDao ipAddressDao;
      @Inject
      private Site2SiteCustomerGatewayDao customerGatewayDao;
      @Inject
      private VMTemplateDao templateDao;
      @Inject
      private DataCenterDao dataCenterDao;
      @Inject
      private HostPodDao hostPodDao;
      @Inject
      private ClusterDao clusterDao;
      @Inject
      private HostDao hostDao;
      @Inject
      private PrimaryDataStoreDao primaryDataStoreDao;
      @Inject
      private ImageStoreDao imageStoreDao;
      @Inject
      private DomainDao domainDao;
      @Inject
      private ServiceOfferingDao serviceOfferingDao;
      @Inject
      private DiskOfferingDao diskOfferingDao;
      @Inject
      private NetworkOfferingDao networkOfferingDao;
      @Inject
      private AutoScaleVmGroupDao autoScaleVmGroupDao;
      @Inject
      private UserDataDao userDataDao;
      @Inject
      private ManagementServerHostDao managementServerHostDao;
      @Inject
      EntityManager entityManager;
  
      private static final List<RoleType> adminRoles = Collections.singletonList(RoleType.Admin);
      private List<KubernetesServiceHelper> kubernetesServiceHelpers;
      public static final Map<EntityType, ApiCommandResourceType> s_typeMap = new HashMap<>();
  
      static {
          s_typeMap.put(EntityType.VM, ApiCommandResourceType.VirtualMachine);
          s_typeMap.put(EntityType.VOLUME, ApiCommandResourceType.Volume);
          s_typeMap.put(EntityType.SNAPSHOT, ApiCommandResourceType.Snapshot);
          s_typeMap.put(EntityType.VM_SNAPSHOT, ApiCommandResourceType.VmSnapshot);
          s_typeMap.put(EntityType.INSTANCE_GROUP, ApiCommandResourceType.None);
          s_typeMap.put(EntityType.SSH_KEYPAIR, ApiCommandResourceType.None);
          s_typeMap.put(EntityType.USER_DATA, ApiCommandResourceType.None);
          s_typeMap.put(EntityType.NETWORK, ApiCommandResourceType.Network);
          s_typeMap.put(EntityType.VPC, ApiCommandResourceType.Vpc);
          s_typeMap.put(EntityType.PUBLIC_IP_ADDRESS, ApiCommandResourceType.IpAddress);
          s_typeMap.put(EntityType.VPN_CUSTOMER_GATEWAY, ApiCommandResourceType.None);
          s_typeMap.put(EntityType.TEMPLATE, ApiCommandResourceType.Template);
          s_typeMap.put(EntityType.ISO, ApiCommandResourceType.Iso);
          s_typeMap.put(EntityType.KUBERNETES_CLUSTER, ApiCommandResourceType.None);
          s_typeMap.put(EntityType.SERVICE_OFFERING, ApiCommandResourceType.ServiceOffering);
          s_typeMap.put(EntityType.DISK_OFFERING, ApiCommandResourceType.DiskOffering);
          s_typeMap.put(EntityType.NETWORK_OFFERING, ApiCommandResourceType.NetworkOffering);
          s_typeMap.put(EntityType.ZONE, ApiCommandResourceType.Zone);
          s_typeMap.put(EntityType.POD, ApiCommandResourceType.Pod);
          s_typeMap.put(EntityType.CLUSTER, ApiCommandResourceType.Cluster);
          s_typeMap.put(EntityType.HOST, ApiCommandResourceType.Host);
          s_typeMap.put(EntityType.DOMAIN, ApiCommandResourceType.Domain);
          s_typeMap.put(EntityType.PRIMARY_STORAGE, ApiCommandResourceType.StoragePool);
          s_typeMap.put(EntityType.SECONDARY_STORAGE, ApiCommandResourceType.ImageStore);
          s_typeMap.put(EntityType.VR, ApiCommandResourceType.DomainRouter);
          s_typeMap.put(EntityType.SYSTEM_VM, ApiCommandResourceType.SystemVm);
          s_typeMap.put(EntityType.AUTOSCALE_VM_GROUP, ApiCommandResourceType.AutoScaleVmGroup);
          s_typeMap.put(EntityType.MANAGEMENT_SERVER, ApiCommandResourceType.Host);
          s_typeMap.put(EntityType.OBJECT_STORAGE, ApiCommandResourceType.ObjectStore);
      }
  
      public List<KubernetesServiceHelper> getKubernetesServiceHelpers() {
          return kubernetesServiceHelpers;
      }
  
      public void setKubernetesServiceHelpers(final List<KubernetesServiceHelper> kubernetesServiceHelpers) {
          this.kubernetesServiceHelpers = kubernetesServiceHelpers;
      }
  
      @Override
      public boolean start() {
          super.start();
          return true;
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          return true;
      }
  
      @Override
      public ListResponse<AnnotationResponse> searchForAnnotations(ListAnnotationsCmd cmd) {
          Pair<List<AnnotationVO>, Integer> annotations = getAnnotationsForApiCmd(cmd);
          List<AnnotationResponse> annotationResponses = convertAnnotationsToResponses(annotations.first());
          return createAnnotationsResponseList(annotationResponses, annotations.second());
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity")
      public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) {
          return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(),
                  addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly());
      }
  
      public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) {
          UserVO userVO = getCallingUserFromContext();
          String userUuid = userVO.getUuid();
          checkAnnotationPermissions(type, userVO);
          isEntityOwnedByTheUser(type.name(), uuid, userVO);
          updateResourceDetailsInContext(uuid, type);
  
          AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly);
          annotation.setUserUuid(userUuid);
          annotation = annotationDao.persist(annotation);
          return createAnnotationResponse(annotation);
      }
  
      private boolean isDomainAdminAllowedType(EntityType type) {
          return type == EntityType.DOMAIN || type == EntityType.DISK_OFFERING || type == EntityType.SERVICE_OFFERING;
      }
  
      private void checkAnnotationPermissions(EntityType type, UserVO user) {
          if (isCallingUserRole(RoleType.Admin)) {
              return;
          }
          List<EntityType> notAllowedTypes = EntityType.getNotAllowedTypesForNonAdmins(getCallingUserRole());
          if (notAllowedTypes.contains(type)) {
              throw new CloudRuntimeException(String.format("User: %s is not allowed to add annotations on type: %s",
                      user.getUsername(), type.name()));
          }
      }
  
      private void updateResourceDetailsInContext(String resourceUuid, EntityType resourceType) {
          ApiCommandResourceType type = s_typeMap.get(resourceType);
          if (type != null && !ApiCommandResourceType.None.equals(type)) {
              CallContext.current().setEventResourceType(type);
              Object obj = entityManager.findByUuid(type.getAssociatedClass(), resourceUuid);
              if (obj != null) {
                  CallContext.current().setEventResourceId(((InternalIdentity)obj).getId());
              }
          }
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity")
      public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) {
          String uuid = removeAnnotationCmd.getUuid();
          AnnotationVO annotation = annotationDao.findByUuid(uuid);
          if (!isCallingUserAllowedToRemoveAnnotation(annotation)) {
              throw new CloudRuntimeException(String.format("Only administrators or entity owner users can delete annotations, " +
                      "cannot remove annotation with uuid: %s - type: %s ", uuid, annotation.getEntityType().name()));
          }
          if(logger.isDebugEnabled()) {
              logger.debug(String.format("Removing annotation uuid: %s - type: %s", uuid, annotation.getEntityType().name()));
          }
          updateResourceDetailsInContext(annotation.getEntityUuid(), annotation.getEntityType());
          annotationDao.remove(annotation.getId());
  
          return createAnnotationResponse(annotation);
      }
  
      @Override
      public AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd) {
          String uuid = cmd.getUuid();
          Boolean adminsOnly = cmd.getAdminsOnly();
          AnnotationVO annotation = annotationDao.findByUuid(uuid);
          if (annotation == null || !isCallingUserRole(RoleType.Admin)) {
              String errDesc = (annotation == null) ? String.format("Annotation id:%s does not exist", uuid) :
                      String.format("Type: %s", annotation.getEntityType().name());
              throw new CloudRuntimeException(String.format("Only admins can update annotations' visibility. " +
                      "Cannot update visibility for annotation with id: %s - %s", uuid, errDesc));
          }
          if(logger.isDebugEnabled()) {
              logger.debug(String.format("Updating annotation with uuid: %s visibility to %B: ", uuid, adminsOnly));
          }
          annotation.setAdminsOnly(adminsOnly);
          annotationDao.update(annotation.getId(), annotation);
          return createAnnotationResponse(annotation);
      }
  
      private boolean isCallingUserAllowedToRemoveAnnotation(AnnotationVO annotation) {
          if (annotation == null) {
              return false;
          }
          if (isCallingUserRole(RoleType.Admin)) {
              return true;
          }
          UserVO callingUser = getCallingUserFromContext();
          String annotationOwnerUuid = annotation.getUserUuid();
          return annotationOwnerUuid != null && annotationOwnerUuid.equals(callingUser.getUuid());
      }
  
      private UserVO getCallingUserFromContext() {
          CallContext ctx = CallContext.current();
          long userId = ctx.getCallingUserId();
          UserVO userVO = userDao.findById(userId);
          if (userVO == null) {
              throw new CloudRuntimeException("Cannot find a user with ID " + userId);
          }
          return userVO;
      }
  
      private RoleType getCallingUserRole() {
          UserVO userVO = getCallingUserFromContext();
          long accountId = userVO.getAccountId();
          AccountVO accountVO = accountDao.findById(accountId);
          if (accountVO == null) {
              throw new CloudRuntimeException("Cannot find account with ID + " + accountId);
          }
          Long roleId = accountVO.getRoleId();
          Role role = roleService.findRole(roleId);
          if (role == null) {
              throw new CloudRuntimeException("Cannot find role with ID " + roleId);
          }
          return role.getRoleType();
      }
  
      private boolean isCallingUserRole(RoleType roleType) {
          RoleType userRoleType = getCallingUserRole();
          return roleType == userRoleType;
      }
  
      private Pair<List<AnnotationVO>, Integer> getAnnotationsForApiCmd(ListAnnotationsCmd cmd) {
          List<AnnotationVO> annotations;
          String userUuid = cmd.getUserUuid();
          String entityUuid = cmd.getEntityUuid();
          String entityType = cmd.getEntityType();
          String annotationFilter = StringUtils.isNotBlank(cmd.getAnnotationFilter()) ? cmd.getAnnotationFilter() : "all";
          boolean isCallerAdmin = isCallingUserRole(RoleType.Admin);
          UserVO callingUser = getCallingUserFromContext();
          String callingUserUuid = callingUser.getUuid();
          String keyword = cmd.getKeyword();
  
          if (cmd.getUuid() != null) {
              annotations = getSingleAnnotationListByUuid(cmd.getUuid(), userUuid, annotationFilter, callingUserUuid, isCallerAdmin);
          } else if (StringUtils.isNotBlank(entityType)) {
              annotations = getAnnotationsForSpecificEntityType(entityType, entityUuid, userUuid, isCallerAdmin,
                      annotationFilter, callingUserUuid, keyword, callingUser);
          } else if (StringUtils.isNotBlank(entityUuid)) {
              annotations = getAnnotationsForSpecificEntityId(entityUuid, userUuid, isCallerAdmin,
                      annotationFilter, callingUserUuid, keyword, callingUser);
          } else {
              annotations = getAllAnnotations(annotationFilter, userUuid, callingUserUuid, isCallerAdmin, keyword);
          }
          List<AnnotationVO> paginated = com.cloud.utils.StringUtils.applyPagination(annotations, cmd.getStartIndex(), cmd.getPageSizeVal());
          return (paginated != null) ? new Pair<>(paginated, annotations.size()) :
                  new Pair<>(annotations, annotations.size());
      }
  
      private List<AnnotationVO> getAllAnnotations(String annotationFilter, String userUuid, String callingUserUuid,
                                                   boolean isCallerAdmin, String keyword) {
          if(logger.isDebugEnabled()) {
              logger.debug("getting all annotations");
          }
          if ("self".equalsIgnoreCase(annotationFilter) && StringUtils.isBlank(userUuid)) {
              userUuid = callingUserUuid;
          }
          List<AnnotationVO> annotations = annotationDao.listAllAnnotations(userUuid, getCallingUserRole(),
                  annotationFilter, keyword);
          if (!isCallerAdmin) {
              annotations = filterUserOwnedAnnotations(annotations);
          }
          return annotations;
      }
  
      private List<AnnotationVO> filterUserOwnedAnnotations(List<AnnotationVO> annotations) {
          UserVO userVO = getCallingUserFromContext();
          return annotations.stream()
                  .filter(x -> isEntityOwnedByTheUser(x.getEntityType().name(), x.getEntityUuid(), userVO))
                  .collect(Collectors.toList());
      }
  
      private List<AnnotationVO> getAnnotationsForSpecificEntityId(String entityUuid, String userUuid, boolean isCallerAdmin,
                                                                   String annotationFilter, String callingUserUuid,
                                                                   String keyword, UserVO callingUser) {
          AnnotationVO annotation = annotationDao.findOneByEntityId(entityUuid);
          if (annotation != null) {
              String type = annotation.getEntityType().name();
              return getAnnotationsByEntityIdAndType(type, entityUuid, userUuid, isCallerAdmin,
                      annotationFilter, callingUserUuid, keyword, callingUser);
          }
          return new ArrayList<>();
      }
  
      private List<AnnotationVO> getAnnotationsForSpecificEntityType(String entityType, String entityUuid, String userUuid,
                                                                     boolean isCallerAdmin, String annotationFilter,
                                                                     String callingUserUuid, String keyword, UserVO callingUser) {
          if (logger.isDebugEnabled()) {
              logger.debug("getting annotations for type: " + entityType);
          }
          if ("self".equalsIgnoreCase(annotationFilter) && StringUtils.isBlank(userUuid)) {
              userUuid = callingUserUuid;
          }
          if (StringUtils.isNotBlank(entityUuid)) {
              return getAnnotationsByEntityIdAndType(entityType, entityUuid, userUuid, isCallerAdmin,
                      annotationFilter, callingUserUuid, keyword, callingUser);
          } else {
              List<AnnotationVO> annotations = annotationDao.listByEntityType(entityType, userUuid, isCallerAdmin,
                      annotationFilter, callingUserUuid, keyword);
              if (!isCallerAdmin) {
                  annotations = filterUserOwnedAnnotations(annotations);
              }
              return annotations;
          }
      }
  
      private List<AnnotationVO> getSingleAnnotationListByUuid(String uuid, String userUuid, String annotationFilter,
                                                               String callingUserUuid, boolean isCallerAdmin) {
          List<AnnotationVO> annotations = new ArrayList<>();
          if(logger.isDebugEnabled()) {
              logger.debug("getting single annotation by uuid: " + uuid);
          }
  
          AnnotationVO annotationVO = annotationDao.findByUuid(uuid);
          if (annotationVO != null && annotationVO.getUserUuid().equals(userUuid) &&
                  (annotationFilter.equalsIgnoreCase("all") ||
                          (annotationFilter.equalsIgnoreCase("self") && annotationVO.getUserUuid().equals(callingUserUuid))) &&
                  annotationVO.isAdminsOnly() == isCallerAdmin) {
              annotations.add(annotationVO);
          }
          return annotations;
      }
  
      private List<AnnotationVO> getAnnotationsByEntityIdAndType(String entityType, String entityUuid, String userUuid,
                                                                 boolean isCallerAdmin, String annotationFilter,
                                                                 String callingUserUuid, String keyword, UserVO callingUser) {
          isEntityOwnedByTheUser(entityType, entityUuid, callingUser);
          if (logger.isDebugEnabled()) {
              logger.debug("getting annotations for entity: " + entityUuid);
          }
          return annotationDao.listByEntity(entityType, entityUuid, userUuid, isCallerAdmin,
                  annotationFilter, callingUserUuid, keyword);
      }
  
      private boolean isEntityOwnedByTheUser(String entityType, String entityUuid, UserVO callingUser) {
          try {
              if (!isCallingUserRole(RoleType.Admin)) {
                  EntityType type = EntityType.valueOf(entityType);
                  List<EntityType> notAllowedTypes = EntityType.getNotAllowedTypesForNonAdmins(getCallingUserRole());
                  if (notAllowedTypes.contains(type)) {
                      return false;
                  }
                  if (isCallingUserRole(RoleType.DomainAdmin)) {
                      if (type == EntityType.SERVICE_OFFERING || type == EntityType.DISK_OFFERING) {
                          return true;
                      } else if (type == EntityType.DOMAIN) {
                          DomainVO domain = domainDao.findByUuid(entityUuid);
                          AccountVO account = accountDao.findById(callingUser.getAccountId());
                          accountService.checkAccess(account, domain);
                          return true;
                      }
                  }
                  ControlledEntity entity = getEntityFromUuidAndType(entityUuid, type);
                  if (entity == null) {
                      String errMsg = String.format("Could not find an entity with type: %s and ID: %s", entityType, entityUuid);
                      logger.error(errMsg);
                      throw new CloudRuntimeException(errMsg);
                  }
                  if (type == EntityType.NETWORK && entity instanceof NetworkVO &&
                          ((NetworkVO) entity).getAclType() == ControlledEntity.ACLType.Domain) {
                      NetworkVO network = (NetworkVO) entity;
                      DomainVO domain = domainDao.findById(network.getDomainId());
                      AccountVO account = accountDao.findById(callingUser.getAccountId());
                      accountService.checkAccess(account, domain);
                  } else {
                      accountService.checkAccess(callingUser, entity);
                  }
              }
          } catch (IllegalArgumentException e) {
              logger.error("Could not parse entity type " + entityType, e);
              return false;
          } catch (PermissionDeniedException e) {
              logger.debug(e.getMessage(), e);
              return false;
          }
          return true;
      }
  
      private ControlledEntity getEntityFromUuidAndType(String entityUuid, EntityType type) {
          switch (type) {
              case VM:
                  return vmInstanceDao.findByUuid(entityUuid);
              case VOLUME:
                  return volumeDao.findByUuid(entityUuid);
              case SNAPSHOT:
                  return snapshotDao.findByUuid(entityUuid);
              case VM_SNAPSHOT:
                  return vmSnapshotDao.findByUuid(entityUuid);
              case INSTANCE_GROUP:
                  return instanceGroupDao.findByUuid(entityUuid);
              case SSH_KEYPAIR:
                  return sshKeyPairDao.findByUuid(entityUuid);
              case USER_DATA:
                  return userDataDao.findByUuid(entityUuid);
              case NETWORK:
                  return networkDao.findByUuid(entityUuid);
              case VPC:
                  return vpcDao.findByUuid(entityUuid);
              case PUBLIC_IP_ADDRESS:
                  return ipAddressDao.findByUuid(entityUuid);
              case VPN_CUSTOMER_GATEWAY:
                  return customerGatewayDao.findByUuid(entityUuid);
              case TEMPLATE:
              case ISO:
                  return templateDao.findByUuid(entityUuid);
              case KUBERNETES_CLUSTER:
                  return kubernetesServiceHelpers.get(0).findByUuid(entityUuid);
              case AUTOSCALE_VM_GROUP:
                  return autoScaleVmGroupDao.findByUuid(entityUuid);
              case MANAGEMENT_SERVER:
                  return managementServerHostDao.findByUuid(entityUuid);
              default:
                  throw new CloudRuntimeException("Invalid entity type " + type);
          }
      }
  
      private List<AnnotationResponse> convertAnnotationsToResponses(List<AnnotationVO> annotations) {
          List<AnnotationResponse> annotationResponses = new ArrayList<>();
          for (AnnotationVO annotation : annotations) {
              annotationResponses.add(createAnnotationResponse(annotation));
          }
          return annotationResponses;
      }
  
      private ListResponse<AnnotationResponse> createAnnotationsResponseList(List<AnnotationResponse> annotationResponses, Integer count) {
          ListResponse<AnnotationResponse> listResponse = new ListResponse<>();
          listResponse.setResponses(annotationResponses, count);
          return listResponse;
      }
  
      public AnnotationResponse createAnnotationResponse(AnnotationVO annotation) {
          AnnotationResponse response = new AnnotationResponse();
          response.setUuid(annotation.getUuid());
          response.setEntityType(annotation.getEntityType());
          response.setEntityUuid(annotation.getEntityUuid());
          response.setAnnotation(annotation.getAnnotation());
          response.setUserUuid(annotation.getUserUuid());
          response.setCreated(annotation.getCreated());
          response.setRemoved(annotation.getRemoved());
          UserVO user = userDao.findByUuid(annotation.getUserUuid());
          if (user != null && StringUtils.isNotBlank(user.getUsername())) {
              response.setUsername(user.getUsername());
          }
          setResponseEntityName(response, annotation.getEntityUuid(), annotation.getEntityType());
          response.setAdminsOnly(annotation.isAdminsOnly());
          response.setObjectName("annotation");
  
          return response;
      }
  
      private String getInfrastructureEntityName(String entityUuid, EntityType entityType) {
          switch (entityType) {
              case ZONE:
                  DataCenterVO zone = dataCenterDao.findByUuid(entityUuid);
                  return zone != null ? zone.getName() : null;
              case POD:
                  HostPodVO pod = hostPodDao.findByUuid(entityUuid);
                  return pod != null ? pod.getName() : null;
              case CLUSTER:
                  ClusterVO cluster = clusterDao.findByUuid(entityUuid);
                  return cluster != null ? cluster.getName() : null;
              case HOST:
                  HostVO host = hostDao.findByUuid(entityUuid);
                  return host != null ? host.getName() : null;
              case PRIMARY_STORAGE:
                  StoragePoolVO primaryStorage = primaryDataStoreDao.findByUuid(entityUuid);
                  return primaryStorage != null ? primaryStorage.getName() : null;
              case SECONDARY_STORAGE:
                  ImageStoreVO imageStore = imageStoreDao.findByUuid(entityUuid);
                  return imageStore != null ? imageStore.getName() : null;
              case DOMAIN:
                  DomainVO domain = domainDao.findByUuid(entityUuid);
                  return domain != null ? domain.getName() : null;
              case SERVICE_OFFERING:
                  ServiceOfferingVO offering = serviceOfferingDao.findByUuid(entityUuid);
                  return offering != null ? offering.getName() : null;
              case DISK_OFFERING:
                  DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(entityUuid);
                  return diskOffering != null ? diskOffering.getName() : null;
              case NETWORK_OFFERING:
                  NetworkOfferingVO networkOffering = networkOfferingDao.findByUuid(entityUuid);
                  return networkOffering != null ? networkOffering.getName() : null;
              case VR:
              case SYSTEM_VM:
                  VMInstanceVO instance = vmInstanceDao.findByUuid(entityUuid);
                  return instance != null ? instance.getInstanceName() : null;
              case MANAGEMENT_SERVER:
                  ManagementServerHostVO mgmtServer = managementServerHostDao.findByUuid(entityUuid);
                  return mgmtServer != null ? mgmtServer.getName() : null;
              default:
                  return null;
          }
      }
  
      private void setResponseEntityName(AnnotationResponse response, String entityUuid, EntityType entityType) {
          String entityName = null;
          if (entityType.isUserAllowed()) {
              ControlledEntity entity = getEntityFromUuidAndType(entityUuid, entityType);
              if (entity != null) {
                  logger.debug(String.format("Could not find an entity with type: %s and ID: %s", entityType.name(), entityUuid));
                  entityName = entity.getName();
              }
          } else {
              entityName = getInfrastructureEntityName(entityUuid, entityType);
          }
          response.setEntityName(entityName);
      }
  
      @Override public List<Class<?>> getCommands() {
          final List<Class<?>> cmdList = new ArrayList<>();
          cmdList.add(AddAnnotationCmd.class);
          cmdList.add(ListAnnotationsCmd.class);
          cmdList.add(RemoveAnnotationCmd.class);
          cmdList.add(UpdateAnnotationVisibilityCmd.class);
          return cmdList;
      }
  
      @Override
      public String getConfigComponentName() {
          return AnnotationManagerImpl.class.getSimpleName();
      }
  
      @Override
      public ConfigKey<?>[] getConfigKeys() {
          return new ConfigKey<?>[]{};
      }
  }
  