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

org.snmp4j.util.TableUtils Maven / Gradle / Ivy

There is a newer version: 3.8.2
Show newest version
/*_############################################################################
  _## 
  _##  SNMP4J - TableUtils.java  
  _## 
  _##  Copyright (C) 2003-2018  Frank Fock and Jochen Katz (SNMP4J.org)
  _##  
  _##  Licensed under the Apache License, Version 2.0 (the "License");
  _##  you may not use this file except in compliance with the License.
  _##  You may obtain a copy of the License at
  _##  
  _##      http://www.apache.org/licenses/LICENSE-2.0
  _##  
  _##  Unless required by applicable law or agreed to in writing, software
  _##  distributed under the License is distributed on an "AS IS" BASIS,
  _##  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  _##  See the License for the specific language governing permissions and
  _##  limitations under the License.
  _##  
  _##########################################################################*/
package org.snmp4j.util;

import java.util.*;

import org.snmp4j.log.*;
import org.snmp4j.*;
import org.snmp4j.event.*;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;

import java.io.*;

/**
 * The TableUtils class provides utility functions to retrieve
 * SNMP tabular data.
 *
 * @author Frank Fock
 * @version 2.5.11
 * @since 1.0.2
 */
public class TableUtils extends AbstractSnmpUtility {

    private static final LogAdapter logger =
            LogFactory.getLogger(TableUtils.class);

    // RowStatus TC enumerated values
    public static final int ROWSTATUS_ACTIVE = 1;
    public static final int ROWSTATUS_NOTINSERVICE = 2;
    public static final int ROWSTATUS_NOTREADY = 3;
    public static final int ROWSTATUS_CREATEANDGO = 4;
    public static final int ROWSTATUS_CREATEANDWAIT = 5;
    public static final int ROWSTATUS_DESTROY = 6;

    private int maxNumOfRowsPerPDU = 10;
    private int maxNumColumnsPerPDU = 10;

    private boolean sendColumnPDUsMultiThreaded;
    private boolean checkLexicographicOrdering = true;
    private int ignoreMaxLexicographicRowOrderingErrors = 3;

    public enum SparseTableMode {
        sparseTable,
        denseTableDropIncompleteRows,
        denseTableDoubleCheckIncompleteRows
    }

    /**
     * Creates a TableUtils instance. The created instance is thread
     * safe as long as the supplied Session and PDUFactory
     * are thread safe.
     *
     * @param snmpSession
     *         a SNMP Session instance.
     * @param pduFactory
     *         a PDUFactory instance that creates the PDU that are used
     *         by this instance to retrieve table data using GETBULK/GETNEXT
     *         operations.
     */
    public TableUtils(Session snmpSession, PDUFactory pduFactory) {
        super(snmpSession, pduFactory);
    }

