package org.infosec.ismp.eventd;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.infosec.ismp.model.event.Event;
import org.infosec.ismp.model.event.EventListener;
import org.infosec.ismp.model.event.EventProxyException;
import org.infosec.ismp.model.event.Events;
import org.infosec.ismp.model.event.Log;
import org.infosec.ismp.util.ThreadCategory;
import org.infosec.ismp.util.concurrent.RunnableConsumerThreadPool;
import org.infosec.ismp.util.queue.FifoQueue;
import org.infosec.ismp.util.queue.FifoQueueException;
import org.infosec.ismp.util.queue.FifoQueueImpl;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An implementation of the EventIpcManager interface that can be used to
* communicate between services in the same JVM
*
*/
public class EventIpcManagerDefaultImpl implements EventIpcManager,
EventIpcBroadcaster, InitializingBean {
/**
* Hash table of list of event listeners keyed by event UEI
*/
private final Map<String, List<EventListener>> m_ueiListeners = new HashMap<String, List<EventListener>>();
/**
* The list of event listeners interested in all events
*/
private final List<EventListener> m_listeners = new ArrayList<EventListener>();
/**
* Hash table of event listener threads keyed by the listener's id
*/
private final Map<String, ListenerThread> m_listenerThreads = new HashMap<String, ListenerThread>();
/**
* The thread pool handling the events
*/
private RunnableConsumerThreadPool m_eventHandlerPool;
private EventHandler m_eventHandler;
private Integer m_handlerPoolSize;
private EventIpcManagerProxy m_eventIpcManagerProxy;
/**
* A thread dedicated to each listener. The events meant for each listener
* is added to a dedicated queue when the 'sendNow()' is called. The
* ListenerThread reads events off of this queue and sends it to the
* appropriate listener
*/
private class ListenerThread implements Runnable {
/**
* Listener to which this thread is dedicated
*/
private final EventListener m_listener;
/**
* Queue from which events for the listener are to be read
*/
private final FifoQueue<Event> m_queue = new FifoQueueImpl<Event>();
/**
* The thread that is running this runnable.
*/
private final Thread m_delegateThread;
/**
* If set true then the thread should stop processing as soon as possible.
*/
private volatile boolean m_shutdown = true;
/**
* Constructor
*/
ListenerThread(EventListener listener) {
m_listener = listener;
m_delegateThread = new Thread(this, m_listener.getName());
}
public void addEvent(Event event) {
try {
m_queue.add(event);
if (log().isDebugEnabled()) {
log().debug(
"Queued event ID " + event.getUei()
+ " to listener thread: "
+ m_listener.getName());
}
} catch (FifoQueueException e) {
log().error(
"Error queueing event " + event.getUei()
+ " to listener thread " + m_listener.getName()
+ ": " + e, e);
} catch (InterruptedException e) {
log().error(
"Error queueing event " + event.getUei()
+ " to listener thread " + m_listener.getName()
+ ": " + e, e);
}
}
/**
* The run method performs the actual work for the runnable. It loops
* infinitely until the shutdown flag is set, during which time it
* processes queue elements. Each element in the queue should be a
* instance of {@link org.opennms.netmgt.xml.event.Event}. After each
* event is read, the 'onEvent' method of the listener is invoked.
*
*/
@Override
public void run() {
if (log().isDebugEnabled()) {
log().debug(
"In ListenerThread " + m_listener.getName() + " run");
}
while (!m_shutdown) {
Event event;
try {
event = m_queue.remove(500);
if (event == null) {
continue;
}
} catch (InterruptedException e) {
m_shutdown = true;
break;
} catch (FifoQueueException e) {
m_shutdown = true;
break;
}
try {
if (log().isInfoEnabled()) {
log().info(
"run: calling onEvent on "
+ m_listener.getName() + " for event "
+ event.getUei() + " with time "
+ event.getTime());
}
// Make sure we restore our log4j logging prefix after
// onEvent is called
String log4jPrefix = ThreadCategory.getPrefix();
try {
m_listener.onEvent(event);
} finally {
ThreadCategory.setPrefix(log4jPrefix);
}
} catch (Throwable t) {
log().warn(
"run: an unexpected error occured during ListenerThread "
+ m_listener.getName() + " run: " + t, t);
}
}
}
/**
* Starts up the thread.
*/
public void start() {
m_shutdown = false;
m_delegateThread.start();
}
/**
* Sets the stop flag in the thread.
*/
public void stop() {
m_shutdown = true;
}
}
public EventIpcManagerDefaultImpl() {
}
@Override
public void send(Event event) throws EventProxyException {
sendNow(event);
}
@Override
public void send(Log eventLog) throws EventProxyException {
sendNow(eventLog);
}
/**
* Called by a service to send an event to other listeners.
*/
@Override
public synchronized void sendNow(Event event) {
Assert.notNull(event, "event argument cannot be null");
Events events = new Events();
events.addEvent(event);
Log eventLog = new Log();
eventLog.setEvents(events);
sendNow(eventLog);
}
/**
* Called by a service to send a set of events to other listeners.
* Creates a new event handler for the event log and queues it to the
* event handler thread pool.
*/
@Override
public synchronized void sendNow(Log eventLog) {
Assert.notNull(eventLog, "eventLog argument cannot be null");
try {
m_eventHandlerPool.getRunQueue().add(
m_eventHandler.createRunnable(eventLog));
} catch (InterruptedException e) {
log().warn(
"Unable to queue event log to the event handler pool queue: "
+ e, e);
throw new UndeclaredEventException(e);
} catch (FifoQueueException e) {
log().warn(
"Unable to queue event log to the event handler pool queue: "
+ e, e);
throw new UndeclaredEventException(e);
}
}
/* (non-Javadoc)
* @see org.opennms.netmgt.eventd.EventIpcBroadcaster#broadcastNow(org.opennms.netmgt.xml.event.Event)
*/
@Override
public synchronized void broadcastNow(Event event) {
if (log().isDebugEnabled()) {
log().debug(
"Event ID " + event.getUei() + " to be broadcasted: "
+ event.getUei());
}
if (.isEmpty()) {
log().debug("No listeners interested in all events");
}
// Send to listeners interested in receiving all events
for (EventListener listener : m_listeners) {
queueEventToListener(event, listener);
}
if (event.getUei() == null) {
if (log().isDebugEnabled()) {
log().debug(
"Event ID "
+ event.getUei()
+ " does not have a UEI, so skipping UEI matching");
}
return;
}
/*
* Send to listeners who are interested in this event UEI.
* Loop to attempt partial wild card "directory" matches.
*/
Set<EventListener> sentToListeners = new HashSet<EventListener>();
for (String uei = event.getUei(); uei.length() > 0;) {
if (m_ueiListeners.containsKey(uei)) {
for (EventListener listener : m_ueiListeners.get(uei)) {
if (!sentToListeners.contains(listener)) {
queueEventToListener(event, listener);
sentToListeners.add(listener);
}
}
}
// Try wild cards: Find / before last character
int i = uei.lastIndexOf("/", uei.length() - 2);
if (i > 0) {
// Split at "/", including the /
uei = uei.substring(0, i + 1);
} else {
// No more wild cards to match
break;
}
}
if (sentToListeners.isEmpty()) {
if (log().isDebugEnabled()) {
log().debug(
"No listener interested in event ID " + ": "
+ event.getUei());
}
}
}
private void queueEventToListener(Event event, EventListener listener) {
m_listenerThreads.get(listener.getName()).addEvent(event);
}
/**
* Register an event listener that is interested in all events.
* Removes this listener from any UEI-specific matches.
*/
@Override
public synchronized void addEventListener(EventListener listener) {
Assert.notNull(listener, "listener argument cannot be null");
createListenerThread(listener);
addMatchAllForListener(listener);
// Since we have a match-all listener, remove any specific UEIs
for (String uei : m_ueiListeners.keySet()) {
removeUeiForListener(uei, listener);
}
}
/**
* Register an event listener interested in the UEIs in the passed list.
*/
@Override
public synchronized void addEventListener(EventListener listener,
Collection<String> ueis) {
Assert.notNull(listener, "listener argument cannot be null");
Assert.notNull(ueis, "ueilist argument cannot be null");
if (ueis.isEmpty()) {
log().warn(
"Not adding event listener "
+ listener.getName()
+ " because the ueilist argument contains no entries");
return;
}
if (log().isDebugEnabled()) {
log().debug(
"Adding event listener "
+ listener.getName()
+ " for UEIs: "
+ StringUtils
.collectionToCommaDelimitedString(ueis));
}
createListenerThread(listener);
for (String uei : ueis) {
addUeiForListener(uei, listener);
}
// Since we have a UEI-specific listener, remove the match-all listener
removeMatchAllForListener(listener);
}
/**
* Register an event listener interested in the passed UEI.
*/
@Override
public synchronized void addEventListener(EventListener listener, String uei) {
Assert.notNull(listener, "listener argument cannot be null");
Assert.notNull(uei, "uei argument cannot be null");
addEventListener(listener, Collections.singletonList(uei));
}
/**
* Removes a registered event listener. The UEI list indicates the list of
* events the listener is no more interested in.
*
* <strong>Note: </strong>The listener thread for this listener is not
* stopped until the 'removeEventListener(EventListener listener)' method is
* called.
*/
@Override
public synchronized void removeEventListener(EventListener listener,
Collection<String> ueis) {
Assert.notNull(listener, "listener argument cannot be null");
Assert.notNull(ueis, "ueilist argument cannot be null");
for (String uei : ueis) {
removeUeiForListener(uei, listener);
}
}
/**
* Removes a registered event listener. The UEI indicates one the listener
* is no more interested in.
*
* <strong>Note: </strong>The listener thread for this listener is not
* stopped until the 'removeEventListener(EventListener listener)' method is
* called.
*/
@Override
public synchronized void removeEventListener(EventListener listener,
String uei) {
Assert.notNull(listener, "listener argument cannot be null");
Assert.notNull(uei, "uei argument cannot be null");
removeUeiForListener(uei, listener);
}
/**
* Removes a registered event listener.
*
* <strong>Note: </strong>Only this method stops the listener thread for the
* listener passed.
*/
@Override
public synchronized void removeEventListener(EventListener listener) {
Assert.notNull(listener, "listener argument cannot be null");
removeMatchAllForListener(listener);
for (String uei : m_ueiListeners.keySet()) {
removeUeiForListener(uei, listener);
}
// stop and remove the listener thread for this listener
if (m_listenerThreads.containsKey(listener.getName())) {
m_listenerThreads.get(listener.getName()).stop();
m_listenerThreads.remove(listener.getName());
}
}
/**
* Create a new queue and listener thread for this listener if one does not
* already exist.
*/
private void createListenerThread(EventListener listener) {
if (m_listenerThreads.containsKey(listener.getName())) {
return;
}
ListenerThread listenerThread = new ListenerThread(listener);
listenerThread.start();
m_listenerThreads.put(listener.getName(), listenerThread);
}
/**
* Add to uei listeners.
*/
private void addUeiForListener(String uei, EventListener listener) {
// Ensure there is a list for this UEI
if (!m_ueiListeners.containsKey(uei)) {
m_ueiListeners.put(uei, new ArrayList<EventListener>());
}
List<EventListener> listenersList = m_ueiListeners.get(uei);
if (!listenersList.contains(listener)) {
listenersList.add(listener);
}
}
/**
* Remove UEI for this listener.
*/
private void removeUeiForListener(String uei, EventListener listener) {
if (m_ueiListeners.containsKey(uei)) {
m_ueiListeners.get(uei).remove(listener);
}
}
/**
* Add listener to list of listeners listening for all events.
*/
private boolean addMatchAllForListener(EventListener listener) {
return m_listeners.add(listener);
}
/**
* Remove from list of listeners listening for all events.
*/
private boolean removeMatchAllForListener(EventListener listener) {
return m_listeners.remove(listener);
}
private ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
@Override
public synchronized void afterPropertiesSet() {
Assert.state(m_eventHandlerPool == null,
"afterPropertiesSet() has already been called");
Assert.state(m_eventHandler != null, "eventHandler not set");
Assert.state(m_handlerPoolSize != null, "handlerPoolSize not set");
m_eventHandlerPool = new RunnableConsumerThreadPool("EventHandlerPool",
0.6f, 1.0f, m_handlerPoolSize);
m_eventHandlerPool.start();
// If the proxy is set, make this class its delegate.
if (m_eventIpcManagerProxy != null) {
m_eventIpcManagerProxy.setDelegate(this);
}
}
public EventHandler getEventHandler() {
return m_eventHandler;
}
public void setEventHandler(EventHandler eventHandler) {
m_eventHandler = eventHandler;
}
public int getHandlerPoolSize() {
return m_handlerPoolSize;
}
public void setHandlerPoolSize(int handlerPoolSize) {
Assert.state(m_eventHandlerPool == null,
"handlerPoolSize property cannot be set after afterPropertiesSet() is called");
m_handlerPoolSize = handlerPoolSize;
}
public EventIpcManagerProxy getEventIpcManagerProxy() {
return m_eventIpcManagerProxy;
}
public void setEventIpcManagerProxy(
EventIpcManagerProxy eventIpcManagerProxy) {
m_eventIpcManagerProxy = eventIpcManagerProxy;
}
}