/*
 * 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.PersistenceContext;
import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.jboss.annotation.IgnoreDependency;

import org.rhq.core.domain.alert.AlertCondition;
import org.rhq.core.domain.alert.AlertConditionCategory;
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.core.domain.alert.notification.AlertNotification;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.common.composite.IntegerOptionItem;
import org.rhq.core.domain.criteria.AlertDefinitionCriteria;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.NumericType;
import org.rhq.core.domain.resource.Resource;
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.server.PersistenceUtility;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.alert.engine.AlertDefinitionEvent;
import org.rhq.enterprise.server.authz.AuthorizationManagerLocal;
import org.rhq.enterprise.server.authz.PermissionException;
import org.rhq.enterprise.server.cloud.StatusManagerLocal;
import org.rhq.enterprise.server.plugin.pc.alert.AlertSender;
import org.rhq.enterprise.server.plugin.pc.alert.AlertSenderPluginManager;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator;
import org.rhq.enterprise.server.util.CriteriaQueryRunner;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator.AuthorizationTokenType;

/**
 * @author Joseph Marques
 */
@Stateless
public class AlertDefinitionManagerBean implements AlertDefinitionManagerLocal, AlertDefinitionManagerRemote {

    private static final Log LOG = LogFactory.getLog(AlertDefinitionManagerBean.class);

    @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
    private EntityManager entityManager;

    @EJB
    private AuthorizationManagerLocal authorizationManager;
    @EJB
    private AlertDefinitionManagerLocal alertDefinitionManager;

    @EJB
    @IgnoreDependency
    private AlertManagerLocal alertManager;

    @EJB
    private StatusManagerLocal agentStatusManager;

    @EJB
    @IgnoreDependency
    private AlertNotificationManagerLocal alertNotificationManager;

    private boolean checkViewPermission(Subject subject, AlertDefinition alertDefinition) {
        if (alertDefinition.getResourceType() != null) { // an alert template
            return authorizationManager.hasGlobalPermission(subject, Permission.MANAGE_SETTINGS);
        } else if (alertDefinition.getResourceGroup() != null) { // a groupAlertDefinition
            return authorizationManager.canViewGroup(subject, alertDefinition.getResourceGroup().getId());
        } else { // an alert definition
            return authorizationManager.canViewResource(subject, alertDefinition.getResource().getId());
        }
    }

    private boolean checkPermission(Subject subject, AlertDefinition alertDefinition) {
        /*
         * system side-effects are primarily call-outs from the template manager to the definition manager, when the
         * template operation is being cascaded
         */
        if (authorizationManager.isOverlord(subject)) {
            return true;
        }

        if (alertDefinition.getResourceType() != null) { // an alert template
            return authorizationManager.hasGlobalPermission(subject, Permission.MANAGE_SETTINGS);
        } else if (alertDefinition.getResourceGroup() != null) { // a groupAlertDefinition
            return authorizationManager.hasGroupPermission(subject, Permission.MANAGE_ALERTS, alertDefinition
                .getResourceGroup().getId());
        } else { // an alert definition
            return authorizationManager.hasResourcePermission(subject, Permission.MANAGE_ALERTS, alertDefinition
                .getResource().getId());
        }
    }

    @SuppressWarnings("unchecked")
    public PageList<AlertDefinition> findAlertDefinitions(Subject subject, int resourceId, PageControl pageControl) {
        pageControl.initDefaultOrderingField("ctime", PageOrdering.DESC);

        Query queryCount = PersistenceUtility.createCountQuery(entityManager, AlertDefinition.QUERY_FIND_BY_RESOURCE);
        Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, AlertDefinition.QUERY_FIND_BY_RESOURCE,
            pageControl);

        queryCount.setParameter("id", resourceId);
        query.setParameter("id", resourceId);

        long totalCount = (Long) queryCount.getSingleResult();
        List<AlertDefinition> list = query.getResultList();

