/* * RHQ Management Platform * Copyright (C) 2005-2008 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.alert; 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.NoResultException; import javax.persistence.NonUniqueResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.exception.ConstraintViolationException; import org.rhq.core.db.DatabaseTypeFactory; import org.rhq.core.domain.alert.Alert; import org.rhq.core.domain.alert.AlertCondition; import org.rhq.core.domain.alert.AlertConditionLog; import org.rhq.core.domain.alert.AlertDampeningEvent; import org.rhq.core.domain.alert.AlertDefinition; import org.rhq.core.domain.alert.BooleanExpression; import org.rhq.enterprise.server.RHQConstants; /** * @author Joseph Marques */ @Stateless public class AlertConditionLogManagerBean implements AlertConditionLogManagerLocal { private final Log log = LogFactory.getLog(AlertConditionLogManagerBean.class); @EJB private AlertConditionLogManagerLocal alertConditionLogManager; @EJB private AlertConditionManagerLocal alertConditionManager; @EJB private AlertDampeningManagerLocal alertDampeningManager; @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @Override public AlertConditionLog getUnmatchedLogByAlertConditionId(int alertConditionId) { Query query = entityManager.createNamedQuery(AlertConditionLog.QUERY_FIND_UNMATCHED_LOG_BY_ALERT_CONDITION_ID); query.setParameter("alertConditionId", alertConditionId); return (AlertConditionLog) query.getSingleResult(); } @Override @SuppressWarnings("unchecked") public List<AlertConditionLog> getUnmatchedLogsByAlertDefinitionId(int alertDefinitionId) { Query unmatchedLogsQuery = entityManager .createNamedQuery(AlertConditionLog.QUERY_FIND_UNMATCHED_LOGS_BY_ALERT_DEFINITION_ID); unmatchedLogsQuery.setParameter("alertDefinitionId", alertDefinitionId); List<AlertConditionLog> unmatchedConditionLogs = unmatchedLogsQuery.getResultList(); return unmatchedConditionLogs; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void updateUnmatchedLogByAlertConditionId(int alertConditionId, long ctime, String value) { /* * this method is marked as REQUIRES_NEW because I want this log work to complete before I resume the * outer-scoping transaction, which will operate on the results. */ try { try { AlertConditionLog alertConditionLog = this.getUnmatchedLogByAlertConditionId(alertConditionId); /* * No exceptions. * * This means that there was exactly one existing, unmatched, active alert condition. This is another * positive event associated against the same alertCondition, so we're going to use its data to update the * "ctime" and "value" properties. */ alertConditionLog.setCtime(ctime); value = DatabaseTypeFactory.getDefaultDatabaseType().getString(value, AlertConditionLog.MAX_LOG_LENGTH); alertConditionLog.setValue(value); if (log.isDebugEnabled()) { log.debug("Updating unmatched alert condition log: " + alertConditionLog); } entityManager.merge(alertConditionLog); // update values, for entityManager.flush(); } catch (NoResultException nre) { // this is the expected case 90% of the time // lookup the condition entity AlertCondition condition = entityManager.find(AlertCondition.class, alertConditionId); // persist the log entry AlertConditionLog conditionLog = new AlertConditionLog(condition, ctime); conditionLog.setValue(value); if (log.isDebugEnabled()) { log.debug("Inserting unmatched alert condition log: " + conditionLog); } entityManager.persist(conditionLog); entityManager.flush(); } catch (NonUniqueResultException nure) { // serious bug in the processing logic log.debug("Found multiple unmatched results for alertConditionId of " + alertConditionId + " while performing activation. There should only be one."); } } catch (Throwable t) { Throwable throwable = t; boolean found = false; while (throwable != null) { if (throwable instanceof ConstraintViolationException) { // we're trying to persist a log entry for an AlertCondition that was just deleted log.debug("ConstraintViolationException thrown during AlertConditionLog persistence"); found = true; break; } throwable = throwable.getCause(); } if (!found) { throw new RuntimeException( "Could not insert log entry for AlertCondition[id=" + alertConditionId + "]", t); } } } @Override public void removeUnmatchedLogByAlertConditionId(int alertConditionId) { try { AlertConditionLog alertConditionLog = this.getUnmatchedLogByAlertConditionId(alertConditionId); if (log.isDebugEnabled()) { log.debug("Removing unmatched alert condition log: " + alertConditionLog); } entityManager.remove(alertConditionLog); } catch (NoResultException nre) { /* * This is OK. * * At the time the cache fired it thought that there was an unmatched, active condition that included this * condition. However, in the time between the sending of the JMS message to "now", the out-of-band process * came along and matched all necessary conditions and created an alert. The act of creating an alert and * associating this alertConditionId with it means that it can no longer be removed by negative events */ } catch (NonUniqueResultException nure) { // serious bug in the processing logic log.debug("Found multiple unmatched results for alertConditionId of " + alertConditionId + " while performing deactivation. There should only be one."); } } @Override public Alert checkForCompletedAlertConditionSet(int alertConditionId) { Integer alertDefinitionId = alertConditionManager .getAlertDefinitionByConditionIdNewTx(alertConditionId); // ok, so figure out whether all of the conditions have been met boolean conditionSetResult = evaluateConditionSet(alertDefinitionId); if (log.isDebugEnabled()) { log.debug("Alert definition with conditionId=" + alertConditionId + " evaluated to " + conditionSetResult); } /* * The AlertDampeningEvents keep a running log of when all conditions have become true, as well as when they * become untrue (if they were most recently known to be true) */ AlertDampeningEvent latestEvent = alertDampeningManager.getLatestEventByAlertDefinitionId(alertDefinitionId); AlertDampeningEvent.Type type = getNextEventType(latestEvent, conditionSetResult); if (log.isDebugEnabled()) { log.debug("Latest event was " + latestEvent + ", " + "next AlertDampeningEvent.Type is " + type); } /* * Finally, operate on the new type event */ if (type != AlertDampeningEvent.Type.UNCHANGED) { /* * But only if it represents a type of event we need to act on */ AlertDefinition flyWeightDefinition = new AlertDefinition(); flyWeightDefinition.setId(alertDefinitionId); AlertDampeningEvent alertDampeningEvent = new AlertDampeningEvent(flyWeightDefinition, type); entityManager.persist(alertDampeningEvent); if (log.isDebugEnabled()) { log.debug("Need to process AlertDampeningEvent.Type of " + type + " " + "for AlertDefinition[ id=" + alertDefinitionId + " ]"); } return alertDampeningManager.processEventType(alertDefinitionId, type); } return null; } private AlertDampeningEvent.Type getNextEventType(AlertDampeningEvent lastEvent, boolean conditionSetResult) { /* * We always want to fire in the positive case. This will give us the ability to compute both time-span and * count dampening categories using the same data set. */ if (conditionSetResult) { /* * If lastEvent was null, we have no events for this AlertDefinition yet, and if we're in the negative state * that we've been in the positive state once before; In both cases, we're moving to the positive state. */ if ((lastEvent == null) || (lastEvent.getEventType() == AlertDampeningEvent.Type.NEGATIVE)) { return AlertDampeningEvent.Type.POSITIVE; } /* * However, we want to add a POSITIVE_AGAIN event type to the history if we know the last event was already * either POSITIVE or POSITIVE_AGAIN. */ else if ((lastEvent.getEventType() == AlertDampeningEvent.Type.POSITIVE) || (lastEvent.getEventType() == AlertDampeningEvent.Type.POSITIVE_AGAIN)) { return AlertDampeningEvent.Type.POSITIVE_AGAIN; } /* * for new functionality, make sure the callers recognize that this method needs to be expanded to support * the new AlertDampeningEvent.Type */ else { throw new RuntimeException("Threshold reached, but AlertDampenintEvent.Type '" + lastEvent.getEventType() + " not supported."); } } /* * Our unmatched logs don't match the expected count, thus our condition set isn't true as a whole due to the * most recent event; thus, we must check to see whether we need to terminate an open-ended interval or not. */ else { /* * special handling to represent that we're already in the negative state, and to suppress sending an * AlertDampeningEventMessage to the alertNotificationProducer altogether */ if ((lastEvent == null) || (lastEvent.getEventType() == AlertDampeningEvent.Type.NEGATIVE)) { return AlertDampeningEvent.Type.UNCHANGED; } /* * here, we were currently in one of the two positive states, so go to the negative state */ else if ((lastEvent.getEventType() == AlertDampeningEvent.Type.POSITIVE) || (lastEvent.getEventType() == AlertDampeningEvent.Type.POSITIVE_AGAIN)) { return AlertDampeningEvent.Type.NEGATIVE; } /* * for new functionality, make sure the callers recognize that this method needs to be expanded to support * the new AlertDampeningEvent.Type */ else { throw new RuntimeException("Threshold missed, but AlertDampenintEvent.Type '" + lastEvent.getEventType() + " not supported."); } } } private boolean evaluateConditionSet(Integer alertDefinitionId) { List<AlertConditionLog> unmatchedLogs = this.getUnmatchedLogsByAlertDefinitionId(alertDefinitionId); BooleanExpression expression = alertConditionLogManager.getConditionExpression(alertDefinitionId); if (expression == BooleanExpression.ANY) { if (log.isDebugEnabled()) { int conditionSetSize = alertConditionLogManager.getConditionCount(alertDefinitionId); log.debug("Need only 1 of " + conditionSetSize + " conditions to be true, " + "found " + unmatchedLogs.size() + " for AlertDefinition[id=" + alertDefinitionId + "]"); } return (unmatchedLogs.size() > 0); } else if (expression == BooleanExpression.ALL) { int conditionSetSize = alertConditionLogManager.getConditionCount(alertDefinitionId); if (log.isDebugEnabled()) { log.debug("Need all " + conditionSetSize + " conditions to be true, " + "found " + unmatchedLogs.size() + " for AlertDefinition[id=" + alertDefinitionId + "]"); } return (unmatchedLogs.size() == conditionSetSize); } else { if (log.isDebugEnabled()) { log.error("AlertConditionLogManager does not support " + expression + " boolean expression yet"); } return false; } } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public BooleanExpression getConditionExpression(int alertDefinitionId) { Query query = entityManager.createQuery("" // + "SELECT ad.conditionExpression " // + " FROM AlertDefinition ad " // + " WHERE ad.id = :alertDefinitionId"); query.setParameter("alertDefinitionId", alertDefinitionId); BooleanExpression expression = (BooleanExpression) query.getSingleResult(); return expression; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public int getConditionCount(int alertDefinitionId) { Query query = entityManager.createQuery("" // + "SELECT COUNT(ac) " // + " FROM AlertDefinition ad " // + " JOIN ad.conditions ac " // + " WHERE ad.id = :alertDefinitionId"); query.setParameter("alertDefinitionId", alertDefinitionId); long conditionCount = (Long) query.getSingleResult(); return (int) conditionCount; } }