/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2006-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) 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 OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.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 java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.opennms.core.concurrent.LogPreservingThreadFactory;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.model.events.EventListener;
import org.opennms.netmgt.model.events.EventProxyException;
import org.opennms.netmgt.xml.event.Event;
import org.opennms.netmgt.xml.event.Events;
import org.opennms.netmgt.xml.event.Log;
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
*
* @author <A HREF="mailto:sowmya@opennms.org">Sowmya Nataraj </A>
* @author <A HREF="http://www.opennms.org">OpenNMS.org </A>
*/
public class EventIpcManagerDefaultImpl implements EventIpcManager, EventIpcBroadcaster, InitializingBean {
public static class DiscardTrapsAndSyslogEvents implements RejectedExecutionHandler {
/**
* Creates a <tt>DiscardOldestPolicy</tt> for the given executor.
*/
public DiscardTrapsAndSyslogEvents() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
/**
* Hash table of list of event listeners keyed by event UEI
*/
private Map<String, List<EventListener>> m_ueiListeners = new HashMap<String, List<EventListener>>();
/**
* The list of event listeners interested in all events
*/
private List<EventListener> m_listeners = new ArrayList<EventListener>();
/**
* Hash table of event listener threads keyed by the listener's id
*/
private Map<String, EventListenerExecutor> m_listenerThreads = new HashMap<String, EventListenerExecutor>();
/**
* The thread pool handling the events
*/
private ExecutorService m_eventHandlerPool;
private EventHandler m_eventHandler;
private Integer m_handlerPoolSize;
private Integer m_handlerQueueLength;
private EventIpcManagerProxy m_eventIpcManagerProxy;
/**
* A thread dedicated to each listener. The events meant for each listener
* is added to an execution queue when the 'sendNow()' is called. The
* ListenerThread reads events off of this queue and sends them to the
* appropriate listener.
*/
private static class EventListenerExecutor {
/**
* Listener to which this thread is dedicated
*/
private final EventListener m_listener;
/**
* The thread that is running this runnable.
*/
private final ExecutorService m_delegateThread;
/**
* Constructor
*/
EventListenerExecutor(EventListener listener, Integer handlerQueueLength) {
m_listener = listener;
// You could also do Executors.newSingleThreadExecutor() here
m_delegateThread = new ThreadPoolExecutor(
1,
1,
0L,
TimeUnit.MILLISECONDS,
handlerQueueLength == null ? new LinkedBlockingQueue<Runnable>() : new LinkedBlockingQueue<Runnable>(handlerQueueLength),
// This ThreadFactory will ensure that the log prefix of the calling thread
// is used for all events that this listener handles. Therefore, if Notifd
// registers for an event then all logs for handling that event will end up
// inside notifd.log.
new LogPreservingThreadFactory(m_listener.getName(), 1, true),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
log().warn("Listener " + m_listener.getName() + "'s event queue is full, discarding event");
}
}
);
}
public void addEvent(final Event event) {
m_delegateThread.execute(new Runnable() {
public void run() {
try {
if (log().isInfoEnabled()) {
log().info("run: calling onEvent on " + m_listener.getName() + " for event " + event.getUei() + " dbid " + event.getDbid() + " 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);
}
}
});
}
/**
* Stops the execution of this listener.
*/
public void stop() {
m_delegateThread.shutdown();
}
}
/**
* <p>Constructor for EventIpcManagerDefaultImpl.</p>
*/
public EventIpcManagerDefaultImpl() {
}
/** {@inheritDoc} */
public void send(Event event) throws EventProxyException {
sendNow(event);
}
/**
* <p>send</p>
*
* @param eventLog a {@link org.opennms.netmgt.xml.event.Log} object.
* @throws org.opennms.netmgt.model.events.EventProxyException if any.
*/
public void send(Log eventLog) throws EventProxyException {
sendNow(eventLog);
}
/**
* {@inheritDoc}
*
* Called by a service to send an event to other listeners.
*/
public 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.
*
* @param eventLog a {@link org.opennms.netmgt.xml.event.Log} object.
*/
public void sendNow(Log eventLog) {
Assert.notNull(eventLog, "eventLog argument cannot be null");
try {
m_eventHandlerPool.execute(m_eventHandler.createRunnable(eventLog));
} catch (RejectedExecutionException 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)
*/
/** {@inheritDoc} */
public void broadcastNow(Event event) {
if (log().isDebugEnabled()) {
log().debug("Event ID " + event.getDbid() + " to be broadcasted: " + event.getUei());
}
if (m_listeners.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.getDbid() + " 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.getDbid() + ": " + event.getUei());
}
}
}
private void queueEventToListener(Event event, EventListener listener) {
m_listenerThreads.get(listener.getName()).addEvent(event);
}
/**
* {@inheritDoc}
*
* Register an event listener that is interested in all events.
* Removes this listener from any UEI-specific matches.
*/
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);
}
}
/**
* {@inheritDoc}
*
* Register an event listener interested in the UEIs in the passed list.
*/
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.
*
* @param listener a {@link org.opennms.netmgt.model.events.EventListener} object.
* @param uei a {@link java.lang.String} object.
*/
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));
}
/**
* {@inheritDoc}
*
* 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.
*/
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.
*
* @param listener a {@link org.opennms.netmgt.model.events.EventListener} object.
* @param uei a {@link java.lang.String} object.
*/
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);
}
/**
* {@inheritDoc}
*
* Removes a registered event listener.
*
* <strong>Note: </strong>Only this method stops the listener thread for the
* listener passed.
*/
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;
}
EventListenerExecutor listenerThread = new EventListenerExecutor(listener, m_handlerQueueLength);
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 static ThreadCategory log() {
return ThreadCategory.getInstance(EventIpcManagerDefaultImpl.class);
}
/**
* <p>afterPropertiesSet</p>
*/
@Override
public 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");
final String prefix = ThreadCategory.getPrefix();
try {
ThreadCategory.setPrefix(Eventd.LOG4J_CATEGORY);
/**
* Create a fixed-size thread pool. The number of threads can be configured by using
* the "receivers" attribute in the config. The queue length for the pool can be configured
* with the "queueLength" attribute in the config.
*/
m_eventHandlerPool = new ThreadPoolExecutor(
m_handlerPoolSize,
m_handlerPoolSize,
0L,
TimeUnit.MILLISECONDS,
m_handlerQueueLength == null ? new LinkedBlockingQueue<Runnable>() : new LinkedBlockingQueue<Runnable>(m_handlerQueueLength),
new LogPreservingThreadFactory(EventIpcManagerDefaultImpl.class.getSimpleName(), m_handlerPoolSize, true)
);
} finally {
ThreadCategory.setPrefix(prefix);
}
// If the proxy is set, make this class its delegate.
if (m_eventIpcManagerProxy != null) {
m_eventIpcManagerProxy.setDelegate(this);
}
}
/**
* <p>getEventHandler</p>
*
* @return a {@link org.opennms.netmgt.eventd.EventHandler} object.
*/
public EventHandler getEventHandler() {
return m_eventHandler;
}
/**
* <p>setEventHandler</p>
*
* @param eventHandler a {@link org.opennms.netmgt.eventd.EventHandler} object.
*/
public void setEventHandler(EventHandler eventHandler) {
m_eventHandler = eventHandler;
}
/**
* <p>getHandlerPoolSize</p>
*
* @return a int.
*/
public int getHandlerPoolSize() {
return m_handlerPoolSize;
}
/**
* <p>setHandlerPoolSize</p>
*
* @param handlerPoolSize a int.
*/
public void setHandlerPoolSize(int handlerPoolSize) {
Assert.state(m_eventHandlerPool == null, "handlerPoolSize property cannot be set after afterPropertiesSet() is called");
m_handlerPoolSize = handlerPoolSize;
}
/**
* <p>getHandlerQueueLength</p>
*
* @return a int.
*/
public int getHandlerQueueLength() {
return m_handlerQueueLength;
}
/**
* <p>setHandlerQueueLength</p>
*
* @param size a int.
*/
public void setHandlerQueueLength(int size) {
Assert.state(m_eventHandlerPool == null, "handlerQueueLength property cannot be set after afterPropertiesSet() is called");
m_handlerQueueLength = size;
}
/**
* <p>getEventIpcManagerProxy</p>
*
* @return a {@link org.opennms.netmgt.eventd.EventIpcManagerProxy} object.
*/
public EventIpcManagerProxy getEventIpcManagerProxy() {
return m_eventIpcManagerProxy;
}
/**
* <p>setEventIpcManagerProxy</p>
*
* @param eventIpcManagerProxy a {@link org.opennms.netmgt.eventd.EventIpcManagerProxy} object.
*/
public void setEventIpcManagerProxy(EventIpcManagerProxy eventIpcManagerProxy) {
m_eventIpcManagerProxy = eventIpcManagerProxy;
}
}