You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2014/09/16 14:46:55 UTC

git commit: AMBARI-7316 - Alerts: Create Alert Notices For Incoming Alert State Changes (jonathanhurley)

Repository: ambari
Updated Branches:
  refs/heads/branch-alerts-dev aaf051357 -> 760bedfe3


AMBARI-7316 - Alerts: Create Alert Notices For Incoming Alert State Changes (jonathanhurley)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/760bedfe
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/760bedfe
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/760bedfe

Branch: refs/heads/branch-alerts-dev
Commit: 760bedfe32f2a673a14cc14e7f2d41794ca3db98
Parents: aaf0513
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Mon Sep 15 14:04:42 2014 -0400
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Mon Sep 15 14:04:42 2014 -0400

----------------------------------------------------------------------
 .../ambari/server/agent/HeartBeatHandler.java   |  20 ++-
 .../server/controller/ControllerModule.java     |  15 ++
 .../apache/ambari/server/events/AlertEvent.java |  58 +++++++
 .../server/events/AlertReceivedEvent.java       |  50 +++++++
 .../server/events/AlertStateChangeEvent.java    |  85 +++++++++++
 .../events/listeners/AlertReceivedListener.java | 150 +++++++++++++++++++
 .../listeners/AlertStateChangedListener.java    |  98 ++++++++++++
 .../events/publishers/AlertEventPublisher.java  | 100 +++++++++++++
 .../ambari/server/orm/dao/AlertDispatchDAO.java |  22 +++
 .../server/orm/entities/AlertGroupEntity.java   |   7 +-
 .../org/apache/ambari/server/state/Alert.java   |  55 ++++---
 .../server/state/cluster/AlertDataManager.java  | 111 --------------
 .../server/orm/dao/AlertDispatchDAOTest.java    |  29 ++++
 .../state/cluster/AlertDataManagerTest.java     | 129 ++++++++++++----
 14 files changed, 753 insertions(+), 176 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
index 492d832..a366301 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
@@ -39,6 +39,9 @@ import org.apache.ambari.server.actionmanager.HostRoleStatus;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.MaintenanceStateHelper;
+import org.apache.ambari.server.events.AlertEvent;
+import org.apache.ambari.server.events.AlertReceivedEvent;
+import org.apache.ambari.server.events.publishers.AlertEventPublisher;
 import org.apache.ambari.server.metadata.ActionMetadata;
 import org.apache.ambari.server.state.AgentVersion;
 import org.apache.ambari.server.state.Alert;
@@ -60,7 +63,6 @@ import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.state.State;
 import org.apache.ambari.server.state.alert.AlertDefinition;
 import org.apache.ambari.server.state.alert.AlertDefinitionHash;
-import org.apache.ambari.server.state.cluster.AlertDataManager;
 import org.apache.ambari.server.state.fsm.InvalidStateTransitionException;
 import org.apache.ambari.server.state.host.HostHealthyHeartbeatEvent;
 import org.apache.ambari.server.state.host.HostRegistrationRequestEvent;
@@ -115,9 +117,12 @@ public class HeartBeatHandler {
 
   @Inject
   private AlertDefinitionHash alertDefinitionHash;
-  
+
+  /**
+   * Publishes {@link AlertEvent} instances.
+   */
   @Inject
-  private AlertDataManager alertManager;
+  private AlertEventPublisher alertEventPublisher;
 
   private Map<String, Long> hostResponseIds = new ConcurrentHashMap<String, Long>();
 
@@ -236,13 +241,16 @@ public class HeartBeatHandler {
     if (null == hostname || null == heartbeat) {
       return;
     }
-    
+
     if (null != heartbeat.getAlerts()) {
       for (Alert alert : heartbeat.getAlerts()) {
-        if (null == alert.getHost())
+        if (null == alert.getHost()) {
           alert.setHost(hostname);
+        }
+
         Cluster cluster = clusterFsm.getCluster(alert.getCluster());
-        alertManager.add(cluster.getClusterId(), alert);
+        AlertEvent event = new AlertReceivedEvent(cluster.getClusterId(), alert);
+        alertEventPublisher.publish(event);
       }
     }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
index c395df6..8413762 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
@@ -51,6 +51,8 @@ import org.apache.ambari.server.controller.internal.HostResourceProvider;
 import org.apache.ambari.server.controller.internal.MemberResourceProvider;
 import org.apache.ambari.server.controller.internal.ServiceResourceProvider;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.events.listeners.AlertReceivedListener;
+import org.apache.ambari.server.events.listeners.AlertStateChangedListener;
 import org.apache.ambari.server.orm.DBAccessor;
 import org.apache.ambari.server.orm.DBAccessorImpl;
 import org.apache.ambari.server.orm.PersistenceType;
@@ -222,6 +224,8 @@ public class ControllerModule extends AbstractModule {
     bind(ViewInstanceHandlerList.class).to(AmbariHandlerList.class);
 
     requestStaticInjection(ExecutionCommandWrapper.class);
+
+    bindEagerSingletons();
   }
 
 
@@ -299,4 +303,15 @@ public class ControllerModule extends AbstractModule {
     bind(HostRoleCommandFactory.class).to(HostRoleCommandFactoryImpl.class);
     bind(SecurityHelper.class).toInstance(SecurityHelperImpl.getInstance());
   }
+
+  /**
+   * Initializes all eager singletons that should be instantiated as soon as
+   * possible and not wait for injection.
+   */
+  private void bindEagerSingletons() {
+    // alert subscribers are "headless" and have no guice references; created
+    // them as eager singletons to have them register with the eventbus
+    bind(AlertReceivedListener.class).asEagerSingleton();
+    bind(AlertStateChangedListener.class).asEagerSingleton();
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/events/AlertEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/AlertEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertEvent.java
new file mode 100644
index 0000000..a75477d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertEvent.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.events;
+
+import org.apache.ambari.server.state.Alert;
+
+/**
+ * The {@link AlertEvent} class the base for all events related to alerts.
+ */
+public abstract class AlertEvent {
+
+  protected long m_clusterId;
+  protected Alert m_alert;
+
+  /**
+   * Constructor.
+   *
+   * @param clusterId
+   * @param alert
+   */
+  public AlertEvent(long clusterId, Alert alert) {
+    m_clusterId = clusterId;
+    m_alert = alert;
+  }
+
+  /**
+   * Gets the cluster ID that the alert belongs to.
+   *
+   * @return the ID of the cluster.
+   */
+  public long getClusterId() {
+    return m_clusterId;
+  }
+
+  /**
+   * Gets the alert that this event is created for.
+   *
+   * @return the alert (never {@code null}).
+   */
+  public Alert getAlert(){
+    return m_alert;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/events/AlertReceivedEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/AlertReceivedEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertReceivedEvent.java
new file mode 100644
index 0000000..558c9da
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertReceivedEvent.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.events;
+
+import org.apache.ambari.server.state.Alert;
+
+/**
+ * The {@link AlertReceivedEvent} is fired when an {@link Alert} is received or
+ * generated.
+ */
+public final class AlertReceivedEvent extends AlertEvent {
+
+  /**
+   * Constructor.
+   *
+   * @param clusterId
+   * @param alert
+   */
+  public AlertReceivedEvent(long clusterId, Alert alert) {
+    super(clusterId, alert);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    StringBuilder buffer = new StringBuilder("AlertReceivedEvent{ ");
+    buffer.append("cluserId=").append(m_clusterId);
+    buffer.append(", alert=").append(m_alert);
+
+    buffer.append("}");
+    return buffer.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java
new file mode 100644
index 0000000..ab2c3dd
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.events;
+
+import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
+import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertState;
+
+/**
+ * The {@link AlertStateChangeEvent} is fired when an {@link Alert} instance has
+ * its {@link AlertState} changed.
+ */
+public final class AlertStateChangeEvent extends AlertEvent {
+
+  /**
+   * The prior alert state.
+   */
+  private final AlertState m_fromState;
+
+  /**
+   * The newly created historical entry.
+   */
+  private final AlertHistoryEntity m_newEntity;
+
+  /**
+   * Constructor.
+   *
+   * @param clusterId
+   * @param alert
+   */
+  public AlertStateChangeEvent(long clusterId, Alert alert,
+      AlertHistoryEntity newEntity, AlertState fromState) {
+    super(clusterId, alert);
+
+    m_newEntity = newEntity;
+    m_fromState = fromState;
+  }
+
+  /**
+   * Gets the newly created item in alert history.
+   *
+   * @return the newly created historical item.
+   */
+  public AlertHistoryEntity getNewHistoricalEntry() {
+    return m_newEntity;
+  }
+
+  /**
+   * Gets the prior state of the alert.
+   *
+   * @return the prior state of the alert.
+   */
+  public AlertState getFromState() {
+    return m_fromState;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    StringBuilder buffer = new StringBuilder("AlertStateChangeEvent{ ");
+    buffer.append("cluserId=").append(m_clusterId);
+    buffer.append(", fromState=").append(m_fromState);
+    buffer.append(", alert=").append(m_alert);
+
+    buffer.append("}");
+    return buffer.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertReceivedListener.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertReceivedListener.java b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertReceivedListener.java
new file mode 100644
index 0000000..1bdd7e2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertReceivedListener.java
@@ -0,0 +1,150 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.events.listeners;
+
+import org.apache.ambari.server.events.AlertEvent;
+import org.apache.ambari.server.events.AlertReceivedEvent;
+import org.apache.ambari.server.events.AlertStateChangeEvent;
+import org.apache.ambari.server.events.publishers.AlertEventPublisher;
+import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
+import org.apache.ambari.server.orm.dao.AlertsDAO;
+import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
+import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertState;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.google.common.eventbus.AllowConcurrentEvents;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * The {@link AlertReceivedListener} class handles {@link AlertReceivedEvent}
+ * and updates the appropirate DAOs. It may also fire new
+ * {@link AlertStateChangeEvent} when an {@link AlertState} change is detected.
+ */
+@Singleton
+public class AlertReceivedListener {
+  /**
+   * Logger.
+   */
+  private static Log LOG = LogFactory.getLog(AlertReceivedListener.class);
+
+  @Inject
+  private AlertsDAO m_alertsDao;
+
+  @Inject
+  private AlertDefinitionDAO m_definitionDao;
+
+  /**
+   * Receives and publishes {@link AlertEvent} instances.
+   */
+  private AlertEventPublisher m_alertEventPublisher;
+
+  /**
+   * Constructor.
+   *
+   * @param publisher
+   */
+  @Inject
+  public AlertReceivedListener(AlertEventPublisher publisher) {
+    m_alertEventPublisher = publisher;
+    m_alertEventPublisher.register(this);
+  }
+
+
+  /**
+   * Adds an alert.  Checks for a new state before creating a new history record.
+   *
+   * @param clusterId the id for the cluster
+   * @param alert the alert to add
+   */
+  @Subscribe
+  @AllowConcurrentEvents
+  public void onAlertEvent(AlertReceivedEvent event) {
+    long clusterId = event.getClusterId();
+    Alert alert = event.getAlert();
+
+    AlertCurrentEntity current = m_alertsDao.findCurrentByHostAndName(clusterId,
+        alert.getHost(), alert.getName());
+
+    if (null == current) {
+      AlertDefinitionEntity definition = m_definitionDao.findByName(clusterId,
+          alert.getName());
+
+      AlertHistoryEntity history = createHistory(clusterId, definition, alert);
+
+      current = new AlertCurrentEntity();
+      current.setAlertHistory(history);
+      current.setLatestTimestamp(Long.valueOf(alert.getTimestamp()));
+      current.setOriginalTimestamp(Long.valueOf(alert.getTimestamp()));
+
+      m_alertsDao.create(current);
+
+    } else if (alert.getState() == current.getAlertHistory().getAlertState()) {
+      current.setLatestTimestamp(Long.valueOf(alert.getTimestamp()));
+      current.setLatestText(alert.getText());
+
+      m_alertsDao.merge(current);
+    } else {
+      AlertState oldState = current.getAlertHistory().getAlertState();
+
+      // insert history, update current
+      AlertHistoryEntity history = createHistory(clusterId,
+          current.getAlertHistory().getAlertDefinition(), alert);
+
+      current.setAlertHistory(history);
+      current.setLatestTimestamp(Long.valueOf(alert.getTimestamp()));
+      current.setOriginalTimestamp(Long.valueOf(alert.getTimestamp()));
+
+      m_alertsDao.merge(current);
+
+      // broadcast the alert changed event for other subscribers
+      AlertStateChangeEvent alertChangedEvent = new AlertStateChangeEvent(
+          event.getClusterId(), event.getAlert(), history, oldState);
+
+      m_alertEventPublisher.publish(alertChangedEvent);
+    }
+  }
+
+  /**
+   * Convenience to create a new alert.
+   * @param clusterId the cluster id
+   * @param definition the definition
+   * @param alert the alert data
+   * @return the new history record
+   */
+  private AlertHistoryEntity createHistory(long clusterId, AlertDefinitionEntity definition, Alert alert) {
+    AlertHistoryEntity history = new AlertHistoryEntity();
+    history.setAlertDefinition(definition);
+    history.setAlertInstance(alert.getInstance());
+    history.setAlertLabel(alert.getLabel());
+    history.setAlertState(alert.getState());
+    history.setAlertText(alert.getText());
+    history.setAlertTimestamp(Long.valueOf(alert.getTimestamp()));
+    history.setClusterId(Long.valueOf(clusterId));
+    history.setComponentName(alert.getComponent());
+    history.setHostName(alert.getHost());
+    history.setServiceName(alert.getService());
+
+    return history;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertStateChangedListener.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertStateChangedListener.java b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertStateChangedListener.java
new file mode 100644
index 0000000..a327110
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/AlertStateChangedListener.java
@@ -0,0 +1,98 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.events.listeners;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ambari.server.events.AlertStateChangeEvent;
+import org.apache.ambari.server.events.publishers.AlertEventPublisher;
+import org.apache.ambari.server.orm.dao.AlertDispatchDAO;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.orm.entities.AlertGroupEntity;
+import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
+import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
+import org.apache.ambari.server.orm.entities.AlertTargetEntity;
+import org.apache.ambari.server.state.NotificationState;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.google.common.eventbus.AllowConcurrentEvents;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * The {@link AlertStateChangedListener} class response to
+ * {@link AlertStateChangeEvent} and updates {@link AlertNoticeEntity} instances
+ * in the database.
+ */
+@Singleton
+public class AlertStateChangedListener {
+
+  /**
+   * Logger.
+   */
+  private static Log LOG = LogFactory.getLog(AlertStateChangedListener.class);
+
+  /**
+   * Used for looking up groups and targets.
+   */
+  @Inject
+  private AlertDispatchDAO m_alertsDispatchDao;
+
+  /**
+   * Constructor.
+   *
+   * @param publisher
+   */
+  @Inject
+  public AlertStateChangedListener(AlertEventPublisher publisher) {
+    publisher.register(this);
+  }
+
+  /**
+   * Listens for when an alert's state has changed.
+   */
+  @Subscribe
+  @AllowConcurrentEvents
+  public void onAlertEvent(AlertStateChangeEvent event) {
+    AlertHistoryEntity history = event.getNewHistoricalEntry();
+    AlertDefinitionEntity definition = history.getAlertDefinition();
+
+    List<AlertGroupEntity> groups = m_alertsDispatchDao.findGroupsByDefinition(definition);
+
+    // for each group, determine if there are any targets that need to receive
+    // a notification about the alert state change event
+    for (AlertGroupEntity group : groups) {
+      Set<AlertTargetEntity> targets = group.getAlertTargets();
+      if (null == targets || targets.size() == 0) {
+        continue;
+      }
+
+      for (AlertTargetEntity target : targets) {
+        AlertNoticeEntity notice = new AlertNoticeEntity();
+        notice.setAlertTarget(target);
+        notice.setAlertHistory(event.getNewHistoricalEntry());
+        notice.setNotifyState(NotificationState.PENDING);
+
+        m_alertsDispatchDao.merge(notice);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/events/publishers/AlertEventPublisher.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/publishers/AlertEventPublisher.java b/ambari-server/src/main/java/org/apache/ambari/server/events/publishers/AlertEventPublisher.java
new file mode 100644
index 0000000..e42e317
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/publishers/AlertEventPublisher.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.events.publishers;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.ambari.server.events.AlertEvent;
+
+import com.google.common.eventbus.AsyncEventBus;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Singleton;
+
+/**
+ * The {@link AlertEventPublisher} is used to wrap a customized instance of an
+ * {@link AsyncEventBus} that is only used for alerts. In general, Ambari should
+ * have its own application-wide event bus for application events (session
+ * information, state changes, etc), but since alerts can contain many events
+ * being published concurrently, it makes sense to encapsulate a specific alert
+ * bus in this publisher.
+ */
+@Singleton
+public final class AlertEventPublisher {
+
+  /**
+   * A multi-threaded event bus that can handle dispatching {@link AlertEvent}s.
+   */
+  private final EventBus s_eventBus;
+
+  /**
+   * Constructor.
+   */
+  public AlertEventPublisher() {
+    s_eventBus = new AsyncEventBus(Executors.newFixedThreadPool(2,
+        new AlertEventBusThreadFactory()));
+  }
+
+  /**
+   * Publishes the specified event to all registered listeners that
+   * {@link Subscribe} to any of the {@link AlertEvent} instances.
+   *
+   * @param event
+   */
+  public void publish(AlertEvent event) {
+    s_eventBus.post(event);
+  }
+
+  /**
+   * Register a listener to receive events. The listener should use the
+   * {@link Subscribe} annotation.
+   *
+   * @param object
+   *          the listener to receive events.
+   */
+  public void register(Object object) {
+    s_eventBus.register(object);
+  }
+
+  /**
+   * A custom {@link ThreadFactory} for the threads that will handle published
+   * {@link AlertEvent}. Threads created will have slightly reduced priority
+   * since {@link AlertEvent} instances are not critical to the system.
+   */
+  private static final class AlertEventBusThreadFactory implements
+      ThreadFactory {
+
+    private static final AtomicInteger s_threadIdPool = new AtomicInteger(1);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Thread newThread(Runnable r) {
+      Thread thread = new Thread(r, "alert-event-bus-"
+          + s_threadIdPool.getAndIncrement());
+
+      thread.setDaemon(false);
+      thread.setPriority(Thread.NORM_PRIORITY - 1);
+
+      return thread;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
index e08c948..6d4d19b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
@@ -22,9 +22,11 @@ import java.util.List;
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.orm.entities.AlertGroupEntity;
 import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
 import org.apache.ambari.server.orm.entities.AlertTargetEntity;
+import org.apache.ambari.server.state.alert.AlertGroup;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -192,6 +194,26 @@ public class AlertDispatchDAO {
   }
 
   /**
+   * Gets all of the {@link AlertGroup} instances that include the specified
+   * alert definition.
+   *
+   * @param definitionEntity
+   *          the definition that the group must include (not {@code null}).
+   * @return all alert groups that have an association with the specified
+   *         definition or empty list if none exist (never {@code null}).
+   */
+  public List<AlertGroupEntity> findGroupsByDefinition(
+      AlertDefinitionEntity definitionEntity) {
+
+    TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertGroupEntity.findByAssociatedDefinition", AlertGroupEntity.class);
+
+    query.setParameter("alertDefinition", definitionEntity);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
    * Gets all alert notifications stored in the database.
    *
    * @return all alert notifications or empty list if none exist (never

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertGroupEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertGroupEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertGroupEntity.java
index f97a0eb..e7fa9c6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertGroupEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertGroupEntity.java
@@ -48,7 +48,8 @@ import javax.persistence.UniqueConstraint;
     @NamedQuery(name = "AlertGroupEntity.findAll", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup"),
     @NamedQuery(name = "AlertGroupEntity.findAllInCluster", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.clusterId = :clusterId"),
     @NamedQuery(name = "AlertGroupEntity.findByName", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.groupName = :groupName"),
-    @NamedQuery(name = "AlertGroupEntity.findByNameInCluster", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.groupName = :groupName AND alertGroup.clusterId = :clusterId"), })
+    @NamedQuery(name = "AlertGroupEntity.findByNameInCluster", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.groupName = :groupName AND alertGroup.clusterId = :clusterId"),
+    @NamedQuery(name = "AlertGroupEntity.findByAssociatedDefinition", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE :alertDefinition MEMBER OF alertGroup.alertDefinitions"), })
 public class AlertGroupEntity {
 
   @Id
@@ -224,7 +225,7 @@ public class AlertGroupEntity {
   /**
    * Adds the specified definition to the definitions that this group will
    * dispatch to.
-   * 
+   *
    * @param definition
    *          the definition to add (not {@code null}).
    */
@@ -240,7 +241,7 @@ public class AlertGroupEntity {
   /**
    * Removes the specified definition from the definitions that this group will
    * dispatch to.
-   * 
+   *
    * @param definition
    *          the definition to remove (not {@code null}).
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java b/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
index 7b8aabd..3675f87 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
@@ -32,8 +32,8 @@ public class Alert {
   private String label = null;
   private String text = null;
   private long timestamp = 0L;
-  
- 
+
+
   /**
    * Constructor.
    * @param alertName the name of the alert
@@ -56,32 +56,32 @@ public class Alert {
 
   public Alert() {
   }
- 
+
   /**
    * @return the name
    */
 
-  @JsonProperty("name")     
+  @JsonProperty("name")
   public String getName() {
     return name;
   }
- 
+
   /**
    * @return the service
    */
-  @JsonProperty("service")    
+  @JsonProperty("service")
   public String getService() {
     return service;
   }
-  
+
   /**
    * @return the component
    */
-  @JsonProperty("component")  
+  @JsonProperty("component")
   public String getComponent() {
     return component;
   }
- 
+
   /**
    * @return the host
    */
@@ -89,7 +89,7 @@ public class Alert {
   public String getHost() {
     return host;
   }
- 
+
   /**
    * @return the state
    */
@@ -101,7 +101,7 @@ public class Alert {
   /**
    * @return a short descriptive label for the alert
    */
-  @JsonProperty("label")  
+  @JsonProperty("label")
   public String getLabel() {
     return label;
   }
@@ -109,32 +109,32 @@ public class Alert {
   /**
    * @param alertLabel a short descriptive label for the alert
    */
-  @JsonProperty("label")   
+  @JsonProperty("label")
   public void setLabel(String alertLabel) {
     label = alertLabel;
   }
- 
+
   /**
    * @return detail text about the alert
    */
-  @JsonProperty("text")   
+  @JsonProperty("text")
   public String getText() {
     return text;
   }
-  
+
   /**
    * @param alertText detail text about the alert
    */
-  @JsonProperty("text")   
+  @JsonProperty("text")
   public void setText(String alertText) {
     text = alertText;
   }
 
-  @JsonProperty("instance")  
+  @JsonProperty("instance")
   public String getInstance() {
     return instance;
   }
-  
+
   @JsonProperty("instance")
   public void setInstance(String instance) {
     this.instance = instance;
@@ -164,24 +164,24 @@ public class Alert {
   public void setState(AlertState state) {
     this.state = state;
   }
-  
+
   @JsonProperty("timestamp")
   public void setTimestamp(long ts) {
     timestamp = ts;
   }
-  
+
   @JsonProperty("timestamp")
   public long getTimestamp() {
     return timestamp;
   }
-  
+
   /**
    * @return
    */
   public String getCluster() {
     return cluster;
   }
-  
+
   @Override
   public int hashCode() {
     int result = alertHashCode();
@@ -197,8 +197,9 @@ public class Alert {
    */
   @Override
   public boolean equals(Object o) {
-    if (null == o || !Alert.class.isInstance(o))
+    if (null == o || !Alert.class.isInstance(o)) {
       return false;
+    }
 
     return hashCode() == o.hashCode();
   }
@@ -217,7 +218,7 @@ public class Alert {
 
   /**
    * Checks equality with another alert, not taking into account instance info
-   * 
+   *
    * @param that
    *          the other alert to compare against
    * @return <code>true</code> when the alert is equal in every way except the
@@ -226,8 +227,7 @@ public class Alert {
   public boolean almostEquals(Alert that) {
     return alertHashCode() == that.alertHashCode();
   }
-  
-  
+
   @Override
   public String toString() {
     StringBuilder sb = new StringBuilder();
@@ -242,7 +242,4 @@ public class Alert {
     sb.append('}');
     return sb.toString();
   }
-
-  
-
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/AlertDataManager.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/AlertDataManager.java b/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/AlertDataManager.java
deleted file mode 100644
index 4a65d5a..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/AlertDataManager.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.ambari.server.state.cluster;
-
-import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
-import org.apache.ambari.server.orm.dao.AlertsDAO;
-import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
-import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
-import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
-import org.apache.ambari.server.state.Alert;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-/**
- * The AlertManager is reponsible for tracking all alerts generated for a cluster. 
- */
-@Singleton
-public class AlertDataManager {
-
-  @Inject
-  private AlertsDAO m_alertsDao;
-  @Inject
-  private AlertDefinitionDAO m_definitionDao;
-
-  
-  AlertDataManager() {
-  }
-  
-  /**
-   * Adds an alert.  Checks for a new state before creating a new history record.
-   * 
-   * @param clusterId the id for the cluster
-   * @param alert the alert to add
-   */
-  public void add(long clusterId, Alert alert) {
-    
-    AlertCurrentEntity current = m_alertsDao.findCurrentByHostAndName(clusterId,
-        alert.getHost(), alert.getName());
-    
-    if (null == current) {
-      AlertDefinitionEntity definition = m_definitionDao.findByName(clusterId,
-          alert.getName());
-      
-      AlertHistoryEntity history = createHistory(clusterId, definition, alert);
-      
-      current = new AlertCurrentEntity();
-      current.setAlertHistory(history);
-      current.setLatestTimestamp(Long.valueOf(alert.getTimestamp()));
-      current.setOriginalTimestamp(Long.valueOf(alert.getTimestamp()));
-      
-      m_alertsDao.create(current);
-      
-    } else if (alert.getState() == current.getAlertHistory().getAlertState()) {
-      current.setLatestTimestamp(Long.valueOf(alert.getTimestamp()));
-      current.setLatestText(alert.getText());
-      
-      m_alertsDao.merge(current);
-    } else {
-      // insert history, update current
-      AlertHistoryEntity history = createHistory(clusterId,
-          current.getAlertHistory().getAlertDefinition(), alert);
-      
-      current.setAlertHistory(history);
-      current.setLatestTimestamp(Long.valueOf(alert.getTimestamp()));
-      current.setOriginalTimestamp(Long.valueOf(alert.getTimestamp()));
-      
-      m_alertsDao.merge(current);
-    }
-    
-  }
-  /**
-   * Convenience to create a new alert.
-   * @param clusterId the cluster id
-   * @param definition the definition
-   * @param alert the alert data
-   * @return the new history record
-   */
-  private AlertHistoryEntity createHistory(long clusterId, AlertDefinitionEntity definition, Alert alert) {
-    AlertHistoryEntity history = new AlertHistoryEntity();
-    history.setAlertDefinition(definition);
-    history.setAlertInstance(alert.getInstance());
-    history.setAlertLabel(alert.getLabel());
-    history.setAlertState(alert.getState());
-    history.setAlertText(alert.getText());
-    history.setAlertTimestamp(Long.valueOf(alert.getTimestamp()));
-    history.setClusterId(Long.valueOf(clusterId));
-    history.setComponentName(alert.getComponent());
-    history.setHostName(alert.getHost());
-    history.setServiceName(alert.getService());
-    
-    return history;
-  }
-  
-
-}

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java
index 8451c9b..1103961 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java
@@ -351,6 +351,35 @@ public class AlertDispatchDAOTest {
   }
 
   /**
+   * Tests finding groups by a definition ID that they are associatd with.
+   * 
+   * @throws Exception
+   */
+  @Test
+  public void testFindGroupsByDefinition() throws Exception {
+    List<AlertDefinitionEntity> definitions = createDefinitions();
+    AlertGroupEntity group = helper.createAlertGroup(clusterId, null);
+
+    group = dao.findGroupById(group.getGroupId());
+    assertNotNull(group);
+
+    for (AlertDefinitionEntity definition : definitions) {
+      group.addAlertDefinition(definition);
+    }
+
+    dao.merge(group);
+
+    group = dao.findGroupByName(group.getGroupName());
+    assertEquals(definitions.size(), group.getAlertDefinitions().size());
+
+    for (AlertDefinitionEntity definition : definitions) {
+      List<AlertGroupEntity> groups = dao.findGroupsByDefinition(definition);
+      assertEquals(1, groups.size());
+      assertEquals(group.getGroupId(), groups.get(0).getGroupId());
+    }
+  }
+
+  /**
    * @return
    */
   private List<AlertDefinitionEntity> createDefinitions() throws Exception {

http://git-wip-us.apache.org/repos/asf/ambari/blob/760bedfe/ambari-server/src/test/java/org/apache/ambari/server/state/cluster/AlertDataManagerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/cluster/AlertDataManagerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/cluster/AlertDataManagerTest.java
index eae1de6..bbf7774 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/cluster/AlertDataManagerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/cluster/AlertDataManagerTest.java
@@ -21,17 +21,28 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
+import org.apache.ambari.server.events.AlertEvent;
+import org.apache.ambari.server.events.AlertReceivedEvent;
+import org.apache.ambari.server.events.AlertStateChangeEvent;
+import org.apache.ambari.server.events.listeners.AlertReceivedListener;
+import org.apache.ambari.server.events.listeners.AlertStateChangedListener;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.orm.OrmTestHelper;
 import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
+import org.apache.ambari.server.orm.dao.AlertDispatchDAO;
 import org.apache.ambari.server.orm.dao.AlertsDAO;
 import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.orm.entities.AlertGroupEntity;
 import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
+import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
+import org.apache.ambari.server.orm.entities.AlertTargetEntity;
 import org.apache.ambari.server.state.Alert;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.alert.Scope;
@@ -44,22 +55,24 @@ import com.google.inject.Injector;
 import com.google.inject.persist.PersistService;
 
 /**
- * Tests the alert manager.
+ * Tests the management of {@link AlertEvent}s in the system.
  */
 public class AlertDataManagerTest {
-  
+
   private static final String ALERT_DEFINITION = "Alert Definition 1";
   private static final String SERVICE = "service1";
   private static final String COMPONENT = "component1";
   private static final String HOST1 = "h1";
   private static final String HOST2 = "h2";
   private static final String ALERT_LABEL = "My Label";
-  
+
   private Long clusterId;
   private Injector injector;
   private OrmTestHelper helper;
   private AlertsDAO dao;
-  
+  private AlertDispatchDAO dispatchDao;
+  private AlertDefinitionDAO definitionDao;
+
   @Before
   public void setup() throws Exception {
     injector = Guice.createInjector(new InMemoryDefaultTestModule());
@@ -67,7 +80,8 @@ public class AlertDataManagerTest {
     helper = injector.getInstance(OrmTestHelper.class);
     clusterId = helper.createCluster();
     dao = injector.getInstance(AlertsDAO.class);
-    AlertDefinitionDAO definitionDao = injector.getInstance(AlertDefinitionDAO.class);
+    dispatchDao = injector.getInstance(AlertDispatchDAO.class);
+    definitionDao = injector.getInstance(AlertDefinitionDAO.class);
 
     // create 5 definitions
     for (int i = 0; i < 5; i++) {
@@ -83,18 +97,16 @@ public class AlertDataManagerTest {
       definition.setSourceType("SCRIPT");
       definitionDao.create(definition);
     }
-  
   }
-  
+
   @After
   public void teardown() {
     injector.getInstance(PersistService.class).stop();
     injector = null;
   }
-  
+
   @Test
   public void testAlertRecords() {
-    
     Alert alert1 = new Alert(ALERT_DEFINITION, null, SERVICE, COMPONENT, HOST1, AlertState.OK);
     alert1.setLabel(ALERT_LABEL);
     alert1.setText("Component component1 is OK");
@@ -103,18 +115,24 @@ public class AlertDataManagerTest {
     Alert alert2 = new Alert(ALERT_DEFINITION, null, SERVICE, COMPONENT, HOST2, AlertState.CRITICAL);
     alert2.setLabel(ALERT_LABEL);
     alert2.setText("Component component2 is not OK");
-    
-    AlertDataManager am = injector.getInstance(AlertDataManager.class);
-    
-    am.add(clusterId.longValue(), alert1);
-    am.add(clusterId.longValue(), alert2);
-    
+
+    AlertReceivedListener listener = injector.getInstance(AlertReceivedListener.class);
+
+    AlertReceivedEvent event1 = new AlertReceivedEvent(clusterId.longValue(),
+        alert1);
+
+    AlertReceivedEvent event2 = new AlertReceivedEvent(clusterId.longValue(),
+        alert2);
+
+    listener.onAlertEvent(event1);
+    listener.onAlertEvent(event2);
+
     List<AlertCurrentEntity> allCurrent = dao.findCurrentByService(clusterId.longValue(), SERVICE);
     assertEquals(2, allCurrent.size());
-    
+
     List<AlertHistoryEntity> allHistory = dao.findAll(clusterId.longValue());
     assertEquals(2, allHistory.size());
-    
+
     AlertCurrentEntity current = dao.findCurrentByHostAndName(clusterId.longValue(), HOST1, ALERT_DEFINITION);
     assertNotNull(current);
     assertEquals(HOST1, current.getAlertHistory().getHostName());
@@ -124,17 +142,21 @@ public class AlertDataManagerTest {
     assertEquals(current.getAlertHistory().getAlertState(), AlertState.OK);
     assertEquals(1L, current.getOriginalTimestamp().longValue());
     assertEquals(1L, current.getLatestTimestamp().longValue());
-    
+
     Long currentId = current.getAlertId();
     Long historyId = current.getAlertHistory().getAlertId();
-    
+
     // no new history since the state is the same
     Alert alert3 = new Alert(ALERT_DEFINITION, null, SERVICE, COMPONENT, HOST1, AlertState.OK);
     alert3.setLabel(ALERT_LABEL);
     alert3.setText("Component component1 is OK");
     alert3.setTimestamp(2L);
-    am.add(clusterId.longValue(), alert3);
-    
+
+    AlertReceivedEvent event3 = new AlertReceivedEvent(clusterId.longValue(),
+        alert3);
+
+    listener.onAlertEvent(event3);
+
     current = dao.findCurrentByHostAndName(clusterId.longValue(), HOST1, ALERT_DEFINITION);
     assertNotNull(current);
     assertEquals(currentId, current.getAlertId());
@@ -146,24 +168,28 @@ public class AlertDataManagerTest {
     assertEquals(current.getAlertHistory().getAlertState(), AlertState.OK);
     assertEquals(1L, current.getOriginalTimestamp().longValue());
     assertEquals(2L, current.getLatestTimestamp().longValue());
-   
+
     allCurrent = dao.findCurrentByService(clusterId.longValue(), SERVICE);
     assertEquals(2, allCurrent.size());
-    
+
     allHistory = dao.findAll(clusterId.longValue());
     assertEquals(2, allHistory.size());
-    
+
     // change to warning
     Alert alert4 = new Alert(ALERT_DEFINITION, null, SERVICE, COMPONENT, HOST1, AlertState.WARNING);
     alert4.setLabel(ALERT_LABEL);
     alert4.setText("Component component1 is about to go down");
     alert4.setTimestamp(3L);
-    am.add(clusterId.longValue(), alert4);
+
+    AlertReceivedEvent event4 = new AlertReceivedEvent(clusterId.longValue(),
+        alert4);
+
+    listener.onAlertEvent(event4);
 
     current = dao.findCurrentByHostAndName(clusterId.longValue(), HOST1, ALERT_DEFINITION);
     assertNotNull(current);
     assertEquals(current.getAlertId(), currentId);
-    assertFalse(historyId.equals(current.getAlertHistory().getAlertId()));    
+    assertFalse(historyId.equals(current.getAlertHistory().getAlertId()));
     assertEquals(HOST1, current.getAlertHistory().getHostName());
     assertEquals(ALERT_DEFINITION, current.getAlertHistory().getAlertDefinition().getDefinitionName());
     assertEquals(ALERT_LABEL, current.getAlertHistory().getAlertLabel());
@@ -174,8 +200,57 @@ public class AlertDataManagerTest {
 
     allCurrent = dao.findCurrentByService(clusterId.longValue(), SERVICE);
     assertEquals(2, allCurrent.size());
-    
+
     allHistory = dao.findAll(clusterId.longValue());
     assertEquals(3, allHistory.size());
   }
+
+  /**
+   * Tests that {@link AlertStateChangeEvent} cause an {@link AlertNoticeEntity}
+   * entry.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testAlertNotices() throws Exception {
+    List<AlertNoticeEntity> notices = dispatchDao.findAllNotices();
+    assertEquals( 0, notices.size() );
+
+    List<AlertDefinitionEntity> definitions = definitionDao.findAll(clusterId);
+    AlertDefinitionEntity definition = definitions.get(0);
+
+    AlertHistoryEntity history = new AlertHistoryEntity();
+    history.setServiceName(definition.getServiceName());
+    history.setClusterId(clusterId);
+    history.setAlertDefinition(definition);
+    history.setAlertLabel(definition.getDefinitionName());
+    history.setAlertText(definition.getDefinitionName());
+    history.setAlertTimestamp(System.currentTimeMillis());
+    history.setHostName(HOST1);
+    history.setAlertState(AlertState.OK);
+    dao.create(history);
+
+    List<AlertHistoryEntity> histories = dao.findAll(clusterId);
+    assertEquals(1, histories.size());
+
+    AlertTargetEntity target = helper.createAlertTarget();
+    Set<AlertTargetEntity> targets = new HashSet<AlertTargetEntity>();
+    targets.add(target);
+
+    AlertGroupEntity group = helper.createAlertGroup(clusterId, targets);
+    group.addAlertDefinition( definitions.get(0) );
+    dispatchDao.merge(group);
+
+    Alert alert1 = new Alert(ALERT_DEFINITION, null, SERVICE, COMPONENT, HOST1,
+        AlertState.OK);
+
+    AlertStateChangeEvent event = new AlertStateChangeEvent(clusterId, alert1,
+        histories.get(0), AlertState.CRITICAL);
+
+    AlertStateChangedListener listener = injector.getInstance(AlertStateChangedListener.class);
+    listener.onAlertEvent(event);
+
+    notices = dispatchDao.findAllNotices();
+    assertEquals(1, notices.size());
+  }
 }