    /**
     * Gets synchronously SNMP tabular data from one or more tables.
     * The data is returned row-by-row as a list of {@link TableEvent} instances.
     * Each instance represents a row (or an error condition). Besides the
     * target agent, the OIDs of the columnar objects have to be specified
     * for which instances should be retrieved. With a lower bound index and
     * an upper bound index, the result set can be narrowed to improve
     * performance. This method can be executed concurrently by multiple threads.
     *
     * @param target
     *         a Target instance.
     * @param columnOIDs
     *         an array of OIDs of the columnar objects whose instances should be
     *         retrieved. The columnar objects may belong to different tables.
     *         Typically they belong to tables that share a common index or sub-index
     *         prefix. Note: The result of this method is not defined if instance OIDs
     *         are supplied in this array!
     * @param lowerBoundIndex
     *         an optional parameter that specifies the lower bound index.
     *         If not null, all returned rows have an index greater than
     *         lowerBoundIndex.
     * @param upperBoundIndex
     *         an optional parameter that specifies the upper bound index.
     *         If not null, all returned rows have an index less or equal
     *         than upperBoundIndex.
     *
     * @return a List of {@link TableEvent} instances. Each instance
     * represents successfully retrieved row or an error condition. Error
     * conditions (any status other than {@link TableEvent#STATUS_OK})
     * may only appear at the last element of the list.
     */
    public List getTable(Target target, OID[] columnOIDs, OID lowerBoundIndex, OID upperBoundIndex) {

        if ((columnOIDs == null) || (columnOIDs.length == 0)) {
            throw new IllegalArgumentException("No column OIDs specified");
        }
        InternalTableListener listener = new InternalTableListener();
        TableRequest req = createTableRequest(target, columnOIDs, listener,
                null,
                lowerBoundIndex,
                upperBoundIndex, SparseTableMode.sparseTable);
        synchronized (listener) {
            if (req.sendNextChunk()) {
                try {
                    while (!listener.isFinished()) {
                        listener.wait();
                    }
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        return listener.getRows();
    }

    protected TableRequest createTableRequest(Target target, OID[] columnOIDs, TableListener listener,
                                              Object userObject, OID lowerBoundIndex, OID upperBoundIndex,
                                              SparseTableMode sparseTableMode) {
        return new TableRequest(target, columnOIDs, listener,
                userObject, lowerBoundIndex, upperBoundIndex, sparseTableMode);
    }

    /**
     * Gets SNMP tabular data from one or more tables. The data is returned
     * asynchronously row-by-row through a supplied callback. Besides the
     * target agent, the OIDs of the columnar objects have to be specified
     * for which instances should be retrieved. With a lower bound index and
     * an upper bound index, the result set can be narrowed to improve
     * performance.
     * 

* This method may call the {@link TableListener#finished} method before * it returns. If you want to synchronize the main thread with the * finishing of the table retrieval, follow this pattern: *

     *      synchronized (this) {
     *         TableListener myListener = ... {
     *            private boolean finished;
     *
     *            public boolean isFinished() {
     *              return finished;
     *            }
     *
     *            public void finished(TableEvent event) {
     *               ..
     *               finished = true;
     *               synchronized (event.getUserObject()) {
     *                  event.getUserObject().notify();
     *               }
     *            }
     *         };
     *         tableUtil.getTable(..,..,myListener,this,..,..);
     *         while (!myListener.isFinished()) {
     *           wait();
     *         }
     *      }
     * 
* * @param target * a Target instance. * @param columnOIDs * an array of OIDs of the columnar objects whose instances should be * retrieved. The columnar objects may belong to different tables. * Typically they belong to tables that share a common index or sub-index * prefix. Note: The result of this method is not defined if instance OIDs * are supplied in this array! * @param listener * a TableListener that is called with {@link TableEvent} * objects when an error occured, new rows have been retrieved, or when * the table has been retrieved completely. * @param userObject * an user object that is transparently supplied to the above call back. * @param lowerBoundIndex * an optional parameter that specifies the lower bound index. * If not null, all returned rows have an index greater than * lowerBoundIndex. * @param upperBoundIndex * an optional parameter that specifies the upper bound index. * If not null, all returned rows have an index less or equal * than upperBoundIndex. * @param sparseTableMode * defines how rows with non existing column values should be handled. * Such rows can occur when new rows are being created or rows are removed * from an agent while it is being */ public void getTable(Target target, OID[] columnOIDs, TableListener listener, Object userObject, OID lowerBoundIndex, OID upperBoundIndex, SparseTableMode sparseTableMode) { if ((columnOIDs == null) || (columnOIDs.length == 0)) { throw new IllegalArgumentException("No column OIDs specified"); } TableRequest req = new TableRequest(target, columnOIDs, listener, userObject, lowerBoundIndex, upperBoundIndex, sparseTableMode); boolean sendMore = req.sendNextChunk(); while (sendColumnPDUsMultiThreaded && sendMore) { sendMore = req.sendNextChunk(); } } /** * Gets SNMP tabular data from one or more tables. The data is returned * asynchronously row-by-row through a supplied callback. Besides the * target agent, the OIDs of the columnar objects have to be specified * for which instances should be retrieved. With a lower bound index and * an upper bound index, the result set can be narrowed to improve * performance. *

* This method may call the {@link TableListener#finished} method before * it returns. If you want to synchronize the main thread with the * finishing of the table retrieval, follow this pattern: *

     *      synchronized (this) {
     *         TableListener myListener = ... {
     *            private boolean finished;
     *
     *            public boolean isFinished() {
     *              return finished;
     *            }
     *
     *            public void finished(TableEvent event) {
     *               ..
     *               finished = true;
     *               synchronized (event.getUserObject()) {
     *                  event.getUserObject().notify();
     *               }
     *            }
     *         };
     *         tableUtil.getTable(..,..,myListener,this,..,..);
     *         while (!myListener.isFinished()) {
     *           wait();
     *         }
     *      }
     * 
* * @param target * a Target instance. * @param columnOIDs * an array of OIDs of the columnar objects whose instances should be * retrieved. The columnar objects may belong to different tables. * Typically they belong to tables that share a common index or sub-index * prefix. Note: The result of this method is not defined if instance OIDs * are supplied in this array! * @param listener * a TableListener that is called with {@link TableEvent} * objects when an error occured, new rows have been retrieved, or when * the table has been retrieved completely. * @param userObject * an user object that is transparently supplied to the above call back. * @param lowerBoundIndex * an optional parameter that specifies the lower bound index. * If not null, all returned rows have an index greater than * lowerBoundIndex. * @param upperBoundIndex * an optional parameter that specifies the upper bound index. * If not null, all returned rows have an index less or equal * than upperBoundIndex. * @since 1.5 */ public void getTable(Target target, OID[] columnOIDs, TableListener listener, Object userObject, OID lowerBoundIndex, OID upperBoundIndex) { getTable(target, columnOIDs, listener, userObject, lowerBoundIndex, upperBoundIndex, SparseTableMode.sparseTable); } /** * Gets SNMP tabular data from one or more tables. The data is returned * asynchronously row-by-row through a supplied callback. Besides the * target agent, the OIDs of the columnar objects have to be specified * for which instances should be retrieved. With a lower bound index and * an upper bound index, the result set can be narrowed to improve * performance. *

* This implementation must not be used with sparse tables, because it * is optimized for dense tables and will not return correct results for * sparse tables. *

* Rows that appear or disappear while is being retrieved, are dropped and * will be not returned partially (see {@link SparseTableMode#denseTableDropIncompleteRows}). * * @param target * a Target instance. * @param columnOIDs * an array of OIDs of the columnar objects whose instances should be * retrieved. The columnar objects may belong to different tables. * Typically they belong to tables that share a common index or sub-index * prefix. Note: The result of this method is not defined if instance OIDs * are supplied in this array! * @param listener * a TableListener that is called with {@link TableEvent} * objects when an error occurred, new rows have been retrieved, or when * the table has been retrieved completely. * @param userObject * an user object that is transparently supplied to the above call back. * @param lowerBoundIndex * an optional parameter that specifies the lower bound index. * If not null, all returned rows have an index greater than * lowerBoundIndex. * @param upperBoundIndex * an optional parameter that specifies the upper bound index. * If not null, all returned rows have an index less or equal * than lowerBoundIndex. * @since 3.0 */ public void getDenseTable(Target target, OID[] columnOIDs, TableListener listener, Object userObject, OID lowerBoundIndex, OID upperBoundIndex) { if ((columnOIDs == null) || (columnOIDs.length == 0)) { throw new IllegalArgumentException("No column OIDs specified"); } TableRequest req = new TableRequest(target, columnOIDs, listener, userObject, lowerBoundIndex, upperBoundIndex, SparseTableMode.denseTableDropIncompleteRows); req.sendNextChunk(); } /** * Gets the maximum number of rows that will be retrieved per SNMP GETBULK * request. * * @return an integer greater than zero that specifies the maximum number of rows * to retrieve per SNMP GETBULK operation. */ public int getMaxNumRowsPerPDU() { return maxNumOfRowsPerPDU; } /** * Sets the maximum number of rows that will be retrieved per SNMP GETBULK * request. The default is 10. * * @param numberOfRowsPerChunk * an integer greater than zero that specifies the maximum number of rows * to retrieve per SNMP GETBULK operation. */ public void setMaxNumRowsPerPDU(int numberOfRowsPerChunk) { if (numberOfRowsPerChunk < 1) { throw new IllegalArgumentException( "The number of rows per PDU must be > 0"); } this.maxNumOfRowsPerPDU = numberOfRowsPerChunk; } /** * Gets the maximum number of columns that will be retrieved per SNMP GETNEXT * or GETBULK request. * * @return an integer greater than zero that specifies the maximum columns of rows * to retrieve per SNMP GETNEXT or GETBULK operation. */ public int getMaxNumColumnsPerPDU() { return maxNumColumnsPerPDU; } /** * Sets the maximum number of columns that will be retrieved per SNMP GETNEXT * or GETBULK request. The default is 10. * * @param numberOfColumnsPerChunk * an integer greater than zero that specifies the maximum columns of rows * to retrieve per SNMP GETNEXT or GETBULK operation. */ public void setMaxNumColumnsPerPDU(int numberOfColumnsPerChunk) { if (numberOfColumnsPerChunk < 1) { throw new IllegalArgumentException( "The number of columns per PDU must be > 0"); } this.maxNumColumnsPerPDU = numberOfColumnsPerChunk; } public boolean isSendColumnPDUsMultiThreaded() { return sendColumnPDUsMultiThreaded; } /** * Enable multi-threaded column PDU sending. If set to {@code true} and if the {@link #maxNumColumnsPerPDU} value * is less than the number of columns to be retrieved in a {@link TableUtils} request, then the requests for the * columns will be splitted in two or more columns and the requests will be send to the agent concurrently without * waiting for the response of the first/previous PDU. By default, this is disabled. * * @param sendColumnPDUsMultiThreaded * if {@code true}, multi-threaded processing of column PDUs is enabled, otherwise only a single request * will be sent to the agent on behalf a {@link #getTable(Target, OID[], OID, OID)} or * {@link #getTable(Target, OID[], TableListener, Object, OID, OID)}. */ public void setSendColumnPDUsMultiThreaded(boolean sendColumnPDUsMultiThreaded) { this.sendColumnPDUsMultiThreaded = sendColumnPDUsMultiThreaded; } /** * Indicates whether a single request on behalf of {@link #getTable(Target, OID[], OID, OID)} or * {@link #getTable(Target, OID[], TableListener, Object, OID, OID)} is sent to the agent or not. * * @return {@code false} if single requests are sent, {@code true} if more than a single request may be sent at a * time. */ public boolean isCheckLexicographicOrdering() { return checkLexicographicOrdering; } /** * Gets the maximum number of rows with wrong lexicographic ordering whicb will be return on a table retrieval * with {@link #isCheckLexicographicOrdering()} set to {@code true}. * * @return the number of ignored row ordering errors. * @since 2.5.11 */ public int getIgnoreMaxLexicographicRowOrderingErrors() { return ignoreMaxLexicographicRowOrderingErrors; } /** * Sets the maximum number of rows that will be returned with status {@link TableEvent#STATUS_WRONG_ORDER} before * the table retrieval will be stopped. If this value is set to zero and lexicographic ordering check is enabled by * {@link #setCheckLexicographicOrdering(boolean)}, then table retrieval finishes immediately when the error is * detected. Otherwise retrieval continues until the maximum errors are detected and then the row cache will be * returned too, although it may contain already incomplete rows based on correctly or incorrectly (!) ordered rows. * The default value is three. That means, even if the ordering error occurs at the end of the table and * * @param ignoreMaxLexicographicRowOrderingErrors * the maximum numbers of rows with lexicographic ordering error to be returned before finishing table * retrieve automatically. Setting this value has no effect if {@link #isCheckLexicographicOrdering()} * is {@code false}. * * @since 2.5.11 */ public void setIgnoreMaxLexicographicRowOrderingErrors(int ignoreMaxLexicographicRowOrderingErrors) { this.ignoreMaxLexicographicRowOrderingErrors = ignoreMaxLexicographicRowOrderingErrors; } /** * Enables or disables lexicographic ordering checks. By default, those checks are enabled, because otherwise * with agents, that do not implement correct lexicographic ordering, endless looping could occur. * * @param checkLexicographicOrdering * {@code false} to disable checks which could increase performance. * * @since 2.5.10 */ public void setCheckLexicographicOrdering(boolean checkLexicographicOrdering) { this.checkLexicographicOrdering = checkLexicographicOrdering; } protected class ColumnsOfRequest { private List columnIDs; private int requestSerial; private LastReceived lastReceived; public ColumnsOfRequest(List columnIDs, int requestSerial, LastReceived lastReceived) { this.columnIDs = columnIDs; this.requestSerial = requestSerial; this.lastReceived = lastReceived; } public List getColumnIDs() { return columnIDs; } public int getRequestSerial() { return requestSerial; } public LastReceived getLastReceived() { return lastReceived; } } public class TableRequest implements ResponseListener { Target target; OID[] columnOIDs; TableListener listener; Object userObject; OID lowerBoundIndex; OID upperBoundIndex; private int sent = 0; private boolean anyMatch = false; private List lastSent = null; private LinkedList rowCache = new LinkedList(); protected LastReceived lastReceived; private int requestSerial = Integer.MIN_VALUE; private List requestSerialsPending = Collections.synchronizedList(new LinkedList()); private int numLexicographicErrors = 0; volatile boolean finished = false; private SparseTableMode sparseTableMode; public TableRequest(Target target, OID[] columnOIDs, TableListener listener, Object userObject, OID lowerBoundIndex, OID upperBoundIndex, SparseTableMode sparseTableMode) { this.target = target; this.columnOIDs = columnOIDs; this.listener = listener; this.userObject = userObject; this.lastReceived = new LastReceived(Arrays.asList(columnOIDs)); this.upperBoundIndex = upperBoundIndex; this.lowerBoundIndex = lowerBoundIndex; if (lowerBoundIndex != null) { for (int i = 0; i < lastReceived.size(); i++) { OID oid = new OID((lastReceived.get(i))); oid.append(lowerBoundIndex); lastReceived.set(i, oid); } } this.sparseTableMode = sparseTableMode; } public SparseTableMode getSparseTableMode() { return sparseTableMode; } /** * Gets the number of lexicographic errors that occurred during request processing. Any errors occurred on the same * row will be count as one error. * * @return the number of rows returned by the agent in wrong lexicographic order (i.e. not strictly ascending). * @since 2.5.11 */ public int getNumLexicographicErrors() { return numLexicographicErrors; } public boolean sendNextChunk() { if (sent >= lastReceived.size()) { return false; } PDU pdu = pduFactory.createPDU(target); if (target.getVersion() == SnmpConstants.version1) { pdu.setType(PDU.GETNEXT); } else if (pdu.getType() != PDU.GETNEXT) { pdu.setType(PDU.GETBULK); } int sz = Math.min(lastReceived.size() - sent, maxNumColumnsPerPDU); if (pdu.getType() == PDU.GETBULK) { if (maxNumOfRowsPerPDU > 0) { pdu.setMaxRepetitions(maxNumOfRowsPerPDU); pdu.setNonRepeaters(0); } else { pdu.setNonRepeaters(sz); pdu.setMaxRepetitions(0); } } lastSent = new ArrayList<>(sz + 1); List sentColumns = new ArrayList(sz); int chunkSize = 0; for (int i = sent; i < sent + sz; i++) { OID col = lastReceived.get(i); // only sent columns that are not complete yet if (col.startsWith(columnOIDs[i])) { VariableBinding vb = new VariableBinding(col); pdu.add(vb); if (pdu.getBERLength() > target.getMaxSizeRequestPDU()) { pdu.trim(); break; } else { lastSent.add(lastReceived.get(i)); chunkSize++; } sentColumns.add(i); } else { chunkSize++; } } try { sent += chunkSize; if (pdu.size() == 0) { return false; } ColumnsOfRequest columnsOfRequest = new ColumnsOfRequest(sentColumns, requestSerial++, isCheckLexicographicOrdering() ? new LastReceived(lastReceived) : null); sendRequest(pdu, target, columnsOfRequest); } catch (Exception ex) { logger.error(ex); if (logger.isDebugEnabled()) { ex.printStackTrace(); } listener.finished(new TableEvent(this, userObject, ex)); return false; } return true; } protected void sendRequest(PDU pdu, Target target, ColumnsOfRequest sendColumns) throws IOException { requestSerialsPending.add(sendColumns.getRequestSerial()); session.send(pdu, target, sendColumns, this); } protected synchronized boolean removePending(int requestSerial) { boolean inOrder = true; for (Iterator it = requestSerialsPending.iterator(); it.hasNext(); ) { int pendingRequestSerial = it.next(); if (pendingRequestSerial == requestSerial) { it.remove(); } else { inOrder = false; } } return inOrder; } @SuppressWarnings("unchecked") public void onResponse(ResponseEvent event) { // Do not forget to cancel the asynchronous request! ;-) session.cancel(event.getRequest(), this); if (finished) { return; } synchronized (this) { if (checkResponse(event)) { boolean anyMatchInChunk = false; ColumnsOfRequest colsOfRequest = (ColumnsOfRequest) event.getUserObject(); boolean receivedInOrder = removePending(colsOfRequest.getRequestSerial()); PDU request = event.getRequest(); PDU response = event.getResponse(); int cols = request.size(); int rows = response.size() / cols; OID lastMinIndex = null; for (int r = 0; r < rows; r++) { Row row = null; anyMatchInChunk = false; for (int c = 0; c < cols; c++) { int pos = colsOfRequest.getColumnIDs().get(c); VariableBinding vb = response.get(r * cols + c); if (vb.isException()) { continue; } OID id = vb.getOid(); OID col = columnOIDs[pos]; if (id.startsWith(col)) { OID index = new OID(id.getValue(), col.size(), id.size() - col.size()); if ((upperBoundIndex != null) && (index.compareTo(upperBoundIndex) > 0)) { continue; } if ((lastMinIndex == null) || (index.compareTo(lastMinIndex) < 0)) { lastMinIndex = index; } anyMatchInChunk = true; if ((row == null) || (!row.getRowIndex().equals(index))) { row = null; for (ListIterator it = rowCache.listIterator(rowCache.size()); it.hasPrevious(); ) { Row lastRow = it.previous(); int compareResult = index.compareTo(lastRow.getRowIndex()); if (compareResult == 0) { row = lastRow; break; } else if (compareResult > 0) { break; } } } if (row == null) { row = new Row(index); if (rowCache.size() == 0) { rowCache.add(row); } else if ((rowCache.getFirst()).getRowIndex().compareTo( index) >= 0) { rowCache.addFirst(row); } else { for (ListIterator it = rowCache.listIterator(rowCache.size()); it.hasPrevious(); ) { Row lastRow = it.previous(); if (index.compareTo(lastRow.index) >= 0) { it.set(row); it.add(lastRow); break; } } } } row.setNumComplete(pos); if (pos < row.getNumComplete()) { row.set(pos, vb); } else { row.add(vb); } if (isCheckLexicographicOrdering()) { OID requested = event.getRequest().get(c).getOid(); if (id.compareTo(requested) <= 0) { if (!row.orderError) { row.orderError = true; } } else if (colsOfRequest.lastReceived != null) { try { Row baseRow = colsOfRequest.lastReceived.getColumnInfos().get(pos).getBasedOn(); if (baseRow != null && baseRow.isOrderError()) { row.orderError = true; } } catch (Exception ex) { // ignore } } // check if current row is based on a wrong order row and mark it too } lastReceived.set(pos, vb.getOid(), row); } else { lastReceived.set(pos, vb.getOid()); } } } anyMatch |= anyMatchInChunk; Row firstCacheRow; while (((firstCacheRow = (rowCache.isEmpty()) ? null : rowCache.getFirst()) != null) && (firstCacheRow.getNumComplete() == columnOIDs.length) && // make sure, row is not prematurely deemed complete (receivedInOrder) && ((sparseTableMode == SparseTableMode.sparseTable) || (!firstCacheRow.hasNullValues())) && ((lastMinIndex == null) || ((rowCache.getFirst()).getRowIndex().compareTo(lastMinIndex) < 0))) { TableEvent tableEvent = getNextTableEvent(); if (isCheckLexicographicOrdering() && (tableEvent != null && tableEvent.status == TableEvent.STATUS_WRONG_ORDER && numLexicographicErrors >= ignoreMaxLexicographicRowOrderingErrors)) { if (ignoreMaxLexicographicRowOrderingErrors > 0) { listener.next(tableEvent); } emptyCache(); finished = true; listener.finished(new TableEvent(this, userObject, TableEvent.STATUS_WRONG_ORDER)); return; } else if (tableEvent == null || !listener.next(tableEvent)) { emptyCache(); finished = true; listener.finished(new TableEvent(this, userObject, getTableStatus())); return; } } if (sparseTableMode == SparseTableMode.denseTableDoubleCheckIncompleteRows && firstCacheRow != null && firstCacheRow.hasNullValues()) { ResponseListener responseListener = new ResponseListener() { @Override public void onResponse(ResponseEvent event) { Row cachedRow = (Row) event.getUserObject(); if (event.getResponse() != null && event.getResponse().getErrorStatus() == PDU.noError) { for (VariableBinding vb : event.getResponse().getVariableBindings()) { if (!vb.isException()) { for (int i=0; i= maxNumColumnsPerPDU) { pdu = sendGetPDU(firstCacheRow, responseListener, pdu); } } if (pdu.size() > 0) { sendGetPDU(firstCacheRow, responseListener, pdu); } } if (receivedInOrder) { boolean sentChunk; if (!(sentChunk = sendNextChunk())) { if (anyMatch) { sent = 0; anyMatch = false; sentChunk = sendNextChunk(); } if (!sentChunk) { emptyCache(); finished = true; listener.finished(new TableEvent(this, userObject, getTableStatus())); } } } } } } protected PDU sendGetPDU(Row firstCacheRow, ResponseListener responseListener, PDU pdu) { try { session.send(pdu, target, firstCacheRow, responseListener); pdu = pduFactory.createPDU(target); pdu.setType(PDU.GET); } catch (IOException e) { logger.error(e); } return pdu; } protected int getTableStatus() { return numLexicographicErrors > 0 ? TableEvent.STATUS_WRONG_ORDER : TableEvent.STATUS_OK; } protected boolean checkResponse(ResponseEvent event) { if (event.getError() != null) { finished = true; emptyCache(); listener.finished(new TableEvent(this, userObject, event.getError())); } else if (event.getResponse() == null) { finished = true; // timeout emptyCache(); listener.finished(new TableEvent(this, userObject, TableEvent.STATUS_TIMEOUT)); } else if (event.getResponse().getType() == PDU.REPORT) { finished = true; emptyCache(); listener.finished(new TableEvent(this, userObject, event.getResponse())); } else if (event.getResponse().getErrorStatus() != PDU.noError) { finished = true; emptyCache(); listener.finished(new TableEvent(this, userObject, event.getResponse().getErrorStatus())); } else { return true; } return false; } private void emptyCache() { while (rowCache.size() > 0) { TableEvent tableEvent = getNextTableEvent(); if (tableEvent == null) { continue; } if (tableEvent.getStatus() != TableEvent.STATUS_WRONG_ORDER || numLexicographicErrors <= ignoreMaxLexicographicRowOrderingErrors) { if (!listener.next(tableEvent)) { break; } } } } private TableEvent getNextTableEvent() { if (rowCache.isEmpty()) { return null; } Row r = rowCache.removeFirst(); r.setNumComplete(columnOIDs.length); while (sparseTableMode != SparseTableMode.sparseTable && r.hasNullValues() && !rowCache.isEmpty()) { if (logger.isDebugEnabled()) { logger.debug("TableUtils dropped incomplete row "+r+" because mode is "+ SparseTableMode.denseTableDropIncompleteRows); } try { r = rowCache.removeFirst(); r.setNumComplete(columnOIDs.length); } catch (NoSuchElementException nsee) { // ignore } } VariableBinding[] vbs = new VariableBinding[r.size()]; vbs = r.toArray(vbs); TableEvent tableEvent = new TableEvent(this, userObject, r.getRowIndex(), vbs); if (r.isOrderError()) { tableEvent.status = TableEvent.STATUS_WRONG_ORDER; numLexicographicErrors++; } return tableEvent; } public Row getRow(OID index) { for (ListIterator it = rowCache.listIterator(rowCache.size() + 1); it.hasPrevious(); ) { Row r = it.previous(); if (index.equals(r.getRowIndex())) { return r; } } return null; } } /** * The DenseTableRequest extends TableRequest to implement a * faster table retrieval than the original. Caution: * This version does not correctly retrieve sparse tables! * * @author Frank Fock * @since 1.5 */ protected class DenseTableRequest extends TableRequest { protected DenseTableRequest(Target target, OID[] columnOIDs, TableListener listener, Object userObject, OID lowerBoundIndex, OID upperBoundIndex) { super(target, columnOIDs, listener, userObject, lowerBoundIndex, upperBoundIndex, SparseTableMode.denseTableDropIncompleteRows); } public synchronized void onResponse(ResponseEvent event) { // Do not forget to cancel the asynchronous request! ;-) session.cancel(event.getRequest(), this); if (finished) { return; } if (checkResponse(event)) { int startCol = (Integer) event.getUserObject(); PDU request = event.getRequest(); PDU response = event.getResponse(); int cols = request.size(); int rows = response.size() / cols; OID lastMinIndex = null; for (int r = 0; r < rows; r++) { Row row = null; for (int c = 0; c < request.size(); c++) { int pos = startCol + c; VariableBinding vb = response.get(r * cols + c); if (vb.isException()) { continue; } OID id = vb.getOid(); OID col = columnOIDs[pos]; if (id.startsWith(col)) { OID index = new OID(id.getValue(), col.size(), id.size() - col.size()); if ((upperBoundIndex != null) && (index.compareTo(upperBoundIndex) > 0)) { continue; } if ((lastMinIndex == null) || (index.compareTo(lastMinIndex) < 0)) { lastMinIndex = index; } if (row == null) { row = new Row(index); } row.setNumComplete(pos); if (pos < row.getNumComplete()) { row.set(pos, vb); } else { row.add(vb); } lastReceived.set(pos, vb.getOid()); } } if (row != null) { if (!listener.next(new TableEvent(this, userObject, row.getRowIndex(), row.toArray(new VariableBinding[row.size()])))) { finished = true; listener.finished(new TableEvent(this, userObject)); return; } } } if (!sendNextChunk()) { finished = true; listener.finished(new TableEvent(this, userObject)); } } } } /** * Creates a SNMP table row for a table that supports the RowStatus * mechanism for row creation. * * @param target * the Target SNMP entity for the operation. * @param rowStatusColumnOID * the column OID of the RowStatus column (without any instance identifier). * @param rowIndex * the OID denoting the index of the table row to create. * @param values * the values of columns to set in the row. If values is * null the row is created via the tripple mode row creation * mechanism (RowStatus is set to createAndWait). Otherwise, each variable * binding has to contain the OID of the columnar object ID (without any * instance identifier) and its value. On return, the variable bindings * will be modified so that the variable binding OIDs will contain the * instance OIDs of the respective columns (thus column OID + rowIndex). * @param type of the target {@link Address} * * @return ResponseEvent * the ResponseEvent instance returned by the SNMP session on response * of the internally sent SET request. If null, an IO * exception has occurred. Otherwise, if the response PDU is * null a timeout has occurred. Otherwise, check the error * status for {@link SnmpConstants#SNMP_ERROR_SUCCESS} to verify that the * row creation was successful. * @since 1.6 */ public ResponseEvent createRow(Target target, OID rowStatusColumnOID, OID rowIndex, VariableBinding[] values) { PDU pdu = pduFactory.createPDU(target); OID rowStatusID = new OID(rowStatusColumnOID); rowStatusID.append(rowIndex); VariableBinding rowStatus = new VariableBinding(rowStatusID); if (values != null) { // one-shot mode rowStatus.setVariable(new Integer32(ROWSTATUS_CREATEANDGO)); } else { rowStatus.setVariable(new Integer32(ROWSTATUS_CREATEANDWAIT)); } pdu.add(rowStatus); if (values != null) { // append index to all columnar values for (VariableBinding value : values) { OID columnOID = new OID(value.getOid()); columnOID.append(rowIndex); value.setOid(columnOID); } pdu.addAll(values); } pdu.setType(PDU.SET); try { return session.send(pdu, target); } catch (IOException ex) { logger.error(ex); } return null; } /** * Destroys a SNMP table row from a table that support the RowStatus * mechanism for row creation/deletion. * * @param target * the Target SNMP entity for the operation. * @param rowStatusColumnOID * the column OID of the RowStatus column (without any instance identifier). * @param rowIndex * the OID denoting the index of the table row to destroy. * @param address type of the target. * * @return ResponseEvent * the ResponseEvent instance returned by the SNMP session on response * of the internally sent SET request. If null, an IO * exception has occurred. Otherwise, if the response PDU is * null a timeout has occurred, Otherwise, check the error * status for {@link SnmpConstants#SNMP_ERROR_SUCCESS} to verify that the * row creation was successful. * @since 1.7.6 */ public ResponseEvent destroyRow(Target target, OID rowStatusColumnOID, OID rowIndex) { PDU pdu = pduFactory.createPDU(target); OID rowStatusID = new OID(rowStatusColumnOID); rowStatusID.append(rowIndex); VariableBinding rowStatus = new VariableBinding(rowStatusID); rowStatus.setVariable(new Integer32(ROWSTATUS_DESTROY)); pdu.add(rowStatus); pdu.setType(PDU.SET); try { ResponseEvent responseEvent = session.send(pdu, target); return responseEvent; } catch (IOException ex) { logger.error(ex); } return null; } protected class LastReceived { private List columnInfos; public LastReceived(LastReceived otherLastReceived) { this.columnInfos = new ArrayList(otherLastReceived.size()); for (LastReceivedColumnInfo columnInfo : otherLastReceived.columnInfos) { this.columnInfos.add(columnInfo); } } public LastReceived(List plainColumnInfos) { this.columnInfos = new ArrayList<>(plainColumnInfos.size()); for (OID columnOID : plainColumnInfos) { columnInfos.add(new LastReceivedColumnInfo(columnOID, null)); } } public void setColumnInfos(List columnInfos) { this.columnInfos = columnInfos; } public List getColumnInfos() { return columnInfos; } public int size() { return columnInfos.size(); } public OID get(int i) { return columnInfos.get(i).getOid(); } public void set(int i, OID oid) { columnInfos.set(i, new LastReceivedColumnInfo(oid, null)); } public void set(int i, OID oid, Row baseRow) { columnInfos.set(i, new LastReceivedColumnInfo(oid, baseRow)); } @Override public String toString() { return "LastReceived{" + "columnInfos=" + columnInfos + '}'; } } protected class LastReceivedColumnInfo { private OID oid; private Row basedOn; public LastReceivedColumnInfo(OID oid, Row basedOn) { this.oid = oid; this.basedOn = basedOn; } public OID getOid() { return oid; } public Row getBasedOn() { return basedOn; } @Override public String toString() { return "LastReceivedColumnInfo{" + "oid=" + oid + ", basedOn=" + basedOn + '}'; } } protected class Row extends ArrayList { private static final long serialVersionUID = -2297277440117636627L; private OID index; private boolean orderError; private int firstNullValue = -1; public Row(OID index) { super(); this.index = index; } public boolean isOrderError() { return orderError; } public OID getRowIndex() { return index; } public int getNumComplete() { return super.size(); } /** * Sets the number of columns in the row cache to a new value. If the number of columns provided is greater than * the number of values in the cache, then columns with {@code null} value are appended to the cache to fill * up the columns until the specified one. * * @param numberOfColumnsComplete * the number of columns received already. * @return * the number of columns added to the row with {@code null} values. */ public int setNumComplete(int numberOfColumnsComplete) { int startSize = getNumComplete(); int newSize = numberOfColumnsComplete - startSize; for (int i = 0; i < newSize; i++) { super.add(null); } if (newSize>0) { firstNullValue = startSize; } return newSize; } public boolean hasNullValues() { return firstNullValue >= 0 && firstNullValue < size(); } @Override public VariableBinding set(int index, VariableBinding element) { VariableBinding newVB = super.set(index, element); if ((firstNullValue == index) && (element != null)) { while (firstNullValue < size() && get(firstNullValue) != null) { firstNullValue++; } if (firstNullValue >= size()) { firstNullValue = -1; } } return newVB; } } protected class InternalTableListener implements TableListener { private List rows = new LinkedList(); private volatile boolean finished = false; public boolean next(TableEvent event) { rows.add(event); return true; } public synchronized void finished(TableEvent event) { if ((event.getStatus() != TableEvent.STATUS_OK) || (event.getIndex() != null)) { rows.add(event); } finished = true; notify(); } public List getRows() { return rows; } public boolean isFinished() { return finished; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy