/*
 * 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, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * 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 and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser 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.core.domain.alert;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;

import org.rhq.core.domain.alert.notification.AlertNotification;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.resource.group.ResourceGroup;

/**
 * @author Joseph Marques
 */
@Entity
@NamedQueries( { //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_ALL, query = "" //
        + "SELECT a " //
        + "  FROM AlertDefinition a " //
        + " WHERE a.deleted = false " //
        + "   AND a.resource IS NOT NULL"),
    @NamedQuery(name = AlertDefinition.QUERY_FIND_ALL_BY_RECOVERY_DEFINITION_ID, query = "" //
        + "SELECT a " //
        + "  FROM AlertDefinition a " //
        + "  LEFT JOIN FETCH a.conditions " //
        + " WHERE a.deleted = false AND a.enabled = true " //
        + "   AND a.recoveryId = :recoveryDefinitionId"),
    @NamedQuery(name = AlertDefinition.QUERY_FIND_BY_ALERT_TEMPLATE_ID, query = "" //
        + "SELECT a.id " //
        + "  FROM AlertDefinition a " //
        + " WHERE a.parentId = :alertTemplateId " //
        + "   AND a.deleted = false" //
        + "   AND a.readOnly = false"), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_BY_GROUP_ALERT_DEFINITION_ID, query = "" //
        + "SELECT a.id " //
        + "  FROM AlertDefinition a " //
        + " WHERE a.groupAlertDefinition.id = :groupAlertDefinitionId " //
        + "   AND a.deleted = false" //
        + "   AND a.readOnly = false"),
    @NamedQuery(name = AlertDefinition.QUERY_FIND_RESOURCE_IDS_NEEDING_TEMPLATE_APPLICATION, query = "" //
        + "SELECT res.id " //
        + "  FROM Resource res " //
        + " WHERE res.resourceType.id = :resourceTypeId " //
        + "   AND res.inventoryStatus = :inventoryStatus " //
        + "   AND NOT EXISTS ( SELECT ad.id " //
        + "                      FROM AlertDefinition ad " //
        + "                     WHERE ad.parentId = :alertTemplateId " // find the definitions for this template
        + "                       AND ad.resource.id = res.id " // correlated to the resource
        + "                       AND ad.deleted = false ) "), // and not deleted
    @NamedQuery(name = AlertDefinition.QUERY_FIND_OPTION_ITEMS_BY_RESOURCE, query = "" //
        + "  SELECT new org.rhq.core.domain.common.composite.IntegerOptionItem(ad.id, ad.name) " //
        + "    FROM AlertDefinition ad " //
        + "   WHERE ad.resource.id = :resourceId " //
        + "     AND ad.deleted = false"), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_OPTION_ITEMS_BY_GROUP, query = "" //
        + "  SELECT new org.rhq.core.domain.common.composite.IntegerOptionItem(ad.id, ad.name) " //
        + "    FROM AlertDefinition ad " //
        + "   WHERE ad.resourceGroup.id = :groupId " //
        + "     AND ad.deleted = false"), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_BY_RESOURCE, query = "" //
        + "SELECT a " //
        + "  FROM AlertDefinition a " //
        + " WHERE a.resource.id = :id " //
        + "   AND a.deleted = false"), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_BY_RESOURCE_TYPE, query = "" //
        + "SELECT a " //
        + "  FROM AlertDefinition a " //
        + " WHERE a.resourceType.id = :typeId " //
        + "   AND a.deleted = false"), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_BY_RESOURCE_GROUP, query = "" //
        + "SELECT a " //
        + "  FROM AlertDefinition a " //
        + " WHERE a.resourceGroup.id = :groupId " //
        + "   AND a.deleted = false"),
    @NamedQuery(name = AlertDefinition.QUERY_DELETE_BY_RESOURCES, query = "" //
        + "DELETE FROM AlertDefinition ad " //
        + " WHERE ad.resource.id IN ( :resourceIds ) "), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_UNUSED_DEFINITION_IDS, query = "" //
        + "SELECT ad.id " //
        + "  FROM AlertDefinition ad " //
        + " WHERE ad.deleted = TRUE " //
        + "   AND ad.id NOT IN ( SELECT alertDef.id " //
        + "                        FROM Alert a " //
        + "                        JOIN a.alertDefinition alertDef )"),
    @NamedQuery(name = AlertDefinition.QUERY_FIND_ALL_COMPOSITES_ADMIN, query = "" //
        + "   SELECT new org.rhq.core.domain.alert.composite.AlertDefinitionComposite" // 
        + "        ( ad, parent.id, parent.name ) " //
        + "     FROM AlertDefinition ad " //
        + "     JOIN ad.resource res " //
        + "LEFT JOIN res.parentResource parent " //
        /* 
         * as much as i want to (for efficiency of the query [namely roundtrips to the db]) i can't use fetching here
         * because, when added, the query parser chokes with "query specified join fetching, but the owner of the 
         * fetched association was not present in the select list"...even though it clearly is  ;/ 
         */
        //+ "     JOIN FETCH ad.conditions ac " //
        + "    WHERE ad.deleted = false " //
        + "      AND (UPPER(res.name) LIKE :resourceFilter ESCAPE :escapeChar OR :resourceFilter IS NULL) " //
        + "      AND (UPPER(parent.name) LIKE :parentFilter ESCAPE :escapeChar OR :parentFilter IS NULL) " //
        + "      AND (ad.ctime > :startTime OR :startTime IS NULL) " //
        + "      AND (ad.ctime < :endTime OR :endTime IS NULL) " //
        + "      AND (ad.id IN ( SELECT aad.id FROM AlertDefinition aad " //
        + "                       JOIN aad.conditions aadc " // 
        + "                      WHERE aadc.category = :category ) " //
        + "           OR :category IS NULL) "), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_ALL_COMPOSITES, query = "" //
        + "   SELECT new org.rhq.core.domain.alert.composite.AlertDefinitionComposite" // 
        + "        ( ad, parent.id, parent.name ) " //
        + "     FROM AlertDefinition ad " //
        + "     JOIN ad.resource res " //
        + "LEFT JOIN res.parentResource parent " //
        /* 
         * as much as i want to (for efficiency of the query [namely roundtrips to the db]) i can't use fetching here
         * because, when added, the query parser chokes with "query specified join fetching, but the owner of the 
         * fetched association was not present in the select list"...even though it clearly is  ;/ 
         */
        //+ "     JOIN FETCH ad.conditions ac " //
        + "    WHERE ad.deleted = false " //
        + "      AND res.id IN ( SELECT rr.id FROM Resource rr " //
        + "                        JOIN rr.implicitGroups g JOIN g.roles r JOIN r.subjects s " //
        + "                       WHERE s.id = :subjectId ) " //
        + "      AND (UPPER(res.name) LIKE :resourceFilter ESCAPE :escapeChar OR :resourceFilter IS NULL) " //
        + "      AND (UPPER(parent.name) LIKE :parentFilter ESCAPE :escapeChar OR :parentFilter IS NULL) " //
        + "      AND (ad.ctime > :startTime OR :startTime IS NULL) " //
        + "      AND (ad.ctime < :endTime OR :endTime IS NULL) " //
        + "      AND (ad.id IN ( SELECT aad.id FROM AlertDefinition aad " //
        + "                       JOIN aad.conditions aadc " // 
        + "                      WHERE aadc.category = :category ) " //
        + "           OR :category IS NULL) "), //
    @NamedQuery(name = AlertDefinition.QUERY_FIND_DEFINITION_ID_BY_CONDITION_ID, query = "" //
        + "SELECT ad.id " //
        + "  FROM AlertCondition ac " //
        + "  JOIN ac.alertDefinition ad" //
        + " WHERE ac.id = :alertConditionId " //
        + "   AND ad.enabled = true "), //
    @NamedQuery(name = AlertDefinition.QUERY_IS_ENABLED, query = "" //
        + "SELECT ad.id " //
        + "  FROM AlertDefinition ad " //
        + " WHERE ad.id = :alertDefinitionId " //
        + "   AND ad.enabled = true "), //
    @NamedQuery(name = AlertDefinition.QUERY_IS_TEMPLATE, query = "" //
        + "SELECT ad.id " //
        + "  FROM AlertDefinition ad " //
        + " WHERE ad.id = :alertDefinitionId " //
        + "   AND ad.resourceType IS NOT NULL "), //
    @NamedQuery(name = AlertDefinition.QUERY_IS_GROUP_ALERT_DEFINITION, query = "" //
        + "SELECT ad.id " //
        + "  FROM AlertDefinition ad " //
        + " WHERE ad.id = :alertDefinitionId " //
        + "   AND ad.resourceGroup IS NOT NULL "), //
    @NamedQuery(name = AlertDefinition.QUERY_IS_RESOURCE_ALERT_DEFINITION, query = "" //
        + "SELECT ad.id " //
        + "  FROM AlertDefinition ad " //
        + " WHERE ad.id = :alertDefinitionId " //
        + "   AND ad.resource IS NOT NULL "), //
    @NamedQuery(name = AlertDefinition.QUERY_UPDATE_SET_DELETED, query = "" //
        + "UPDATE AlertDefinition ad " //
        + "   SET ad.deleted = TRUE " //
        + " WHERE ad.id IN ( :groupAlertDefinitionIds ) " //
        + "    OR ad.groupAlertDefinition.id IN ( :groupAlertDefinitionIds ) "), //
    @NamedQuery(name = AlertDefinition.QUERY_UPDATE_SET_ENABLED, query = "" //
        + "UPDATE AlertDefinition ad " //
        + "   SET ad.enabled = TRUE " //
        + " WHERE ad.id IN ( :groupAlertDefinitionIds ) " //
        + "    OR ad.groupAlertDefinition.id IN ( :groupAlertDefinitionIds ) "), //
    @NamedQuery(name = AlertDefinition.QUERY_UPDATE_SET_DISABLED, query = "" //
        + "UPDATE AlertDefinition ad " //
        + "   SET ad.enabled = FALSE " //
        + " WHERE ad.id IN ( :groupAlertDefinitionIds ) " //
        + "    OR ad.groupAlertDefinition.id IN ( :groupAlertDefinitionIds ) "), //
    @NamedQuery(name = AlertDefinition.QUERY_UPDATE_SET_PARENTS_NULL, query = "" //
        + "UPDATE AlertDefinition ad " //
        + "   SET ad.groupAlertDefinition = NULL " //
        + " WHERE ad.id IN ( :childrenDefinitionIds ) "),
    @NamedQuery(name = AlertDefinition.QUERY_FIND_RESOURCE_IDS_NEEDING_GROUP_APPLICATION, query = "" //
        + "SELECT res.id " //
        + "  FROM Resource res " //
        + "  JOIN res.explicitGroups rg " //
        + " WHERE rg.id = :resourceGroupId " //
        + "   AND res.inventoryStatus = :inventoryStatus " //
        + "   AND NOT EXISTS ( SELECT ad.id " //
        + "                      FROM AlertDefinition ad " //
        + "                     WHERE ad.groupAlertDefinition.id = :groupAlertDefinitionId " // find the children for this group alert def
        + "                       AND ad.resource.id = res.id " // correlated to the resource
        + "                       AND ad.deleted = false ) ") // and not deleted
})
@SequenceGenerator(name = "RHQ_ALERT_DEFINITION_ID_SEQ", sequenceName = "RHQ_ALERT_DEFINITION_ID_SEQ", allocationSize = 10)
@Table(name = "RHQ_ALERT_DEFINITION")
@XmlAccessorType(XmlAccessType.FIELD)
public class AlertDefinition implements Serializable {
    private static final long serialVersionUID = 1L;

