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.ambari.server.orm.dao;
  
  import java.text.MessageFormat;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.locks.Lock;
  import java.util.stream.Collectors;
  
  import jakarta.persistence.EntityManager;
  import jakarta.persistence.TypedQuery;
  import jakarta.persistence.criteria.CriteriaQuery;
  import jakarta.persistence.criteria.Order;
  import jakarta.persistence.metamodel.SingularAttribute;
  
  import org.apache.ambari.server.AmbariException;
  import org.apache.ambari.server.agent.stomp.dto.AlertGroupUpdate;
  import org.apache.ambari.server.api.query.JpaPredicateVisitor;
  import org.apache.ambari.server.api.query.JpaSortBuilder;
  import org.apache.ambari.server.controller.AlertNoticeRequest;
  import org.apache.ambari.server.controller.RootService;
  import org.apache.ambari.server.controller.spi.Predicate;
  import org.apache.ambari.server.controller.utilities.PredicateHelper;
  import org.apache.ambari.server.events.AlertGroupsUpdateEvent;
  import org.apache.ambari.server.events.UpdateEventType;
  import org.apache.ambari.server.events.publishers.STOMPUpdatePublisher;
  import org.apache.ambari.server.orm.RequiresSession;
  import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
  import org.apache.ambari.server.orm.entities.AlertGroupEntity;
  import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
  import org.apache.ambari.server.orm.entities.AlertNoticeEntity_;
  import org.apache.ambari.server.orm.entities.AlertTargetEntity;
  import org.apache.ambari.server.state.Cluster;
  import org.apache.ambari.server.state.Clusters;
  import org.apache.ambari.server.state.NotificationState;
  import org.apache.ambari.server.state.Service;
  import org.eclipse.persistence.config.HintValues;
  import org.eclipse.persistence.config.QueryHints;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.google.common.util.concurrent.Striped;
  import com.google.inject.Inject;
  import com.google.inject.Provider;
  import com.google.inject.Singleton;
  import com.google.inject.persist.Transactional;
  
  /**
   * The {@link AlertDispatchDAO} class manages the {@link AlertTargetEntity},
   * {@link AlertGroupEntity}, and the associations between them.
   */
  @Singleton
  public class AlertDispatchDAO {
    /**
     * JPA entity manager
     */
    @Inject
    private Provider<EntityManager> entityManagerProvider;
  
    /**
     * DAO utilities for dealing mostly with {@link TypedQuery} results.
     */
    @Inject
    private DaoUtils daoUtils;
  
    /**
     * Used to retrieve a cluster and its services when creating a default
     * {@link AlertGroupEntity} for a service.
     */
    @Inject
    private Provider<Clusters> m_clusters;
  
    @Inject
    private STOMPUpdatePublisher STOMPUpdatePublisher;
  
    /**
     * Used for ensuring that the concurrent nature of the event handler methods
     * don't collide when attempting to creation alert groups for the same
     * service.
     */
    private Striped<Lock> m_locksByService = Striped.lazyWeakLock(20);
  
    private static final Logger LOG = LoggerFactory.getLogger(AlertDispatchDAO.class);
  
      /**
       * Gets an alert group with the specified ID.
       *
       * @param groupId
       *          the ID of the group to retrieve.
       * @return the group or {@code null} if none exists.
       */
  
    @RequiresSession
    public AlertGroupEntity findGroupById(long groupId) {
      return entityManagerProvider.get().find(AlertGroupEntity.class, groupId);
    }
  
    /**
     * Gets all of the alert groups for the list of IDs given.
     *
     * @param groupIds
     *          the IDs of the groups to retrieve.
     * @return the groups or an empty list (never {@code null}).
     */
    @RequiresSession
    public List<AlertGroupEntity> findGroupsById(List<Long> groupIds) {
      TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertGroupEntity.findByIds", AlertGroupEntity.class);
  
      query.setParameter("groupIds", groupIds);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets an alert target with the specified ID.
     *
     * @param targetId
     *          the ID of the target to retrieve.
     * @return the target or {@code null} if none exists.
     */
    @RequiresSession
    public AlertTargetEntity findTargetById(long targetId) {
      return entityManagerProvider.get().find(AlertTargetEntity.class, targetId);
    }
  
    /**
     * Gets all of the alert targets for the list of IDs given.
     *
     * @param targetIds
     *          the IDs of the targets to retrieve.
     * @return the targets or an empty list (never {@code null}).
     */
    @RequiresSession
    public List<AlertTargetEntity> findTargetsById(List<Long> targetIds) {
      TypedQuery<AlertTargetEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertTargetEntity.findByIds", AlertTargetEntity.class);
  
      query.setParameter("targetIds", targetIds);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets a notification with the specified ID.
     *
     * @param noticeId
     *          the ID of the notification to retrieve.
     * @return the notification or {@code null} if none exists.
     */
    @RequiresSession
    public AlertNoticeEntity findNoticeById(long noticeId) {
      return entityManagerProvider.get().find(AlertNoticeEntity.class, noticeId);
    }
  
    /**
     * Gets a notification with the specified UUID.
     *
     * @param uuid
     *          the UUID of the notification to retrieve.
     * @return the notification or {@code null} if none exists.
     */
    @RequiresSession
    public AlertNoticeEntity findNoticeByUuid(String uuid) {
      TypedQuery<AlertNoticeEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertNoticeEntity.findByUuid", AlertNoticeEntity.class);
  
      query.setParameter("uuid", uuid);
  
      return daoUtils.selectOne(query);
    }
  
    /**
     * Gets all {@link AlertNoticeEntity} instances that are
     * {@link NotificationState#PENDING} and not yet dispatched.
     *
     * @return the notices that are waiting to be dispatched, or an empty list
     *         (never {@code null}).
     */
    @RequiresSession
    public List<AlertNoticeEntity> findPendingNotices() {
      TypedQuery<AlertNoticeEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertNoticeEntity.findByState", AlertNoticeEntity.class);
  
      query.setParameter("notifyState", NotificationState.PENDING);
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets an alert group with the specified name for the given cluster. Alert
     * group names are unique within a cluster.
     *
     * @param clusterId
     *          the ID of the cluster.
     * @param groupName
     *          the name of the group (not {@code null}).
     * @return the alert group or {@code null} if none exists.
     */
    @RequiresSession
    public AlertGroupEntity findGroupByName(long clusterId, String groupName) {
      TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertGroupEntity.findByNameInCluster", AlertGroupEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("groupName", groupName);
  
      return daoUtils.selectSingle(query);
    }
  
    /**
     * Gets an alert target with the specified name. Alert target names are unique
     * across all clusters.
     *
     * @param targetName
     *          the name of the target (not {@code null}).
     * @return the alert target or {@code null} if none exists.
     */
    @RequiresSession
    public AlertTargetEntity findTargetByName(String targetName) {
      TypedQuery<AlertTargetEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertTargetEntity.findByName", AlertTargetEntity.class);
  
      query.setParameter("targetName", targetName);
  
      return daoUtils.selectSingle(query);
    }
  
    /**
     * Gets all alert groups stored in the database across all clusters.
     *
     * @return all alert groups or empty list if none exist (never {@code null}).
     */
    @RequiresSession
    public List<AlertGroupEntity> findAllGroups() {
      TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertGroupEntity.findAll", AlertGroupEntity.class);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert groups stored in the database for the specified cluster.
     *
     * @return all alert groups in the specified cluster or empty list if none
     *         exist (never {@code null}).
     */
    @RequiresSession
    public List<AlertGroupEntity> findAllGroups(long clusterId) {
      TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertGroupEntity.findAllInCluster", AlertGroupEntity.class);
  
      query.setParameter("clusterId", clusterId);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert targets stored in the database.
     *
     * @return all alert targets or empty list if none exist (never {@code null}).
     */
    @RequiresSession
    public List<AlertTargetEntity> findAllTargets() {
      TypedQuery<AlertTargetEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertTargetEntity.findAll", AlertTargetEntity.class);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all global alert targets stored in the database.
     *
     * @return all global alert targets or empty list if none exist (never
     *         {@code null}).
     */
    @RequiresSession
    public List<AlertTargetEntity> findAllGlobalTargets() {
      TypedQuery<AlertTargetEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertTargetEntity.findAllGlobal", AlertTargetEntity.class);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all of the {@link AlertGroupEntity} instances that include the
     * specified alert definition. Service default groups will also be returned.
     *
     * @param definitionEntity
     *          the definition that the group must include (not {@code null}).
     * @return all alert groups that have an association with the specified
     *         definition and the definition's service default group or empty list
     *         if none exist (never {@code null}).
     */
    @RequiresSession
    public List<AlertGroupEntity> findGroupsByDefinition(
        AlertDefinitionEntity definitionEntity) {
  
      TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertGroupEntity.findByAssociatedDefinition", AlertGroupEntity.class);
  
      query.setParameter("alertDefinition", definitionEntity);
      query.setHint(QueryHints.REFRESH, HintValues.TRUE);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets the default group for the specified cluster and service.
     *
     * @param clusterId
     *          the cluster that the group belongs to
     * @param serviceName
     *          the name of the service (not {@code null}).
     * @return the default group, or {@code null} if the service name is not valid
     *         for an installed service; otherwise {@code null} should not be
     *         possible.
     */
    @RequiresSession
    public AlertGroupEntity findDefaultServiceGroup(long clusterId,
        String serviceName) {
      TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertGroupEntity.findServiceDefaultGroup", AlertGroupEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("serviceName", serviceName);
      return daoUtils.selectSingle(query);
    }
  
    /**
     * Gets all alert notifications stored in the database.
     *
     * @return all alert notifications or empty list if none exist (never
     *         {@code null}).
     */
    @RequiresSession
    public List<AlertNoticeEntity> findAllNotices() {
      TypedQuery<AlertNoticeEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertNoticeEntity.findAll", AlertNoticeEntity.class);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert notifications stored in the database that match the given
     * predicate, pagination, and sorting.
     *
     * @param request
     * @return all alert notifications or empty list if none exist (never
     *         {@code null}).
     */
    @RequiresSession
    public List<AlertNoticeEntity> findAllNotices(AlertNoticeRequest request) {
      EntityManager entityManager = entityManagerProvider.get();
  
      // convert the Ambari predicate into a JPA predicate
      NoticePredicateVisitor visitor = new NoticePredicateVisitor();
      PredicateHelper.visit(request.Predicate, visitor);
  
      CriteriaQuery<AlertNoticeEntity> query = visitor.getCriteriaQuery();
      jakarta.persistence.criteria.Predicate jpaPredicate = visitor.getJpaPredicate();
  
      if (null != jpaPredicate) {
        query.where(jpaPredicate);
      }
  
      // sorting
      JpaSortBuilder<AlertNoticeEntity> sortBuilder = new JpaSortBuilder<>();
      List<Order> sortOrders = sortBuilder.buildSortOrders(request.Sort, visitor);
      query.orderBy(sortOrders);
  
      // pagination
      TypedQuery<AlertNoticeEntity> typedQuery = entityManager.createQuery(query);
      if (null != request.Pagination) {
        typedQuery.setFirstResult(request.Pagination.getOffset());
        typedQuery.setMaxResults(request.Pagination.getPageSize());
      }
  
      return daoUtils.selectList(typedQuery);
    }
  
    /**
     * Gets the total count of all {@link AlertNoticeEntity} rows that match the
     * specified {@link Predicate}.
     *
     * @param predicate
     *          the predicate to apply, or {@code null} for none.
     * @return the total count of rows that would be returned in a result set.
     */
    @RequiresSession
    public int getNoticesCount(Predicate predicate) {
      return 0;
    }
  
    /**
     * Persists new alert groups.
     *
     * @param entities
     *          the groups to persist (not {@code null}).
     */
    @Transactional
    public void createGroups(List<AlertGroupEntity> entities) {
      if (null == entities) {
        return;
      }
  
      List<AlertGroupUpdate> alertGroupUpdates = new ArrayList<>(entities.size());
      for (AlertGroupEntity entity : entities) {
        create(entity, false);
        alertGroupUpdates.add(new AlertGroupUpdate(entity));
      }
      AlertGroupsUpdateEvent alertGroupsUpdateEvent = new AlertGroupsUpdateEvent(alertGroupUpdates,
          UpdateEventType.CREATE);
      STOMPUpdatePublisher.publish(alertGroupsUpdateEvent);
    }
  
    /**
     * Persists a new alert group.
     *
     * @param group
     *          the group to persist (not {@code null}).
     */
    @Transactional
    public void create(AlertGroupEntity group) {
      create(group, true);
    }
  
    /**
     * Persists a new alert group.
     *
     * @param group the group to persist (not {@code null}).
     * @param fireEvent should alert group update event to be fired
     */
    @Transactional
    public void create(AlertGroupEntity group, boolean fireEvent) {
  
      entityManagerProvider.get().persist(group);
  
      // associate the group with all alert targets
      List<AlertTargetEntity> targets = findAllGlobalTargets();
      if (!targets.isEmpty()) {
        for (AlertTargetEntity target : targets) {
          group.addAlertTarget(target);
        }
        entityManagerProvider.get().merge(group);
      }
      if (fireEvent) {
        AlertGroupsUpdateEvent alertGroupsUpdateEvent = new AlertGroupsUpdateEvent(
            Collections.singletonList(new AlertGroupUpdate(group)),
            UpdateEventType.CREATE);
        STOMPUpdatePublisher.publish(alertGroupsUpdateEvent);
      }
    }
  
    /**
     * Creates a default group in the specified cluster and service. If the
     * service is not valid, then this will throw an {@link AmbariException}.
     *
     * @param clusterId
     *          the cluster that the group is in.
     * @param serviceName
     *          the name of the group which is also the service name.
     */
    @Transactional
    public AlertGroupEntity createDefaultGroup(long clusterId, String serviceName)
        throws AmbariException {
  
      // AMBARI is a special service that we let through, otherwise we need to
      // verify that the service exists before we create the default group
      String ambariServiceName = RootService.AMBARI.name();
      if (!ambariServiceName.equals(serviceName)) {
        Cluster cluster = m_clusters.get().getClusterById(clusterId);
        Map<String, Service> services = cluster.getServices();
  
        if (!services.containsKey(serviceName)) {
          String message = MessageFormat.format(
              "Unable to create a default alert group for unknown service {0} in cluster {1}",
              serviceName, cluster.getClusterName());
          throw new AmbariException(message);
        }
      }
  
      Lock lock = m_locksByService.get(serviceName);
      lock.lock();
  
      try {
        AlertGroupEntity group = findDefaultServiceGroup(clusterId, serviceName);
        if (null != group) {
          return group;
        }
  
        group = new AlertGroupEntity();
        group.setClusterId(clusterId);
        group.setDefault(true);
        group.setGroupName(serviceName);
        group.setServiceName(serviceName);
  
        create(group);
        return group;
      } finally {
        lock.unlock();
      }
    }
  
    /**
     * Refresh the state of the alert group from the database.
     *
     * @param alertGroup
     *          the group to refresh (not {@code null}).
     */
    @Transactional
    public void refresh(AlertGroupEntity alertGroup) {
      entityManagerProvider.get().refresh(alertGroup);
    }
  
    /**
     * Merge the speicified alert group with the existing group in the database.
     *
     * @param alertGroup
     *          the group to merge (not {@code null}).
     * @return the updated group with merged content (never {@code null}).
     */
    @Transactional
    public AlertGroupEntity merge(AlertGroupEntity alertGroup) {
      return entityManagerProvider.get().merge(alertGroup);
    }
  
    /**
     * Removes the specified alert group from the database.
     *
     * @param alertGroup
     *          the group to remove.
     */
    @Transactional
    public void remove(AlertGroupEntity alertGroup) {
      remove(alertGroup, true);
    }
  
    /**
     * Removes the specified alert group from the database.
     *
     * @param alertGroup the group to remove.
     * @param fireEvent should alert group update event to be fired.
     */
    @Transactional
    public void remove(AlertGroupEntity alertGroup, boolean fireEvent) {
      entityManagerProvider.get().remove(merge(alertGroup));
      if (fireEvent) {
        AlertGroupsUpdateEvent alertGroupsUpdateEvent = AlertGroupsUpdateEvent.deleteAlertGroupsUpdateEvent(
            Collections.singletonList(alertGroup.getGroupId()));
        STOMPUpdatePublisher.publish(alertGroupsUpdateEvent);
      }
    }
  
    /**
     * Removes all {@link AlertDefinitionEntity} that are associated with the
     * specified cluster ID.
     *
     * @param clusterId
     *          the cluster ID.
     */
    @Transactional
    public void removeAllGroups(long clusterId) {
      List<AlertGroupEntity> groups = findAllGroups(clusterId);
      for (AlertGroupEntity group : groups) {
        remove(group, false);
      }
      AlertGroupsUpdateEvent alertGroupsUpdateEvent = AlertGroupsUpdateEvent.deleteAlertGroupsUpdateEvent(
          groups.stream().map(AlertGroupEntity::getGroupId).collect(Collectors.toList()));
      STOMPUpdatePublisher.publish(alertGroupsUpdateEvent);
    }
  
    /**
     * Persists new alert targets.
     *
     * @param entities
     *          the targets to persist (not {@code null}).
     */
    @Transactional
    public void createTargets(List<AlertTargetEntity> entities) {
      if (null == entities) {
        return;
      }
  
      for (AlertTargetEntity entity : entities) {
        create(entity);
      }
    }
  
    /**
     * Creates new alert notices using the {@link EntityManager#merge(Object)}
     * method to ensure that the associated {@link AlertTargetEntity} instances
     * are also updated.
     * <p/>
     * The method returns the newly managed entities as the ones passed in will
     * not be managed.
     *
     * @param entities
     *          the targets to create (not {@code null}).
     */
    @Transactional
    public List<AlertNoticeEntity> createNotices(List<AlertNoticeEntity> entities) {
      if (null == entities || entities.isEmpty()) {
        return entities;
      }
  
      List<AlertNoticeEntity> managedEntities = new ArrayList<>(entities.size());
      for (AlertNoticeEntity entity : entities) {
        AlertNoticeEntity managedEntity = merge(entity);
        managedEntities.add(managedEntity);
      }
  
      return managedEntities;
    }
  
    /**
     * Persists a new alert target.
     *
     * @param alertTarget
     *          the target to persist (not {@code null}).
     */
    @Transactional
    public void create(AlertTargetEntity alertTarget) {
      entityManagerProvider.get().persist(alertTarget);
  
      if (alertTarget.isGlobal()) {
        List<AlertGroupEntity> groups = findAllGroups();
        for (AlertGroupEntity group : groups) {
          group.addAlertTarget(alertTarget);
          merge(group);
          AlertGroupsUpdateEvent alertGroupsUpdateEvent = new AlertGroupsUpdateEvent(Collections.singletonList(
              new AlertGroupUpdate(group)),
              UpdateEventType.UPDATE);
          STOMPUpdatePublisher.publish(alertGroupsUpdateEvent);
        }
      }
    }
  
    /**
     * Refresh the state of the alert target from the database.
     *
     * @param alertTarget
     *          the target to refresh (not {@code null}).
     */
    @Transactional
    public void refresh(AlertTargetEntity alertTarget) {
      entityManagerProvider.get().refresh(alertTarget);
    }
  
    /**
     * Merge the speicified alert target with the existing target in the database.
     *
     * @param alertTarget
     *          the target to merge (not {@code null}).
     * @return the updated target with merged content (never {@code null}).
     */
    @Transactional
    public AlertTargetEntity merge(AlertTargetEntity alertTarget) {
      return entityManagerProvider.get().merge(alertTarget);
    }
  
    /**
     * Removes the specified alert target from the database.
     *
     * @param alertTarget
     *          the target to remove.
     */
    @Transactional
    public void remove(AlertTargetEntity alertTarget) {
      List<AlertGroupUpdate> alertGroupUpdates = new ArrayList<>();
      for (AlertGroupEntity alertGroupEntity : alertTarget.getAlertGroups()) {
        AlertGroupUpdate alertGroupUpdate = new AlertGroupUpdate(alertGroupEntity);
        alertGroupUpdate.getTargets().remove(alertTarget.getTargetId());
        alertGroupUpdates.add(alertGroupUpdate);
      }
      STOMPUpdatePublisher.publish(new AlertGroupsUpdateEvent(alertGroupUpdates, UpdateEventType.UPDATE));
      entityManagerProvider.get().remove(alertTarget);
    }
  
    /**
     * Persists a new notification.
     *
     * @param alertNotice
     *          the notification to persist (not {@code null}).
     */
    @Transactional
    public void create(AlertNoticeEntity alertNotice) {
      entityManagerProvider.get().persist(alertNotice);
    }
  
    /**
     * Refresh the state of the notification from the database.
     *
     * @param alertNotice
     *          the notification to refresh (not {@code null}).
     */
    @Transactional
    public void refresh(AlertNoticeEntity alertNotice) {
      entityManagerProvider.get().refresh(alertNotice);
    }
  
    /**
     * Merge the specified notification with the existing target in the database.
     *
     * @param alertNotice
     *          the notification to merge (not {@code null}).
     * @return the updated notification with merged content (never {@code null}).
     */
    @Transactional
    public AlertNoticeEntity merge(AlertNoticeEntity alertNotice) {
      return entityManagerProvider.get().merge(alertNotice);
    }
  
    /**
     * Removes the specified notification from the database.
     *
     * @param alertNotice
     *          the notification to remove.
     */
    @Transactional
    public void remove(AlertNoticeEntity alertNotice) {
      entityManagerProvider.get().remove(alertNotice);
    }
  
    /**
     * Removes notifications for the specified alert definition ID. This will
     * invoke {@link EntityManager#clear()} when completed since the JPQL
     * statement will remove entries without going through the EM.
     *
     * @param definitionId
     *          the ID of the definition to remove.
     */
    @Transactional
    public void removeNoticeByDefinitionId(long definitionId) {
      LOG.info("Deleting AlertNotice entities by definition id.");
      EntityManager entityManager = entityManagerProvider.get();
      TypedQuery<Integer> historyIdQuery = entityManager.createNamedQuery(
        "AlertHistoryEntity.findHistoryIdsByDefinitionId", Integer.class);
      historyIdQuery.setParameter("definitionId", definitionId);
      List<Integer> ids = daoUtils.selectList(historyIdQuery);
      // Batch delete notice
      int BATCH_SIZE = 999;
      TypedQuery<AlertNoticeEntity> noticeQuery = entityManager.createNamedQuery(
        "AlertNoticeEntity.removeByHistoryIds", AlertNoticeEntity.class);
      if (ids != null && !ids.isEmpty()) {
        for (int i = 0; i < ids.size(); i += BATCH_SIZE) {
          int endIndex = (i + BATCH_SIZE) > ids.size() ? ids.size() : (i + BATCH_SIZE);
          List<Integer> idsSubList = ids.subList(i, endIndex);
          LOG.info("Deleting AlertNotice entity batch with history ids: " +
            idsSubList.get(0) + " - " + idsSubList.get(idsSubList.size() - 1));
          noticeQuery.setParameter("historyIds", idsSubList);
          noticeQuery.executeUpdate();
        }
      }
  
      entityManager.clear();
    }
  
    /**
     * The {@link NoticePredicateVisitor} is used to convert an Ambari
     * {@link Predicate} into a JPA {@link jakarta.persistence.criteria.Predicate}.
     */
    private final class NoticePredicateVisitor extends
        JpaPredicateVisitor<AlertNoticeEntity> {
  
      /**
       * Constructor.
       *
       */
      public NoticePredicateVisitor() {
        super(entityManagerProvider.get(), AlertNoticeEntity.class);
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public Class<AlertNoticeEntity> getEntityClass() {
        return AlertNoticeEntity.class;
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public List<? extends SingularAttribute<?, ?>> getPredicateMapping(
          String propertyId) {
        return AlertNoticeEntity_.getPredicateMapping().get(propertyId);
      }
    }
  }
  