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.util.Collections;
  import java.util.List;
  import java.util.Set;
  
  import jakarta.persistence.EntityManager;
  import jakarta.persistence.TypedQuery;
  
  import org.apache.ambari.server.AmbariException;
  import org.apache.ambari.server.agent.stomp.dto.AlertGroupUpdate;
  import org.apache.ambari.server.controller.RootComponent;
  import org.apache.ambari.server.controller.RootService;
  import org.apache.ambari.server.controller.internal.AlertDefinitionResourceProvider;
  import org.apache.ambari.server.events.AlertDefinitionChangedEvent;
  import org.apache.ambari.server.events.AlertDefinitionDeleteEvent;
  import org.apache.ambari.server.events.AlertDefinitionRegistrationEvent;
  import org.apache.ambari.server.events.AlertGroupsUpdateEvent;
  import org.apache.ambari.server.events.UpdateEventType;
  import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
  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.state.alert.AlertDefinition;
  import org.apache.ambari.server.state.alert.AlertDefinitionFactory;
  import org.apache.ambari.server.state.alert.Scope;
  import org.apache.ambari.server.state.alert.SourceType;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.google.inject.Inject;
  import com.google.inject.Provider;
  import com.google.inject.Singleton;
  import com.google.inject.persist.Transactional;
  
  /**
   * The {@link AlertDefinitionDAO} class is used to manage the persistence and
   * retrieval of {@link AlertDefinitionEntity} instances.
   */
  @Singleton
  public class AlertDefinitionDAO {
    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(AlertDefinitionDAO.class);
  
    /**
     * JPA entity manager
     */
    @Inject
    private Provider<EntityManager> entityManagerProvider;
  
    /**
     * DAO utilities for dealing mostly with {@link TypedQuery} results.
     */
    @Inject
    private DaoUtils daoUtils;
  
    /**
     * Alert history DAO.
     */
    @Inject
    private AlertsDAO alertsDao;
  
    /**
     * Alert dispatch DAO.
     */
    @Inject
    private AlertDispatchDAO dispatchDao;
  
    /**
     * Publishes the following events:
     * <ul>
     * <li>{@link AlertDefinitionRegistrationEvent} when new alerts are merged
     * from the stack or created from the {@link AlertDefinitionResourceProvider}</li>
     * <li>{@link AlertDefinitionChangedEvent} when alerts are updated.</li>
     * <li>{@link AlertDefinitionDeleteEvent} when alerts are removed</li>
     * </ul>
     */
    @Inject
    private AmbariEventPublisher eventPublisher;
  
    /**
     * A factory that assists in the creation of {@link AlertDefinition} and
     * {@link AlertDefinitionEntity}.
     */
    @Inject
    private AlertDefinitionFactory alertDefinitionFactory;
  
    @Inject
    private STOMPUpdatePublisher STOMPUpdatePublisher;
  
    /**
     * Gets an alert definition with the specified ID.
     *
     * @param definitionId
     *          the ID of the definition to retrieve.
     * @return the alert definition or {@code null} if none exists.
     */
    @RequiresSession
    public AlertDefinitionEntity findById(long definitionId) {
      return entityManagerProvider.get().find(AlertDefinitionEntity.class,
          definitionId);
    }
  
    /**
     * Gets an alert definition with the specified name. Alert definition names
     * are unique within a cluster.
     *
     * @param clusterId
     *          the ID of the cluster.
     * @param definitionName
     *          the name of the definition (not {@code null}).
     * @return the alert definition or {@code null} if none exists.
     */
    @RequiresSession
    public AlertDefinitionEntity findByName(long clusterId, String definitionName) {
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findByName", AlertDefinitionEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("definitionName", definitionName);
  
      return daoUtils.selectSingle(query);
    }
  
    /**
     * Gets all alert definitions stored in the database.
     *
     * @return all alert definitions or an empty list if none exist (never
     *         {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findAll() {
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findAll", AlertDefinitionEntity.class);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert definitions stored in the database.
     *
     * @return all alert definitions or empty list if none exist (never
     *         {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findAll(long clusterId) {
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findAllInCluster", AlertDefinitionEntity.class);
  
      query.setParameter("clusterId", clusterId);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all enabled alert definitions stored in the database for the specified
     * cluster.
     *
     * @return all enabled alert definitions or empty list if none exist (never
     *         {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findAllEnabled(long clusterId) {
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findAllEnabledInCluster",
          AlertDefinitionEntity.class);
  
      query.setParameter("clusterId", clusterId);
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all of the alert definitions for the list of IDs given.
     *
     * @param definitionIds
     *          the IDs of the definitions to retrieve.
     * @return the definition or an empty list (never {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findByIds(List<Long> definitionIds) {
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findByIds", AlertDefinitionEntity.class);
  
      query.setParameter("definitionIds", definitionIds);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert definitions for the given service in the specified cluster.
     *
     * @param clusterId
     *          the ID of the cluster.
     * @param serviceName
     *          the name of the service.
     *
     * @return all alert definitions for the service or empty list if none exist
     *         (never {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findByService(long clusterId,
        String serviceName) {
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findByService", AlertDefinitionEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("serviceName", serviceName);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert definitions for the specified services that do not have a
     * component and do not belong to AGGREGATE source type. These definitions are assumed to be run on the master hosts.
     *
     * @param clusterId
     *          the ID of the cluster.
     * @param services
     *          the services to match on.
     *
     * @return all alert definitions for the services or empty list if none exist
     *         (never {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findByServiceMaster(long clusterId,
        Set<String> services) {
      if (null == services || services.size() == 0) {
        return Collections.emptyList();
      }
  
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findByServiceMaster",
          AlertDefinitionEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("services", services);
      query.setParameter("scope", Scope.SERVICE);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert definitions that are not bound to a particular service. An
     * example of this type of definition is a host capacity alert.
     *
     * @param clusterId
     *          the ID of the cluster.
     * @param serviceName
     *          the name of the service (not {@code null}).
     * @param componentName
     *          the name of the service component (not {@code null}).
     * @return all alert definitions that are not bound to a service or an empty
     *         list (never {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findByServiceComponent(long clusterId,
        String serviceName, String componentName) {
      if (null == serviceName || null == componentName) {
        return Collections.emptyList();
      }
  
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findByServiceAndComponent",
          AlertDefinitionEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("serviceName", serviceName);
      query.setParameter("componentName", componentName);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets all alert definitions that are not bound to a particular service. An
     * example of this type of definition is a host capacity alert.
     *
     * @param clusterId
     *          the ID of the cluster.
     * @return all alert definitions that are not bound to a service or an empty
     *         list (never {@code null}).
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findAgentScoped(long clusterId) {
      TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
          "AlertDefinitionEntity.findByServiceAndComponent",
          AlertDefinitionEntity.class);
  
      query.setParameter("clusterId", clusterId);
  
      query.setParameter("serviceName",
          RootService.AMBARI.name());
  
      query.setParameter("componentName",
          RootComponent.AMBARI_AGENT.name());
  
      return daoUtils.selectList(query);
    }
  
    /**
     * @return all definitions with the given sourceType
     */
    @RequiresSession
    public List<AlertDefinitionEntity> findBySourceType(Long clusterId, SourceType sourceType) {
      return daoUtils.selectList(
        entityManagerProvider.get()
          .createNamedQuery("AlertDefinitionEntity.findBySourceType", AlertDefinitionEntity.class)
          .setParameter("clusterId", clusterId)
          .setParameter("sourceType", sourceType));
    }
  
    /**
     * Persists a new alert definition, also creating the associated
     * {@link AlertGroupEntity} relationship for the definition's service default
     * group. Fires an {@link AlertDefinitionRegistrationEvent}.
     *
     * @param alertDefinition
     *          the definition to persist (not {@code null}).
     */
    @Transactional
    public void create(AlertDefinitionEntity alertDefinition)
        throws AmbariException {
      EntityManager entityManager = entityManagerProvider.get();
      entityManager.persist(alertDefinition);
  
      AlertGroupEntity group = dispatchDao.findDefaultServiceGroup(alertDefinition.getClusterId(),
          alertDefinition.getServiceName());
  
      if (null == group) {
        // create the default alert group for the new service; this MUST be done
        // before adding definitions so that they are properly added to the
        // default group
        String serviceName = alertDefinition.getServiceName();
        group = dispatchDao.createDefaultGroup(alertDefinition.getClusterId(), serviceName);
      }
  
      group.addAlertDefinition(alertDefinition);
      AlertGroupsUpdateEvent alertGroupsUpdateEvent = new AlertGroupsUpdateEvent(Collections.singletonList(
          new AlertGroupUpdate(group)),
          UpdateEventType.UPDATE);
      STOMPUpdatePublisher.publish(alertGroupsUpdateEvent);
      dispatchDao.merge(group);
  
      // publish the alert definition registration
      AlertDefinition coerced = alertDefinitionFactory.coerce(alertDefinition);
      if (null != coerced) {
        AlertDefinitionRegistrationEvent event = new AlertDefinitionRegistrationEvent(
            alertDefinition.getClusterId(), coerced);
        eventPublisher.publish(event);
      } else {
        LOG.warn("Unable to broadcast alert registration event for {}",
            alertDefinition.getDefinitionName());
      }
  
      entityManager.refresh(alertDefinition);
    }
  
    /**
     * Refresh the state of the alert definition from the database.
     *
     * @param alertDefinition
     *          the definition to refresh (not {@code null}).
     */
    @Transactional
    public void refresh(AlertDefinitionEntity alertDefinition) {
      entityManagerProvider.get().refresh(alertDefinition);
    }
  
    /**
     * Merge the speicified alert definition with the existing definition in the
     * database. Fires an {@link AlertDefinitionChangedEvent}.
     *
     * @param alertDefinition
     *          the definition to merge (not {@code null}).
     * @return the updated definition with merged content (never {@code null}).
     */
    @Transactional
    public AlertDefinitionEntity merge(AlertDefinitionEntity alertDefinition) {
      AlertDefinitionEntity entity = entityManagerProvider.get().merge(alertDefinition);
  
      AlertDefinition definition = alertDefinitionFactory.coerce(entity);
  
      AlertDefinitionChangedEvent event = new AlertDefinitionChangedEvent(
          alertDefinition.getClusterId(), definition);
  
      eventPublisher.publish(event);
  
      return entity;
    }
  
    /**
     * Creates or updates the specified entity. This method will check
     * {@link AlertDefinitionEntity#getDefinitionId()} in order to determine
     * whether the entity should be created or merged.
     *
     * @param alertDefinition
     *          the definition to create or update (not {@code null}).
     */
    public void createOrUpdate(AlertDefinitionEntity alertDefinition)
        throws AmbariException {
      if (null == alertDefinition.getDefinitionId()) {
        create(alertDefinition);
      } else {
        merge(alertDefinition);
      }
    }
  
    /**
     * Removes the specified alert definition and all related history and
     * associations from the database. Fires an {@link AlertDefinitionDeleteEvent}
     * .
     *
     * @param alertDefinition
     *          the definition to remove.
     */
    @Transactional
    public void remove(AlertDefinitionEntity alertDefinition) {
      dispatchDao.removeNoticeByDefinitionId(alertDefinition.getDefinitionId());
      alertsDao.removeByDefinitionId(alertDefinition.getDefinitionId());
  
      EntityManager entityManager = entityManagerProvider.get();
  
      alertDefinition = findById(alertDefinition.getDefinitionId());
      if (null != alertDefinition) {
        entityManager.remove(alertDefinition);
  
        // publish the alert definition removal
        AlertDefinition coerced = alertDefinitionFactory.coerce(alertDefinition);
        if (null != coerced) {
          AlertDefinitionDeleteEvent event = new AlertDefinitionDeleteEvent(
                  alertDefinition.getClusterId(), coerced);
  
          eventPublisher.publish(event);
        } else {
          LOG.warn("Unable to broadcast alert removal event for {}",
                  alertDefinition.getDefinitionName());
        }
      }
    }
  
    /**
     * Removes all {@link AlertDefinitionEntity} that are associated with the
     * specified cluster ID.
     *
     * @param clusterId
     *          the cluster ID.
     */
    @Transactional
    public void removeAll(long clusterId) {
      List<AlertDefinitionEntity> definitions = findAll(clusterId);
      for (AlertDefinitionEntity definition : definitions) {
        remove(definition);
      }
    }
  }
  