    public static final String QUERY_FIND_ALL = "AlertDefinition.findAll";
    public static final String QUERY_FIND_ALL_BY_RECOVERY_DEFINITION_ID = "AlertDefinition.findAllByRecoveryDefinitionId";
    public static final String QUERY_FIND_BY_ALERT_TEMPLATE_ID = "AlertDefinition.findByAlertTemplateId";
    public static final String QUERY_FIND_BY_GROUP_ALERT_DEFINITION_ID = "AlertDefinition.findByGroupAlertDefinitionId";
    public static final String QUERY_FIND_RESOURCE_IDS_NEEDING_TEMPLATE_APPLICATION = "AlertDefinition.findResourceIdsNeedingTemplateApplication";
    public static final String QUERY_FIND_OPTION_ITEMS_BY_RESOURCE = "AlertDefinition.findOptionItemsByResource";
    public static final String QUERY_FIND_OPTION_ITEMS_BY_GROUP = "AlertDefinition.findOptionItemsByGroup";
    public static final String QUERY_FIND_BY_RESOURCE = "AlertDefinition.findByResource";
    public static final String QUERY_FIND_BY_RESOURCE_TYPE = "AlertDefinition.findByResourceType";
    public static final String QUERY_FIND_BY_RESOURCE_GROUP = "AlertDefinition.findByResourceGroup";
    public static final String QUERY_DELETE_BY_RESOURCES = "AlertDefinition.deleteByResources";
    public static final String QUERY_FIND_UNUSED_DEFINITION_IDS = "AlertDefinition.findUnusedDefinitionIds";
    public static final String QUERY_FIND_DEFINITION_ID_BY_CONDITION_ID = "AlertDefinition.findDefinitionIdByConditionId";
    public static final String QUERY_IS_ENABLED = "AlertDefinition.isEnabled";
    public static final String QUERY_IS_TEMPLATE = "AlertDefinition.isTemplate";
    public static final String QUERY_IS_GROUP_ALERT_DEFINITION = "AlertDefinition.isGroupAlertDefinition";
    public static final String QUERY_IS_RESOURCE_ALERT_DEFINITION = "AlertDefinition.isResourceAlertDefinition";

    // group alert definitions
    public static final String QUERY_UPDATE_SET_DELETED = "AlertDefinition.updateSetDeleted";
    public static final String QUERY_UPDATE_SET_ENABLED = "AlertDefinition.updateSetEnabled";
    public static final String QUERY_UPDATE_SET_DISABLED = "AlertDefinition.updateSetDisabled";
    public static final String QUERY_UPDATE_SET_PARENTS_NULL = "AlertDefinition.updateSetParentsNull";
    public static final String QUERY_FIND_RESOURCE_IDS_NEEDING_GROUP_APPLICATION = "AlertDefinition.findResourceIdsNeedingGroupApplication";

    // for subsystem view
    public static final String QUERY_FIND_ALL_COMPOSITES = "AlertDefinition.findAllComposites";
    public static final String QUERY_FIND_ALL_COMPOSITES_ADMIN = "AlertDefinition.findAllComposites_admin";

    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "RHQ_ALERT_DEFINITION_ID_SEQ")
    @Id
    private int id;

    @Column(name = "NAME", nullable = false)
    private String name;

    @Column(name = "CTIME", nullable = false)
    private long ctime;

    @Column(name = "MTIME", nullable = false)
    private long mtime;

    /**
     * If this field is non-null, then this alert def is a copy of the resource type alert def with the specified id.
     */
    @Column(name = "PARENT_ID", nullable = false)
    private Integer parentId = new Integer(0);

    @JoinColumn(name = "GROUP_ALERT_DEF_ID")
    @ManyToOne(fetch = FetchType.LAZY)
    private AlertDefinition groupAlertDefinition;

    // do not cascade remove - group removal will be detaching children alert defs from the group def,
    // and then letting the children be deleted slowly by existing alert def removal mechanisms
    @OneToMany(mappedBy = "groupAlertDefinition", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
    @OrderBy
    private Set<AlertDefinition> groupAlertDefinitionChildren = new LinkedHashSet<AlertDefinition>();

    @Column(name = "DESCRIPTION")
    private String description;

    @Column(name = "PRIORITY", nullable = false)
    @Enumerated(EnumType.STRING)
    private AlertPriority priority;

    @JoinColumn(name = "RESOURCE_TYPE_ID", nullable = true)
    @ManyToOne
    private ResourceType resourceType;

    @JoinColumn(name = "RESOURCE_ID", nullable = true)
    @ManyToOne
    @XmlTransient
    private Resource resource;

    @JoinColumn(name = "RESOURCE_GROUP_ID", nullable = true)
    @ManyToOne
    @XmlTransient
    private ResourceGroup resourceGroup;

    @Column(name = "ENABLED", nullable = false)
    private boolean enabled;

    @Embedded
    private AlertDampening alertDampening;

    /*
     * recoveryId and willRecover==true are mutually exclusive
     *
     * you are either a recovery alert, or an alert to be recovered
     */
    @Column(name = "RECOVERY_ID")
    private Integer recoveryId;

    @Column(name = "WILL_RECOVER", nullable = false)
    private boolean willRecover;

    @Column(name = "NOTIFY_FILTERED", nullable = false)
    private boolean notifyFiltered;

    @Column(name = "CONTROL_FILTERED", nullable = false)
    private boolean controlFiltered;

    @Column(name = "DELETED", nullable = false)
    private boolean deleted;

    @Column(name = "READ_ONLY", nullable = false)
    private boolean readOnly;

    @Column(name = "REQUIRED", nullable = false)
    @Enumerated(EnumType.ORDINAL)
    private BooleanExpression conditionExpression;

    @OneToMany(mappedBy = "alertDefinition", cascade = CascadeType.ALL)
    @OrderBy
    @org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    // primary key
    private Set<AlertCondition> conditions = new LinkedHashSet<AlertCondition>(1); // Most alerts will only have one condition.

    @OneToMany(mappedBy = "alertDefinition", cascade = CascadeType.ALL)
    @org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    private List<AlertNotification> alertNotifications = new ArrayList<AlertNotification>();

    /*
     * As of Sept 29, 2007 there is no reason to expose this at the java layer.  However, this is required if we want to
     * be able to cascade delete the AlertDampeningEvents when an AlertDefinition is removed from the db, due to
     * deleting a Resource from inventory.
     */
    @OneToMany(mappedBy = "alertDefinition", cascade = { CascadeType.REFRESH, CascadeType.REMOVE })
    @org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    private Set<AlertDampeningEvent> alertDampeningEvents = new HashSet<AlertDampeningEvent>();

    /**
     * NOTE: This field is currently not accessed, so we don't need cascading for PERSIST/MERGE/REFRESH. However, we
     * still need cascading for REMOVE, so we don't get FK constraint violations when an Alert is removed.
     * AlertDefinitions aren't actually deleted, instead they have their 'deleted' flag set to true. But, if they were
     * actually deleted from persistent storage, we would still want the cascade remove.
     */
    @OneToMany(mappedBy = "alertDefinition", cascade = CascadeType.REMOVE)
    @OrderBy
    // primary key
    @XmlTransient
    private Set<Alert> alerts = new LinkedHashSet<Alert>();

    private transient AlertDefinitionContext context;

    /**
     * Creates a new alert definition.
     */
    public AlertDefinition() {
        // ctime and mtime will be set upon persist/update.
    }

    /**
     * Creates a skeletal copy of the specified alert definition.
     *
     * @param alertDef the alert definition to be copied
     */
    public AlertDefinition(AlertDefinition alertDef) {
        this();
        this.update(alertDef);
    }

    public void update(AlertDefinition alertDef) {
        /*
         * Don't copy the id, ctime, or mtime.
         */
        setName(alertDef.name);
        this.description = alertDef.description;
        this.priority = alertDef.priority;
        this.enabled = alertDef.enabled;
        this.readOnly = alertDef.readOnly;

        /*
         * parentId, resource, resourceType need to be managed by the calling context
         */

        if (alertDampening == null) {
            // an alert template is being created, so it's creating children definitions for the first time,
            // OR...an alert template is being updated, but this children definition was deleted at some point
            this.alertDampening = new AlertDampening(alertDef.getAlertDampening());
        } else {
            // an alert template is being updated, so it's updated it's existing children definitions
            this.alertDampening.update(alertDef.getAlertDampening());
        }

        this.willRecover = alertDef.willRecover;
        this.notifyFiltered = alertDef.notifyFiltered;
        this.controlFiltered = alertDef.controlFiltered;

        this.deleted = alertDef.deleted;
        this.conditionExpression = alertDef.conditionExpression;

        this.recoveryId = alertDef.recoveryId;

        // copy conditions
        List<AlertCondition> copiedConditions = new ArrayList<AlertCondition>();
        for (AlertCondition oldCondition : alertDef.getConditions()) {
            AlertCondition newCondition = new AlertCondition(oldCondition);
            newCondition.setAlertDefinition(this);
            copiedConditions.add(newCondition);
        }
        this.removeAllConditions();
        this.getConditions().addAll(copiedConditions);

        // copy notifications
        List<AlertNotification> copiedNotifications = new ArrayList<AlertNotification>();
        for (AlertNotification oldNotification : alertDef.getAlertNotifications()) {
            AlertNotification newNotification = new AlertNotification(oldNotification);
            newNotification.setAlertDefinition(this);
            copiedNotifications.add(newNotification);
        }
        this.removeAllAlertNotifications();
        this.getAlertNotifications().addAll(copiedNotifications);
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getCtime() {
        return this.ctime;
    }

    public void setCtime(long ctime) {
        this.ctime = ctime;
    }

    @PrePersist
    void onPersist() {
        this.mtime = this.ctime = System.currentTimeMillis();
    }

    public long getMtime() {
        return this.mtime;
    }

    public void setMtime(long mtime) {
        this.mtime = mtime;
    }

    @PreUpdate
    void onUpdate() {
        this.mtime = System.currentTimeMillis();
    }

    public Integer getParentId() {
        return this.parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }

    public AlertDefinition getGroupAlertDefinition() {
        return groupAlertDefinition;
    }

    public void setGroupAlertDefinition(AlertDefinition groupAlertDefinition) {
        this.groupAlertDefinition = groupAlertDefinition;
    }

    public Resource getResource() {
        return resource;
    }

    public void setResource(Resource resource) {
        this.resource = resource;
        if (this.resource != null) {
            this.resource.getAlertDefinitions().add(this);
        }
    }

    public ResourceGroup getResourceGroup() {
        return resourceGroup;
    }

    public void setResourceGroup(ResourceGroup resourceGroup) {
        this.resourceGroup = resourceGroup;
        /*
        if (this.resourceGroup != null) {
            this.resourceGroup.getAlertDefinitions().add(this);
        }
        */
    }

    public ResourceType getResourceType() {
        return resourceType;
    }

    public void setResourceType(ResourceType resourceType) {
        this.resourceType = resourceType;
    }

    public String getDescription() {
        return this.description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public AlertPriority getPriority() {
        return this.priority;
    }

    public void setPriority(AlertPriority priority) {
        this.priority = priority;
    }

    public boolean getEnabled() {
        return this.enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public AlertDampening getAlertDampening() {
        return alertDampening;
    }

    public void setAlertDampening(AlertDampening alertDampening) {
        this.alertDampening = alertDampening;
    }

    public boolean getWillRecover() {
        return this.willRecover;
    }

    public void setWillRecover(boolean willRecover) {
        if (willRecover && getRecoveryId() != 0) {
            throw new IllegalStateException(
                "An alert definition can either be a recovery definition or a definition to-be-recovered, but not both.");
        }
        this.willRecover = willRecover;
    }

    public boolean getNotifyFiltered() {
        return this.notifyFiltered;
    }

    public void setNotifyFiltered(boolean notifyFiltered) {
        this.notifyFiltered = notifyFiltered;
    }

    public boolean getControlFiltered() {
        return this.controlFiltered;
    }

    public void setControlFiltered(boolean controlFiltered) {
        this.controlFiltered = controlFiltered;
    }

    public Integer getRecoveryId() {
        return this.recoveryId;
    }

    public void setRecoveryId(Integer actOnTriggerId) {
        if (getWillRecover() && actOnTriggerId != 0) {
            throw new IllegalStateException(
                "An alert definition can either be a recovery definition or a definition to-be-recovered, but not both.");
        }
        this.recoveryId = actOnTriggerId;
    }

    public boolean getDeleted() {
        return this.deleted;
    }

    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }

    /**
     * A definition is "read-only" with respect to updates that come from the group/template level.
     * If "read only" is true, then changes to the parent group/template alert definition will not
     * change this resource alert def. If read only is false, changes to the parent propagate to the
     * child resource alert.
     *  
     * @return read only flag
     */
    public boolean isReadOnly() {
        return this.readOnly;
    }

    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
    }

    public BooleanExpression getConditionExpression() {
        return this.conditionExpression;
    }

    public void setConditionExpression(BooleanExpression conditionExpression) {
        this.conditionExpression = conditionExpression;
    }

    public Set<AlertCondition> getConditions() {
        return this.conditions;
    }

    public void setConditions(Set<AlertCondition> conditions) {
        removeAllConditions();
        if (conditions != null) {
            for (AlertCondition condition : conditions) {
                addCondition(condition);
            }
        }
    }

    public void addCondition(AlertCondition condition) {
        this.conditions.add(condition);
        condition.setAlertDefinition(this);
    }

    public void removeAllConditions() {
        for (AlertCondition condition : this.conditions) {
            condition.setAlertDefinition(null);
        }

        this.conditions.clear();
    }

    public Set<Alert> getAlerts() {
        return this.alerts;
    }

    public void addAlert(Alert alert) {
        this.alerts.add(alert);
    }

    public List<AlertNotification> getAlertNotifications() {
        return alertNotifications;
    }

    public void setAlertNotifications(List<AlertNotification> alertNotifications) {
        if (alertNotifications == null) {
            alertNotifications = new ArrayList<AlertNotification>();
        }
        this.alertNotifications = alertNotifications;
    }

    public void addAlertNotification(AlertNotification alertNotification) {
        this.alertNotifications.add(alertNotification);
    }

    public void removeAllAlertNotifications() {
        for (AlertNotification notification : this.alertNotifications) {
            notification.prepareForOrphanDelete();
        }

        this.alertNotifications.clear();
    }

    public Set<AlertDampeningEvent> getAlertDampeningEvents() {
        return alertDampeningEvents;
    }

    public boolean removeAlertDampeningEvent(AlertDampeningEvent event) {
        return alertDampeningEvents.remove(event);
    }

    public void calculateContext() {
        context = AlertDefinitionContext.get(this);
    }

    public void setContext(AlertDefinitionContext context) {
        this.context = context;
    }

    public AlertDefinitionContext getContext() {
        if (context == null) {
            calculateContext();
        }
        return context;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if ((obj == null) || !(obj instanceof AlertDefinition)) {
            return false;
        }

        AlertDefinition that = (AlertDefinition) obj;
        if (id != that.id) {
            return false;
        }

        if ((name != null) ? (!name.equals(that.name)) : (that.name != null)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = (31 * result) + ((name != null) ? name.hashCode() : 0);
        return result;
    }

    public String toSimpleString() {
        return "AlertDefinition" + "[ " + "id=" + id + ", " + "name=" + name + " ]";
    }

    @Override
    public String toString() {
        return "org.rhq.core.domain.alert.AlertDefinition" + "[ " + "id=" + id + ", " + "name=" + name + ", "
            + "conditionExpression=" + conditionExpression + ", " + "priority=" + priority + ", "
            + ((resource != null) ? ("resourceId=" + resource.getId()) : "")
            + ((resourceType != null) ? ("resourceTypeId=" + resourceType.getId()) : "") + " ]";
    }
}