/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2009-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.alarmd.api.support; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.opennms.netmgt.alarmd.api.Preservable; /** * Based on Matt's queue implementation of event forwarding in opennmsd (OVAPI daemon) * When in forwarding state, uses Nagle's algorithm to batch up alarms for forwarding by the NBI. * * FIXME: Need to make sure the are reasonable defaults in the configuration just-in-case * the NBI implementations don't set the batch size, etc. * * @auther <a mailto:brozow@opennms.org>Matt Brozowski</a> * @author <a mailto:david@opennms.org>David Hustace</a> */ class AlarmQueue<T extends Preservable> { private static Logger log = Logger.getLogger(AlarmQueue.class); public abstract class State { abstract List<T> getAlarmsToForward() throws InterruptedException; abstract void forwardSuccessful(List<T> alarms); abstract void forwardFailed(List<T> alarms); protected void addToPreservedQueue(T a) { if (m_preservedQueue.size() >= m_maxPreservedAlarms) { m_nextBatch.clear(); m_preservedQueue.clear(); m_preservedQueue.offer(m_statusFactory.createSyncLostMessage()); } m_preservedQueue.offer(a); } protected void discardNonPreservedAlarms() { List<T> alarms = new ArrayList<T>(m_queue.size()); m_queue.drainTo(alarms); addPreservedToPreservedQueue(alarms); } protected void addPreservedToPreservedQueue(List<T> alarms) { for(Iterator<T> it = alarms.iterator(); it.hasNext(); ) { T a = it.next(); if (a.isPreserved()) { addToPreservedQueue(a); } } } protected void loadNextBatch() { m_preservedQueue.drainTo(m_nextBatch, m_maxBatchSize - m_nextBatch.size()); } } private final State FORWARDING = new State() { public List<T> getAlarmsToForward() throws InterruptedException { List<T> alarms = new ArrayList<T>(m_maxBatchSize); T a = m_queue.take(); alarms.add(a); m_queue.drainTo(alarms, m_maxBatchSize - alarms.size()); if (m_naglesDelay <= 0) { return alarms; } long now = System.currentTimeMillis(); long expirationTime = now + m_naglesDelay; while (alarms.size() < m_maxBatchSize && now < expirationTime) { T alarm = m_queue.poll(expirationTime - now, TimeUnit.MILLISECONDS); if (alarm != null) { alarms.add(alarm); m_queue.drainTo(alarms, m_maxBatchSize - alarms.size()); } now = System.currentTimeMillis(); } return alarms; } public void forwardSuccessful(List<T> alarms) { // no need to do anything here } public void forwardFailed(List<T> alarms) { addPreservedToPreservedQueue(alarms); if (!m_preservedQueue.isEmpty()) { setState(FAILING); } } public String toString() { return "FORWARDING"; } }; private final State FAILING = new State() { public List<T> getAlarmsToForward() { discardNonPreservedAlarms(); loadNextBatch(); //Matt, why are we returning a field? return m_nextBatch; } public void forwardFailed(List<T> alarms) { // do nothing we are already failing } public void forwardSuccessful(List<T> alarms) { m_nextBatch.clear(); if (m_preservedQueue.isEmpty()) { setState(FORWARDING); } else { setState(RECOVERING); } } public String toString() { return "FAILING"; } }; private final State RECOVERING = new State() { public List<T> getAlarmsToForward() { loadNextBatch(); return m_nextBatch; } public void forwardFailed(List<T> alarms) { setState(FAILING); } public void forwardSuccessful(List<T> alarms) { m_nextBatch.clear(); if (m_preservedQueue.isEmpty()) { setState(FORWARDING); } } public String toString() { return "RECOVERING"; } }; // operational parameters private int m_maxPreservedAlarms = 300000; private int m_maxBatchSize = 100; private long m_naglesDelay = 1000; // queue for all alarms to be forwarded private BlockingQueue<T> m_queue = new LinkedBlockingQueue<T>(); // queue for preserving alarms that are being saved during a forwarding failure private BlockingQueue<T> m_preservedQueue = new LinkedBlockingQueue<T>(); // a list of alarms that are pending due to a forwarding failure private List<T> m_nextBatch; // used to define the behavior of the getNext and forwardSuccessful and forwardFailed private State m_state = FORWARDING; // creates messages use to indicate that a connection failure has // occurred or queue has overflowed private StatusFactory<T> m_statusFactory; public AlarmQueue(StatusFactory<T> statusFactory) { m_statusFactory = statusFactory; } private void setState(State state) { m_state = state; log.debug("Setting state of AlarmQueue to "+m_state); } public long getNaglesDelay() { return m_naglesDelay; } public void setNaglesDelay(long delay) { m_naglesDelay = delay; } public int getMaxPreservedAlarms() { return m_maxPreservedAlarms; } public void setMaxPreservedAlarms(int maxPreservedAlarms) { m_maxPreservedAlarms = maxPreservedAlarms; } public int getMaxBatchSize() { return m_maxBatchSize; } public void setMaxBatchSize(int maxBatchSize) { m_maxBatchSize = maxBatchSize; } public void init() { m_nextBatch = new ArrayList<T>(m_maxBatchSize); } public void discard(T a) { // do nothing } public void accept(T a) { m_queue.offer(a); } public void preserve(T a) { a.setPreserved(true); m_queue.offer(a); } public List<T> getAlarmsToForward() throws InterruptedException { return m_state.getAlarmsToForward(); } public void forwardSuccessful(List<T> alarms) { m_state.forwardSuccessful(alarms); } public void forwardFailed(List<T> alarms) { m_state.forwardFailed(alarms); } }