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.Collection;
  import java.util.Collections;
  import java.util.List;
  
  import jakarta.persistence.EntityManager;
  import jakarta.persistence.NoResultException;
  import jakarta.persistence.TypedQuery;
  import jakarta.persistence.criteria.CriteriaBuilder;
  import jakarta.persistence.criteria.CriteriaQuery;
  import jakarta.persistence.criteria.Root;
  
  import org.apache.ambari.server.orm.RequiresSession;
  import org.apache.ambari.server.orm.entities.ClusterConfigEntity;
  import org.apache.ambari.server.orm.entities.ClusterEntity;
  import org.apache.ambari.server.orm.entities.StackEntity;
  import org.apache.ambari.server.state.StackId;
  
  import com.google.inject.Inject;
  import com.google.inject.Provider;
  import com.google.inject.Singleton;
  import com.google.inject.persist.Transactional;
  
  @Singleton
  public class ClusterDAO {
  
    @Inject
    private Provider<EntityManager> entityManagerProvider;
  
    @Inject
    private DaoUtils daoUtils;
  
    @Inject
    private StackDAO stackDAO;
  
    /**
     * Looks for Cluster by ID
     * @param id ID of Cluster
     * @return Found entity or NULL
     */
    @RequiresSession
    public ClusterEntity findById(long id) {
      return entityManagerProvider.get().find(ClusterEntity.class, id);
    }
  
    @RequiresSession
    public ClusterEntity findByName(String clusterName) {
      TypedQuery<ClusterEntity> query = entityManagerProvider.get().createNamedQuery("clusterByName", ClusterEntity.class);
      query.setParameter("clusterName", clusterName);
      try {
        return query.getSingleResult();
      } catch (NoResultException ignored) {
        return null;
      }
    }
  
  
    @RequiresSession
    public ClusterEntity findByResourceId(long resourceId) {
      TypedQuery<ClusterEntity> query = entityManagerProvider.get().createNamedQuery("clusterByResourceId", ClusterEntity.class);
      query.setParameter("resourceId", resourceId);
      try {
        return query.getSingleResult();
      } catch (NoResultException ignored) {
        return null;
      }
    }
  
    @RequiresSession
    public List<ClusterEntity> findAll() {
      TypedQuery<ClusterEntity> query = entityManagerProvider.get().createNamedQuery("allClusters", ClusterEntity.class);
      try {
        return query.getResultList();
      } catch (NoResultException ignored) {
      }
      return null;
    }
  
    @RequiresSession
    public ClusterConfigEntity findConfig(Long configEntityPK) {
      return entityManagerProvider.get().find(ClusterConfigEntity.class,
        configEntityPK);
    }
  
    @RequiresSession
    public ClusterConfigEntity findConfig(Long clusterId, String type, String tag) {
      CriteriaBuilder cb = entityManagerProvider.get().getCriteriaBuilder();
      CriteriaQuery<ClusterConfigEntity> cq = cb.createQuery(ClusterConfigEntity.class);
      Root<ClusterConfigEntity> config = cq.from(ClusterConfigEntity.class);
      cq.where(cb.and(
          cb.equal(config.get("clusterId"), clusterId)),
          cb.equal(config.get("type"), type),
          cb.equal(config.get("tag"), tag)
      );
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createQuery(cq);
      return daoUtils.selectOne(query);
    }
  
    @RequiresSession
    public List<ClusterConfigEntity> getEnabledConfigsByTypes(Long clusterId, Collection<String> types) {
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
          "ClusterConfigEntity.findEnabledConfigsByTypes",
        ClusterConfigEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("types", types);
  
      return daoUtils.selectList(query);
    }
  
    @RequiresSession
    public ClusterConfigEntity findConfig(Long clusterId, String type, Long version) {
      CriteriaBuilder cb = entityManagerProvider.get().getCriteriaBuilder();
      CriteriaQuery<ClusterConfigEntity> cq = cb.createQuery(ClusterConfigEntity.class);
      Root<ClusterConfigEntity> config = cq.from(ClusterConfigEntity.class);
      cq.where(cb.and(
          cb.equal(config.get("clusterId"), clusterId)),
        cb.equal(config.get("type"), type),
        cb.equal(config.get("version"), version)
      );
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createQuery(cq);
      return daoUtils.selectOne(query);
    }
  
    /**
     * Gets the next version that will be created for a given
     * {@link ClusterConfigEntity}.
     *
     * @param clusterId
     *          the cluster that the service is a part of.
     * @param configType
     *          the name of the configuration type (not {@code null}).
     * @return the highest existing value of the version column + 1
     */
    @RequiresSession
    public Long findNextConfigVersion(long clusterId, String configType) {
      TypedQuery<Number> query = entityManagerProvider.get().createNamedQuery(
          "ClusterConfigEntity.findNextConfigVersion", Number.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("configType", configType);
  
      return daoUtils.selectSingle(query).longValue();
    }
  
    /**
     * Get all configurations for the specified cluster and stack. This will
     * return different versions of the same configuration type (cluster-env v1
     * and cluster-env v2) if they exist.
     *
     * @param clusterId
     *          the cluster (not {@code null}).
     * @param stackId
     *          the stack (not {@code null}).
     * @return all service configurations for the cluster and stack.
     */
    @RequiresSession
    public List<ClusterConfigEntity> getAllConfigurations(Long clusterId,
        StackId stackId) {
  
      StackEntity stackEntity = stackDAO.find(stackId.getStackName(),
          stackId.getStackVersion());
  
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
          "ClusterConfigEntity.findAllConfigsByStack", ClusterConfigEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("stack", stackEntity);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets the latest configurations for a given stack for all of the
     * configurations of the specified cluster. This method does not take into
     * account the configuration being enabled, as the latest for a given stack
     * may not be "selected".
     *
     * @param clusterId
     *          the cluster that the service is a part of.
     * @param stackId
     *          the stack to get the latest configurations for (not {@code null}).
     * @return the latest configurations for the specified cluster and stack.
     */
    @RequiresSession
    public List<ClusterConfigEntity> getLatestConfigurations(long clusterId,
        StackId stackId) {
      StackEntity stackEntity = stackDAO.find(stackId.getStackName(),
          stackId.getStackVersion());
  
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
          "ClusterConfigEntity.findLatestConfigsByStack",
          ClusterConfigEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("stack", stackEntity);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets the latest configurations for a given stack with any of the given config types.
     * This method does not take into account the configuration being enabled.
     */
    @RequiresSession
    public List<ClusterConfigEntity> getLatestConfigurationsWithTypes(long clusterId, StackId stackId, Collection<String> configTypes) {
      StackEntity stackEntity = stackDAO.find(stackId.getStackName(), stackId.getStackVersion());
      if (configTypes.isEmpty()) {
        return Collections.emptyList();
      }
      return daoUtils.selectList(
        entityManagerProvider.get()
        .createNamedQuery("ClusterConfigEntity.findLatestConfigsByStackWithTypes", ClusterConfigEntity.class)
        .setParameter("clusterId", clusterId)
        .setParameter("stack", stackEntity)
        .setParameter("types", configTypes));
    }
  
    /**
     * Gets the latest configurations for a given stack for all of the
     * configurations of the specified cluster.
     *
     * @param clusterId
     *          the cluster that the service is a part of.
     * @param stackId
     *          the stack to get the latest configurations for (not {@code null}).
     * @return the latest configurations for the specified cluster and stack.
     */
    @RequiresSession
    public List<ClusterConfigEntity> getEnabledConfigsByStack(long clusterId, StackId stackId) {
      StackEntity stackEntity = stackDAO.find(stackId.getStackName(), stackId.getStackVersion());
  
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
          "ClusterConfigEntity.findEnabledConfigsByStack", ClusterConfigEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("stack", stackEntity);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets the latest configurations for the specified cluster.
     *
     * @param clusterId
     *          the cluster that the service is a part of.
     * @return the latest configurations for the specified cluster.
     */
    @RequiresSession
    public List<ClusterConfigEntity> getEnabledConfigs(long clusterId) {
  
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
          "ClusterConfigEntity.findEnabledConfigs", ClusterConfigEntity.class);
  
      query.setParameter("clusterId", clusterId);
  
      return daoUtils.selectList(query);
    }
  
    /**
     * Gets the latest config in the given cluster by type name. Only a config
     * which is enabled can be returned.
     *
     * @param clusterId
     *          the ID of the cluster.
     * @param type
     *          the config type (not {@code null}).
     * @return a config, or {@code null} if there is none enabled.
     */
    @RequiresSession
    public ClusterConfigEntity findEnabledConfigByType(long clusterId, String type) {
  
      TypedQuery<ClusterConfigEntity> query = entityManagerProvider.get().createNamedQuery(
          "ClusterConfigEntity.findEnabledConfigByType", ClusterConfigEntity.class);
  
      query.setParameter("clusterId", clusterId);
      query.setParameter("type", type);
  
      return daoUtils.selectOne(query);
    }
  
    /**
     * Create Cluster entity in Database
     * @param clusterEntity entity to create
     */
    @Transactional
    public void create(ClusterEntity clusterEntity) {
      entityManagerProvider.get().persist(clusterEntity);
    }
  
    /**
     * Creates a cluster configuration in the DB.
     */
    @Transactional
    public void createConfig(ClusterConfigEntity entity) {
      entityManagerProvider.get().persist(entity);
    }
  
    /**
     * Remove a cluster configuration in the DB.
     */
    @Transactional
    public void removeConfig(ClusterConfigEntity entity) {
      entityManagerProvider.get().remove(entity);
    }
  
    /**
     * Retrieve entity data from DB
     *
     * @param clusterEntity
     *          entity to refresh
     */
    @Transactional
    public void refresh(ClusterEntity clusterEntity) {
      entityManagerProvider.get().refresh(clusterEntity);
    }
  
    /**
     * Merge the specified entity into the current persistence context.
     *
     * @param clusterEntity
     *          the entity to merge (not {@code null}).
     * @return the managed entity which was merged (never {@code null}).
     */
    public ClusterEntity merge(ClusterEntity clusterEntity) {
      return merge(clusterEntity, false);
    }
  
    /**
     * Merge the specified entity into the current persistence context, optionally
     * instructing the {@link EntityManager} to write any queued persist/merges
     * into the database immediately.
     *
     * @param clusterEntity
     *          the entity to merge (not {@code null}).
     * @param flush
     *          if {@code true} then {@link EntityManager#flush()} will be invoked
     *          immediately after the merge.
     * @return the managed entity which was merged (never {@code null}).
     */
    @Transactional
    public ClusterEntity merge(ClusterEntity clusterEntity, boolean flush) {
      EntityManager entityManager = entityManagerProvider.get();
      clusterEntity = entityManager.merge(clusterEntity);
  
      // force any queued persist/merges to be written to the database, including
      // the merge from above
      if (flush) {
        entityManager.flush();
      }
  
      return clusterEntity;
    }
  
    /**
     * Merge the specified entity into the current persistence context.
     *
     * @param clusterConfigEntity
     *          the entity to merge (not {@code null}).
     * @return the managed entity which was merged (never {@code null}).
     */
    @Transactional
    public ClusterConfigEntity merge(ClusterConfigEntity clusterConfigEntity) {
      return merge(clusterConfigEntity, false);
    }
  
    /**
     * Merge the specified entities into the current persistence context.
     * @param clusterConfigEntities list of cluster configs to merge (not {@code null}).
     */
    @Transactional
    public void merge(Collection<ClusterConfigEntity> clusterConfigEntities) {
      merge(clusterConfigEntities, false);
    }
  
    /**
     * Merge the specified entity into the current persistence context.
     *
     * @param clusterConfigEntity
     *          the entity to merge (not {@code null}).
     * @param flush
     *          if {@code true} then {@link EntityManager#flush()} will be invoked
     *          immediately after the merge.
     * @return the managed entity which was merged (never {@code null}).
     */
    @Transactional
    public ClusterConfigEntity merge(ClusterConfigEntity clusterConfigEntity, boolean flush) {
      EntityManager entityManager = entityManagerProvider.get();
      ClusterConfigEntity clusterConfigEntityRes = entityManager.merge(clusterConfigEntity);
      if(flush) {
        entityManager.flush();
      }
      return clusterConfigEntityRes;
    }
  
    /**
     * Merge the specified list of cluster config entities into the current persistence context.
     * @param clusterConfigEntities the entity to merge (not {@code null}).
     * @param flush if {@code true} then {@link EntityManager#flush()} will be invoked
     *              immediately after the merge.
     */
    @Transactional
    public void merge(Collection<ClusterConfigEntity> clusterConfigEntities, boolean flush) {
      EntityManager entityManager = entityManagerProvider.get();
      for (ClusterConfigEntity clusterConfigEntity : clusterConfigEntities) {
        entityManager.merge(clusterConfigEntity);
      }
      if(flush) {
        entityManager.flush();
      }
    }
  
    @Transactional
    public void remove(ClusterEntity clusterEntity) {
      entityManagerProvider.get().remove(clusterEntity);
    }
  
    @Transactional
    public void removeByName(String clusterName) {
      remove(findByName(clusterName));
    }
  
    @Transactional
    public void removeByPK(long id) {
      remove(findById(id));
    }
  
    @RequiresSession
    public boolean isManaged(ClusterEntity entity) {
      return entityManagerProvider.get().contains(entity);
    }
  }
  