/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2008-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.model.events; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import org.opennms.core.utils.LogUtils; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.model.events.annotations.EventExceptionHandler; import org.opennms.netmgt.model.events.annotations.EventHandler; import org.opennms.netmgt.model.events.annotations.EventListener; import org.opennms.netmgt.model.events.annotations.EventPostProcessor; import org.opennms.netmgt.model.events.annotations.EventPreProcessor; import org.opennms.netmgt.xml.event.Event; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * AnnotationBasedEventListenerAdapter * * @author brozow * @version $Id: $ */ public class AnnotationBasedEventListenerAdapter implements StoppableEventListener, InitializingBean, DisposableBean { private volatile String m_name = null; private volatile Object m_annotatedListener; private volatile String m_logPrefix = null; private volatile EventSubscriptionService m_subscriptionService; private final Map<String, Method> m_ueiToHandlerMap = new HashMap<String, Method>(); private final List<Method> m_eventPreProcessors = new LinkedList<Method>(); private final List<Method> m_eventPostProcessors = new LinkedList<Method>(); private final SortedSet<Method> m_exceptionHandlers = new TreeSet<Method>(createExceptionHandlerComparator()); /** * <p>Constructor for AnnotationBasedEventListenerAdapter.</p> * * @param name a {@link java.lang.String} object. * @param annotatedListener a {@link java.lang.Object} object. * @param subscriptionService a {@link org.opennms.netmgt.model.events.EventSubscriptionService} object. */ public AnnotationBasedEventListenerAdapter(String name, Object annotatedListener, EventSubscriptionService subscriptionService) { m_name = name; m_annotatedListener = annotatedListener; m_subscriptionService = subscriptionService; afterPropertiesSet(); } /** * <p>Constructor for AnnotationBasedEventListenerAdapter.</p> * * @param annotatedListener a {@link java.lang.Object} object. * @param subscriptionService a {@link org.opennms.netmgt.model.events.EventSubscriptionService} object. */ public AnnotationBasedEventListenerAdapter(Object annotatedListener, EventSubscriptionService subscriptionService) { this(null, annotatedListener, subscriptionService); } /** * <p>Constructor for AnnotationBasedEventListenerAdapter.</p> */ public AnnotationBasedEventListenerAdapter() { // this is here to support dependency injection style } /* (non-Javadoc) * @see org.opennms.netmgt.eventd.EventListener#getName() */ /** * <p>getName</p> * * @return a {@link java.lang.String} object. */ public String getName() { return m_name; } /** * <p>setName</p> * * @param name a {@link java.lang.String} object. */ public void setName(String name) { m_name = name; } /** * <p>getLogPrefix</p> * * @return the logPrefix */ public String getLogPrefix() { return m_logPrefix; } /** * <p>setLogPrefix</p> * * @param logPrefix the logPrefix to set */ public void setLogPrefix(String logPrefix) { m_logPrefix = logPrefix; } /* (non-Javadoc) * @see org.opennms.netmgt.eventd.EventListener#onEvent(org.opennms.netmgt.xml.event.Event) */ /** {@inheritDoc} */ public void onEvent(Event event) { if (event.getUei() == null) { return; } Method method = m_ueiToHandlerMap.get(event.getUei()); if (method == null) { // Try to get a catch-all event handler method = m_ueiToHandlerMap.get(EventHandler.ALL_UEIS); if (method == null) { throw new IllegalArgumentException("Received an event for which we have no handler!"); } } String oldPrefix = ThreadCategory.getPrefix(); try { if (m_logPrefix != null) { ThreadCategory.setPrefix(m_logPrefix); } preprocessEvent(event); processEvent(event, method); postprocessEvent(event); } catch (IllegalArgumentException e) { throw e; } catch (IllegalAccessException e) { throw new UndeclaredThrowableException(e); } catch (InvocationTargetException e) { handleException(event, e.getCause()); } finally { ThreadCategory.setPrefix(oldPrefix); } } /** * <p>postprocessEvent</p> * * @param event a {@link org.opennms.netmgt.xml.event.Event} object. * @throws java.lang.IllegalAccessException if any. * @throws java.lang.reflect.InvocationTargetException if any. */ protected void postprocessEvent(Event event) throws IllegalAccessException, InvocationTargetException { for(Method m : m_eventPostProcessors) { processEvent(event, m); } } /** * <p>processEvent</p> * * @param event a {@link org.opennms.netmgt.xml.event.Event} object. * @param method a {@link java.lang.reflect.Method} object. * @throws java.lang.IllegalAccessException if any. * @throws java.lang.reflect.InvocationTargetException if any. */ protected void processEvent(Event event, Method method) throws IllegalAccessException, InvocationTargetException { method.invoke(m_annotatedListener, event); } /** * <p>preprocessEvent</p> * * @param event a {@link org.opennms.netmgt.xml.event.Event} object. * @throws java.lang.IllegalAccessException if any. * @throws java.lang.reflect.InvocationTargetException if any. */ protected void preprocessEvent(Event event) throws IllegalAccessException, InvocationTargetException { for(Method m : m_eventPreProcessors) { processEvent(event, m); } } /** * <p>handleException</p> * * @param event a {@link org.opennms.netmgt.xml.event.Event} object. * @param cause a {@link java.lang.Throwable} object. */ protected void handleException(Event event, Throwable cause) { for(Method method : m_exceptionHandlers) { if (ClassUtils.isAssignableValue(method.getParameterTypes()[1], cause)) { try { method.invoke(m_annotatedListener, event, cause); // we found the correct handler to we are done return; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } } LogUtils.debugf(this, cause, "Caught an unhandled exception while processing event %s, for listener %s. Add EventExceptionHandler annotation to the listener", event.getUei(), m_annotatedListener); } /** * <p>setAnnotatedListener</p> * * @param annotatedListener a {@link java.lang.Object} object. */ public void setAnnotatedListener(Object annotatedListener) { m_annotatedListener = annotatedListener; } /** * <p>afterPropertiesSet</p> */ @Override public void afterPropertiesSet() { Assert.state(m_subscriptionService != null, "subscriptionService must be set"); Assert.state(m_annotatedListener != null, "must set the annotatedListener property"); EventListener listenerInfo = findEventListenerAnnotation(m_annotatedListener); Assert.state(listenerInfo != null, "value of annotatedListener property of class "+m_annotatedListener.getClass()+" must be annotated as "+EventListener.class.getName()); if (m_name == null) { m_name = listenerInfo.name(); } if (m_logPrefix == null) { if (listenerInfo.logPrefix() != null && !"".equals(listenerInfo.logPrefix())) { m_logPrefix = listenerInfo.logPrefix(); } else { m_logPrefix = m_name; } } populatePreProcessorList(); populateUeiToHandlerMap(); populatePostProcessorList(); populateExceptionHandlersSet(); // If we only have one EventHandler that is intended to be used as a handler for any UEI, then // register this class as an EventListener for all UEIs if (m_ueiToHandlerMap.size() == 1 && EventHandler.ALL_UEIS.equals(m_ueiToHandlerMap.keySet().toArray()[0])) { m_subscriptionService.addEventListener(this); } else { m_subscriptionService.addEventListener(this, new HashSet<String>(m_ueiToHandlerMap.keySet())); } } private static EventListener findEventListenerAnnotation(Object annotatedListener) { return annotatedListener.getClass().getAnnotation(EventListener.class); } private void populateExceptionHandlersSet() { Method[] methods = m_annotatedListener.getClass().getMethods(); for(Method method : methods) { if (method.isAnnotationPresent(EventExceptionHandler.class)) { validateMethodAsEventExceptionHandler(method); m_exceptionHandlers.add(method); } } } private static void validateMethodAsEventExceptionHandler(Method method) { Assert.state(method.getParameterTypes().length == 2, "Invalid number of paremeters. EventExceptionHandler methods must take 2 arguments with types (Event, ? extends Throwable)"); Assert.state(ClassUtils.isAssignable(Event.class, method.getParameterTypes()[0]), "First parameter of incorrect type. EventExceptionHandler first paramenter must be of type Event"); Assert.state(ClassUtils.isAssignable(Throwable.class, method.getParameterTypes()[1]), "Second parameter of incorrect type. EventExceptionHandler second paramenter must be of type ? extends Throwable"); } private static class ClassComparator<T> implements Comparator<Class<? extends T>> { public int compare(Class<? extends T> lhsType, Class<? extends T> rhsType) { return ClassUtils.isAssignable(lhsType, rhsType) ? 1 : -1; } } private Comparator<Method> createExceptionHandlerComparator() { final ClassComparator<Throwable> classComparator = new ClassComparator<Throwable>(); Comparator<Method> comparator = new Comparator<Method>() { public int compare(Method left, Method right) { Class<? extends Throwable> lhsType = left.getParameterTypes()[1].asSubclass(Throwable.class); Class<? extends Throwable> rhsType = right.getParameterTypes()[1].asSubclass(Throwable.class); EventExceptionHandler leftHandlerInfo = AnnotationUtils.findAnnotation(left, EventExceptionHandler.class); EventExceptionHandler rightHandlerInfo = AnnotationUtils.findAnnotation(right, EventExceptionHandler.class); if (leftHandlerInfo.order() == rightHandlerInfo.order()) { return classComparator.compare(lhsType, rhsType); } else { return leftHandlerInfo.order() - rightHandlerInfo.order(); } } }; return comparator; } private void populatePostProcessorList() { Method[] methods = m_annotatedListener.getClass().getMethods(); for(Method method : methods) { if (method.isAnnotationPresent(EventPostProcessor.class)) { validateMethodAsEventHandler(method); m_eventPostProcessors.add(method); } } } private void populatePreProcessorList() { Method[] methods = m_annotatedListener.getClass().getMethods(); for(Method method : methods) { EventPreProcessor ann = AnnotationUtils.findAnnotation(method, EventPreProcessor.class); if (ann != null) { validateMethodAsEventHandler(method); m_eventPreProcessors.add(method); } } } private void populateUeiToHandlerMap() { Method[] methods = m_annotatedListener.getClass().getMethods(); for(Method method : methods) { EventHandler handlerInfo = AnnotationUtils.findAnnotation(method, EventHandler.class); if (handlerInfo != null) { String uei = handlerInfo.uei(); Assert.state(!m_ueiToHandlerMap.containsKey(uei), "Cannot define method "+method+" as a handler for event "+uei+" since "+m_ueiToHandlerMap.get(uei)+" is already defined as a handler"); validateMethodAsEventHandler(method); m_ueiToHandlerMap.put(uei, method); } } Assert.state(!m_ueiToHandlerMap.isEmpty(), "annotatedListener must have public EventHandler annotated methods"); } private static void validateMethodAsEventHandler(Method method) { Assert.state(method.getParameterTypes().length == 1, "Invalid number of paremeters for method "+method+". EventHandler methods must take a single event argument"); Assert.state(method.getParameterTypes()[0].isAssignableFrom(Event.class), "Parameter of incorrent type for method "+method+". EventHandler methods must take a single event argument"); } /** * <p>stop</p> */ public void stop() { m_subscriptionService.removeEventListener(this); } /** * <p>destroy</p> * * @throws java.lang.Exception if any. */ public void destroy() throws Exception { stop(); } /** * <p>setEventSubscriptionService</p> * * @param subscriptionService a {@link org.opennms.netmgt.model.events.EventSubscriptionService} object. */ public void setEventSubscriptionService(EventSubscriptionService subscriptionService) { m_subscriptionService = subscriptionService; } }