/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.enterprise.server.alert;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.alert.AlertDefinition;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.criteria.AlertDefinitionCriteria;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.group.ResourceGroup;
import org.rhq.core.domain.server.PersistenceUtility;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.PageOrdering;
import org.rhq.core.util.collection.ArrayUtils;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.auth.SubjectManagerLocal;
import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal;
import org.rhq.enterprise.server.safeinvoker.HibernateDetachUtility;
import org.rhq.enterprise.server.safeinvoker.HibernateDetachUtility.SerializationType;
import org.rhq.enterprise.server.util.BatchIterator;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator;
import org.rhq.enterprise.server.util.CriteriaQueryRunner;
/**
* @author Joseph Marques
*/
@Stateless
public class GroupAlertDefinitionManagerBean implements GroupAlertDefinitionManagerLocal {
private static final Log LOG = LogFactory.getLog(GroupAlertDefinitionManagerBean.class);
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@EJB
private AlertDefinitionManagerLocal alertDefinitionManager;
@EJB
//@IgnoreDependency
private ResourceGroupManagerLocal resourceGroupManager;
@EJB
private SubjectManagerLocal subjectManager;
@Override
@SuppressWarnings("unchecked")
@Deprecated
// remove along with portal war
public PageList<AlertDefinition> findGroupAlertDefinitions(Subject subject, int resourceGroupId,
PageControl pageControl) {
pageControl.initDefaultOrderingField("ctime", PageOrdering.DESC);
Query queryCount = PersistenceUtility.createCountQuery(entityManager,
AlertDefinition.QUERY_FIND_BY_RESOURCE_GROUP);
Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
AlertDefinition.QUERY_FIND_BY_RESOURCE_GROUP, pageControl);
queryCount.setParameter("groupId", resourceGroupId);
query.setParameter("groupId", resourceGroupId);
long totalCount = (Long) queryCount.getSingleResult();
List<AlertDefinition> list = query.getResultList();
return new PageList<AlertDefinition>(list, (int) totalCount, pageControl);
}
private List<Integer> getChildrenAlertDefinitionIds(int groupAlertDefinitionId) {
TypedQuery<Integer> query = entityManager.createNamedQuery(
AlertDefinition.QUERY_FIND_BY_GROUP_ALERT_DEFINITION_ID, Integer.class);
query.setParameter("groupAlertDefinitionId", groupAlertDefinitionId);
return query.getResultList();
}
@Override
public int removeGroupAlertDefinitions(Subject subject, Integer[] groupAlertDefinitionIds) {
if (null == groupAlertDefinitionIds || groupAlertDefinitionIds.length == 0) {
return 0;
}
int modified = 0;
List<Integer> allChildDefinitionIds = new ArrayList<Integer>();
for (Integer groupAlertDefinitionId : groupAlertDefinitionIds) {
AlertDefinition groupAlertDefinition = entityManager.find(AlertDefinition.class, groupAlertDefinitionId);
if (null == groupAlertDefinition) {
continue;
}
// remove the group def
groupAlertDefinition.setDeleted(true);
groupAlertDefinition.setGroup(null); // break bonds so corresponding ResourceGroup can be purged
++modified;
// remove the child resource-level defs
Subject overlord = subjectManager.getOverlord();
List<Integer> childDefinitionIds = getChildrenAlertDefinitionIds(groupAlertDefinitionId);
if (childDefinitionIds.isEmpty()) {
continue;
}
allChildDefinitionIds.addAll(childDefinitionIds);
alertDefinitionManager.removeResourceAlertDefinitions(overlord,
ArrayUtils.unwrapCollection(childDefinitionIds));
// finally, detach protected alert defs
Query query = entityManager
.createNamedQuery(AlertDefinition.QUERY_UPDATE_DETACH_PROTECTED_BY_GROUP_ALERT_DEFINITION_ID);
query.setParameter("groupAlertDefinitionId", groupAlertDefinitionId);
int numDetached = query.executeUpdate();
}
if (!allChildDefinitionIds.isEmpty()) {
breakLinks(allChildDefinitionIds);
}
return modified;
}
private void breakLinks(List<Integer> ids) {
/*
* break the Hibernate relationships used for navigating between the groupAlertDefinition and the
* children alertDefinitions so that the async deletion mechanism can delete without FK violations
*/
Query breakLinksQuery = entityManager.createNamedQuery(AlertDefinition.QUERY_UPDATE_SET_PARENTS_NULL);
BatchIterator<Integer> batchIterator = new BatchIterator<Integer>(ids, 500);
for (List<Integer> nextBatch : batchIterator) {
breakLinksQuery.setParameter("childrenDefinitionIds", nextBatch);
breakLinksQuery.executeUpdate();
}
}
@SuppressWarnings("unchecked")
private List<Integer> getCommittedResourceIdsNeedingGroupAlertDefinitionApplication(Subject subject,
int groupAlertDefinitionId, int resourceGroupId) {
Query query = entityManager.createNamedQuery(AlertDefinition.QUERY_FIND_RESOURCE_IDS_NEEDING_GROUP_APPLICATION);
query.setParameter("groupAlertDefinitionId", groupAlertDefinitionId);
query.setParameter("resourceGroupId", resourceGroupId);
query.setParameter("inventoryStatus", InventoryStatus.COMMITTED);
List<Integer> list = query.getResultList();
return list;
}
@Override
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public int createGroupAlertDefinitions(Subject subject, AlertDefinition groupAlertDefinition,
Integer resourceGroupId) throws InvalidAlertDefinitionException, AlertDefinitionCreationException {
ResourceGroup group = resourceGroupManager.getResourceGroupById(subject, resourceGroupId, null);
groupAlertDefinition.setGroup(group);
AlertDefinition persistedDefinition = null;
int groupAlertDefinitionId = 0;
try {
persistedDefinition = alertDefinitionManager.createAlertDefinitionInNewTransaction(subject,
groupAlertDefinition, null, true);
groupAlertDefinitionId = persistedDefinition.getId();
} catch (Throwable t) {
throw new AlertDefinitionCreationException("Could not create groupAlertDefinitions for " + group
+ " with data " + groupAlertDefinition.toSimpleString(), t);
}
Throwable firstThrowable = null;
List<Integer> resourceIdsForGroup = getCommittedResourceIdsNeedingGroupAlertDefinitionApplication(subject,
groupAlertDefinitionId, resourceGroupId);
List<Integer> resourceIdsInError = new ArrayList<Integer>();
for (Integer resourceId : resourceIdsForGroup) {
try {
// construct the child
AlertDefinition childAlertDefinition = new AlertDefinition(persistedDefinition);
childAlertDefinition.setGroupAlertDefinition(groupAlertDefinition);
// persist the child
alertDefinitionManager.createDependentAlertDefinition(subject, childAlertDefinition, resourceId);
} catch (Throwable t) {
// continue on error, create as many as possible
if (firstThrowable == null) {
firstThrowable = t;
}
resourceIdsInError.add(resourceId);
}
}
if (firstThrowable != null) {
throw new AlertDefinitionCreationException("Could not create alert definition child for Resources "
+ resourceIdsInError + " with group " + groupAlertDefinition.toSimpleString(), firstThrowable);
}
return groupAlertDefinitionId;
}
@Override
public int disableGroupAlertDefinitions(Subject subject, Integer[] groupAlertDefinitionIds) {
if (null == groupAlertDefinitionIds || groupAlertDefinitionIds.length == 0) {
return 0;
}
int modified = 0;
for (Integer groupAlertDefinitionId : groupAlertDefinitionIds) {
AlertDefinition groupAlertDefinition = entityManager.find(AlertDefinition.class, groupAlertDefinitionId);
if (null == groupAlertDefinition || !groupAlertDefinition.getEnabled() || groupAlertDefinition.getDeleted()) {
continue;
}
// enable the template
groupAlertDefinition.setEnabled(false);
++modified;
// enable the child resource-level defs
List<Integer> childDefinitionIds = getChildrenAlertDefinitionIds(groupAlertDefinitionId);
if (childDefinitionIds.isEmpty()) {
continue;
}
Subject overlord = subjectManager.getOverlord();
alertDefinitionManager.disableResourceAlertDefinitions(overlord,
ArrayUtils.unwrapCollection(childDefinitionIds));
}
return modified;
}
@Override
public int enableGroupAlertDefinitions(Subject subject, Integer[] groupAlertDefinitionIds) {
if (null == groupAlertDefinitionIds || groupAlertDefinitionIds.length == 0) {
return 0;
}
int modified = 0;
for (Integer groupAlertDefinitionId : groupAlertDefinitionIds) {
AlertDefinition groupAlertDefinition = entityManager.find(AlertDefinition.class, groupAlertDefinitionId);
if (null == groupAlertDefinition || groupAlertDefinition.getEnabled() || groupAlertDefinition.getDeleted()) {
continue;
}
// enable the template
groupAlertDefinition.setEnabled(true);
++modified;
// enable the child resource-level defs
List<Integer> childDefinitionIds = getChildrenAlertDefinitionIds(groupAlertDefinitionId);
if (childDefinitionIds.isEmpty()) {
continue;
}
Subject overlord = subjectManager.getOverlord();
alertDefinitionManager.enableResourceAlertDefinitions(overlord,
ArrayUtils.unwrapCollection(childDefinitionIds));
}
return modified;
}
@Override
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public AlertDefinition updateGroupAlertDefinitions(Subject subject, AlertDefinition groupAlertDefinition,
boolean resetMatching) throws InvalidAlertDefinitionException, AlertDefinitionUpdateException {
if (LOG.isDebugEnabled()) {
LOG.debug("updateGroupAlertDefinition: " + groupAlertDefinition);
}
// first update the actual alert group alert definition
AlertDefinition updated = null;
try {
updated = alertDefinitionManager.updateAlertDefinition(subject, groupAlertDefinition.getId(),
groupAlertDefinition, resetMatching); // do not allow direct undeletes of an alert definition
} catch (Throwable t) {
throw new AlertDefinitionUpdateException("Failed to update a GroupAlertDefinition: "
+ groupAlertDefinition.toSimpleString(), t);
}
// overlord will be used for all system-side effects as a result of updating this alert template
Subject overlord = subjectManager.getOverlord();
Throwable firstThrowable = null;
// update all of the definitions that were spawned from this group alert definition
List<Integer> alertDefinitions = getChildrenAlertDefinitionIds(groupAlertDefinition.getId());
if (LOG.isDebugEnabled()) {
LOG.debug("Need to update the following children alert definition ids: " + alertDefinitions);
}
List<Integer> alertDefinitionIdsInError = new ArrayList<Integer>();
for (Integer alertDefinitionId : alertDefinitions) {
try {
alertDefinitionManager.updateDependentAlertDefinition(subject, alertDefinitionId, updated,
resetMatching);
} catch (Throwable t) {
// continue on error, update as many as possible
if (firstThrowable == null) {
firstThrowable = t;
}
alertDefinitionIdsInError.add(alertDefinitionId);
}
}
// if the subject deleted the alertDefinition spawned from a groupAlertDefinition, cascade update will recreate it
List<Integer> resourceIds = getCommittedResourceIdsNeedingGroupAlertDefinitionApplication(overlord,
groupAlertDefinition.getId(), getResourceGroupIdForAlertDefinitionId(groupAlertDefinition.getId()));
List<Integer> resourceIdsInError = new ArrayList<Integer>();
for (Integer resourceId : resourceIds) {
try {
// construct the child
AlertDefinition childAlertDefinition = new AlertDefinition(updated);
childAlertDefinition.setGroupAlertDefinition(groupAlertDefinition);
// persist the child
alertDefinitionManager.createAlertDefinitionInNewTransaction(subject, childAlertDefinition, resourceId,
false);
} catch (Throwable t) {
// continue on error, update as many as possible
if (firstThrowable == null) {
firstThrowable = t;
}
resourceIdsInError.add(resourceId);
}
}
if (firstThrowable != null) {
StringBuilder error = new StringBuilder();
if (alertDefinitionIdsInError.size() != 0) {
error.append("Failed to update child AlertDefinitions ").append(alertDefinitionIdsInError)
.append(" ; ");
}
if (resourceIdsInError.size() != 0) {
error.append("Failed to re-create child AlertDefinition for Resources ").append(resourceIdsInError).append("; ");
}
throw new AlertDefinitionUpdateException(error.toString(), firstThrowable);
}
return updated;
}
@Override
public void addGroupMemberAlertDefinitions(Subject subject, int resourceGroupId, int[] addedResourceIds)
throws AlertDefinitionCreationException {
if (addedResourceIds == null || addedResourceIds.length == 0) {
return;
}
List<Integer> resourceIdsInError = new ArrayList<Integer>();
Throwable firstThrowable = null;
// We want to copy the group level AlertDefinitions, so fetch them with the relevant lazy fields, so we
// have everything we need when calling the copy constructor, minimizing
AlertDefinitionCriteria criteria = new AlertDefinitionCriteria();
criteria.addFilterResourceGroupIds(resourceGroupId);
criteria.fetchGroupAlertDefinition(false);
criteria.fetchConditions(true);
criteria.fetchAlertNotifications(true);
// Apply paging when optionally fetching collections, to avoid duplicates. Hibernate seems to apply DISTINCT,
// which is what we want. Use a huge # because we want them all.
criteria.setPaging(0, Integer.MAX_VALUE);
List<AlertDefinition> groupAlertDefinitions = alertDefinitionManager.findAlertDefinitionsByCriteria(subject,
criteria);
for (AlertDefinition groupAlertDefinition : groupAlertDefinitions) {
for (Integer resourceId : addedResourceIds) {
try {
// Construct the resource-level AlertDefinition by using the copy constructor.
AlertDefinition childAlertDefinition = new AlertDefinition(groupAlertDefinition);
// groupAlertDefinition is an attached entity. It is dangerous to pass attached entities (with
// Hibernate proxies for the current session) across sessions. Since the call to create the new
// AlertDefinition is performed in a new transaction, make sure not to pass the attached entity.
// Just use a simple stand-in to create the link to the group alert definition.
AlertDefinition groupAlertDefinitionPojo = new AlertDefinition();
groupAlertDefinitionPojo.setId(groupAlertDefinition.getId());
childAlertDefinition.setGroupAlertDefinition(groupAlertDefinitionPojo);
// Persist the resource-level (child) alert definition, cleanse any further proxies left
// over from the copy constructor.
HibernateDetachUtility.nullOutUninitializedFields(childAlertDefinition,
SerializationType.SERIALIZATION);
alertDefinitionManager.createAlertDefinitionInNewTransaction(subjectManager.getOverlord(),
childAlertDefinition, resourceId, false);
} catch (Throwable t) {
// continue on error, create as many as possible
if (firstThrowable == null) {
firstThrowable = t;
}
resourceIdsInError.add(resourceId);
}
}
}
if (firstThrowable != null) {
throw new AlertDefinitionCreationException(
"Could not create group alert definition children for Resources " + resourceIdsInError
+ " under ResourceGroup[id=" + resourceGroupId + "]", firstThrowable);
}
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void purgeAllGroupAlertDefinitions(Subject subject, int resourceGroupId) {
Integer[] groupAlertDefinitionIdsForResourceGroup = findGroupAlertDefinitionIds(resourceGroupId);
removeGroupAlertDefinitions(subject, groupAlertDefinitionIdsForResourceGroup);
}
@SuppressWarnings("unchecked")
private Integer[] findGroupAlertDefinitionIds(int resourceGroupId) {
AlertDefinitionCriteria criteria = new AlertDefinitionCriteria();
criteria.addFilterResourceGroupIds(resourceGroupId);
criteria.setPageControl(PageControl.getUnlimitedInstance());
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(criteria);
generator.alterProjection("alertdefinition.id");
Query query = generator.getQuery(entityManager);
List<Integer> groupAlertDefinitionIds = query.getResultList();
Integer[] results = groupAlertDefinitionIds.toArray(new Integer[groupAlertDefinitionIds.size()]);
return results;
}
@Override
public void removeGroupMemberAlertDefinitions(Subject subject, int resourceGroupId, Integer[] removedResourceIds) {
if (removedResourceIds == null || removedResourceIds.length == 0) {
return;
}
// fetch the resource-level AlertDefs tied to the Group from which resources are being removed
AlertDefinitionCriteria criteria = new AlertDefinitionCriteria();
criteria.addFilterResourceIds(removedResourceIds);
criteria.addFilterGroupAlertDefinitionGroupId(resourceGroupId);
criteria.addFilterDeleted(false);
criteria.clearPaging();
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
CriteriaQueryRunner<AlertDefinition> queryRunner = new CriteriaQueryRunner<AlertDefinition>(criteria,
generator, entityManager);
List<AlertDefinition> alertDefinitions = queryRunner.execute();
// No group alert defs, just return
if (alertDefinitions.isEmpty()) {
return;
}
// 1) remove only the attached (i.e. non-protected) alert defs.
// 2) break all of the FK references to the group
List<Integer> allMemberAlertDefinitionIds = new ArrayList<Integer>(alertDefinitions.size());
List<Integer> attachedMemberAlertDefinitionIds = new ArrayList<Integer>(alertDefinitions.size());
for (AlertDefinition ad : alertDefinitions) {
Integer id = ad.getId();
allMemberAlertDefinitionIds.add(id);
if (!ad.isReadOnly()) {
attachedMemberAlertDefinitionIds.add(id);
}
}
// 1) remove only the attached (i.e. non-protected) alert defs.
if (!attachedMemberAlertDefinitionIds.isEmpty()) {
int[] groupMemberAlertDefinitionIdsArray = ArrayUtils.unwrapCollection(attachedMemberAlertDefinitionIds);
alertDefinitionManager.removeAlertDefinitions(subjectManager.getOverlord(),
groupMemberAlertDefinitionIdsArray);
}
// 2) break all of the references to the group
breakLinks(allMemberAlertDefinitionIds);
alertDefinitions.clear();
allMemberAlertDefinitionIds.clear();
attachedMemberAlertDefinitionIds.clear();
}
private int getResourceGroupIdForAlertDefinitionId(int groupAlertDefinitionId) {
Query query = entityManager.createQuery("" //
+ "SELECT groupAlertDefinition.group.id " //
+ " FROM AlertDefinition groupAlertDefinition " //
+ " WHERE groupAlertDefinition.id = :groupAlertDefinitionId");
query.setParameter("groupAlertDefinitionId", groupAlertDefinitionId);
int groupId = ((Number) query.getSingleResult()).intValue();
return groupId;
}
}