/**
* 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.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.ambari.server.AmbariException;
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.RootServiceResponseFactory.Services;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.utilities.PredicateHelper;
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;
/**
* 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();
javax.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;
}
for (AlertGroupEntity entity : entities) {
create(entity);
}
}
/**
* Persists a new alert group.
*
* @param group
* the group to persist (not {@code null}).
*/
@Transactional
public void create(AlertGroupEntity group) {
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);
}
}
/**
* 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 = Services.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) {
entityManagerProvider.get().remove(merge(alertGroup));
}
/**
* 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);
}
}
/**
* 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);
}
}
}
/**
* 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) {
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 javax.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);
}
}
}