All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.snmp4j.agent.mo.BufferedMOTableModel Maven / Gradle / Ivy

There is a newer version: 4.1.0
Show newest version
/*_############################################################################
  _##
  _##  SNMP4J-AgentX - BufferedMOTableModel.java
  _##
  _##  Copyright (C) 2005-2014  Frank Fock (SNMP4J.org)
  _##
  _##  This program is free software; you can redistribute it and/or modify
  _##  it under the terms of the GNU General Public License version 2 as
  _##  published by the Free Software Foundation.
  _##
  _##  This program 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 this program; if not, write to the Free Software
  _##  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  _##  MA  02110-1301  USA
  _##
  _##########################################################################*/

package org.snmp4j.agent.mo;

import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.Variable;

import java.util.*;

/**
 * The BufferedMOTableModel is a read-only table model that dynamically loads data from an
 * backend as needed. The internal buffer holds up to {@link #getMaxBufferSize()} rows fetched from the backend.
 * Rows are removed from the buffer either when they are older than {@link #getBufferTimeoutNanoSeconds()} or
 * if the maximum number of buffered rows is reached.
 * 

* The {@link org.snmp4j.agent.mo.MOTableRowFactory} is needed to create table rows from the * backend plain row data. *

* * @author Frank Fock * @since 2.2 */ public abstract class BufferedMOTableModel implements MOTableModel { protected final SortedMap> bufferedRows = Collections.synchronizedSortedMap(new TreeMap>()); protected final List> bufferedChunksList = Collections.synchronizedList(new LinkedList>()); protected BufferedMOTableRow firstRow; protected BufferedMOTableRow lastRow; protected MOTableRowFactory rowFactory; /** * The number of columns in this table model. */ protected int columnCount = 0; /** * The timeout for buffered rows in 10^-9 seconds. */ protected long bufferTimeoutNanoSeconds = 5L * SnmpConstants.MILLISECOND_TO_NANOSECOND * 1000L; /** * The number of rows that are fetched with {@link #fetchNextRows(org.snmp4j.smi.OID, int)} consecutively to * minimize the number of backend read operations. */ protected int chunkSize = 10; /** * The maximum number of rows in the buffer. While requests processing the actual upper bound of the buffer * might be the sum of {@link #getMaxBufferSize()} and {@link #getChunkSize()}. */ protected int maxBufferSize = 100; /** * The number of rows that were read from the buffer without backend access. */ protected long bufferHits = 0; /** * The number of rows that were read from the backend, because they were not found or not valid in the buffer. */ protected long bufferMisses = 0; /** * Creates a BufferedMOTableModel with the specified {@link org.snmp4j.agent.mo.MOTableRowFactory}. * @param rowFactory * the row factory to be used to create rows from backend data. */ protected BufferedMOTableModel(MOTableRowFactory rowFactory) { this.rowFactory = rowFactory; } /** * Sets the factory instance to be used for creating rows for this model. * * @param rowFactory * a MOTableRowFactory instance or null to * disable row creation. */ public > void setRowFactory(F rowFactory) { this.rowFactory = rowFactory; } /** * Gets the timeout nano-seconds for buffered rows. * @return * the number of nano-seconds after which an buffered row is invalidated and removed from the buffer. */ public long getBufferTimeoutNanoSeconds() { return bufferTimeoutNanoSeconds; } /** * Sets the timeout nano-seconds for buffered rows. * @param bufferTimeoutNanoSeconds * the number of nano-seconds after which an buffered row is invalidated and removed from the buffer. */ public void setBufferTimeoutNanoSeconds(long bufferTimeoutNanoSeconds) { this.bufferTimeoutNanoSeconds = bufferTimeoutNanoSeconds; } /** * Returns the maximum number of rows in the buffer. * @return * the size of the row buffer. */ public int getMaxBufferSize() { return maxBufferSize; } /** * Sets the maximum number of rows in the buffer. The default is * @param maxBufferSize * the size of the row buffer. */ public void setMaxBufferSize(int maxBufferSize) { this.maxBufferSize = maxBufferSize; } /** * Returns the chunk size for GETNEXT like buffer fetching with the {@link #fetchNextRows(org.snmp4j.smi.OID, int)} * operation. The default is 10. * @return * the chunk size for consecutive row fetching from the backend. */ public int getChunkSize() { return chunkSize; } /** * Sets the chunk size for GETNEXT like buffer fetching with the {@link #fetchNextRows(org.snmp4j.smi.OID, int)} * operation. The default is 10. * @param chunkSize * the chunk size for consecutive row fetching from the backend. */ public void setChunkSize(int chunkSize) { this.chunkSize = chunkSize; } @Override public int getColumnCount() { return columnCount; } @Override public abstract int getRowCount(); @Override public abstract boolean containsRow(OID index); @Override public R getRow(OID index) { return getRow(index, true); } /** * Gets a row from the internal buffer or the backend and puts it into the buffer if specified. * @param index * the index of the target row. * @param putRowIntoBuffer * if true then the fetched row (not from the buffer) will be put into the buffer. * @return * the row with the given index or null if it does not exist. */ protected R getRow(OID index, boolean putRowIntoBuffer) { BufferedMOTableRow row = getRowFromBuffer(index); if (row == null) { bufferMisses++; Variable[] rowValues = fetchRow(index); if (rowValues != null) { R r = rowFactory.createRow(index, rowValues); row = new BufferedMOTableRow(r); } else { row = new BufferedMOTableRow(null); } if (putRowIntoBuffer) { bufferedRows.put(index, row); } } else { bufferHits++; } return row.getBufferedRow(); } @Override public Iterator iterator() { return tailIterator(null); } @Override public Iterator tailIterator(OID lowerBound) { return new RowBufferIterator(lowerBound); } @Override public abstract OID lastIndex(); @Override public abstract OID firstIndex(); @Override public R firstRow() { BufferedMOTableRow firstBufferRow = getFirstBufferRow(); if (firstBufferRow != null) { return firstBufferRow.getBufferedRow(); } return null; } /** * Gets the first row from the buffer or the backend if not available from the buffer. * @return * the first row as buffered row. */ protected synchronized BufferedMOTableRow getFirstBufferRow() { if (firstRow != null) { if (isRowValid(firstRow.getLastRefresh())) { return firstRow; } else { bufferedRows.remove(firstRow.getIndex()); } } OID firstIndex = firstIndex(); if (firstIndex != null) { R row = getRow(firstIndex, true); if (row != null) { this.firstRow = getRowFromBuffer(firstIndex); } return firstRow; } return null; } @Override public R lastRow() { BufferedMOTableRow lastBufferRow = getLastBufferRow(); if (lastBufferRow != null) { return lastBufferRow.getBufferedRow(); } return null; } /** * Gets the last row from the buffer or the backend if not available from the buffer. * @return * the last row as buffered row. */ protected synchronized BufferedMOTableRow getLastBufferRow() { if (lastRow != null) { if (isRowValid(lastRow.getLastRefresh())) { return lastRow; } else { bufferedRows.remove(lastRow.getIndex()); } } OID lastIndex = lastIndex(); if (lastIndex != null) { // do not put into queue to avoid R row = getRow(lastIndex, false); if (row != null) { this.lastRow = getRowFromBuffer(lastIndex); } return lastRow; } return null; } /** * Tests if the given timestamp (in nano seconds as retrieved from {@link System#nanoTime()}) denotes * a valid row. * @param lastRefreshNanoTime * the last refresh timestamp from a buffered row. * @return * true if
(System.nanoTime() - lastRefreshNanoTime < bufferTimeoutNanoSeconds)
*/ protected boolean isRowValid(long lastRefreshNanoTime) { return (System.nanoTime() - lastRefreshNanoTime < bufferTimeoutNanoSeconds); } /** * Gets a row from the buffer. * @param index * the index of the target row. * @return * null if a row with the given index is not buffered (or no longer valid) and * the buffered row with that index if that row could be found and is still valid. */ protected BufferedMOTableRow getRowFromBuffer(OID index) { BufferedMOTableRow row = bufferedRows.get(index); if (row != null) { if (!isRowValid(row.getLastRefresh())) { bufferedRows.remove(index); return null; } return row; } return null; } /** * Fetches the specified row from the backend source. * @param index * the rows index OID value. * @return * the values of the fetched row or null if the row does not exists. */ protected abstract Variable[] fetchRow(OID index); /** * Fetches a list of rows from the backend source. * @param lowerBound * the lower bound index (inclusive) of the first row to return. * @param chunkSize * the maximum number of rows to return. Less rows may be returned even if there are more * available. * @return * a list of rows fetched from the backend. If the last element of the returned list is * null, no more rows are available at the source. */ protected abstract List fetchNextRows(OID lowerBound, int chunkSize); /** * Removes a row from the table. By default this method throws a {@link java.lang.UnsupportedOperationException}. * @param index * the index of the target row. * @return * the removed row and null if such a row does not exist. */ public R removeRow(OID index) { throw new UnsupportedOperationException(); } /** * The RowBufferIterator implements the iterator needed by the {@link org.snmp4j.agent.mo.MOTableModel} to * traverse the model's rows. */ protected class RowBufferIterator implements Iterator { private OID currentIndex; private BufferedMOTableRow nextRow; /** * Creates the iterator with the specified start row. * @param lowerBound * the lower bound index (inclusive) of the first row to return. If null is specified, the * first row will be returned by first call of {@link #next()}. */ public RowBufferIterator(OID lowerBound) { this.currentIndex = lowerBound; if (currentIndex == null) { nextRow = getFirstBufferRow(); } else { nextRow = fetchNextBufferRow(currentIndex, null, true); } } @Override public boolean hasNext() { return nextRow != null; } @Override public R next() { BufferedMOTableRow next = nextRow; if (next != null) { nextRow = fetchNextBufferRow(next.getIndex(), nextRow, false); } if (next == null) { throw new NoSuchElementException(); } return next.getBufferedRow(); } @Override public void remove() { removeRow(currentIndex); } protected BufferedMOTableRow fetchNextBufferRow(OID lowerBound, BufferedMOTableRow predecessor, boolean includeLowerBound) { if (predecessor != null) { BufferedMOTableRow next = predecessor.getNextRow(); if ((next != null) && (isRowValid(next.getLastRefresh()))) { while (next != null && next.getBufferedRow() == null) { next = (isRowValid(next.getLastRefresh())) ? next.getNextRow() : null; } if (next != null) { bufferHits++; return next; } } } OID lowerBoundInc = (includeLowerBound) ? lowerBound : lowerBound.successor(); synchronized (bufferedRows) { SortedMap> nextRows = bufferedRows.tailMap(lowerBoundInc); if (!nextRows.isEmpty()) { BufferedMOTableRow bufferedRow = null; for (BufferedMOTableRow row : nextRows.values()) { bufferedRow = row; if (bufferedRow.getIndex() != null) { break; } } if ((bufferedRow != null) && isRowValid(bufferedRow.getLastRefresh())) { if (includeLowerBound && bufferedRow.getIndex().equals(lowerBoundInc)) { bufferHits++; return bufferedRow; } else if (bufferedRow.getNextRow() != null) { // validate that there is no intermediate row in backend table List newBufferRows = fetchNextRows(lowerBoundInc, 1); if (newBufferRows != null && !newBufferRows.isEmpty() && newBufferRows.get(0).getIndex().equals(bufferedRow.getIndex())) { bufferMisses++; bufferedRow.setBufferedRow(newBufferRows.get(0)); bufferedRow.setLastRefresh(System.nanoTime()); return bufferedRow; } } } } } bufferMisses++; List newBufferRows = fetchNextRows(lowerBoundInc, chunkSize); return updateBuffer(newBufferRows, predecessor); } } /** * Updates the internal buffer with a list of consecutive rows. * @param newBufferRows * a list of rows which must be in lexicographic order (regarding their index values) and * without holes. * @return * the buffer row of the first row in the list. As buffer rows are linked by * {@link org.snmp4j.agent.mo.BufferedMOTableModel.BufferedMOTableRow#getNextRow()} * the buffered rows can be fully traversed by using that first row. */ protected BufferedMOTableRow updateBuffer(List newBufferRows, BufferedMOTableRow predecessor) { BufferedMOTableRow firstBufferRow = null; BufferedMOTableRow lastBufferRow = null; for (R row : newBufferRows) { BufferedMOTableRow bufferRow = new BufferedMOTableRow(row); bufferedRows.put(row.getIndex(), bufferRow); if (lastBufferRow != null) { lastBufferRow.setNextRow(bufferRow); } if (firstBufferRow == null) { firstBufferRow = bufferRow; if (predecessor != null) { predecessor.setNextRow(firstBufferRow); } } lastBufferRow = bufferRow; } if (firstBufferRow != null) { bufferedChunksList.add(firstBufferRow); } if (bufferedRows.size() > maxBufferSize) { cleanupBuffer(); } return firstBufferRow; } /** * Removes any rows from the buffer that exceed the buffer's size limit (FIFO). * @return * the number of rows actually removed from the buffer. */ protected synchronized int cleanupBuffer() { int removed = 0; int oversize = bufferedRows.size() - maxBufferSize; if (oversize > 0) { for (int i=oversize; (i>=0) && (!bufferedChunksList.isEmpty()); i--) { BufferedMOTableRow disposeRow = bufferedChunksList.remove(0); if (disposeRow != null) { bufferedRows.remove(disposeRow.getIndex()); removed++; for (disposeRow = disposeRow.getNextRow(); (disposeRow != null) && (--i>0); disposeRow = disposeRow.getNextRow()) { bufferedRows.remove(disposeRow.getIndex()); if ((!bufferedChunksList.isEmpty()) && disposeRow.getIndex().equals(bufferedChunksList.get(0).getIndex())) { bufferedChunksList.remove(0); } removed++; } } } } return removed; } /** * Returns the number of row accesses that were served by the buffer. * @return * the number of row accesses through the buffered. */ public long getBufferHits() { return bufferHits; } /** * The number of row accesses that could not be served from the buffer and thus * needed backend processing. * @return * the number of buffer misses. */ public long getBufferMisses() { return bufferMisses; } /** * Removes all rows from the internal buffer. This method is synchronized to ensure a consistent buffer. * It can be used to force a full buffer refresh. */ public synchronized void resetBuffer() { bufferedRows.clear(); bufferedChunksList.clear(); firstRow = null; lastRow = null; } @Override public String toString() { return "BufferedMOTableModel{" + "columnCount=" + columnCount + ", chunkSize=" + chunkSize + ", bufferTimeoutNanoSeconds=" + bufferTimeoutNanoSeconds + ", maxBufferSize=" + maxBufferSize + ", bufferHits=" + bufferHits + ", bufferMisses=" + bufferMisses + ", rowFactory=" + rowFactory + '}'; } /** * The BufferedMOTableRow is a wrapper class that holds additional information for the buffering. * It implements a forward linked list to direct successor rows for fast and easy traversal of the * buffer. * * @param * the payload row class which has to match the models row class as created by {@link #rowFactory}. */ protected class BufferedMOTableRow implements MOTableRow { private long lastRefresh; private R bufferedRow; private BufferedMOTableRow nextRow; /** * Creates a new buffer row for the specified payload row. It also sets the * {@link #getLastRefresh()} time to the current time. * @param bufferedRow * the buffered payload row. */ public BufferedMOTableRow(R bufferedRow) { this.bufferedRow = bufferedRow; lastRefresh = System.nanoTime(); } /** * Returns the buffered row. * @return * the buffered row. */ public R getBufferedRow() { return bufferedRow; } public void setBufferedRow(R bufferedRow) { this.bufferedRow = bufferedRow; } /** * Gets the immediately following row in the buffer. * @return * the immediate successor row and null if such a row * has not been buffered yet. */ public BufferedMOTableRow getNextRow() { return nextRow; } /** * Sets the immediately following row in the buffer. * @param nextRow * the immediate successor row and null if such a row * has not been buffered yet. */ public void setNextRow(BufferedMOTableRow nextRow) { this.nextRow = nextRow; } @Override public OID getIndex() { return (bufferedRow == null) ? null : bufferedRow.getIndex(); } @Override public Variable getValue(int column) { return bufferedRow.getValue(column); } @Override public MOTableRow getBaseRow() { return bufferedRow.getBaseRow(); } @Override public void setBaseRow(MOTableRow baseRow) { bufferedRow.setBaseRow(baseRow); } @Override public int size() { return bufferedRow.size(); } /** * Returns the time when this buffered row has been refreshed from the backend last. * @return * the last refresh time in nanoseconds. */ public long getLastRefresh() { return lastRefresh; } /** * Sets the time when this buffered row has been refreshed from the backend last. * @param lastRefresh * the last refresh time in nanoseconds. */ public void setLastRefresh(long lastRefresh) { this.lastRefresh = lastRefresh; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy