org.opennms.ng.services.capsd.BroadcastEventProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.opennms.ng.services.capsd.BroadcastEventProcessor.java

Source

/*******************************************************************************
 * This file is part of OpenNMS(R).
 *
 * Copyright (C) 2006-2012 The OpenNMS Group, Inc.
 * OpenNMS(R) is Copyright (C) 1999-2012 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.ng.services.capsd;

import java.net.InetAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.opennms.core.db.DataSourceFactory;
import org.opennms.core.utils.DBUtils;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.netmgt.EventConstants;
import org.opennms.netmgt.capsd.EventUtils;
import org.opennms.netmgt.capsd.FailedOperationException;
import org.opennms.netmgt.capsd.InsufficientInformationException;
import org.opennms.netmgt.model.OnmsNode;
import org.opennms.netmgt.model.capsd.DbIfServiceEntry;
import org.opennms.netmgt.model.capsd.DbIpInterfaceEntry;
import org.opennms.netmgt.model.capsd.DbNodeEntry;
import org.opennms.netmgt.model.events.EventIpcManager;
import org.opennms.netmgt.model.events.annotations.EventHandler;
import org.opennms.netmgt.model.events.annotations.EventListener;
import org.opennms.netmgt.xml.event.Event;
import org.opennms.ng.services.capsdconfig.CapsdConfig;
import org.opennms.ng.services.capsdconfig.CapsdConfigFactory;
import org.opennms.ng.services.pollerconfig.PollerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

/**
 * <p>BroadcastEventProcessor class.</p>
 *
 * @author <a href="mailto:matt@opennms.org">Matt Brozowski </a>
 * @author <a href="http://www.opennms.org/">OpenNMS </a>
 */
@EventListener(name = "Capsd:BroadcastEventProcessor", logPrefix = "capsd")
public class BroadcastEventProcessor implements InitializingBean {

    private SuspectEventProcessor suspectEventProcessor;
    private Set<String> queuedSuspectTracker = new HashSet<String>();
    private static final Logger LOG = LoggerFactory.getLogger(BroadcastEventProcessor.class);
    private EventIpcManager eventIpcManager;
    private PollerConfig pollerConfig;

    public void setPollerConfig(PollerConfig pollerConfig) {
        this.pollerConfig = pollerConfig;
    }

    public void setEventIpcManager(EventIpcManager eventIpcManager) {
        this.eventIpcManager = eventIpcManager;
    }

    /**
     * SQL statement used to add an interface/server mapping into the database;
     */
    private static String SQL_ADD_INTERFACE_TO_SERVER = "INSERT INTO serverMap VALUES (?, ?)";
    /**
     * SQL statement used to add an interface/service mapping into the database.
     */
    private static String SQL_ADD_SERVICE_TO_MAPPING = "INSERT INTO serviceMap VALUES (?, ?)";
    /**
     * SQL statement used to delete all services mapping to a specified
     * interface from the database.
     */
    private static String SQL_DELETE_ALL_SERVICES_INTERFACE_MAPPING = "DELETE FROM serviceMap WHERE ipaddr = ?";
    /**
     * SQL statement used to delete an interface/server mapping from the
     * database.
     */
    private static String SQL_DELETE_INTERFACE_ON_SERVER = "DELETE FROM serverMap WHERE ipaddr = ? AND servername = ?";
    /**
     * SQL statement used to delete an interface/service mapping from the
     * database.
     */
    private static String SQL_DELETE_SERVICE_INTERFACE_MAPPING = "DELETE FROM serviceMap WHERE ipaddr = ? AND servicemapname = ?";
    /**
     * SQL statement used to verify if an ipinterface with the specified ip
     * address exists in the database and retrieve the nodeid if exists.
     */
    private static String SQL_QUERY_IPADDRESS_EXIST = "SELECT nodeid FROM ipinterface WHERE ipaddr = ? AND isManaged !='D'";
    /**
     * SQL statement used to query the 'node' and 'ipinterface' tables to verify
     * if a specified ipaddr and node label have already exist in the database.
     */
    private static String SQL_QUERY_IPINTERFACE_EXIST = "SELECT nodelabel, ipaddr FROM node, ipinterface WHERE node.nodeid = ipinterface.nodeid AND"
            + " node.nodelabel = ? AND ipinterface.ipaddr = ? AND isManaged !='D' AND nodeType !='D'";
    /**
     * SQL statement used to query if a node with the specified nodelabel exist
     * in the database, and the nodeid from the database if exists.
     */
    private static String SQL_QUERY_NODE_EXIST = "SELECT nodeid, dpname FROM node WHERE nodelabel = ? AND nodeType !='D'";
    /**
     * SQL statement used to verify if an ifservice with the specified ip
     * address and service name exists in the database.
     */
    private static String SQL_QUERY_SERVICE_EXIST = "SELECT nodeid FROM ifservices, service WHERE ifservices.serviceid = service.serviceid AND "
            + "ipaddr = ? AND servicename = ? AND status !='D'";
    /**
     * SQL statement used to query if an interface/service mapping already
     * exists in the database.
     */
    private static String SQL_QUERY_SERVICE_MAPPING_EXIST = "SELECT * FROM serviceMap WHERE ipaddr = ? AND servicemapname = ?";
    /**
     * SQL query to retrieve nodeid of a particulary interface address
     */
    private static String SQL_RETRIEVE_NODEID = "select nodeid from ipinterface where ipaddr=? and isManaged!='D'";
    /**
     * SQL statement used to retrieve the serviced id from the database with a
     * specified service name.
     */
    private static String SQL_RETRIEVE_SERVICE_ID = "SELECT serviceid FROM service WHERE servicename = ?";
    /**
     * local openNMS server name
     */
    private String m_localServer = null;
    /**
     * The Capsd rescan scheduler
     */
    private Scheduler m_scheduler;
    /**
     * The location where suspectInterface events are enqueued for processing.
     */
    private ExecutorService m_suspectQ;
    private SuspectEventProcessorFactory m_suspectEventProcessorFactory;

    /**
     * Determines if deletePropagation is enabled in the Outage Manager.
     *
     * @return true if deletePropagation is enable, false otherwise
     */
    public static boolean isPropagationEnabled() {
        return CapsdConfigFactory.getInstance().getDeletePropagationEnabled();
    }

    /**
     * Convenience method checking Capsd's config for status of XmlRpc API
     *
     * @return Returns the xmlrpc.
     */
    public static boolean isXmlRpcEnabled() {
        return CapsdConfigFactory.getInstance().getXmlrpc().equals("true");
    }

    /**
     * Counts the number of interfaces on the node other than a given interface
     *
     * @param dbConn the database connection
     * @param nodeid the node to check interfaces for
     * @param ipAddr the interface not to include in the count
     * @return the numer of interfaces other than the given one
     * @throws java.sql.SQLException if an error occurs talking to the database
     */
    private int countOtherInterfacesOnNode(Connection dbConn, long nodeId, String ipAddr) throws SQLException {
        final String DB_COUNT_OTHER_INTERFACES_ON_NODE = "SELECT count(*) FROM ipinterface WHERE nodeID=? and ipAddr != ? and isManaged != 'D'";

        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());

        try {
            stmt = dbConn.prepareStatement(DB_COUNT_OTHER_INTERFACES_ON_NODE);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);
            rs = stmt.executeQuery();
            d.watch(rs);
            int count = 0;
            while (rs.next()) {
                count = rs.getInt(1);
            }

            LOG.debug("countServicesForInterface: count services for interface {}/{}: found {}", nodeId, ipAddr,
                    count);

            return count;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Counts the number of other non deleted services associated with the
     * interface defined by nodeid/ipAddr
     *
     * @param dbConn  the database connection
     * @param nodeId  the node to chck
     * @param ipAddr  the interface to check
     * @param service the name of the service not to include
     * @return the number of non deleted services, other than serviceId
     */
    private int countOtherServicesOnInterface(Connection dbConn, long nodeId, String ipAddr, String service)
            throws SQLException {

        final String DB_COUNT_OTHER_SERVICES_ON_IFACE = "SELECT count(*) FROM ifservices, service "
                + "WHERE ifservices.serviceId = service.serviceId AND ifservices.status != 'D' "
                + "AND ifservices.nodeID=? AND ifservices.ipAddr=? AND service.servicename != ?";

        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());

        try {
            stmt = dbConn.prepareStatement(DB_COUNT_OTHER_SERVICES_ON_IFACE);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);
            stmt.setString(3, service);
            rs = stmt.executeQuery();
            d.watch(rs);
            int count = 0;
            while (rs.next()) {
                count = rs.getInt(1);
            }

            LOG.debug("countServicesForInterface: count services for interface {}/{}: found {}", nodeId, ipAddr,
                    count);

            return count;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Counts the number of non deleted services on a node on interfaces other
     * than a given interface
     *
     * @param dbConn the database connection
     * @param nodeId the nodeid to check
     * @param ipAddr the address of the interface not to include
     * @return the number of non deleted services on other interfaces
     */
    private int countServicesOnOtherInterfaces(Connection dbConn, long nodeId, String ipAddr) throws SQLException {
        final String DB_COUNT_SERVICES_ON_OTHER_INTERFACES = "SELECT count(*) FROM ifservices WHERE nodeID=? and ipAddr != ? and status != 'D'";

        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(DB_COUNT_SERVICES_ON_OTHER_INTERFACES);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);
            rs = stmt.executeQuery();
            d.watch(rs);

            int count = 0;
            while (rs.next()) {
                count = rs.getInt(1);
            }

            LOG.debug("countServicesOnOtherInterfaces: count services for node {}: found {}", nodeId, count);

            return count;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Helper method used to create add an interface to a node.
     *
     * @param dbConn
     * @param nodeLabel
     * @param ipaddr
     * @return a LinkedList of events to be sent
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private List<Event> createInterfaceOnNode(Connection dbConn, String nodeLabel, String ipaddr)
            throws SQLException, FailedOperationException {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            // There is no ipinterface associated with the specified nodeLabel
            // exist in the database. Verify if a node with the nodeLabel already
            // exist in the database. If not, create a node with the nodeLabel and add it
            // to the database, and also add the ipaddress associated with this node to
            // the database. If the node with the nodeLabel exists in the node
            // table, just add the ip address to the database.
            stmt = dbConn.prepareStatement(SQL_QUERY_NODE_EXIST);
            d.watch(stmt);

            stmt.setString(1, nodeLabel);

            rs = stmt.executeQuery();
            d.watch(rs);
            List<Event> eventsToSend = new LinkedList<Event>();
            while (rs.next()) {

                LOG.debug("addInterfaceHandler:  add interface: {} to the database.", ipaddr);

                // Node already exists. Add the ipaddess to the ipinterface
                // table
                InetAddress ifaddr;
                try {
                    ifaddr = InetAddressUtils.addr(ipaddr);
                } catch (final IllegalArgumentException e) {
                    throw new FailedOperationException("unable to resolve host " + ipaddr + ": " + e.getMessage(),
                            e);
                }
                int nodeId = rs.getInt(1);
                String dpName = rs.getString(2);

                DbIpInterfaceEntry ipInterface = DbIpInterfaceEntry.create(nodeId, ifaddr);
                ipInterface.setHostname(ifaddr.getHostName());
                ipInterface.setManagedState(DbIpInterfaceEntry.STATE_MANAGED);
                ipInterface.setPrimaryState(DbIpInterfaceEntry.SNMP_NOT_ELIGIBLE);
                ipInterface.store(dbConn);

                // create a nodeEntry
                DbNodeEntry nodeEntry = DbNodeEntry.get(nodeId, dpName);
                Event newEvent = EventUtils.createNodeGainedInterfaceEvent(nodeEntry, ifaddr);
                eventsToSend.add(newEvent);
            }
            return eventsToSend;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * This method add a node with the specified node label to the database. If
     * also adds in interface with the given ipaddress to the node, if the
     * ipaddr is not null
     *
     * @param conn      The JDBC Database connection.
     * @param nodeLabel the node label to identify the node to create.
     * @param ipaddr    the ipaddress to be added into the ipinterface table.
     * @throws java.sql.SQLException                             if a database error occurs
     * @throws org.opennms.netmgt.capsd.FailedOperationException if the ipaddr is not resolvable
     */
    private List<Event> createNodeWithInterface(Connection conn, String nodeLabel, String ipaddr)
            throws SQLException, FailedOperationException {
        if (nodeLabel == null) {
            return Collections.emptyList();
        }

        LOG.debug("addNode:  Add a node {} to the database", nodeLabel);

        List<Event> eventsToSend = new LinkedList<Event>();
        DbNodeEntry node = DbNodeEntry.create();
        Date now = new Date();
        node.setCreationTime(now);
        node.setNodeType(OnmsNode.NodeType.ACTIVE);
        node.setLabel(nodeLabel);
        node.setLabelSource(OnmsNode.NodeLabelSource.USER);
        node.store(conn);

        Event newEvent = EventUtils.createNodeAddedEvent(node);
        eventsToSend.add(newEvent);

        if (ipaddr != null) {
            LOG.debug("addNode:  Add an IP Address {} to the database", ipaddr);
        }

        // add the ipaddess to the database
        InetAddress ifaddress;
        try {
            ifaddress = InetAddressUtils.addr(ipaddr);
        } catch (final IllegalArgumentException e) {
            throw new FailedOperationException("unable to resolve host " + ipaddr + ": " + e.getMessage(), e);
        }
        DbIpInterfaceEntry ipInterface = DbIpInterfaceEntry.create(node.getNodeId(), ifaddress);
        ipInterface.setHostname(ifaddress.getHostName());
        ipInterface.setManagedState(DbIpInterfaceEntry.STATE_MANAGED);
        ipInterface.setPrimaryState(DbIpInterfaceEntry.SNMP_NOT_ELIGIBLE);
        ipInterface.store(conn);

        Event gainIfEvent = EventUtils.createNodeGainedInterfaceEvent(node, ifaddress);
        eventsToSend.add(gainIfEvent);
        return eventsToSend;
    }

    /**
     * Helper method to add an interface to a node.
     *
     * @param dbConn
     * @param nodeLabel
     * @param ipaddr
     * @return eventsToSend
     * A List Object containing events to be sent
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private List<Event> doAddInterface(Connection dbConn, String nodeLabel, String ipaddr)
            throws SQLException, FailedOperationException {
        List<Event> eventsToSend;
        if (interfaceExists(dbConn, nodeLabel, ipaddr)) {
            LOG.debug("addInterfaceHandler: node {} with IPAddress {} already exist in the database.", nodeLabel,
                    ipaddr);
            eventsToSend = Collections.emptyList();
        } else {
            if (nodeExists(dbConn, nodeLabel)) {
                eventsToSend = createInterfaceOnNode(dbConn, nodeLabel, ipaddr);
            } else {
                // The node does not exist in the database, add the node and
                // the ipinterface into the database.
                eventsToSend = createNodeWithInterface(dbConn, nodeLabel, ipaddr);
            }
        }
        return eventsToSend;
    }

    /**
     * Perform the buld of the work for processing an addNode event
     *
     * @param dbConn    the database connection
     * @param nodeLabel the label for the node to add
     * @param ipaddr    an interface on the node (may be null if no interface is
     *                  supplied)
     * @return a list of events that need to be sent in response to these
     * changes
     * @throws java.sql.SQLException                             if a database error occurrs
     * @throws org.opennms.netmgt.capsd.FailedOperationException if other errors occur
     */
    private List<Event> doAddNode(Connection dbConn, String nodeLabel, String ipaddr)
            throws SQLException, FailedOperationException {
        List<Event> eventsToSend;
        if (!nodeExists(dbConn, nodeLabel)) {
            // the node does not exist in the database. Add the node with the
            // specified
            // node label and add the ipaddress to the database.
            eventsToSend = createNodeWithInterface(dbConn, nodeLabel, ipaddr);
        } else {
            eventsToSend = Collections.emptyList();
            LOG.debug("doAddNode: node {} with IPAddress {} already exist in the database.", nodeLabel, ipaddr);
        }
        return eventsToSend;
    }

    /**
     * Helper method used to update the mapping of interfaces to services.
     *
     * @param dbConn
     * @param ipaddr
     * @param serviceName
     * @param action
     * @param txNo
     * @return a list of events that need to be sent in response to these
     * changes
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private List<Event> doAddServiceMapping(Connection dbConn, String ipaddr, String serviceName, long txNo)
            throws SQLException, FailedOperationException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(SQL_ADD_SERVICE_TO_MAPPING);
            d.watch(stmt);

            stmt.setString(1, ipaddr);
            stmt.setString(2, serviceName);
            stmt.executeUpdate();

            LOG.debug("updateServiceHandler: add service {} to interface: {}", serviceName, ipaddr);

            return doChangeService(dbConn, ipaddr, serviceName, "ADD", txNo);
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Helper method used to add a service to an interface.
     *
     * @param dbConn
     * @param sourceUei
     * @param ipaddr
     * @param serviceName
     * @param serviceId
     * @param txNo
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private List<Event> doAddServiceToInterface(Connection dbConn, String ipaddr, String serviceName, int serviceId,
            long txNo) throws SQLException, FailedOperationException {

        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(SQL_QUERY_IPADDRESS_EXIST);
            d.watch(stmt);
            stmt.setString(1, ipaddr);
            rs = stmt.executeQuery();
            d.watch(rs);

            List<Event> eventsToSend = new LinkedList<Event>();
            while (rs.next()) {
                LOG.debug("changeServiceHandler: add service {} to interface: {}", serviceName, ipaddr);

                InetAddress inetAddr;
                try {
                    inetAddr = InetAddressUtils.addr(ipaddr);
                } catch (final IllegalArgumentException e) {
                    throw new FailedOperationException("unable to resolve host " + ipaddr + ": " + e.getMessage(),
                            e);
                }
                final int nodeId = rs.getInt(1);
                // insert service
                DbIfServiceEntry service = DbIfServiceEntry.create(nodeId, inetAddr, serviceId);
                service.setSource(DbIfServiceEntry.SOURCE_PLUGIN);
                service.setStatus(DbIfServiceEntry.STATUS_ACTIVE);
                service.setNotify(DbIfServiceEntry.NOTIFY_ON);
                service.store(dbConn, true);

                // Create a nodeGainedService event to eventd.
                DbNodeEntry nodeEntry = DbNodeEntry.get(nodeId);
                Event newEvent = EventUtils.createNodeGainedServiceEvent(nodeEntry, inetAddr, serviceName, txNo);
                eventsToSend.add(newEvent);
            }
            return eventsToSend;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Helper method used to change the state of a service for an interface.  Currently, add or delete.
     *
     * @param dbConn
     * @param sourceUei
     * @param ipaddr
     * @param serviceName
     * @param action
     * @param txNo
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private List<Event> doChangeService(Connection dbConn, String ipaddr, String serviceName, String action,
            long txNo) throws SQLException, FailedOperationException {
        List<Event> eventsToSend = null;
        int serviceId = verifyServiceExists(dbConn, serviceName);

        if (action.equalsIgnoreCase("DELETE")) {
            eventsToSend = new LinkedList<Event>();
            // find the node Id associated with the serviceName and interface
            int[] nodeIds = findNodeIdForServiceAndInterface(dbConn, ipaddr, serviceName);
            for (int i = 0; i < nodeIds.length; i++) {
                int nodeId = nodeIds[i];
                // delete the service from the database
                eventsToSend.addAll(doDeleteService(dbConn, "OpenNMS.Capsd", nodeId, ipaddr, serviceName, txNo));
            }
        } else {
            if (action.equalsIgnoreCase("ADD")) {
                eventsToSend = doAddServiceToInterface(dbConn, ipaddr, serviceName, serviceId, txNo);
            } else {
                eventsToSend = Collections.emptyList();
            }
        }
        return eventsToSend;
    }

    /**
     * Helper method used to create mapping of interface to service.
     *
     * @param dbConn
     * @param nodeLabel
     * @param ipaddr
     * @param hostName
     * @param txNo
     * @return Collections.singletonList()
     * A List containing event(s) to send.
     * @throws java.sql.SQLException
     */
    private List<Event> doCreateInterfaceMappings(Connection dbConn, String nodeLabel, String ipaddr,
            String hostName, long txNo) throws SQLException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(SQL_ADD_INTERFACE_TO_SERVER);
            d.watch(stmt);

            stmt.setString(1, ipaddr);
            stmt.setString(2, hostName);
            stmt.executeUpdate();

            LOG.debug("updateServerHandler: added interface {} into NMS server: {}", ipaddr, hostName);

            // Create a addInterface event and process it.
            // FIXME: do I need to make a direct call here?
            Event newEvent = EventUtils.createAddInterfaceEvent("OpenNMS.Capsd", nodeLabel, ipaddr, hostName, txNo);
            return Collections.singletonList(newEvent);
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Mark as deleted the specified interface and its associated services, if
     * delete propagation is enable and the interface is the only one on the
     * node, delete the node as well.
     *
     * @param dbConn the database connection
     * @param source the source for any events that must be sent
     * @param nodeid the id of the node the interface resides on
     * @param ipAddr the ip address of the interface to be deleted
     * @param txNo   a transaction number to associate with the deletion
     * @return a list of events that need to be sent w.r.t. this deletion
     * @throws java.sql.SQLException if any database errors occur
     */

    private List<Event> doDeleteInterface(Connection dbConn, String source, long nodeid, String ipAddr, long txNo)
            throws SQLException {
        return doDeleteInterface(dbConn, source, nodeid, ipAddr, -1, txNo);
    }

    /**
     * Mark as deleted the specified interface and its associated services, and/or
     * also the snmpinterface, if it exists. If delete propagation is enabled and
     * the interface is the only one on the node, delete the node as well.
     *
     * @param dbConn  the database connection
     * @param source  the source for any events that must be sent
     * @param nodeid  the id of the node the interface resides on
     * @param ipAddr  the ip address of the interface to be deleted
     * @param ifIndex the ifIndex of the interface to be deleted
     * @param txNo    a transaction number to associate with the deletion
     * @return a list of events that need to be sent w.r.t. this deletion
     * @throws java.sql.SQLException if any database errors occur
     */
    private List<Event> doDeleteInterface(Connection dbConn, String source, long nodeid, String ipAddr, int ifIndex,
            long txNo) throws SQLException {
        List<Event> eventsToSend = new LinkedList<Event>();

        // if this is the last ip interface for the node then delete the node
        // instead
        if (!EventUtils.isNonIpInterface(ipAddr) && isPropagationEnabled()
                && countOtherInterfacesOnNode(dbConn, nodeid, ipAddr) == 0) {
            // there are no other ifs for this node so delete the node
            eventsToSend = doDeleteNode(dbConn, source, nodeid, txNo);
        } else {
            if (!EventUtils.isNonIpInterface(ipAddr)) {
                eventsToSend.addAll(markAllServicesForInterfaceDeleted(dbConn, source, nodeid, ipAddr, txNo));
            }
            eventsToSend.addAll(markInterfaceDeleted(dbConn, source, nodeid, ipAddr, ifIndex, txNo));
        }
        deleteAlarmsForInterface(dbConn, nodeid, ipAddr);
        if (ifIndex > -1) {
            deleteAlarmsForSnmpInterface(dbConn, nodeid, ifIndex);
        }
        return eventsToSend;
    }

    /**
     * Helper method to remove all mappings of services to @param nodeLabel, @param ipaddr
     *
     * @param dbConn
     * @param nodeLabel
     * @param ipaddr
     * @param hostName
     * @param txNo
     * @param log
     * @return eventsToSend
     * a list of events to be sent.
     * @throws java.sql.SQLException
     */
    private List<Event> doDeleteInterfaceMappings(Connection dbConn, String nodeLabel, String ipaddr,
            String hostName, long txNo) throws SQLException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            List<Event> eventsToSend = new LinkedList<Event>();

            // Delete all services on the specified interface in
            // interface/service
            // mapping
            //
            LOG.debug("updateServer: delete all services on the interface: {} in the interface/service mapping.",
                    ipaddr);
            stmt = dbConn.prepareStatement(SQL_DELETE_ALL_SERVICES_INTERFACE_MAPPING);
            d.watch(stmt);
            stmt.setString(1, ipaddr);
            stmt.executeUpdate();

            // Delete the interface on interface/server mapping
            LOG.debug("updateServer: delete interface: {} on NMS server: {}", ipaddr, hostName);
            stmt = dbConn.prepareStatement(SQL_DELETE_INTERFACE_ON_SERVER);
            stmt.setString(1, ipaddr);
            stmt.setString(2, hostName);
            stmt.executeUpdate();

            // Now mark the interface as deleted (and its services as well)
            long[] nodeIds = findNodeIdsForInterfaceAndLabel(dbConn, nodeLabel, ipaddr);
            for (int i = 0; i < nodeIds.length; i++) {
                long nodeId = nodeIds[i];
                eventsToSend.addAll(doDeleteInterface(dbConn, "OpenNMS.Capsd", nodeId, ipaddr, txNo));
            }
            return eventsToSend;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Mark as deleted the specified node, its associated interfaces and
     * services.
     *
     * @param dbConn the connection to the database
     * @param source the source for any events to send
     * @param nodeid the nodeid to be deleted
     * @param txNo   a transaction id to associate with this deletion
     * @return the list of events that need to be sent in response to the node
     * being deleted
     * @throws java.sql.SQLException if any exception occurs communicating with the database
     */
    private List<Event> doDeleteNode(Connection dbConn, String source, long nodeid, long txNo) throws SQLException {
        List<Event> eventsToSend = new LinkedList<Event>();
        eventsToSend.addAll(markInterfacesAndServicesDeleted(dbConn, source, nodeid, txNo));
        eventsToSend.addAll(markNodeDeleted(dbConn, source, nodeid, txNo));

        //Note: left this call to deleteAlarmsForNode because I wanted to indicate that alarms are now
        //deleted by the DB with a delete cascade fk constraint on the alarm table
        //when the node is actually deleted.  We have to leave this in here for Capsd because
        //it only flags the node as deleted whereas the provisioner actually deletes the node.
        deleteAlarmsForNode(dbConn, nodeid);
        return eventsToSend;
    }

    private void deleteAlarmsForNode(Connection dbConn, long nodeId) throws SQLException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement("DELETE FROM alarms WHERE nodeid = ?");
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            int count = stmt.executeUpdate();

            LOG.debug("deleteAlarmsForNode: deleted: {} alarms for node: {}", count, nodeId);
        } finally {
            d.cleanUp();
        }
    }

    private void deleteAlarmsForInterface(Connection dbConn, long nodeId, String ipAddr) throws SQLException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement("DELETE FROM alarms WHERE nodeid = ? AND ipaddr = ?");
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);
            int count = stmt.executeUpdate();

            LOG.debug("deleteAlarmsForInterace: deleted: {} alarms for interface: {}", count, ipAddr);
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Delete alarms for the specified snmp interface
     *
     * @param dbConn  the connection to the database
     * @param nodeid  the nodeid for the interface to be deleted
     * @param ifIndex the ifIndex for the interface to be deleted
     * @throws java.sql.SQLException if any exception occurs communicating with the database
     */
    private void deleteAlarmsForSnmpInterface(Connection dbConn, long nodeId, int ifIndex) throws SQLException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement("DELETE FROM alarms WHERE nodeid = ? AND ifindex = ?");
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setInt(2, ifIndex);
            int count = stmt.executeUpdate();

            LOG.debug("deleteAlarmsForSnmpInterace: deleted: {} alarms for node {} ifIndex: {}", count, nodeId,
                    ifIndex);
        } finally {
            d.cleanUp();
        }
    }

    private void deleteAlarmsForService(Connection dbConn, long nodeId, String ipAddr, String service)
            throws SQLException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());

        try {
            stmt = dbConn.prepareStatement("DELETE FROM alarms " + "WHERE nodeid = ? " + " AND ipaddr = ? "
                    + " AND serviceid " + "  IN (SELECT serviceid " + "FROM service " + "WHERE servicename = ?)");
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);
            stmt.setString(3, service);
            int count = stmt.executeUpdate();

            LOG.debug("deleteAlarmsForService: deleted: {} alarms for service: {}", count, service);
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Mark as deleted the specified service, if this is the only service on an
     * interface or node and deletePropagation is enabled, the interface or node
     * is marked as deleted as well.
     *
     * @param dbConn  the connection to the database
     * @param source  the source for any events to send
     * @param nodeid  the nodeid that the service resides on
     * @param ipAddr  the interface that the service resides on
     * @param service the name of the service
     * @param txNo    a transaction id to associate with this deletion
     * @return the list of events that need to be sent in response to the
     * service being deleted
     * @throws java.sql.SQLException if any exception occurs communicating with the database
     */
    private List<Event> doDeleteService(Connection dbConn, String source, long nodeid, String ipAddr,
            String service, long txNo) throws SQLException {
        List<Event> eventsToSend = new LinkedList<Event>();

        if (isPropagationEnabled()) {
            // if this is the last service for the interface or the last service
            // for the node then send delete events for the interface or node
            // instead
            int otherSvcsOnIfCnt = countOtherServicesOnInterface(dbConn, nodeid, ipAddr, service);
            if (otherSvcsOnIfCnt == 0 && countServicesOnOtherInterfaces(dbConn, nodeid, ipAddr) == 0) {
                // no services on this interface or any other interface on this
                // node so delete
                // node
                LOG.debug("Propagating service delete to node {}", nodeid);
                eventsToSend.addAll(doDeleteNode(dbConn, source, nodeid, txNo));
            } else {
                if (otherSvcsOnIfCnt == 0) {
                    // no services on this interface so delete interface
                    LOG.debug("Propagting service delete to interface {}/{}", nodeid, ipAddr);
                    eventsToSend.addAll(doDeleteInterface(dbConn, source, nodeid, ipAddr, txNo));
                } else {
                    LOG.debug("No need to Propagate service delete {}/{}/{}", nodeid, ipAddr, service);
                    // otherwise just mark the service as deleted and send a
                    // serviceDeleted event
                    eventsToSend.addAll(markServiceDeleted(dbConn, source, nodeid, ipAddr, service, txNo));
                }
            }
        } else {
            LOG.debug("Propagation disabled:  deleting only service {}/{}/{}", nodeid, ipAddr, service);
            // otherwise just mark the service as deleted and send a
            // serviceDeleted event
            eventsToSend.addAll(markServiceDeleted(dbConn, source, nodeid, ipAddr, service, txNo));
        }
        deleteAlarmsForService(dbConn, nodeid, ipAddr, service);
        return eventsToSend;
    }

    /**
     * Helper method to handle the processes involved after receiving a delete service event.
     * FIXME: see FIXME in javadoc of the doUpdateService method.
     *
     * @param dbConn
     * @param ipaddr
     * @param serviceName
     * @param action
     * @param txNo
     * @return An object of type List that may contain events that need to be sent.
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private List<Event> doDeleteServiceMapping(Connection dbConn, String ipaddr, String serviceName, long txNo)
            throws SQLException, FailedOperationException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            LOG.debug("handleUpdateService: delete service: {} on IPAddress: {}", serviceName, ipaddr);
            stmt = dbConn.prepareStatement(SQL_DELETE_SERVICE_INTERFACE_MAPPING);
            d.watch(stmt);

            stmt.setString(1, ipaddr);
            stmt.setString(2, serviceName);

            stmt.executeUpdate();

            return doChangeService(dbConn, ipaddr, serviceName, "DELETE", txNo);
        } finally {
            d.cleanUp();
        }
    }

    private List<Event> doUpdateServer(Connection dbConn, String nodeLabel, String ipaddr, String action,
            String hostName, long txNo) throws SQLException, FailedOperationException {

        boolean exists = existsInServerMap(dbConn, hostName, ipaddr);

        //TODO: this logic changed from stable, verify that it should not be backported
        if ("DELETE".equalsIgnoreCase(action)) {
            return doDeleteInterfaceMappings(dbConn, nodeLabel, ipaddr, hostName, txNo);
        } else {
            if ("ADD".equalsIgnoreCase(action)) {
                if (exists) {
                    throw new FailedOperationException("Could not add interface " + ipaddr + " to NMS server: "
                            + hostName + " because it already exists!");
                } else {
                    return doCreateInterfaceMappings(dbConn, nodeLabel, ipaddr, hostName, txNo);
                }
            } else {
                LOG.error(
                        "updateServerHandler: could not process interface: {} on NMS server: {}: action {} unknown",
                        ipaddr, hostName, action);
                throw new FailedOperationException("Undefined operation " + action + " for updateServer event!");
            }
        }
    }

    /**
     * This method adds/updates a service for @param nodeLabel, @param ipaddr, @param serviceName.
     * FIXME: Found inconsistency in the sub-types of List returned.  Need to verify issues with jUnit tests.
     *
     * @param dbConn
     * @param ipaddr
     * @param serviceName
     * @param action
     * @param txNo
     * @return An object of type List containing events to be sent.
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private List<Event> doUpdateService(Connection dbConn, String nodeLabel, String ipaddr, String serviceName,
            String action, long txNo) throws SQLException, FailedOperationException {
        List<Event> eventsToSend;
        verifyServiceExists(dbConn, serviceName);
        verifyInterfaceExists(dbConn, nodeLabel, ipaddr);

        boolean mapExists = serviceMappingExists(dbConn, ipaddr, serviceName);

        if (mapExists && "DELETE".equalsIgnoreCase(action)) {
            // the mapping exists and should be deleted
            eventsToSend = doDeleteServiceMapping(dbConn, ipaddr, serviceName, txNo);
        } else {
            if (!mapExists && "ADD".equalsIgnoreCase(action)) {
                // we need to add the mapping, it doesn't exist
                eventsToSend = doAddServiceMapping(dbConn, ipaddr, serviceName, txNo);
            } else {
                eventsToSend = Collections.emptyList();
            }
        }
        return eventsToSend;
    }

    /**
     * Helper method to check if a service exists for a host and IP address.
     * <p/>
     * FIXME: Very inconsistent naming in the current "model": nodelabel, hostname, servername, etc.
     * right here this simple method indicates a problem with the model.
     *
     * @param dbConn
     * @param hostName
     * @param ipaddr
     * @return A logical evaluation of a service existing for hostname and IP address
     * @throws java.sql.SQLException
     */
    private boolean existsInServerMap(Connection dbConn, String hostName, String ipaddr) throws SQLException {
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            /**
             * SQL statement used to query if an interface/server mapping
             * already exists in the database.
             */
            final String SQL_QUERY_INTERFACE_ON_SERVER = "SELECT count(*)  FROM serverMap WHERE ipaddr = ? AND servername = ?";

            // Verify if the interface already exists on the NMS server
            stmt = dbConn.prepareStatement(SQL_QUERY_INTERFACE_ON_SERVER);
            d.watch(stmt);

            stmt.setString(1, ipaddr);
            stmt.setString(2, hostName);

            ResultSet rs = stmt.executeQuery();
            int count = 0;
            while (rs.next()) {
                count = rs.getInt(1);
            }

            return count > 0;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Helper method that returns the node id for the @param ipaddr and @param serviceName.
     * FIXME: Notice how some of these methods return node id arrays as int(s) and some as long(s).
     *
     * @param dbConn
     * @param ipaddr
     * @param serviceName
     * @return An int Array of node ids.
     * @throws java.sql.SQLException
     */
    private int[] findNodeIdForServiceAndInterface(Connection dbConn, String ipaddr, String serviceName)
            throws SQLException {
        int[] nodeIds;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());

        try {
            // Verify if the specified service already exist.
            stmt = dbConn.prepareStatement(SQL_QUERY_SERVICE_EXIST);
            d.watch(stmt);

            stmt.setString(1, ipaddr);
            stmt.setString(2, serviceName);

            rs = stmt.executeQuery();
            d.watch(rs);
            List<Integer> nodeIdList = new LinkedList<Integer>();
            while (rs.next()) {
                LOG.debug("changeService: service {} on IPAddress {} already exists in the database.", serviceName,
                        ipaddr);
                int nodeId = rs.getInt(1);
                nodeIdList.add(nodeId);
            }
            nodeIds = new int[nodeIdList.size()];
            int i = 0;
            for (Integer n : nodeIdList) {
                nodeIds[i++] = n.intValue();
            }
            return nodeIds;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Helper method for retrieving list of node ids that match @param nodeLabel and @param ipAddr
     *
     * @param dbConn
     * @param nodeLabel
     * @param ipAddr
     * @return Array of node ids from the db (JDBC)
     * @throws java.sql.SQLException
     */
    private long[] findNodeIdsForInterfaceAndLabel(Connection dbConn, String nodeLabel, String ipAddr)
            throws SQLException {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());

        try {
            stmt = dbConn.prepareStatement(
                    "SELECT node.nodeid FROM node, ipinterface WHERE node.nodeid = ipinterface.nodeid AND node.nodelabel = ? AND ipinterface.ipaddr = ?"
                            + " AND isManaged !='D' AND nodeType !='D'");
            d.watch(stmt);
            stmt.setString(1, nodeLabel);
            stmt.setString(2, ipAddr);

            rs = stmt.executeQuery();
            d.watch(rs);
            List<Long> nodeIdList = new LinkedList<Long>();
            while (rs.next()) {
                nodeIdList.add(rs.getLong(1));
            }

            long[] nodeIds = new long[nodeIdList.size()];
            int i = 0;
            for (Long nodeId : nodeIdList) {
                nodeIds[i++] = nodeId.longValue();
            }
            return nodeIds;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Return an id for this event listener
     *
     * @return a {@link String} object.
     */
    public String getName() {
        return "Capsd:BroadcastEventProcessor";
    }

    /**
     * Process the event, add the specified interface into database. If the
     * associated node does not exist in the database yet, add a node into the
     * database.
     *
     * @param event The event to process.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if the event is missing essential information
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if the operation fails (because of database error for
     *                                                                   example)
     */
    @EventHandler(uei = EventConstants.ADD_INTERFACE_EVENT_UEI)
    public void handleAddInterface(Event event) throws InsufficientInformationException, FailedOperationException {
        EventUtils.checkInterface(event);
        EventUtils.requireParm(event, EventConstants.PARM_NODE_LABEL);
        if (isXmlRpcEnabled()) {
            EventUtils.requireParm(event, EventConstants.PARM_TRANSACTION_NO);
        }
        LOG.debug("addInterfaceHandler:  processing addInterface event for {}", event.getInterface());

        String nodeLabel = EventUtils.getParm(event, EventConstants.PARM_NODE_LABEL);
        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);

        // First make sure the specified node label and ipaddress do not exist
        // in the database
        // before trying to add them in.
        Connection dbConn = null;
        List<Event> eventsToSend = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);

            eventsToSend = doAddInterface(dbConn, nodeLabel, event.getInterface());
        } catch (SQLException sqlE) {
            LOG.error("addInterfaceHandler: SQLException during add node and ipaddress to the database.", sqlE);
            throw new FailedOperationException("Database error: " + sqlE.getMessage(), sqlE);
        } finally {
            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleAddInterface: Threw Exception during commit: ", ex);
                    throw new FailedOperationException("Database error: " + ex.getMessage(), ex);
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.setAutoCommit(true); //TODO:verify this
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleAddInterface: Threw Exception during close: ", ex);
                        }
                    }
                }
            }
        }
    }

    private Connection getConnection() throws SQLException {
        return DataSourceFactory.getInstance().getConnection();
    }

    /**
     * Process an addNode event.
     *
     * @param event The event to process.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if the event is missing information
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if an error occurs during processing
     */
    @EventHandler(uei = EventConstants.ADD_NODE_EVENT_UEI)
    public void handleAddNode(Event event) throws InsufficientInformationException, FailedOperationException {

        EventUtils.requireParm(event, EventConstants.PARM_NODE_LABEL);
        if (isXmlRpcEnabled()) {
            EventUtils.requireParm(event, EventConstants.PARM_TRANSACTION_NO);
        }

        String ipaddr = event.getInterface();
        String nodeLabel = EventUtils.getParm(event, EventConstants.PARM_NODE_LABEL);
        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);
        LOG.debug("addNodeHandler:  processing addNode event for {}", ipaddr);
        Connection dbConn = null;
        List<Event> eventsToSend = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);

            eventsToSend = doAddNode(dbConn, nodeLabel, ipaddr);
        } catch (SQLException sqlE) {
            LOG.error("addNodeHandler: SQLException during add node and ipaddress to tables", sqlE);
            throw new FailedOperationException("database error: " + sqlE.getMessage(), sqlE);
        } finally {
            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleAddNode: Threw Exception during commit: ", ex);
                    throw new FailedOperationException("database error: " + ex.getMessage(), ex);
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleAddNode: Threw Exception during close: ", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * Process the event, add or remove a specified service from an interface.
     * An 'action' parameter wraped in the event will tell which action to take
     * to the service.
     *
     * @param event The event to process.
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if any.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if any.
     */
    @EventHandler(uei = EventConstants.CHANGE_SERVICE_EVENT_UEI)
    public void handleChangeService(Event event) throws InsufficientInformationException, FailedOperationException {
        EventUtils.checkInterface(event);
        EventUtils.checkService(event);
        EventUtils.requireParm(event, EventConstants.PARM_ACTION);
        if (isXmlRpcEnabled()) {
            EventUtils.requireParm(event, EventConstants.PARM_TRANSACTION_NO);
        }

        String action = EventUtils.getParm(event, EventConstants.PARM_ACTION);
        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);

        LOG.debug("changeServiceHandler:  processing changeService event on: {}", event.getInterface());

        Connection dbConn = null;
        List<Event> eventsToSend = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);

            eventsToSend = doChangeService(dbConn, event.getInterface(), event.getService(), action, txNo);
        } catch (SQLException sqlE) {
            LOG.error("SQLException during changeService on database.", sqlE);
            throw new FailedOperationException("exeption processing changeService: " + sqlE.getMessage(), sqlE);
        } finally {
            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleChangeService: Exception thrown during commit/rollback: ", ex);
                    throw new FailedOperationException("exeption processing changeService: " + ex.getMessage(), ex);
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleChangeService: Exception thrown closing connection: {}", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * Handle a deleteInterface Event. Here we process the event by marking all
     * the appropriate data rows as deleted.
     *
     * @param event The event indicating what interface to delete
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if the required information is not part of the event
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if any.
     */
    @EventHandler(uei = EventConstants.DELETE_INTERFACE_EVENT_UEI)
    public void handleDeleteInterface(Event event)
            throws InsufficientInformationException, FailedOperationException {
        // validate event
        EventUtils.checkEventId(event);
        EventUtils.checkInterfaceOrIfIndex(event);
        EventUtils.checkNodeId(event);
        int ifIndex = -1;
        if (event.hasIfIndex()) {
            ifIndex = event.getIfIndex();
        }
        if (isXmlRpcEnabled()) {
            EventUtils.requireParm(event, EventConstants.PARM_TRANSACTION_NO);
        }

        // log the event
        LOG.debug(
                "handleDeleteInterface: Event\nuei\t\t{}\neventid\t\t{}\nnodeId\t\t{}\nipaddr\t\t{}\nifIndex\t\t{}\neventtime\t{}",
                event.getUei(), event.getDbid(), event.getNodeid(),
                (event.getInterface() != null ? event.getInterface() : "N/A"), (ifIndex > -1 ? ifIndex : "N/A"),
                (event.getTime() != null ? event.getTime() : "<null>"));

        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);

        // update the database
        Connection dbConn = null;
        List<Event> eventsToSend = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);

            String source = (event.getSource() == null ? "OpenNMS.Capsd" : event.getSource());

            eventsToSend = doDeleteInterface(dbConn, source, event.getNodeid(), event.getInterface(), ifIndex,
                    txNo);
        } catch (SQLException ex) {
            LOG.error(
                    "handleDeleteInterface:  Database error deleting interface on node {} with ip address {} and ifIndex {}",
                    event.getNodeid(), (event.getInterface() != null ? event.getInterface() : "null"),
                    (event.hasIfIndex() ? event.getIfIndex() : "null"), ex);
            throw new FailedOperationException("database error: " + ex.getMessage(), ex);
        } finally {
            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleDeleteInterface: Exception thrown during commit/rollback: ", ex);
                    throw new FailedOperationException("exeption processing delete interface: " + ex.getMessage(),
                            ex);
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleDeleteInterface: Exception thrown closing connection: ", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * Handle a deleteNode Event. Here we process the event by marking all the
     * appropriate data rows as deleted.
     *
     * @param event The event indicating what node to delete
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if the required information is not part of the event
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if any.
     */
    @EventHandler(uei = EventConstants.DELETE_NODE_EVENT_UEI)
    public void handleDeleteNode(Event event) throws InsufficientInformationException, FailedOperationException {
        // validate event
        EventUtils.checkEventId(event);
        EventUtils.checkNodeId(event);
        if (isXmlRpcEnabled()) {
            EventUtils.requireParm(event, EventConstants.PARM_TRANSACTION_NO);
        }

        // log the event
        long nodeid = event.getNodeid();
        LOG.debug("handleDeleteNode: Event\nuei\t\t{}\neventid\t\t{}\nnodeId\t\t{}\neventtime\t{}", event.getUei(),
                event.getDbid(), nodeid, (event.getTime() != null ? event.getTime() : "<null>"));

        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);

        // update the database
        Connection dbConn = null;
        List<Event> eventsToSend = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);

            String source = (event.getSource() == null ? "OpenNMS.Capsd" : event.getSource());

            eventsToSend = doDeleteNode(dbConn, source, nodeid, txNo);
        } catch (SQLException ex) {
            LOG.error("handleDeleteService:  Database error deleting service {} on ipAddr {} for node {}",
                    event.getService(), event.getInterface(), nodeid, ex);
            throw new FailedOperationException("database error: " + ex.getMessage(), ex);
        } finally {

            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleDeleteNode: Exception thrown during commit/rollback: ", ex);
                    throw new FailedOperationException("exeption processing deleteNode: " + ex.getMessage(), ex);
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleDeleteNode: Exception thrown closing connection: ", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * Handle a deleteService Event. Here we process the event by marking all
     * the appropriate data rows as deleted.
     *
     * @param event The event indicating what service to delete
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if the required information is not part of the event
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if any.
     */
    @EventHandler(uei = EventConstants.DELETE_SERVICE_EVENT_UEI)
    public void handleDeleteService(Event event) throws InsufficientInformationException, FailedOperationException {

        // validate event
        EventUtils.checkEventId(event);
        EventUtils.checkNodeId(event);
        EventUtils.checkInterface(event);
        EventUtils.checkService(event);

        // log the event
        LOG.debug(
                "handleDeleteService: Event\nuei\t\t{}\neventid\t\t{}\nnodeid\t\t{}\nipaddr\t\t{}\nservice\t\t{}\neventtime\t{}",
                event.getUei(), event.getDbid(), event.getNodeid(), event.getInterface(), event.getService(),
                (event.getTime() != null ? event.getTime() : "<null>"));

        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);

        // update the database
        Connection dbConn = null;
        List<Event> eventsToSend = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);
            String source = (event.getSource() == null ? "OpenNMS.Capsd" : event.getSource());
            eventsToSend = doDeleteService(dbConn, source, event.getNodeid(), event.getInterface(),
                    event.getService(), txNo);
        } catch (SQLException ex) {
            LOG.error("handleDeleteService:  Database error deleting service {} on ipAddr {} for node {}",
                    event.getService(), event.getInterface(), event.getNodeid(), ex);
            throw new FailedOperationException("database error: " + ex.getMessage(), ex);
        } finally {

            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleDeleteService: Exception thrown during commit/rollback: ", ex);
                    throw new FailedOperationException("exeption processing deleteService: " + ex.getMessage(), ex);
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleDeleteService: Exception thrown closing connection: ", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * This helper method removes a deleted node from Capsd's re-scan schedule.  Doesn't remove it
     * from the new suspect scan schedule.
     *
     * @param event a {@link org.opennms.netmgt.xml.event.Event} object.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if any.
     */
    @EventHandler(uei = EventConstants.DUP_NODE_DELETED_EVENT_UEI)
    public void handleDupNodeDeleted(Event event) throws InsufficientInformationException {

        EventUtils.checkNodeId(event);

        // Remove the deleted node from the scheduler
        m_scheduler.unscheduleNode(event.getNodeid().intValue());
    }

    /**
     * Helper method that takes IP address from the force rescan event,
     * retrieves the nodeid (JDBC) and adds it to the rescan schedule for immediate
     * processing when the next thread is available.
     *
     * @param event a {@link org.opennms.netmgt.xml.event.Event} object.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if any.
     */
    @EventHandler(uei = EventConstants.FORCE_RESCAN_EVENT_UEI)
    public void handleForceRescan(Event event) throws InsufficientInformationException {
        // If the event has a node identifier use it otherwise
        // will need to use the interface to lookup the node id
        // from the database
        Long nodeid = -1L;

        if (event.hasNodeid()) {
            nodeid = event.getNodeid();
        } else {
            // Extract interface from the event and use it to
            // lookup the node identifier associated with the
            // interface from the database.
            //

            // ensure the ipaddr is set
            EventUtils.checkInterface(event);

            // Get database connection and retrieve nodeid
            Connection dbc = null;
            PreparedStatement stmt = null;
            ResultSet rs = null;
            final DBUtils d = new DBUtils(getClass());
            try {
                dbc = getConnection();
                d.watch(dbc);

                // Retrieve node id
                stmt = dbc.prepareStatement(SQL_RETRIEVE_NODEID);
                d.watch(stmt);
                stmt.setString(1, event.getInterface());
                rs = stmt.executeQuery();
                d.watch(rs);
                if (rs.next()) {
                    nodeid = rs.getLong(1);
                }
            } catch (SQLException sqlE) {
                LOG.error("handleForceRescan: Database error during nodeid retrieval for interface {}",
                        event.getInterface(), sqlE);
            } finally {
                d.cleanUp();
            }
        }

        if (nodeid == null || nodeid == -1) {
            LOG.error("handleForceRescan: Nodeid retrieval for interface {} failed.  Unable to perform rescan.",
                    event.getInterface());
            return;
        }

        // discard this forceRescan if one is already enqueued for the same node ID
        if (RescanProcessor.isRescanQueuedForNode(nodeid.intValue())) {
            LOG.info(
                    "Ignoring forceRescan event for node {} because a forceRescan for that node already exists in the queue",
                    nodeid);
            return;
        }

        // Rescan the node.
        m_scheduler.forceRescan(nodeid.intValue());
    }

    /**
     * Helper method to add a node from the new suspect event Event to the suspect scan schedule.
     *
     * @param event a {@link org.opennms.netmgt.xml.event.Event} object.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if any.
     */
    @EventHandler(uei = EventConstants.NEW_SUSPECT_INTERFACE_EVENT_UEI)
    public void handleNewSuspect(final Event event) throws InsufficientInformationException {
        // ensure the event has an interface
        EventUtils.checkInterface(event);

        final String interfaceValue = event.getInterface();

        // discard this newSuspect if one is already enqueued for the same IP address
        //TODO
        // if (isScanQueuedForAddress(interfaceValue)) {
        //  LOG.info("Ignoring newSuspect event for interface {} because a newSuspect scan for that interface already exists in the queue",
        //    interfaceValue);
        // return;
        //}

        // new suspect event
        try {
            LOG.debug("onMessage: Adding interface to suspectInterface Q: {}", interfaceValue);
            m_suspectQ.execute(m_suspectEventProcessorFactory.createSuspectEventProcessor(interfaceValue,
                    pollerConfig, capsdConfig, eventIpcManager));
        } catch (final Throwable ex) {
            LOG.error("onMessage: Failed to add interface to suspect queue", ex);
        }
    }

    public boolean isScanQueuedForAddress(String ipAddr) {
        synchronized (queuedSuspectTracker) {
            return (queuedSuspectTracker.contains(ipAddr));
        }
    }

    private CapsdConfig capsdConfig;

    public void setCapsdConfig(CapsdConfig capsdConfig) {
        this.capsdConfig = capsdConfig;
    }

    /**
     * Adds the node from the event to the rescan schedule.
     *
     * @param event a {@link org.opennms.netmgt.xml.event.Event} object.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if any.
     */
    @EventHandler(uei = EventConstants.NODE_ADDED_EVENT_UEI)
    public void handleNodeAdded(Event event) throws InsufficientInformationException {
        EventUtils.checkNodeId(event);

        // Schedule the new node.
        try {
            m_scheduler.scheduleNode(event.getNodeid().intValue());
        } catch (SQLException sqlE) {
            LOG.error("onMessage: SQL exception while attempting to schedule node {}", event.getNodeid(), sqlE);
        }
    }

    /**
     * Handles the process of removing a deleted node from the rescan schedule.
     *
     * @param event a {@link org.opennms.netmgt.xml.event.Event} object.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if any.
     */
    @EventHandler(uei = EventConstants.NODE_DELETED_EVENT_UEI)
    public void handleNodeDeleted(Event event) throws InsufficientInformationException {

        EventUtils.checkNodeId(event);

        // Remove the deleted node from the scheduler
        m_scheduler.unscheduleNode(event.getNodeid().intValue());
    }

    /**
     * Handles the process of adding/updating a node.
     * TODO: Change the name of this to something that makes more sense.  This impacts the UEI of the named event
     * for consistency and clearity, however, being called "Server" is unclear to itself.  Change the event from
     * "Server" to "Node" and all related methods.
     *
     * @param event a {@link org.opennms.netmgt.xml.event.Event} object.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if any.
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if any.
     */
    @EventHandler(uei = EventConstants.UPDATE_SERVER_EVENT_UEI)
    public void handleUpdateServer(Event event) throws InsufficientInformationException, FailedOperationException {
        // If there is no interface or NMS server found then it cannot be
        // processed
        EventUtils.checkInterface(event);
        EventUtils.checkHost(event);
        EventUtils.requireParm(event, EventConstants.PARM_ACTION);
        EventUtils.requireParm(event, EventConstants.PARM_NODE_LABEL);
        if (isXmlRpcEnabled()) {
            EventUtils.requireParm(event, EventConstants.PARM_TRANSACTION_NO);
        }

        String action = EventUtils.getParm(event, EventConstants.PARM_ACTION);
        String nodeLabel = EventUtils.getParm(event, EventConstants.PARM_NODE_LABEL);
        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);

        LOG.debug("updateServerHandler:  processing updateServer event for: {} on OpenNMS server: {}",
                event.getInterface(), m_localServer);

        Connection dbConn = null;
        List<Event> eventsToSend = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);

            eventsToSend = doUpdateServer(dbConn, nodeLabel, event.getInterface(), action, m_localServer, txNo);
        } catch (SQLException sqlE) {
            LOG.error("SQLException during updateServer on database.", sqlE);
            throw new FailedOperationException("SQLException during updateServer on database.", sqlE);
        } finally {
            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleUpdateServer: Exception thrown during commit/rollback: ", ex);
                    throw new FailedOperationException("SQLException during updateServer on database.", ex);
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleUpdateServer: Exception thrown closing connection: ", ex);
                        }
                    }
                }
            }
        }
    }

    public void setQueuedSuspectTracker(Set<String> queuedSuspectTracker) {
        this.queuedSuspectTracker = queuedSuspectTracker;
    }

    /**
     * Process the event, add or remove a specified interface/service pair into
     * the database. this event will cause an changeService event with the
     * specified action. An 'action' parameter wraped in the event will tell
     * which action to take to the service on the specified interface. The
     * ipaddress of the interface, the service name must be included in the
     * event.
     *
     * @param event The event to process.
     * @throws org.opennms.netmgt.capsd.InsufficientInformationException if there is missing information in the event
     * @throws org.opennms.netmgt.capsd.FailedOperationException         if the operation fails for some reason
     */
    @EventHandler(uei = EventConstants.UPDATE_SERVICE_EVENT_UEI)
    public void handleUpdateService(Event event) throws InsufficientInformationException, FailedOperationException {

        EventUtils.checkInterface(event);
        EventUtils.checkService(event);
        EventUtils.requireParm(event, EventConstants.PARM_NODE_LABEL);
        EventUtils.requireParm(event, EventConstants.PARM_ACTION);
        if (isXmlRpcEnabled()) {
            EventUtils.requireParm(event, EventConstants.PARM_TRANSACTION_NO);
        }

        long txNo = EventUtils.getLongParm(event, EventConstants.PARM_TRANSACTION_NO, -1L);
        String action = EventUtils.getParm(event, EventConstants.PARM_ACTION);
        String nodeLabel = EventUtils.getParm(event, EventConstants.PARM_NODE_LABEL);

        LOG.debug("handleUpdateService:  processing updateService event for : {} on : {}", event.getService(),
                event.getInterface());

        List<Event> eventsToSend = null;
        Connection dbConn = null;
        try {
            dbConn = getConnection();
            dbConn.setAutoCommit(false);

            eventsToSend = doUpdateService(dbConn, nodeLabel, event.getInterface(), event.getService(), action,
                    txNo);
        } catch (SQLException sqlE) {
            LOG.error("SQLException during handleUpdateService on database.", sqlE);
            throw new FailedOperationException(sqlE.getMessage());
        } finally {

            if (dbConn != null) {
                try {
                    if (eventsToSend != null) {
                        dbConn.commit();
                        for (Event e : eventsToSend) {
                            EventUtils.sendEvent(e, event.getUei(), txNo, isXmlRpcEnabled());
                        }
                    } else {
                        dbConn.rollback();
                    }
                } catch (SQLException ex) {
                    LOG.error("handleUpdateService: Exception thrown during commit/rollback: ", ex);
                    throw new FailedOperationException(ex.getMessage());
                } finally {
                    if (dbConn != null) {
                        try {
                            dbConn.close();
                        } catch (SQLException ex) {
                            LOG.error("handleUpdateService: Exception thrown during close: ", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * Returns true if and only an interface with the given ipaddr on a node
     * with the give label exists
     *
     * @param dbConn    a database connection
     * @param nodeLabel the label of the node the interface must reside on
     * @param ipaddr    the ip address the interface should have
     * @return true iff the interface exists
     * @throws java.sql.SQLException if a database error occurs
     */
    private boolean interfaceExists(Connection dbConn, String nodeLabel, String ipaddr) throws SQLException {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(SQL_QUERY_IPINTERFACE_EXIST);
            d.watch(stmt);

            stmt.setString(1, nodeLabel);
            stmt.setString(2, ipaddr);

            rs = stmt.executeQuery();
            d.watch(rs);
            return rs.next();
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Mark all the services associated with a given interface as deleted and
     * create service deleted events for each one that gets deleted
     *
     * @param dbConn the database connection
     * @param nodeId the node that interface resides on
     * @param ipAddr the ipAddress of the interface
     * @param txNo   a transaction number that can be associated with this deletion
     * @return a List of serviceDeleted events, one for each service marked
     * @throws java.sql.SQLException if a database error occurs
     */
    private List<Event> markAllServicesForInterfaceDeleted(Connection dbConn, String source, long nodeId,
            String ipAddr, long txNo) throws SQLException {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            List<Event> eventsToSend = new LinkedList<Event>();

            final String DB_FIND_SERVICES_FOR_INTERFACE = "SELECT DISTINCT service.serviceName FROM ifservices as ifservices, "
                    + "service as service WHERE ifservices.nodeID = ? and ifservices.ipAddr = ? and ifservices.status != 'D' and ifservices.serviceID ="
                    + " service.serviceID";
            stmt = dbConn.prepareStatement(DB_FIND_SERVICES_FOR_INTERFACE);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);
            rs = stmt.executeQuery();
            d.watch(rs);

            Set<String> services = new HashSet<String>();
            while (rs.next()) {
                String serviceName = rs.getString(1);
                LOG.debug("found service {} for ipAddr {} node {}", serviceName, ipAddr, nodeId);
                services.add(serviceName);
            }

            final String DB_MARK_SERVICES_FOR_INTERFACE = "UPDATE ifservices SET status = 'D' where ifservices.nodeID = ? and ifservices.ipAddr = ?";
            stmt = dbConn.prepareStatement(DB_MARK_SERVICES_FOR_INTERFACE);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);

            stmt.executeUpdate();

            for (String serviceName : services) {
                LOG.debug("creating event for service {} for ipAddr {} node {}", serviceName, ipAddr, nodeId);
                eventsToSend.add(EventUtils.createServiceDeletedEvent(source, nodeId, ipAddr, serviceName, txNo));
            }

            LOG.debug("markServicesDeleted: marked service deleted: {}/{}", nodeId, ipAddr);

            return eventsToSend;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Mark the given interface deleted
     *
     * @param dbConn the database connection
     * @param source the source for any events set
     * @param nodeId the id the interface resides on
     * @param ipAddr the ipAddress of the interface
     * @param txNo   a transaction no to associate with this deletion
     * @return a List containing an interfaceDeleted event for the interface if
     * it was actually marked
     * @throws java.sql.SQLException if a database error occurs
     */
    private List<Event> markInterfaceDeleted(Connection dbConn, String source, long nodeId, String ipAddr,
            long txNo) throws SQLException {
        return markInterfaceDeleted(dbConn, source, nodeId, ipAddr, -1, txNo);
    }

    /**
     * Mark the given interface deleted
     *
     * @param dbConn  the database connection
     * @param source  the source for any events set
     * @param nodeId  the id the interface resides on
     * @param ipAddr  the ipAddress of the interface
     * @param ifIndex the ifIndex of the interface
     * @param txNo    a transaction no to associate with this deletion
     * @return a List containing an interfaceDeleted event for the interface if
     * it was actually marked
     * @throws java.sql.SQLException if a database error occurs
     */
    private List<Event> markInterfaceDeleted(Connection dbConn, String source, long nodeId, String ipAddr,
            int ifIndex, long txNo) throws SQLException {
        final String DB_FIND_INTERFACE = "UPDATE ipinterface SET isManaged = 'D' WHERE nodeid = ? and ipAddr = ? and isManaged != 'D'";
        final String DB_FIND_SNMPINTERFACE = "UPDATE snmpinterface SET snmpcollect = 'D' WHERE nodeid = ? and snmpifindex = ? and snmpcollect != 'D'";
        PreparedStatement stmt = null;
        final DBUtils d = new DBUtils(getClass());
        int countip = 0;
        int countsnmp = 0;
        try {

            if (!EventUtils.isNonIpInterface(ipAddr)) {
                stmt = dbConn.prepareStatement(DB_FIND_INTERFACE);
                d.watch(stmt);
                stmt.setLong(1, nodeId);
                stmt.setString(2, ipAddr);
                countip = stmt.executeUpdate();
            }
            if (ifIndex > -1) {
                stmt = dbConn.prepareStatement(DB_FIND_SNMPINTERFACE);
                d.watch(stmt);
                stmt.setLong(1, nodeId);
                stmt.setInt(2, ifIndex);
                countsnmp = stmt.executeUpdate();
            }

            if (countip > 0) {
                LOG.debug("markInterfaceDeleted: marked ip interface deleted: node = {}, IP address = {}", nodeId,
                        ipAddr);
            }
            if (countsnmp > 0) {
                LOG.debug("markInterfaceDeleted: marked snmp interface deleted: node = {}, ifIndex = {}", nodeId,
                        ifIndex);
            }
            if (countip > 0 || countsnmp > 0) {
                return Collections.singletonList(
                        EventUtils.createInterfaceDeletedEvent(source, nodeId, ipAddr, ifIndex, txNo));
            } else {
                LOG.debug(
                        "markInterfaceDeleted: Interface not found: node = {}, with ip address {}, and ifIndex {}",
                        nodeId, (ipAddr != null ? ipAddr : "null"), (ifIndex > -1 ? ifIndex : "N/A"));
                return Collections.emptyList();
            }
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Marks all the interfaces and services for a given node deleted and
     * constructs events for each. The order of events is significant
     * representing the hierarchy, service events preceed the event for the
     * interface the service is on
     *
     * @param dbConn the database connection
     * @param source the source for use in the constructed events
     * @param nodeId the node whose interfaces and services are to be deleted
     * @param txNo   a transaction number to associate with this deletion
     * @return a List of events indicating which nodes and services have been
     * deleted
     * @throws java.sql.SQLException
     */
    private List<Event> markInterfacesAndServicesDeleted(Connection dbConn, String source, long nodeId, long txNo)
            throws SQLException {
        List<Event> eventsToSend = new LinkedList<Event>();

        final String DB_FIND_IFS_FOR_NODE = "SELECT ipinterface.ipaddr FROM ipinterface WHERE ipinterface.nodeid = ? and ipinterface.ismanaged != "
                + "'D'";

        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(DB_FIND_IFS_FOR_NODE);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            rs = stmt.executeQuery();
            d.watch(rs);

            Set<String> ipAddrs = new HashSet<String>();
            while (rs.next()) {
                String ipAddr = rs.getString(1);
                LOG.debug("found interface {} for node {}", ipAddr, nodeId);
                ipAddrs.add(ipAddr);
            }

            for (String ipAddr : ipAddrs) {
                LOG.debug("deleting interface {} for node {}", ipAddr, nodeId);
                eventsToSend.addAll(markAllServicesForInterfaceDeleted(dbConn, source, nodeId, ipAddr, txNo));
                eventsToSend.addAll(markInterfaceDeleted(dbConn, source, nodeId, ipAddr, txNo));
            }

            return eventsToSend;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Marks a node deleted and creates an event for it if necessary.
     *
     * @param dbConn the database connection
     * @param source the source to use for constructed events
     * @param nodeId the node to delete
     * @param txNo   a transaction number to associate with this deletion
     * @return a List containing the node deleted event if necessary
     * @throws java.sql.SQLException if a database error occurs
     */
    private List<Event> markNodeDeleted(Connection dbConn, String source, long nodeId, long txNo)
            throws SQLException {
        final String DB_FIND_INTERFACE = "UPDATE node SET nodeType = 'D' WHERE nodeid = ? and nodeType != 'D'";
        PreparedStatement stmt = null;

        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(DB_FIND_INTERFACE);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            int count = stmt.executeUpdate();

            LOG.debug("markServicesDeleted: marked service deleted: {}", nodeId);

            if (count > 0) {
                return Collections.singletonList(EventUtils.createNodeDeletedEvent(source, nodeId, txNo));
            } else {
                return Collections.emptyList();
            }
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Marks the service deleted in the database and returns a serviceDeleted
     * event for the service, if and only if the service existed
     *
     * @param dbConn  the database connection
     * @param source  the source for any events sent
     * @param nodeId  the node the service resides on
     * @param ipAddr  the interface the service resides on
     * @param service the name of the service
     * @param txNo    a transaction number to associate with this deletion
     * @return a List containing a service deleted event.
     * @throws java.sql.SQLException if an error occurs communicating with the database
     */
    private List<Event> markServiceDeleted(Connection dbConn, String source, long nodeId, String ipAddr,
            String service, long txNo) throws SQLException {
        PreparedStatement stmt = null;

        final String DB_MARK_SERVICE_DELETED = "UPDATE ifservices SET status='D' " + "FROM service "
                + "WHERE ifservices.serviceID = service.serviceID "
                + "AND ifservices.nodeID=? AND ifservices.ipAddr=? AND service.serviceName=?";

        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(DB_MARK_SERVICE_DELETED);
            d.watch(stmt);
            stmt.setLong(1, nodeId);
            stmt.setString(2, ipAddr);
            stmt.setString(3, service);
            int count = stmt.executeUpdate();

            LOG.debug("markServiceDeleted: marked service deleted: {}/{}/{}", nodeId, ipAddr, service);

            if (count > 0) {
                return Collections
                        .singletonList(EventUtils.createServiceDeletedEvent(source, nodeId, ipAddr, service, txNo));
            } else {
                return Collections.emptyList();
            }
        } finally {
            d.cleanUp();
        }
    }

    /**
     * Returns true if and only a node with the give label exists
     *
     * @param dbConn    a database connection
     * @param nodeLabel the label to check
     * @return true iff the node exists
     * @throws java.sql.SQLException if a database error occurs
     */
    private boolean nodeExists(Connection dbConn, String nodeLabel) throws SQLException {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(SQL_QUERY_NODE_EXIST);
            d.watch(stmt);
            stmt.setString(1, nodeLabel);

            rs = stmt.executeQuery();
            d.watch(rs);
            return rs.next();
        } finally {
            d.cleanUp();
        }
    }

    /**
     * JDBC Query to service map table.  This will soon be cleaned up with Hibernate/DAO code.
     *
     * @param dbConn
     * @param ipaddr
     * @param serviceName
     * @return
     * @throws java.sql.SQLException
     */
    private boolean serviceMappingExists(Connection dbConn, String ipaddr, String serviceName) throws SQLException {
        boolean mapExists;
        PreparedStatement stmt = null;
        // Verify if the specified service already exists on the
        // interface/service
        // mapping.
        final DBUtils d = new DBUtils(getClass());
        try {
            stmt = dbConn.prepareStatement(SQL_QUERY_SERVICE_MAPPING_EXIST);
            d.watch(stmt);

            stmt.setString(1, ipaddr);
            stmt.setString(2, serviceName);

            ResultSet rs = stmt.executeQuery();
            d.watch(rs);

            mapExists = rs.next();
            return mapExists;
        } finally {
            d.cleanUp();
        }
    }

    /**
     * JDBC Query using @param serviceName to determine if the serviceName is indeed as
     * service name in the service table.
     * <p/>
     * FIXME: This sucks:
     * 1) No transaction management
     * 2) Such a small table should be cached and accessed via a synchronized call.  Quit
     * going to the DB for such trivial information.  This is a performance killer.  Especially
     * since our current db factory (factories) use the synchonized DriverManager JDBC class.
     *
     * @param dbConn
     * @param serviceName
     * @return
     * @throws java.sql.SQLException
     * @throws org.opennms.netmgt.capsd.FailedOperationException
     */
    private int verifyServiceExists(Connection dbConn, String serviceName)
            throws SQLException, FailedOperationException {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        final DBUtils d = new DBUtils(getClass());
        try {
            // Retrieve the serviceId
            stmt = dbConn.prepareStatement(SQL_RETRIEVE_SERVICE_ID);
            d.watch(stmt);
            stmt.setString(1, serviceName);

            rs = stmt.executeQuery();
            d.watch(rs);
            int serviceId = -1;
            while (rs.next()) {
                LOG.debug("verifyServiceExists: retrieve serviceid for service {}", serviceName);
                serviceId = rs.getInt(1);
            }

            if (serviceId < 0) {
                LOG.debug("verifyServiceExists: the specified service: {} does not exist in the database.",
                        serviceName);
                throw new FailedOperationException("Invalid service: " + serviceName);
            }

            return serviceId;
        } finally {
            d.cleanUp();
        }
    }

    private void verifyInterfaceExists(Connection dbConn, String nodeLabel, String ipaddr)
            throws SQLException, FailedOperationException {
        if (!interfaceExists(dbConn, nodeLabel, ipaddr)) {
            throw new FailedOperationException(
                    "Interface " + ipaddr + " does not exist on a node with nodeLabel " + nodeLabel);
        }
    }

    /**
     * <p>setSuspectEventProcessorFactory</p>
     *
     * @param suspectEventProcessorFactory a {@link org.opennms.netmgt.capsd.SuspectEventProcessorFactory} object.
     */
    public void setSuspectEventProcessorFactory(SuspectEventProcessorFactory suspectEventProcessorFactory) {
        m_suspectEventProcessorFactory = suspectEventProcessorFactory;
    }

    /**
     * <p>setScheduler</p>
     *
     * @param scheduler a {@link org.opennms.netmgt.capsd.Scheduler} object.
     */
    public void setScheduler(Scheduler scheduler) {
        m_scheduler = scheduler;
    }

    /**
     * <p>setSuspectQueue</p>
     *
     * @param suspectQ a {@link java.util.concurrent.ExecutorService} object.
     */
    public void setSuspectQueue(ExecutorService suspectQ) {
        m_suspectQ = suspectQ;
    }

    /**
     * <p>setLocalServer</p>
     *
     * @param localServer a {@link String} object.
     */
    public void setLocalServer(String localServer) {
        m_localServer = localServer;
    }

    /**
     * <p>afterPropertiesSet</p>
     *
     * @throws Exception if any.
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.state(m_suspectEventProcessorFactory != null, "The suspectEventProcessor must be set");
        Assert.state(m_scheduler != null, "The schedule must be set");
        Assert.state(m_suspectQ != null, "The suspectQueue must be set");
        Assert.state(m_localServer != null, "The localServer must be set");
    }
}