        return new PageList<AlertDefinition>(list, (int) totalCount, pageControl);
    }

    public AlertDefinition getAlertDefinitionById(Subject subject, int alertDefinitionId) {
        AlertDefinition alertDefinition = entityManager.find(AlertDefinition.class, alertDefinitionId);
        if (alertDefinition == null) {
            return null; // fail-fast to avoid downstream NPEs
        }

        if (checkViewPermission(subject, alertDefinition) == false) {
            throw new PermissionException("User[" + subject.getName()
                + "] does not have permission to view alertDefinition[id=" + alertDefinitionId + "] for resource[id="
                + alertDefinition.getResource().getId() + "]");
        }

        alertDefinition.getConditions().size();
        // this is now lazy
        for (AlertCondition cond : alertDefinition.getConditions()) {
            if (cond.getMeasurementDefinition() != null) {
                cond.getMeasurementDefinition().getId();
            }
        }
        // DO NOT LOAD ALL ALERTS FOR A DEFINITION... This would be all alerts that have been fired
        //alertDefinition.getAlertsForResource().size();
        for (AlertNotification notification : alertDefinition.getAlertNotifications()) {
            notification.getConfiguration().getProperties().size(); // eager load configuration and properties too
            if (notification.getExtraConfiguration() != null) {
                notification.getExtraConfiguration().getProperties().size();
            }
        }

        return alertDefinition;
    }

    @SuppressWarnings("unchecked")
    public List<IntegerOptionItem> findAlertDefinitionOptionItemsForResource(Subject subject, int resourceId) {
        PageControl pageControl = PageControl.getUnlimitedInstance();
        pageControl.initDefaultOrderingField("ad.name", PageOrdering.ASC);

        Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
            AlertDefinition.QUERY_FIND_OPTION_ITEMS_BY_RESOURCE, pageControl);

        query.setParameter("resourceId", resourceId);
        List<IntegerOptionItem> results = query.getResultList();

        return results;
    }

    @SuppressWarnings("unchecked")
    public List<IntegerOptionItem> findAlertDefinitionOptionItemsForGroup(Subject subject, int groupId) {
        PageControl pageControl = PageControl.getUnlimitedInstance();
        pageControl.initDefaultOrderingField("ad.name", PageOrdering.ASC);

        Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
            AlertDefinition.QUERY_FIND_OPTION_ITEMS_BY_GROUP, pageControl);

        query.setParameter("groupId", groupId);
        List<IntegerOptionItem> results = query.getResultList();

        return results;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public int createAlertDefinition(Subject subject, AlertDefinition alertDefinition, Integer resourceId)
        throws InvalidAlertDefinitionException {
        checkAlertDefinition(subject, alertDefinition, resourceId);

        // if this is an alert definition, set up the link to a resource
        if (resourceId != null) {
            // don't attach an alertTemplate or groupAlertDefinition to any particular resource
            // they should have already been attached to the resourceType or resourceGroup by the caller

            //Resource resource = LookupUtil.getResourceManager().getResourceById(user, resourceId);
            // use proxy trick to subvert having to load the entire resource into memory
            alertDefinition.setResource(new Resource(resourceId));
        }

        // after the resource is set up (in the case of non-templates), we can use the checkPermission on it
        if (checkPermission(subject, alertDefinition) == false) {
            if (alertDefinition.getResourceType() != null) {
                throw new PermissionException("User [" + subject.getName()
                    + "] does not have permission to create alert templates for type ["
                    + alertDefinition.getResourceType() + "]");
            } else if (alertDefinition.getResourceGroup() != null) {
                throw new PermissionException("User [" + subject.getName()
                    + "] does not have permission to create alert definitions for group ["
                    + alertDefinition.getResourceGroup() + "]");
            } else {
                throw new PermissionException("User [" + subject.getName()
                    + "] does not have permission to create alert definitions for resource ["
                    + alertDefinition.getResource() + "]");
            }
        }

        /*
         * performance optimization for the common case of single-condition alerts; it's easier for the
         * out-of-band process to check whether or not ANY conditions are true rather than ALL of them
         */
        if (alertDefinition.getConditions().size() == 1) {
            alertDefinition.setConditionExpression(BooleanExpression.ANY);
        }

        fixRecoveryId(alertDefinition);

        entityManager.persist(alertDefinition);

        boolean addToCache = false;
        // don't notify on an alert template, only for those that get attached to a resource
        // Only add to the cache if the alert definition was created as active
        if ((resourceId != null) && alertDefinition.getEnabled()) {
            // if this is a recovery alert
            if (alertDefinition.getRecoveryId() != 0) {
                // only add to the cache if the to-be-recovered definition is disabled, and thus needs recovering
                // use entityManager direct to bypass security checks, we already know this user is authorized
                AlertDefinition toBeRecoveredDefinition = entityManager.find(AlertDefinition.class, alertDefinition
                    .getRecoveryId());
                if (toBeRecoveredDefinition.getEnabled() == false) {
                    addToCache = true;
                }
            } else {
                addToCache = true;
            }
        }

        if (addToCache) {
            notifyAlertConditionCacheManager(subject, "createAlertDefinition", alertDefinition,
                AlertDefinitionEvent.CREATED);
        }

        return alertDefinition.getId();
    }

    private void fixRecoveryId(AlertDefinition definition) {
        try {
            if (definition.getParentId() != 0 && definition.getRecoveryId() != 0) {
                // so we need to set the resource-level recovery id properly
                String findCorrectRecoveryId = "" //
                    + " SELECT toBeRecovered.id " //
                    + "   FROM AlertDefinition toBeRecovered " //
                    + "  WHERE toBeRecovered.resource.id = :resourceId " //
                    + "    AND toBeRecovered.parentId = :parentId ";
                Query fixRecoveryIdQuery = entityManager.createQuery(findCorrectRecoveryId);
                fixRecoveryIdQuery.setParameter("resourceId", definition.getResource().getId());
                // definition.recoveryId current points at the toBeRecovered template, we want the definition
                fixRecoveryIdQuery.setParameter("parentId", definition.getRecoveryId()); // wrong one to be replaced
                Integer correctRecoveryId = (Integer) fixRecoveryIdQuery.getSingleResult();
                definition.setRecoveryId(correctRecoveryId);
            } else if (definition.getGroupAlertDefinition() != null && definition.getRecoveryId() != 0) {
                // so we need to set the resource-level recovery id properly
                String findCorrectRecoveryId = "" //
                    + " SELECT toBeRecovered.id " //
                    + "   FROM AlertDefinition toBeRecovered " //
                    + "  WHERE toBeRecovered.resource.id = :resourceId " //
                    + "    AND toBeRecovered.groupAlertDefinition.id = :groupAlertDefinitionId ";
                Query fixRecoveryIdQuery = entityManager.createQuery(findCorrectRecoveryId);
                fixRecoveryIdQuery.setParameter("resourceId", definition.getResource().getId());
                // definition.recoveryId current points at the toBeRecovered template, we want the definition
                fixRecoveryIdQuery.setParameter("groupAlertDefinitionId", definition.getRecoveryId()); // wrong one to be replaced
                Integer correctRecoveryId = (Integer) fixRecoveryIdQuery.getSingleResult();
                definition.setRecoveryId(correctRecoveryId);
            }
        } catch (NoResultException nre) {
            // expected when the recovery ids have already been fixed
        }
    }

    public int removeAlertDefinitions(Subject subject, int[] alertDefinitionIds) {
        int modifiedCount = 0;
        boolean isResourceLevel = false;

        for (int alertDefId : alertDefinitionIds) {
            AlertDefinition alertDefinition = entityManager.find(AlertDefinition.class, alertDefId);

            // TODO GH: Can be more efficient
            if (checkPermission(subject, alertDefinition)) {
                alertDefinition.setDeleted(true);
                modifiedCount++;

                // alertTemplates and groupAlertDefinitions do not need to update the cache
                isResourceLevel = (null != alertDefinition.getResource());
                if (isResourceLevel) {
                    notifyAlertConditionCacheManager(subject, "removeAlertDefinitions", alertDefinition,
                        AlertDefinitionEvent.DELETED);
                }
                if (alertDefinition.getResourceGroup() != null) {
                    alertDefinition.setResourceGroup(null); // break bonds so corresponding ResourceGroup can be purged
                }
            }
        }

        return modifiedCount;
    }

    public int enableAlertDefinitions(Subject subject, int[] alertDefinitionIds) {
        int modifiedCount = 0;
        boolean isResourceLevel = false;
        for (int alertDefId : alertDefinitionIds) {
            AlertDefinition alertDefinition = entityManager.find(AlertDefinition.class, alertDefId);

            // TODO GH: Can be more efficient
            if (checkPermission(subject, alertDefinition)) {
                // only enable an alert if it's not currently enabled
                if (alertDefinition.getEnabled() == false) {
                    alertDefinition.setEnabled(true);
                    modifiedCount++;

                    // alertTemplates and groupAlertDefinitions do not need to update the cache
                    isResourceLevel = (null != alertDefinition.getResource());
                    if (isResourceLevel) {
                        notifyAlertConditionCacheManager(subject, "enableAlertDefinitions", alertDefinition,
                            AlertDefinitionEvent.ENABLED);
                    }
                }
            }
        }

        return modifiedCount;
    }

    @SuppressWarnings("unchecked")
    public boolean isEnabled(Integer definitionId) {
        Query enabledQuery = entityManager.createNamedQuery(AlertDefinition.QUERY_IS_ENABLED);
        enabledQuery.setParameter("alertDefinitionId", definitionId);
        List<Integer> resultIds = enabledQuery.getResultList();
        return (resultIds.size() == 1);
    }

    @SuppressWarnings("unchecked")
    public boolean isTemplate(Integer definitionId) {
        Query query = entityManager.createNamedQuery(AlertDefinition.QUERY_IS_TEMPLATE);
        query.setParameter("alertDefinitionId", definitionId);
        List<Integer> resultIds = query.getResultList();
        return (resultIds.size() == 1);
    }

    @SuppressWarnings("unchecked")
    public boolean isGroupAlertDefinition(Integer definitionId) {
        Query query = entityManager.createNamedQuery(AlertDefinition.QUERY_IS_GROUP_ALERT_DEFINITION);
        query.setParameter("alertDefinitionId", definitionId);
        List<Integer> resultIds = query.getResultList();
        return (resultIds.size() == 1);
    }

    @SuppressWarnings("unchecked")
    public boolean isResourceAlertDefinition(Integer definitionId) {
        Query query = entityManager.createNamedQuery(AlertDefinition.QUERY_IS_RESOURCE_ALERT_DEFINITION);
        query.setParameter("alertDefinitionId", definitionId);
        List<Integer> resultIds = query.getResultList();
        return (resultIds.size() == 1);
    }

    public int disableAlertDefinitions(Subject subject, int[] alertDefinitionIds) {
        int modifiedCount = 0;
        boolean isResourceLevel;
        for (int alertDefId : alertDefinitionIds) {
            AlertDefinition alertDefinition = entityManager.find(AlertDefinition.class, alertDefId);

            // TODO GH: Can be more efficient
            if (checkPermission(subject, alertDefinition)) {
                // only disable an alert if it's currently enabled
                if (alertDefinition.getEnabled() == true) {
                    alertDefinition.setEnabled(false);
                    modifiedCount++;

                    // alertTemplates and groupAlertDefinitions do not need to update the cache
                    isResourceLevel = (null != alertDefinition.getResource());
                    if (isResourceLevel) {
                        notifyAlertConditionCacheManager(subject, "disableAlertDefinitions", alertDefinition,
                            AlertDefinitionEvent.DISABLED);
                    }
                }
            }
        }

        return modifiedCount;
    }

    public void copyAlertDefinitions(Subject subject, Integer[] alertDefinitionIds) {
        for (int alertDefId : alertDefinitionIds) {
            AlertDefinition alertDefinition = entityManager.find(AlertDefinition.class, alertDefId);

            // TODO GH: Can be more efficient
            if (checkPermission(subject, alertDefinition)) {
                AlertDefinition newAlertDefinition = new AlertDefinition(alertDefinition);
                newAlertDefinition.setEnabled(false);

                // this is a "true" copy, so update parentId, resource, and resourceType, group, groupAlertDefinition
                newAlertDefinition.setParentId(alertDefinition.getParentId());
                newAlertDefinition.setResource(alertDefinition.getResource());
                newAlertDefinition.setResourceType(alertDefinition.getResourceType());
                newAlertDefinition.setResourceGroup(alertDefinition.getResourceGroup());
                newAlertDefinition.setGroupAlertDefinition(alertDefinition.getGroupAlertDefinition());

                entityManager.persist(newAlertDefinition);

                notifyAlertConditionCacheManager(subject, "copyAlertDefinitions", alertDefinition,
                    AlertDefinitionEvent.CREATED);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public List<AlertDefinition> findAllRecoveryDefinitionsById(Subject subject, Integer alertDefinitionId) {
        if (authorizationManager.isOverlord(subject) == false) {
            throw new PermissionException("User [" + subject.getName() + "] does not have permission to call "
                + "getAllRecoveryDefinitionsById; only the overlord has that right");
        }

        Query query = entityManager.createNamedQuery(AlertDefinition.QUERY_FIND_ALL_BY_RECOVERY_DEFINITION_ID);
        query.setParameter("recoveryDefinitionId", alertDefinitionId);

        List<AlertDefinition> list = query.getResultList();
        return list;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public AlertDefinition updateAlertDefinition(Subject subject, int alertDefinitionId,
        AlertDefinition alertDefinition, boolean purgeInternals) throws InvalidAlertDefinitionException,
        AlertDefinitionUpdateException {
        if (purgeInternals) {
            alertDefinitionManager.purgeInternals(alertDefinitionId);
        }

        /*
         * Method for catching ENABLE / DISABLE changes will use switch logic off of the delta instead of calling out to
         * the enable/disable functions
         */
        AlertDefinition oldAlertDefinition = entityManager.find(AlertDefinition.class, alertDefinitionId);

        if (checkPermission(subject, oldAlertDefinition) == false) {
            if (oldAlertDefinition.getResourceType() != null) {
                throw new PermissionException("User [" + subject.getName()
                    + "] does not have permission to modify alert templates for type ["
                    + oldAlertDefinition.getResourceType() + "]");
            } else if (oldAlertDefinition.getResourceGroup() != null) {
                throw new PermissionException("User [" + subject.getName()
                    + "] does not have permission to modify alert definitions for group ["
                    + oldAlertDefinition.getResourceGroup() + "]");
            } else {
                throw new PermissionException("User [" + subject.getName()
                    + "] does not have permission to modify alert definitions for resource ["
                    + oldAlertDefinition.getResource() + "]");
            }
        }

        /*
         * only need to check the validity of the new alert definition if the authz checks pass *and* the old definition
         * is not currently deleted
         */
        boolean isResourceLevel = (oldAlertDefinition.getResource() != null);
        checkAlertDefinition(subject, alertDefinition, isResourceLevel ? oldAlertDefinition.getResource().getId()
            : null);

        /*
         * Should not be able to update an alert definition if the old alert definition is in an invalid state
         */
        if (oldAlertDefinition.getDeleted()) {
            throw new AlertDefinitionUpdateException("Can not update deleted " + oldAlertDefinition.toSimpleString());
        }

        AlertDefinitionUpdateType updateType = AlertDefinitionUpdateType.get(oldAlertDefinition, alertDefinition);

        if (isResourceLevel
            && ((updateType == AlertDefinitionUpdateType.JUST_DISABLED) || (updateType == AlertDefinitionUpdateType.STILL_ENABLED))) {
            /*
             * if you were JUST_DISABLED or STILL_ENABLED, you are coming from the ENABLED state, which means you need
             * to be removed from the cache as the first half of this update
             */
            LOG.debug("Updating AlertConditionCacheManager with AlertDefinition[ id=" + oldAlertDefinition.getId()
                + " ]...DELETING");
            for (AlertCondition nextCondition : oldAlertDefinition.getConditions()) {
                LOG.debug("OldAlertCondition[ id=" + nextCondition.getId() + " ]");
            }
            notifyAlertConditionCacheManager(subject, "updateAlertDefinition", oldAlertDefinition,
                AlertDefinitionEvent.DELETED);
        }

        /*
         * performance optimization for the common case of single-condition alerts; it's easier for the
         * out-of-band process to check whether or not ANY conditions are true rather than ALL of them
         */
        if (alertDefinition.getConditions().size() == 1) {
            alertDefinition.setConditionExpression(BooleanExpression.ANY);
        }

        oldAlertDefinition.update(alertDefinition);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Updating: " + oldAlertDefinition);
            for (AlertCondition nextCondition : oldAlertDefinition.getConditions()) {
                LOG.debug("Condition: " + nextCondition);
            }
            for (AlertNotification nextNotification : oldAlertDefinition.getAlertNotifications()) {
                LOG.debug("Notification: " + nextNotification);
                LOG.debug("Notification-Configuration: " + nextNotification.getConfiguration().toString(true));
                if (nextNotification.getExtraConfiguration() != null) {
                    LOG.debug("Notification-Extra-Configuration: "
                        + nextNotification.getExtraConfiguration().toString(true));
                }
            }
        }

        fixRecoveryId(oldAlertDefinition);
        oldAlertDefinition.setMtime(System.currentTimeMillis());

        AlertDefinition newAlertDefinition = entityManager.merge(oldAlertDefinition);

        if (isResourceLevel
            && ((updateType == AlertDefinitionUpdateType.JUST_ENABLED) || (updateType == AlertDefinitionUpdateType.STILL_ENABLED))) {
            /*
             * if you were JUST_ENABLED or STILL_ENABLED, you are moving to the ENABLED state, which means you need to
             * be added to the cache as the last half of this update
             */

            boolean addToCache = false;
            // if this was a recovery alert, or was recently turned into one
            if (newAlertDefinition.getRecoveryId() != 0) {
                // only add to the cache if the to-be-recovered definition is disabled, and thus needs recovering
                AlertDefinition toBeRecoveredDefinition = getAlertDefinitionById(subject, newAlertDefinition
                    .getRecoveryId());
                if (toBeRecoveredDefinition.getEnabled() == false) {
                    addToCache = true;
                }
            } else {
                addToCache = true;
            }

            if (addToCache) {
                LOG.debug("Updating AlertConditionCacheManager with AlertDefinition[ id=" + newAlertDefinition.getId()
                    + " ]...CREATING");
                for (AlertCondition nextCondition : newAlertDefinition.getConditions()) {
                    LOG.debug("NewAlertCondition[ id=" + nextCondition.getId() + " ]");
                }
                notifyAlertConditionCacheManager(subject, "updateAlertDefinition", newAlertDefinition,
                    AlertDefinitionEvent.CREATED);
            }
        }

        /*
         * note, nothing is done to the cache in the STILL_DISABLED case because nothing should've been in the cache to
         * begin with, and nothing needs to be added to the cache as a result
         */

        return newAlertDefinition;
    }

    /*
     * A helper enum to make for cleaner logic in updateAlertDefinition( Subject, AlertDefinition, boolean )
     */
    enum AlertDefinitionUpdateType {
        JUST_ENABLED, JUST_DISABLED, STILL_ENABLED, STILL_DISABLED;

        public static AlertDefinitionUpdateType get(AlertDefinition oldDefinition, AlertDefinition newDefinition) {
            if ((oldDefinition.getEnabled() == false) && (newDefinition.getEnabled() == true)) {
                return AlertDefinitionUpdateType.JUST_ENABLED;
            } else if ((oldDefinition.getEnabled() == true) && (newDefinition.getEnabled() == false)) {
                return AlertDefinitionUpdateType.JUST_DISABLED;
            } else if ((oldDefinition.getEnabled() == true) && (newDefinition.getEnabled() == true)) {
                return AlertDefinitionUpdateType.STILL_ENABLED;
            } else {
                return AlertDefinitionUpdateType.STILL_DISABLED;
            }
        }
    }

    private void checkAlertDefinition(Subject subject, AlertDefinition alertDefinition, Integer resourceId)
        throws InvalidAlertDefinitionException {
        // if someone enters a really long description, we need to truncate it - the column is only 250 chars
        if (alertDefinition.getDescription() != null && alertDefinition.getDescription().length() > 250) {
            alertDefinition.setDescription(alertDefinition.getDescription().substring(0, 250));
        }

        for (AlertCondition alertCondition : alertDefinition.getConditions()) {
            AlertConditionCategory alertConditionCategory = alertCondition.getCategory();
            if (alertConditionCategory == AlertConditionCategory.ALERT) {
                throw new InvalidAlertDefinitionException(
                    "AlertDefinitionManager does not yet support condition category: " + alertConditionCategory);
            }
            if (alertConditionCategory == AlertConditionCategory.BASELINE) {

                MeasurementDefinition def = alertCondition.getMeasurementDefinition();
                NumericType numType = def.getNumericType();
                if (numType == null) {
                    def = entityManager.getReference(MeasurementDefinition.class, def.getId());
                    numType = def.getNumericType();
                }
                if (numType != NumericType.DYNAMIC) {
                    throw new InvalidAlertDefinitionException("Invalid Condition: '" + def.getDisplayName()
                        + "' is a trending metric, and thus will never have baselines calculated for it.");
                }
            }
        }

        if (!alertNotificationManager.finalizeNotifications(subject, alertDefinition.getAlertNotifications())) {
            throw new InvalidAlertDefinitionException("Some of the notifications failed to validate.");
        }
    }

    private void notifyAlertConditionCacheManager(Subject subject, String methodName, AlertDefinition alertDefinition,
        AlertDefinitionEvent alertDefinitionEvent) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Invoking... " + methodName + " with AlertDefinitionEvent[" + alertDefinitionEvent + "]");
        }
        if (alertDefinitionEvent == AlertDefinitionEvent.CREATED) {
            if (alertDefinition.getResource() != null) {
                int resourceId = alertDefinition.getResource().getId();
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Invoking... agentStatusManager.updateByResource(" + resourceId + ")");
                }
                agentStatusManager.updateByResource(subject, resourceId);
            } else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("notifyAlertConditionCacheManager skipping alert template or group alert definition");
                }
            }
        } else {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Invoking... agentStatusManager.updateByAlertDefinition(" + alertDefinition.getId() + ")");
            }
            agentStatusManager.updateByAlertDefinition(subject, alertDefinition.getId());
        }
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void purgeInternals(int alertDefinitionId) {
        try {
            Query alertDampeningEventPurgeQuery = entityManager
                .createNamedQuery(AlertDampeningEvent.QUERY_DELETE_BY_ALERT_DEFINITION_ID);
            Query unmatchedAlertConditionLogPurgeQuery = entityManager
                .createNamedQuery(AlertConditionLog.QUERY_DELETE_UNMATCHED_BY_ALERT_DEFINITION_ID);

            alertDampeningEventPurgeQuery.setParameter("alertDefinitionId", alertDefinitionId);
            unmatchedAlertConditionLogPurgeQuery.setParameter("alertDefinitionId", alertDefinitionId);

            int alertDampeningEventPurgeCount = alertDampeningEventPurgeQuery.executeUpdate();
            int unmatchedAlertConditionLogPurgeCount = unmatchedAlertConditionLogPurgeQuery.executeUpdate();

            if (LOG.isDebugEnabled()) {
                LOG.debug("Update to AlertDefinition[id=" + alertDefinitionId
                    + " caused a purge of internal, dampening constructs.");
                if (alertDampeningEventPurgeCount > 0) {
                    LOG.debug("Removed " + alertDampeningEventPurgeCount + " AlertDampeningEvent"
                        + (alertDampeningEventPurgeCount == 1 ? "" : "s"));
                }
                if (unmatchedAlertConditionLogPurgeCount > 0) {
                    LOG.debug("Removed " + unmatchedAlertConditionLogPurgeCount + " unmatched AlertConditionLog"
                        + (unmatchedAlertConditionLogPurgeCount == 1 ? "" : "s"));
                }
            }
        } catch (Throwable t) {
            LOG.debug("Could not purge internal alerting constructs for: " + alertDefinitionId, t);
        }
    }

    @SuppressWarnings("unchecked")
    public int purgeUnusedAlertDefinitions() {
        Query purgeQuery = entityManager.createNamedQuery(AlertDefinition.QUERY_FIND_UNUSED_DEFINITION_IDS);
        List<Integer> resultIds = purgeQuery.getResultList();

        int removed = 0;
        for (int unusedDefinitionId : resultIds) {
            AlertDefinition unusedDefinition = entityManager.find(AlertDefinition.class, unusedDefinitionId);
            if (unusedDefinition != null) {
                entityManager.remove(unusedDefinition);
                removed++;
            } else {
                LOG.warn("Could not find alertDefinition[id=" + unusedDefinitionId + "] for purge");
            }
        }

        return removed;
    }

    public AlertDefinition getAlertDefinition(Subject subject, int alertDefinitionId) {
        return getAlertDefinitionById(subject, alertDefinitionId);
    }

    @SuppressWarnings("unchecked")
    public PageList<AlertDefinition> findAlertDefinitionsByCriteria(Subject subject, AlertDefinitionCriteria criteria) {
        CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);

        // Inv managers can do anything and anyone can inspect templates
        if (!authorizationManager.isInventoryManager(subject) && !criteria.isTemplateCriteria()) {

            // otherwise, for group alert defs ensure group view authz and for everything else, assume resource view authz
            AuthorizationTokenType tokenType = criteria.isGroupCriteria() ? AuthorizationTokenType.GROUP
                : AuthorizationTokenType.RESOURCE;

            generator.setAuthorizationResourceFragment(tokenType, subject.getId());
        }

        CriteriaQueryRunner<AlertDefinition> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager);
        return queryRunner.execute();
    }

    public String[] getAlertNotificationConfigurationPreview(Subject sessionSubject, AlertNotification[] notifications) {
        if (notifications == null || notifications.length == 0) {
            return new String[0];
        }

        AlertSenderPluginManager alertPluginManager = alertManager.getAlertPluginManager();

        String[] previews = new String[notifications.length];
        int i = 0;
        for (AlertNotification notif : notifications) {
            AlertSender<?> sender = alertPluginManager.getAlertSenderForNotification(notif);
            if (sender != null) {
                previews[i++] = sender.previewConfiguration();
            } else {
                previews[i++] = "n/a (unknown sender)";
            }
        }

        return previews;
    }

}