/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.galerts.processor;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.galerts.server.session.GalertDef;
import org.hyperic.hq.galerts.server.session.GalertDefDAO;
import org.hyperic.hq.hibernate.SessionManager;
import org.hyperic.hq.hibernate.SessionManager.SessionRunner;
import org.hyperic.hq.zevents.Zevent;
import org.hyperic.hq.zevents.ZeventEnqueuer;
import org.hyperic.hq.zevents.ZeventSourceId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* This class acts as the central manager for all in-memory alerting.
* It performs the following functions:
*
* - Manages lists of active alert definitions
* - Reloads defs as configurations change
* - Listens to events from the zevents subsystem
* - Listens to group membership events from the grouping subsystem
*
* In the future, it will also:
* - Come online when a 'master' alerting node is specified in High Availability
*
* Since the galerting subsystem is broken up into 2 pieces, the persisted
* objects and the runtime objects, the 2 are split into separate packages.
*/
@Component
public class GalertProcessorImpl implements GalertProcessor {
private final Object cfgLock = new Object();
private final Log _log = LogFactory.getLog(GalertProcessor.class);
private final ZeventEnqueuer _zMan;
private GalertDefDAO _defDAO;
// Integer ids -> MemGalertDefs
private Map _alertDefs = new HashMap();
// ZeventSourceId -> Set of {@link Gtrigger}s
private Map _listeners = new HashMap();
@Autowired
public GalertProcessorImpl(ZeventEnqueuer zEventManager, GalertDefDAO galertDefDAO) {
_zMan = zEventManager;
_zMan.addBufferedGlobalListener(new EventListener(this));
this._defDAO = galertDefDAO;
}
/**
* Entry point to the processor from the {@link EventListener}
*
* @param events A list of {@link Zevent}s to process
*
* TODO: This needs to be optimized so that the EventListener buffers
* up many events and calls this method. The overhead of creating
* and checking session existance is too high on a per-event
* basis.
*/
public void processEvents(final List<Zevent> events) {
for (Iterator i=events.iterator(); i.hasNext(); ) {
Zevent z = (Zevent)i.next();
processEvent(z);
}
}
private void processEvent(final Zevent event) {
ZeventSourceId source = event.getSourceId();
Set listenerDupe;
synchronized (cfgLock) {
Set listeners = (Set)_listeners.get(source);
if (listeners == null)
return;
listenerDupe = new HashSet(listeners);
}
for (Iterator i=listenerDupe.iterator(); i.hasNext(); ) {
final Gtrigger t = (Gtrigger)i.next();
// Synchronize around all event processing for a trigger, since
// they keep state and will need to be flushed
synchronized(t) {
try {
SessionManager.runInSession(new SessionRunner() {
public String getName() {
return "Event Processor";
}
public void run() throws Exception {
t.processEvent(event);
}
});
} catch (Exception e) {
_log.warn("Error processing events", e);
}
}
}
}
/**
* Load a {@link GalertDef} into the processor, initializing the triggers,
* strategy, etc. If the definition is already loaded, it will be
* re-initialized as though it were loaded for the first time.
*
* If the load or reload fails, the processor will be in the same state
* as it was prior to the call.
*
* @param defId Id of a {@link GalertDef} to load.
*/
private void handleUpdate(MemGalertDef def) {
_log.debug("Handling load/update of alert def: " + def.getName());
synchronized (cfgLock) {
MemGalertDef oldDef = (MemGalertDef)_alertDefs.get(def.getId());
// Out with the old
if (oldDef != null) {
removeListeners(oldDef);
}
// In with the new
addListeners(def);
_alertDefs.put(def.getId(), def);
_log.debug("galert[id=" + def.getId() + ",name=" + def.getName() +
"] loaded");
}
}
/**
* Remove an alert definition from the processor.
*/
private void handleUnload(Integer defId) {
_log.debug("Handling unload of alertdef[" + defId + "]");
synchronized (cfgLock) {
MemGalertDef oldDef = (MemGalertDef)_alertDefs.get(defId);
if (oldDef == null) {
_log.warn("Attempted to unload defid=" + defId +
" but it wasn't loaded");
return;
}
removeListeners(oldDef);
_alertDefs.remove(defId);
}
}
private void addListeners(MemGalertDef def) {
Map eventMap = def.getInterestedEvents();
for (Iterator i=eventMap.entrySet().iterator(); i.hasNext(); ) {
Map.Entry ent = (Map.Entry)i.next();
Gtrigger t = (Gtrigger)ent.getKey();
Set sourceIds = (Set)ent.getValue();
for (Iterator j=sourceIds.iterator(); j.hasNext(); ) {
ZeventSourceId sourceId = (ZeventSourceId)j.next();
Set listeners = (Set)_listeners.get(sourceId);
if (listeners == null) {
listeners = new HashSet();
_listeners.put(sourceId, listeners);
}
listeners.add(t);
}
}
}
private void removeListeners(MemGalertDef def) {
Map eventMap = def.getInterestedEvents();
// Nuke all the old listeners
for (Iterator i=eventMap.entrySet().iterator(); i.hasNext(); ) {
Map.Entry ent = (Map.Entry)i.next();
Gtrigger t = (Gtrigger)ent.getKey();
Set sourceIds = (Set)ent.getValue();
for (Iterator j=sourceIds.iterator(); j.hasNext(); ) {
ZeventSourceId sourceId = (ZeventSourceId)j.next();
Set listeners = (Set)_listeners.get(sourceId);
listeners.remove(t);
}
}
}
/**
* Called when primitive information has been updated which doens't
* require a reload of the entire definition.
*/
public void alertDefUpdated(GalertDef def, final String newName) {
final Integer defId = def.getId();
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
public void suspend() {
}
public void resume() {
}
public void flush() {
}
public void beforeCompletion() {
}
public void beforeCommit(boolean readOnly) {
}
public void afterCompletion(int status) {
}
public void afterCommit() {
synchronized (cfgLock) {
MemGalertDef memDef = (MemGalertDef)
_alertDefs.get(defId);
if (memDef != null)
memDef.setName(newName);
}
}
});
}
/**
* Call this if an alert-def is created or updated. The internal state
* of the processor will be updated after the current transaction
* successfully commits.
*
* This method should be called for major changes to the definition, such
* as conditions, enablement, etc., since it fully unloads the existing
* definition and reloads it (meaning that internal state of the
* triggers, such as previously processed metrics, etc. will be reset)
*/
public void loadReloadOrUnload(GalertDef def) {
final boolean isUnload = !def.isEnabled();
final Integer defId = def.getId();
final MemGalertDef memDef;
if (isUnload) {
memDef = null;
} else {
memDef = new MemGalertDef(def);
}
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
public void suspend() {
}
public void resume() {
}
public void flush() {
}
public void beforeCompletion() {
}
public void beforeCommit(boolean readOnly) {
}
public void afterCompletion(int status) {
}
public void afterCommit() {
if (isUnload) {
handleUnload(defId);
} else {
handleUpdate(memDef);
}
}
});
}
/**
* Call this if an alert-def is deleted. After the transaction is
* successfully committed, it will be removed from the processor.
*/
public void alertDefDeleted(final Integer defId) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
public void suspend() {
}
public void resume() {
}
public void flush() {
}
public void beforeCompletion() {
}
public void beforeCommit(boolean readOnly) {
}
public void afterCompletion(int status) {
}
public void afterCommit() {
handleUnload(defId);
}
});
}
/**
* Called to initialize the state of the processor. This should be
* called during application startup with all the alert definitions
* in the system.
*/
@Transactional
public void startupInitialize() {
Collection galertDefs = _defDAO.findAll();
for (Iterator i=galertDefs.iterator(); i.hasNext(); ) {
GalertDef def = (GalertDef)i.next();
MemGalertDef memDef;
if (!def.isEnabled())
continue;
try {
memDef = new MemGalertDef(def);
handleUpdate(memDef);
} catch(Exception e) {
_log.warn("Unable to load alert definition[id=" +
def.getId() + ",name=" + def.getName(), e);
}
}
}
public boolean validateAlertDef(GalertDef def) {
try {
new MemGalertDef(def);
return true;
} catch(Exception e) {
_log.warn("Unable to create alert def [" + def + "]", e);
return false;
}
}
}