/******************************************************************************* * 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.xmlrpcd; import org.opennms.core.fiber.PausableFiber; import org.opennms.core.queue.FifoQueue; import org.opennms.core.queue.FifoQueueException; import org.opennms.core.utils.LogUtils; import org.opennms.netmgt.EventConstants; import org.opennms.netmgt.config.XmlrpcdConfigFactory; import org.opennms.netmgt.config.xmlrpcd.XmlrpcServer; import org.opennms.netmgt.xml.event.Event; import org.opennms.netmgt.xml.event.Parm; import org.opennms.netmgt.xml.event.Value; /** * The EventQueueProcessor processes the events received by xmlrpcd and sends * notifications to the external XMLRPC server via XMLRPC protocol. * * @author <a href="mailto:brozow@opennms.org">Mathew Brozowski</a> * @author <a href="mailto:david@opennms.org">David Hustace</a> * @author <a href="mailto:tarus@opennms.org">Tarus Balog</a> * @author <A HREF="mailto:jamesz@opennms.com">James Zuo </A> * @author <A HREF="http://www.opennms.org">OpenNMS.org </A> * */ class EventQueueProcessor implements Runnable, PausableFiber { /** * The input queue */ private FifoQueue<Event> m_eventQ; /** * The max size of the event queue */ private int m_maxQSize; /** * An object used to communicate with exteranl xmlrpc servers */ private XmlRpcNotifier m_notifier; /** * Current status of the fiber */ private int m_status; /** * The thread that is executing the <code>run</code> method on behalf of * the fiber. */ private Thread m_worker; /** * Use generic messages flag -- based on a setting in the config file, * if this flags is true, then we will send all events with the sendEvent * RPC call. If it's false, we'll use the backward-compatible 6 specific * event RPC calls. */ private boolean m_useGenericMessages; /** * The constructor */ EventQueueProcessor(final FifoQueue<Event> eventQ, final XmlrpcServer[] rpcServers, final int retries, final int elapseTime, final boolean verifyServer, final String localServer, final int maxQSize) { m_eventQ = eventQ; m_maxQSize = maxQSize; m_notifier = new XmlRpcNotifier(rpcServers, retries, elapseTime, verifyServer, localServer); m_useGenericMessages = XmlrpcdConfigFactory.getInstance().getConfiguration().getGenericMsgs(); } private void processEvent(final Event event) { final String uei = event.getUei(); if (uei == null) { LogUtils.debugf(this, "Event received with null UEI, ignoring event"); return; } LogUtils.debugf(this, "About to process event: %s", event.getUei()); LogUtils.debugf(this, event.toString()); if (m_useGenericMessages) { // new single RPC for all events (subject to config uei filter) if (!m_notifier.sendEvent(event)) { pushBackEvent(event); } } else { /* * original specific RPC calls -- limits us to exporting a max of * 6 specific events */ if (uei.equals(EventConstants.NODE_LOST_SERVICE_EVENT_UEI)) { if (!m_notifier.sendServiceDownEvent(event)) { pushBackEvent(event); } } else if (uei.equals(EventConstants.INTERFACE_DOWN_EVENT_UEI)) { if (!m_notifier.sendInterfaceDownEvent(event)) { pushBackEvent(event); } } else if (uei.equals(EventConstants.NODE_DOWN_EVENT_UEI)) { if (!m_notifier.sendNodeDownEvent(event)) { pushBackEvent(event); } } else if (uei.equals(EventConstants.NODE_UP_EVENT_UEI)) { if (!m_notifier.sendNodeUpEvent(event)) { pushBackEvent(event); } } else if (uei.equals(EventConstants.INTERFACE_UP_EVENT_UEI)) { if (!m_notifier.sendInterfaceUpEvent(event)) { pushBackEvent(event); } } else if (uei.equals(EventConstants.NODE_REGAINED_SERVICE_EVENT_UEI)) { if (!m_notifier.sendServiceUpEvent(event)) { pushBackEvent(event); } } } if (uei.equals(EventConstants.XMLRPC_NOTIFICATION_EVENT_UEI)) { xmlrpcNotificationEventHandler(event); } } /** * Process xmlrpcNotificationEvent according the status flag to determine to * send a notifyReceivedEvent, or a notifySuccess, or a notifyFailure * notification to XMLRPC Server. */ private void xmlrpcNotificationEventHandler(final Event event) { long txNo = -1L; String sourceUei = null; String notification = null; int status = -1; String parmName = null; Value parmValue = null; String parmContent = null; for (Parm parm : event.getParmCollection()) { parmName = parm.getParmName(); parmValue = parm.getValue(); if (parmValue == null) { continue; } else { parmContent = parmValue.getContent(); } LogUtils.debugf(this, "ParmName: %s /parmContent: ", parmName, parmContent); // get txNo if (parmName.equals(EventConstants.PARM_TRANSACTION_NO)) { final String temp = parmContent; try { txNo = Long.valueOf(temp).longValue(); } catch (final NumberFormatException nfe) { LogUtils.warnf(this, nfe, "Parameter %s cannot be non-numeric", EventConstants.PARM_TRANSACTION_NO); txNo = -1L; } } else if (parmName.equals(EventConstants.PARM_SOURCE_EVENT_UEI)) { sourceUei = parmContent; } else if (parmName.equals(EventConstants.PARM_SOURCE_EVENT_MESSAGE)) { notification = parmContent; } else if (parmName.equals(EventConstants.PARM_SOURCE_EVENT_STATUS)) { String temp = parmContent; try { status = Integer.valueOf(temp).intValue(); } catch (final NumberFormatException nfe) { LogUtils.warnf(this, nfe, "Parameter %s cannot be non-numeric", EventConstants.PARM_SOURCE_EVENT_STATUS); status = -1; } } } final boolean validParameters = (txNo != -1L) && (sourceUei != null) && (notification != null) && (status != -1); if (!validParameters) { LogUtils.errorf(this, "Invalid parameters."); return; } switch (status) { case EventConstants.XMLRPC_NOTIFY_RECEIVED: if (!m_notifier.notifyReceivedEvent(txNo, sourceUei, notification)) { pushBackEvent(event); } break; case EventConstants.XMLRPC_NOTIFY_SUCCESS: if (!m_notifier.notifySuccess(txNo, sourceUei, notification)) { pushBackEvent(event); } break; case EventConstants.XMLRPC_NOTIFY_FAILURE: if (!m_notifier.notifyFailure(txNo, sourceUei, notification)) { pushBackEvent(event); } } } /** * Push the event back to the event queue if OpenNMS failed to send message * to the external XMLRPC server, so that an attempt to send to the server * can be made again later. */ private void pushBackEvent(final Event event) { // push the event back to the event queue try { if (m_eventQ.size() < m_maxQSize) { m_eventQ.add(event); LogUtils.debugf(this, "Push the event back to queue."); } // re-establish connection to xmlrpc servers m_notifier.createConnection(); } catch (final FifoQueueException e) { LogUtils.errorf(this, e, "Failed to push the event back to queue"); } catch (final InterruptedException e) { LogUtils.errorf(this, e, "Failed to push the event back to queue"); } } /** * Returns true if the status is ok and the thread should continue running. * If the status returend is false then the thread should exit. * */ private synchronized boolean statusOK() { boolean exitThread = false; boolean exitCheck = false; // Loop until there is a new client or we are shutdown while (!exitCheck) { // check the child thread! if (m_worker.isAlive() == false && m_status != STOP_PENDING) { LogUtils.warnf(this, "%s terminated abnormally", getName()); m_status = STOP_PENDING; } // do normal status checks now if (m_status == STOP_PENDING) { exitCheck = true; exitThread = true; m_status = STOPPED; } else if (m_status == PAUSE_PENDING) { pause(); } else if (m_status == RESUME_PENDING) { resume(); } else if (m_status == PAUSED) { try { wait(); } catch (final InterruptedException e) { m_status = STOP_PENDING; } } else if (m_status == RUNNING) { exitCheck = true; } } // end !exit check return !exitThread; } // statusOK /** * Starts the current fiber. If the fiber has already been started, * regardless of it's current state, then an IllegalStateException is * thrown. * * @throws java.lang.IllegalStateException * Thrown if the fiber has already been started. */ public synchronized void start() { if (m_worker != null) { throw new IllegalStateException("The fiber is running or has already run"); } m_status = STARTING; m_worker = new Thread(this, getName()); m_worker.start(); LogUtils.infof(this, "%s started", getName()); } /** * Pauses the current fiber. */ public synchronized void pause() { if (m_worker == null || m_worker.isAlive() == false) { throw new IllegalStateException("The fiber is not running"); } m_status = PAUSED; notifyAll(); } /** * Resumes the currently paused fiber. */ public synchronized void resume() { if (m_worker == null || m_worker.isAlive() == false) { throw new IllegalStateException("The fiber is not running"); } m_status = RUNNING; notifyAll(); } /** * <p> * Stops this fiber. If the fiber has never been started then an * <code>IllegalStateExceptio</code> is generated. * </p> * * @throws java.lang.IllegalStateException * Thrown if the fiber has never been started. */ public synchronized void stop() { if (m_worker == null) { throw new IllegalStateException("The fiber has never run"); } m_status = STOP_PENDING; m_worker.interrupt(); notifyAll(); } /** * Returns the name of the fiber. * * @return The name of the Fiber. */ public String getName() { return "EventQueueProcessor"; } /** * Returns the current status of the fiber * * @return The status of the Fiber. */ public synchronized int getStatus() { if (m_worker != null && !m_worker.isAlive()) { m_status = STOPPED; } return m_status; } /** * Reads off of the event queue and depends on the uei of the event of read, * process the event to send a notification to the external XMLRPC server * via XMLRPC protocol. */ public void run() { synchronized (this) { m_status = RUNNING; } while (statusOK()) { Event event = null; try { event = m_eventQ.remove(1000); } catch (final InterruptedException iE) { LogUtils.debugf(this, iE, "Caught interrupted exception, transitioning to STOP_PENDING status"); event = null; m_status = STOP_PENDING; } catch (final FifoQueueException qE) { LogUtils.debugf(this, qE, "Caught FIFO queue exception."); event = null; m_status = STOP_PENDING; } if (event != null && statusOK()) { try { processEvent(event); } catch (Throwable t) { LogUtils.errorf(this, t, "Unexpected error processing event."); } } if (event != null && !statusOK()) { LogUtils.errorf(this, "EventQueueProcessor not OK, exiting with status: %d", m_status); } } } }