/* * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jmx.snmp.agent; import java.io.Serializable; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import java.util.logging.Level; import javax.management.ListenerNotFoundException; import javax.management.MBeanNotificationInfo; import javax.management.Notification; import javax.management.NotificationBroadcaster; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import static com.sun.jmx.defaults.JmxProperties.SNMP_ADAPTOR_LOGGER; import com.sun.jmx.snmp.EnumRowStatus; import com.sun.jmx.snmp.SnmpInt; import com.sun.jmx.snmp.SnmpOid; import com.sun.jmx.snmp.SnmpStatusException; import com.sun.jmx.snmp.SnmpValue; import com.sun.jmx.snmp.SnmpVarBind; /** * This class is the base class for SNMP table metadata. * <p> * Its responsibility is to manage a sorted array of OID indexes * according to the SNMP indexing scheme over the "real" table. * Each object of this class can be bound to an * {@link com.sun.jmx.snmp.agent.SnmpTableEntryFactory} to which it will * forward remote entry creation requests, and invoke callbacks * when an entry has been successfully added to / removed from * the OID index array. * </p> * * <p> * For each table defined in the MIB, mibgen will generate a specific * class called Table<i>TableName</i> that will implement the * SnmpTableEntryFactory interface, and a corresponding * <i>TableName</i>Meta class that will extend this class. <br> * The Table<i>TableName</i> class corresponds to the MBean view of the * table while the <i>TableName</i>Meta class corresponds to the * MIB metadata view of the same table. * </p> * * <p> * Objects of this class are instantiated by the generated * whole MIB class extending {@link com.sun.jmx.snmp.agent.SnmpMib} * You should never need to instantiate this class directly. * </p> * * <p><b>This API is a Sun Microsystems internal API and is subject * to change without notice.</b></p> * @see com.sun.jmx.snmp.agent.SnmpMib * @see com.sun.jmx.snmp.agent.SnmpMibEntry * @see com.sun.jmx.snmp.agent.SnmpTableEntryFactory * @see com.sun.jmx.snmp.agent.SnmpTableSupport * */ public abstract class SnmpMibTable extends SnmpMibNode implements NotificationBroadcaster, Serializable { /** * Create a new <CODE>SnmpMibTable</CODE> metadata node. * * <p> * @param mib The SNMP MIB to which the metadata will be linked. */ public SnmpMibTable(SnmpMib mib) { this.theMib= mib; setCreationEnabled(false); } // ------------------------------------------------------------------- // PUBLIC METHODS // ------------------------------------------------------------------- /** * This method is invoked when the creation of a new entry is requested * by a remote SNMP manager. * <br>By default, remote entry creation is disabled - and this method * will not be called. You can dynamically switch the entry creation * policy by calling <code>setCreationEnabled(true)</code> and <code> * setCreationEnabled(false)</code> on this object. * <p><b><i> * This method is called internally by the SNMP runtime and you * should never need to call it directly. </b></i>However you might want * to extend it in order to implement your own specific application * behaviour, should the default behaviour not be at your convenience. * </p> * <p> * @param req The SNMP subrequest requesting this creation * @param rowOid The OID indexing the conceptual row (entry) for which * the creation was requested. * @param depth The position of the columnar object arc in the OIDs * from the varbind list. * * @exception SnmpStatusException if the entry cannot be created. */ public abstract void createNewEntry(SnmpMibSubRequest req, SnmpOid rowOid, int depth) throws SnmpStatusException; /** * Tell whether the specific version of this metadata generated * by <code>mibgen</code> requires entries to be registered with * the MBeanServer. In this case an ObjectName will have to be * passed to addEntry() in order for the table to behave correctly * (case of the generic metadata). * <p> * If that version of the metadata does not require entry to be * registered, then passing an ObjectName becomes optional (null * can be passed instead). * * @return <code>true</code> if registration is required by this * version of the metadata. */ public abstract boolean isRegistrationRequired(); /** * Tell whether a new entry should be created when a SET operation * is received for an entry that does not exist yet. * * @return true if a new entry must be created, false otherwise.<br> * [default: returns <CODE>false</CODE>] **/ public boolean isCreationEnabled() { return creationEnabled; } /** * This method lets you dynamically switch the creation policy. * * <p> * @param remoteCreationFlag Tells whether remote entry creation must * be enabled or disabled. * <ul><li> * <CODE>setCreationEnabled(true)</CODE> will enable remote entry * creation via SET operations.</li> * <li> * <CODE>setCreationEnabled(false)</CODE> will disable remote entry * creation via SET operations.</li> * <p> By default remote entry creation via SET operation is disabled. * </p> * </ul> **/ public void setCreationEnabled(boolean remoteCreationFlag) { creationEnabled = remoteCreationFlag; } /** * Return <code>true</code> if the conceptual row contains a columnar * object used to control creation/deletion of rows in this table. * <p> * This columnar object can be either a variable with RowStatus * syntax as defined by RFC 2579, or a plain variable whose * semantics is table specific. * <p> * By default, this function returns <code>false</code>, and it is * assumed that the table has no such control variable.<br> * When <code>mibgen</code> is used over SMIv2 MIBs, it will generate * an <code>hasRowStatus()</code> method returning <code>true</code> * for each table containing an object with RowStatus syntax. * <p> * When this method returns <code>false</code> the default mechanism * for remote entry creation is used. * Otherwise, creation/deletion is performed as specified * by the control variable (see getRowAction() for more details). * <p> * This method is called internally when a SET request involving * this table is processed. * <p> * If you need to implement a control variable which do not use * the RowStatus convention as defined by RFC 2579, you should * subclass the generated table metadata class in order to redefine * this method and make it returns <code>true</code>.<br> * You will then have to redefine the isRowStatus(), mapRowStatus(), * isRowReady(), and setRowStatus() methods to suit your specific * implementation. * <p> * @return <li><code>true</code> if this table contains a control * variable (eg: a variable with RFC 2579 RowStatus syntax), * </li> * <li><code>false</code> if this table does not contain * any control variable.</li> * **/ public boolean hasRowStatus() { return false; } // --------------------------------------------------------------------- // // Implements the method defined in SnmpMibNode. // // --------------------------------------------------------------------- /** * Generic handling of the <CODE>get</CODE> operation. * <p> The default implementation of this method is to * <ul> * <li> check whether the entry exists, and if not register an * exception for each varbind in the list. * <li> call the generated * <CODE>get(req,oid,depth+1)</CODE> method. </li> * </ul> * <p> * <pre> * public void get(SnmpMibSubRequest req, int depth) * throws SnmpStatusException { * boolean isnew = req.isNewEntry(); * * // if the entry does not exists, then registers an error for * // each varbind involved (nb: this should not happen, since * // the error should already have been detected earlier) * // * if (isnew) { * SnmpVarBind var = null; * for (Enumeration e= req.getElements(); e.hasMoreElements();) { * var = (SnmpVarBind) e.nextElement(); * req.registerGetException(var,noSuchNameException); * } * } * * final SnmpOid oid = req.getEntryOid(); * get(req,oid,depth+1); * } * </pre> * <p> You should not need to override this method in any cases, because * it will eventually call * <CODE>get(SnmpMibSubRequest req, int depth)</CODE> on the generated * derivative of <CODE>SnmpMibEntry</CODE>. If you need to implement * specific policies for minimizing the accesses made to some remote * underlying resources, or if you need to implement some consistency * checks between the different values provided in the varbind list, * you should then rather override * <CODE>get(SnmpMibSubRequest req, int depth)</CODE> on the generated * derivative of <CODE>SnmpMibEntry</CODE>. * <p> * */ @Override public void get(SnmpMibSubRequest req, int depth) throws SnmpStatusException { final boolean isnew = req.isNewEntry(); final SnmpMibSubRequest r = req; // if the entry does not exists, then registers an error for // each varbind involved (nb: should not happen, the error // should have been registered earlier) if (isnew) { SnmpVarBind var; for (Enumeration<SnmpVarBind> e= r.getElements(); e.hasMoreElements();) { var = e.nextElement(); r.registerGetException(var,noSuchInstanceException); } } final SnmpOid oid = r.getEntryOid(); // SnmpIndex index = buildSnmpIndex(oid.longValue(false), 0); // get(req,index,depth+1); // get(req,oid,depth+1); } // --------------------------------------------------------------------- // // Implements the method defined in SnmpMibNode. // // --------------------------------------------------------------------- /** * Generic handling of the <CODE>check</CODE> operation. * <p> The default implementation of this method is to * <ul> * <li> check whether a new entry must be created, and if remote * creation of entries is enabled, create it. </li> * <li> call the generated * <CODE>check(req,oid,depth+1)</CODE> method. </li> * </ul> * <p> * <pre> * public void check(SnmpMibSubRequest req, int depth) * throws SnmpStatusException { * final SnmpOid oid = req.getEntryOid(); * final int action = getRowAction(req,oid,depth+1); * * beginRowAction(req,oid,depth+1,action); * check(req,oid,depth+1); * } * </pre> * <p> You should not need to override this method in any cases, because * it will eventually call * <CODE>check(SnmpMibSubRequest req, int depth)</CODE> on the generated * derivative of <CODE>SnmpMibEntry</CODE>. If you need to implement * specific policies for minimizing the accesses made to some remote * underlying resources, or if you need to implement some consistency * checks between the different values provided in the varbind list, * you should then rather override * <CODE>check(SnmpMibSubRequest req, int depth)</CODE> on the generated * derivative of <CODE>SnmpMibEntry</CODE>. * <p> * */ @Override public void check(SnmpMibSubRequest req, int depth) throws SnmpStatusException { final SnmpOid oid = req.getEntryOid(); final int action = getRowAction(req,oid,depth+1); if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "check", "Calling beginRowAction"); } beginRowAction(req,oid,depth+1,action); if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "check", "Calling check for " + req.getSize() + " varbinds"); } check(req,oid,depth+1); if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "check", "check finished"); } } // --------------------------------------------------------------------- // // Implements the method defined in SnmpMibNode. // // --------------------------------------------------------------------- /** * Generic handling of the <CODE>set</CODE> operation. * <p> The default implementation of this method is to * call the generated * <CODE>set(req,oid,depth+1)</CODE> method. * <p> * <pre> * public void set(SnmpMibSubRequest req, int depth) * throws SnmpStatusException { * final SnmpOid oid = req.getEntryOid(); * final int action = getRowAction(req,oid,depth+1); * * set(req,oid,depth+1); * endRowAction(req,oid,depth+1,action); * } * </pre> * <p> You should not need to override this method in any cases, because * it will eventually call * <CODE>set(SnmpMibSubRequest req, int depth)</CODE> on the generated * derivative of <CODE>SnmpMibEntry</CODE>. If you need to implement * specific policies for minimizing the accesses made to some remote * underlying resources, or if you need to implement some consistency * checks between the different values provided in the varbind list, * you should then rather override * <CODE>set(SnmpMibSubRequest req, int depth)</CODE> on the generated * derivative of <CODE>SnmpMibEntry</CODE>. * <p> * */ @Override public void set(SnmpMibSubRequest req, int depth) throws SnmpStatusException { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "set", "Entering set"); } final SnmpOid oid = req.getEntryOid(); final int action = getRowAction(req,oid,depth+1); if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "set", "Calling set for " + req.getSize() + " varbinds"); } set(req,oid,depth+1); if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "set", "Calling endRowAction"); } endRowAction(req,oid,depth+1,action); if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "set", "RowAction finished"); } } /** * Add a new entry in this <CODE>SnmpMibTable</CODE>. * Also triggers the addEntryCB() callback of the * {@link com.sun.jmx.snmp.agent.SnmpTableEntryFactory} interface * if this node is bound to a factory. * * This method assumes that the given entry will not be registered. * If the entry is going to be registered, or if ObjectName's are * required, then * {@link com.sun.jmx.snmp.agent.SnmpMibTable#addEntry(SnmpOid, * ObjectName, Object)} should be prefered. * <br> This function is mainly provided for backward compatibility. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row to be added. * @param entry The entry to add. * * @exception SnmpStatusException The entry couldn't be added * at the position identified by the given * <code>rowOid</code>, or this version of the metadata * requires ObjectName's. */ // public void addEntry(SnmpIndex index, Object entry) public void addEntry(SnmpOid rowOid, Object entry) throws SnmpStatusException { addEntry(rowOid, null, entry); } /** * Add a new entry in this <CODE>SnmpMibTable</CODE>. * Also triggers the addEntryCB() callback of the * {@link com.sun.jmx.snmp.agent.SnmpTableEntryFactory} interface * if this node is bound to a factory. * * <p> * @param oid The <CODE>SnmpOid</CODE> identifying the table * row to be added. * * @param name The ObjectName with which this entry is registered. * This parameter can be omitted if isRegistrationRequired() * return false. * * @param entry The entry to add. * * @exception SnmpStatusException The entry couldn't be added * at the position identified by the given * <code>rowOid</code>, or if this version of the metadata * requires ObjectName's, and the given name is null. */ // protected synchronized void addEntry(SnmpIndex index, ObjectName name, // Object entry) public synchronized void addEntry(SnmpOid oid, ObjectName name, Object entry) throws SnmpStatusException { if (isRegistrationRequired() == true && name == null) throw new SnmpStatusException(SnmpStatusException.badValue); if (size == 0) { // indexes.addElement(index); // XX oids.addElement(oid); insertOid(0,oid); if (entries != null) entries.addElement(entry); if (entrynames != null) entrynames.addElement(name); size++; // triggers callbacks on the entry factory // if (factory != null) { try { factory.addEntryCb(0,oid,name,entry,this); } catch (SnmpStatusException x) { removeOid(0); if (entries != null) entries.removeElementAt(0); if (entrynames != null) entrynames.removeElementAt(0); throw x; } } // sends the notifications // sendNotification(SnmpTableEntryNotification.SNMP_ENTRY_ADDED, (new Date()).getTime(), entry, name); return; } // Get the insertion position ... // int pos= 0; // bug jaw.00356.B : use oid rather than index to get the // insertion point. // pos= getInsertionPoint(oid,true); if (pos == size) { // Add a new element in the vectors ... // // indexes.addElement(index); // XX oids.addElement(oid); insertOid(tablecount,oid); if (entries != null) entries.addElement(entry); if (entrynames != null) entrynames.addElement(name); size++; } else { // Insert new element ... // try { // indexes.insertElementAt(index, pos); // XX oids.insertElementAt(oid, pos); insertOid(pos,oid); if (entries != null) entries.insertElementAt(entry, pos); if (entrynames != null) entrynames.insertElementAt(name,pos); size++; } catch(ArrayIndexOutOfBoundsException e) { } } // triggers callbacks on the entry factory // if (factory != null) { try { factory.addEntryCb(pos,oid,name,entry,this); } catch (SnmpStatusException x) { removeOid(pos); if (entries != null) entries.removeElementAt(pos); if (entrynames != null) entrynames.removeElementAt(pos); throw x; } } // sends the notifications // sendNotification(SnmpTableEntryNotification.SNMP_ENTRY_ADDED, (new Date()).getTime(), entry, name); } /** * Remove the specified entry from the table. * Also triggers the removeEntryCB() callback of the * {@link com.sun.jmx.snmp.agent.SnmpTableEntryFactory} interface * if this node is bound to a factory. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row to remove. * * @param entry The entry to be removed. This parameter is not used * internally, it is simply passed along to the * removeEntryCB() callback. * * @exception SnmpStatusException if the specified entry couldn't * be removed (if the given <code>rowOid</code> is not * valid for instance). */ public synchronized void removeEntry(SnmpOid rowOid, Object entry) throws SnmpStatusException { int pos = findObject(rowOid); if (pos == -1) return; removeEntry(pos,entry); } /** * Remove the specified entry from the table. * Also triggers the removeEntryCB() callback of the * {@link com.sun.jmx.snmp.agent.SnmpTableEntryFactory} interface * if this node is bound to a factory. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row to remove. * * @exception SnmpStatusException if the specified entry couldn't * be removed (if the given <code>rowOid</code> is not * valid for instance). */ public void removeEntry(SnmpOid rowOid) throws SnmpStatusException { int pos = findObject(rowOid); if (pos == -1) return; removeEntry(pos,null); } /** * Remove the specified entry from the table. * Also triggers the removeEntryCB() callback of the * {@link com.sun.jmx.snmp.agent.SnmpTableEntryFactory} interface * if this node is bound to a factory. * * <p> * @param pos The position of the entry in the table. * * @param entry The entry to be removed. This parameter is not used * internally, it is simply passed along to the * removeEntryCB() callback. * * @exception SnmpStatusException if the specified entry couldn't * be removed. */ public synchronized void removeEntry(int pos, Object entry) throws SnmpStatusException { if (pos == -1) return; if (pos >= size) return; Object obj = entry; if (entries != null && entries.size() > pos) { obj = entries.elementAt(pos); entries.removeElementAt(pos); } ObjectName name = null; if (entrynames != null && entrynames.size() > pos) { name = entrynames.elementAt(pos); entrynames.removeElementAt(pos); } final SnmpOid rowOid = tableoids[pos]; removeOid(pos); size --; if (obj == null) obj = entry; if (factory != null) factory.removeEntryCb(pos,rowOid,name,obj,this); sendNotification(SnmpTableEntryNotification.SNMP_ENTRY_REMOVED, (new Date()).getTime(), obj, name); } /** * Get the entry corresponding to the specified rowOid. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the * row to be retrieved. * * @return The entry. * * @exception SnmpStatusException There is no entry with the specified * <code>rowOid</code> in the table. */ public synchronized Object getEntry(SnmpOid rowOid) throws SnmpStatusException { int pos= findObject(rowOid); if (pos == -1) throw new SnmpStatusException(SnmpStatusException.noSuchInstance); return entries.elementAt(pos); } /** * Get the ObjectName of the entry corresponding to the * specified rowOid. * The result of this method is only meaningful if * isRegistrationRequired() yields true. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row whose ObjectName we want to retrieve. * * @return The object name of the entry. * * @exception SnmpStatusException There is no entry with the specified * <code>rowOid</code> in the table. */ public synchronized ObjectName getEntryName(SnmpOid rowOid) throws SnmpStatusException { int pos = findObject(rowOid); if (entrynames == null) return null; if (pos == -1 || pos >= entrynames.size()) throw new SnmpStatusException(SnmpStatusException.noSuchInstance); return entrynames.elementAt(pos); } /** * Return the entries stored in this table <CODE>SnmpMibTable</CODE>. * <p> * If the subclass generated by mibgen uses the generic way to access * the entries (i.e. if it goes through the MBeanServer) then some of * the entries may be <code>null</code>. It all depends whether a non * <code>null</code> entry was passed to addEntry().<br> * Otherwise, if it uses the standard way (access the entry directly * through their standard MBean interface) this array will contain all * the entries. * <p> * @return The entries array. */ public Object[] getBasicEntries() { Object[] array= new Object[size]; entries.copyInto(array); return array; } /** * Get the size of the table. * * @return The number of entries currently registered in this table. */ public int getSize() { return size; } // EVENT STUFF //------------ /** * Enable to add an SNMP entry listener to this * <CODE>SnmpMibTable</CODE>. * * <p> * @param listener The listener object which will handle the * notifications emitted by the registered MBean. * * @param filter The filter object. If filter is null, no filtering * will be performed before handling notifications. * * @param handback The context to be sent to the listener when a * notification is emitted. * * @exception IllegalArgumentException Listener parameter is null. */ @Override public synchronized void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) { // Check listener // if (listener == null) { throw new java.lang.IllegalArgumentException ("Listener can't be null") ; } // looking for listener in handbackTable // Vector<Object> handbackList = handbackTable.get(listener) ; Vector<NotificationFilter> filterList = filterTable.get(listener) ; if ( handbackList == null ) { handbackList = new Vector<>() ; filterList = new Vector<>() ; handbackTable.put(listener, handbackList) ; filterTable.put(listener, filterList) ; } // Add the handback and the filter // handbackList.addElement(handback) ; filterList.addElement(filter) ; } /** * Enable to remove an SNMP entry listener from this * <CODE>SnmpMibTable</CODE>. * * @param listener The listener object which will handle the * notifications emitted by the registered MBean. * This method will remove all the information related to this * listener. * * @exception ListenerNotFoundException The listener is not registered * in the MBean. */ @Override public synchronized void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException { // looking for listener in handbackTable // java.util.Vector<?> handbackList = handbackTable.get(listener) ; if ( handbackList == null ) { throw new ListenerNotFoundException("listener"); } // If handback is null, remove the listener entry // handbackTable.remove(listener) ; filterTable.remove(listener) ; } /** * Return a <CODE>NotificationInfo</CODE> object containing the * notification class and the notification type sent by the * <CODE>SnmpMibTable</CODE>. */ @Override public MBeanNotificationInfo[] getNotificationInfo() { String[] types = {SnmpTableEntryNotification.SNMP_ENTRY_ADDED, SnmpTableEntryNotification.SNMP_ENTRY_REMOVED}; MBeanNotificationInfo[] notifsInfo = { new MBeanNotificationInfo (types, "com.sun.jmx.snmp.agent.SnmpTableEntryNotification", "Notifications sent by the SnmpMibTable") }; return notifsInfo; } /** * Register the factory through which table entries should * be created when remote entry creation is enabled. * * <p> * @param factory The * {@link com.sun.jmx.snmp.agent.SnmpTableEntryFactory} through * which entries will be created when a remote SNMP manager * request the creation of a new entry via an SNMP SET request. */ public void registerEntryFactory(SnmpTableEntryFactory factory) { this.factory = factory; } // ---------------------------------------------------------------------- // PROTECTED METHODS - RowStatus // ---------------------------------------------------------------------- /** * Return true if the columnar object identified by <code>var</code> * is used to control the addition/deletion of rows in this table. * * <p> * By default, this method assumes that there is no control variable * and always return <code>false</code> * <p> * If this table was defined using SMIv2, and if it contains a * control variable with RowStatus syntax, <code>mibgen</code> * will generate a non default implementation for this method * that will identify the RowStatus control variable. * <p> * You will have to redefine this method if you need to implement * control variables that do not conform to RFC 2579 RowStatus * TEXTUAL-CONVENTION. * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param var The OID arc identifying the involved columnar object. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * **/ protected boolean isRowStatus(SnmpOid rowOid, long var, Object userData) { return false; } /** * Return the RowStatus code value specified in this request. * <p> * The RowStatus code value should be one of the values defined * by {@link com.sun.jmx.snmp.EnumRowStatus}. These codes correspond * to RowStatus codes as defined in RFC 2579, plus the <i>unspecified</i> * value which is SNMP Runtime specific. * <p> * * @param req The sub-request that must be handled by this node. * * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param depth The depth reached in the OID tree. * * @return The RowStatus code specified in this request, if any: * <ul> * <li>If the specified row does not exist and this table do * not use any variable to control creation/deletion of * rows, then default creation mechanism is assumed and * <i>createAndGo</i> is returned</li> * <li>Otherwise, if the row exists and this table do not use any * variable to control creation/deletion of rows, * <i>unspecified</i> is returned.</li> * <li>Otherwise, if the request does not contain the control variable, * <i>unspecified</i> is returned.</li> * <li>Otherwise, mapRowStatus() is called to extract the RowStatus * code from the SnmpVarBind that contains the control variable.</li> * </ul> * * @exception SnmpStatusException if the value of the control variable * could not be mapped to a RowStatus code. * * @see com.sun.jmx.snmp.EnumRowStatus **/ protected int getRowAction(SnmpMibSubRequest req, SnmpOid rowOid, int depth) throws SnmpStatusException { final boolean isnew = req.isNewEntry(); final SnmpVarBind vb = req.getRowStatusVarBind(); if (vb == null) { if (isnew && ! hasRowStatus()) return EnumRowStatus.createAndGo; else return EnumRowStatus.unspecified; } try { return mapRowStatus(rowOid, vb, req.getUserData()); } catch( SnmpStatusException x) { checkRowStatusFail(req, x.getStatus()); } return EnumRowStatus.unspecified; } /** * Map the value of the <code>vbstatus</code> varbind to the * corresponding RowStatus code defined in * {@link com.sun.jmx.snmp.EnumRowStatus}. * These codes correspond to RowStatus codes as defined in RFC 2579, * plus the <i>unspecified</i> value which is SNMP Runtime specific. * <p> * By default, this method assumes that the control variable is * an Integer, and it simply returns its value without further * analysis. * <p> * If this table was defined using SMIv2, and if it contains a * control variable with RowStatus syntax, <code>mibgen</code> * will generate a non default implementation for this method. * <p> * You will have to redefine this method if you need to implement * control variables that do not conform to RFC 2579 RowStatus * TEXTUAL-CONVENTION. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param vbstatus The SnmpVarBind containing the value of the control * variable, as identified by the isRowStatus() method. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return The RowStatus code mapped from the value contained * in <code>vbstatus</code>. * * @exception SnmpStatusException if the value of the control variable * could not be mapped to a RowStatus code. * * @see com.sun.jmx.snmp.EnumRowStatus **/ protected int mapRowStatus(SnmpOid rowOid, SnmpVarBind vbstatus, Object userData) throws SnmpStatusException { final SnmpValue rsvalue = vbstatus.value; if (rsvalue instanceof SnmpInt) return ((SnmpInt)rsvalue).intValue(); else throw new SnmpStatusException( SnmpStatusException.snmpRspInconsistentValue); } /** * Set the control variable to the specified <code>newStatus</code> * value. * * <p> * This method maps the given <code>newStatus</code> to the appropriate * value for the control variable, then sets the control variable in * the entry identified by <code>rowOid</code>. It returns the new * value of the control variable. * <p> * By default, it is assumed that there is no control variable so this * method does nothing and simply returns <code>null</code>. * <p> * If this table was defined using SMIv2, and if it contains a * control variable with RowStatus syntax, <code>mibgen</code> * will generate a non default implementation for this method. * <p> * You will have to redefine this method if you need to implement * control variables that do not conform to RFC 2579 RowStatus * TEXTUAL-CONVENTION. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param newStatus The new status for the row: one of the * RowStatus code defined in * {@link com.sun.jmx.snmp.EnumRowStatus}. These codes * correspond to RowStatus codes as defined in RFC 2579, * plus the <i>unspecified</i> value which is SNMP Runtime specific. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return The new value of the control variable (usually * <code>new SnmpInt(newStatus)</code>) or <code>null</code> * if the table do not have any control variable. * * @exception SnmpStatusException If the given <code>newStatus</code> * could not be set on the specified entry, or if the * given <code>newStatus</code> is not valid. * * @see com.sun.jmx.snmp.EnumRowStatus **/ protected SnmpValue setRowStatus(SnmpOid rowOid, int newStatus, Object userData) throws SnmpStatusException { return null; } /** * Tell whether the specified row is ready and can be put in the * <i>notInService</i> state. * <p> * This method is called only once, after all the varbind have been * set on a new entry for which <i>createAndWait</i> was specified. * <p> * If the entry is not yet ready, this method should return false. * It will then be the responsibility of the entry to switch its * own state to <i>notInService</i> when it becomes ready. * No further call to <code>isRowReady()</code> will be made. * <p> * By default, this method always return true. <br> * <code>mibgen</code> will not generate any specific implementation * for this method - meaning that by default, a row created using * <i>createAndWait</i> will always be placed in <i>notInService</i> * state at the end of the request. * <p> * If this table was defined using SMIv2, and if it contains a * control variable with RowStatus syntax, <code>mibgen</code> * will generate an implementation for this method that will * delegate the work to the metadata class modelling the conceptual * row, so that you can override the default behaviour by subclassing * that metadata class. * <p> * You will have to redefine this method if this default mechanism * does not suit your needs. * * <p> * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return <code>true</code> if the row can be placed in * <i>notInService</i> state. * * @exception SnmpStatusException An error occured while trying * to retrieve the row status, and the operation should * be aborted. * * @see com.sun.jmx.snmp.EnumRowStatus **/ protected boolean isRowReady(SnmpOid rowOid, Object userData) throws SnmpStatusException { return true; } /** * Check whether the control variable of the given row can be * switched to the new specified <code>newStatus</code>. * <p> * This method is called during the <i>check</i> phase of a SET * request when the control variable specifies <i>active</i> or * <i>notInService</i>. * <p> * By default it is assumed that nothing prevents putting the * row in the requested state, and this method does nothing. * It is simply provided as a hook so that specific checks can * be implemented. * <p> * Note that if the actual row deletion fails afterward, the * atomicity of the request is no longer guaranteed. * * <p> * @param req The sub-request that must be handled by this node. * * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param depth The depth reached in the OID tree. * * @param newStatus The new status for the row: one of the * RowStatus code defined in * {@link com.sun.jmx.snmp.EnumRowStatus}. These codes * correspond to RowStatus codes as defined in RFC 2579, * plus the <i>unspecified</i> value which is SNMP Runtime specific. * * @exception SnmpStatusException if switching to this new state * would fail. * **/ protected void checkRowStatusChange(SnmpMibSubRequest req, SnmpOid rowOid, int depth, int newStatus) throws SnmpStatusException { } /** * Check whether the specified row can be removed from the table. * <p> * This method is called during the <i>check</i> phase of a SET * request when the control variable specifies <i>destroy</i> * <p> * By default it is assumed that nothing prevents row deletion * and this method does nothing. It is simply provided as a hook * so that specific checks can be implemented. * <p> * Note that if the actual row deletion fails afterward, the * atomicity of the request is no longer guaranteed. * * <p> * @param req The sub-request that must be handled by this node. * * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param depth The depth reached in the OID tree. * * @exception SnmpStatusException if the row deletion must be * rejected. **/ protected void checkRemoveTableRow(SnmpMibSubRequest req, SnmpOid rowOid, int depth) throws SnmpStatusException { } /** * Remove a table row upon a remote manager request. * * This method is called internally when <code>getRowAction()</code> * yields <i>destroy</i> - i.e.: it is only called when a remote * manager requests the removal of a table row.<br> * You should never need to call this function directly. * <p> * By default, this method simply calls <code>removeEntry(rowOid) * </code>. * <p> * You can redefine this method if you need to implement some * specific behaviour when a remote row deletion is invoked. * <p> * Note that specific checks should not be implemented in this * method, but rather in <code>checkRemoveTableRow()</code>. * If <code>checkRemoveTableRow()</code> succeeds and this method * fails afterward, the atomicity of the original SET request can no * longer be guaranteed. * <p> * * @param req The sub-request that must be handled by this node. * * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param depth The depth reached in the OID tree. * * @exception SnmpStatusException if the actual row deletion fails. * This should not happen since it would break the * atomicity of the SET request. Specific checks should * be implemented in <code>checkRemoveTableRow()</code> * if needed. If the entry does not exists, no exception * is generated and the method simply returns. * **/ protected void removeTableRow(SnmpMibSubRequest req, SnmpOid rowOid, int depth) throws SnmpStatusException { removeEntry(rowOid); } /** * This method takes care of initial RowStatus handling during the * check() phase of a SET request. * * In particular it will: * <ul><li>check that the given <code>rowAction</code> returned by * <code>getRowAction()</code> is valid.</li> * <li>Then depending on the <code>rowAction</code> specified it will: * <ul><li>either call <code>createNewEntry()</code> (<code> * rowAction = <i>createAndGo</i> or <i>createAndWait</i> * </code>),</li> * <li>or call <code>checkRemoveTableRow()</code> (<code> * rowAction = <i>destroy</i></code>),</li> * <li>or call <code>checkRowStatusChange()</code> (<code> * rowAction = <i>active</i> or <i>notInService</i></code>),</li> * <li>or generate a SnmpStatusException if the passed <code> * rowAction</code> is not correct.</li> * </ul></li></ul> * <p> * In principle, you should not need to redefine this method. * <p> * <code>beginRowAction()</code> is called during the check phase * of a SET request, before actual checking on the varbind list * is performed. * * <p> * @param req The sub-request that must be handled by this node. * * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param depth The depth reached in the OID tree. * * @param rowAction The requested action as returned by <code> * getRowAction()</code>: one of the RowStatus codes defined in * {@link com.sun.jmx.snmp.EnumRowStatus}. These codes * correspond to RowStatus codes as defined in RFC 2579, * plus the <i>unspecified</i> value which is SNMP Runtime specific. * * @exception SnmpStatusException if the specified <code>rowAction</code> * is not valid or cannot be executed. * This should not happen since it would break the * atomicity of the SET request. Specific checks should * be implemented in <code>beginRowAction()</code> if needed. * * @see com.sun.jmx.snmp.EnumRowStatus **/ protected synchronized void beginRowAction(SnmpMibSubRequest req, SnmpOid rowOid, int depth, int rowAction) throws SnmpStatusException { final boolean isnew = req.isNewEntry(); final SnmpOid oid = rowOid; final int action = rowAction; switch (action) { case EnumRowStatus.unspecified: if (isnew) { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Failed to create row[" + rowOid + "] : RowStatus = unspecified"); } checkRowStatusFail(req,SnmpStatusException.snmpRspNoAccess); } break; case EnumRowStatus.createAndGo: case EnumRowStatus.createAndWait: if (isnew) { if (isCreationEnabled()) { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Creating row[" + rowOid + "] : RowStatus = createAndGo | createAndWait"); } createNewEntry(req,oid,depth); } else { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Can't create row[" + rowOid + "] : RowStatus = createAndGo | createAndWait " + "but creation is disabled"); } checkRowStatusFail(req, SnmpStatusException.snmpRspNoAccess); } } else { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Can't create row[" + rowOid + "] : RowStatus = createAndGo | createAndWait " + "but row already exists"); } checkRowStatusFail(req, SnmpStatusException.snmpRspInconsistentValue); } break; case EnumRowStatus.destroy: if (isnew) { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Warning: can't destroy row[" + rowOid + "] : RowStatus = destroy but row does not exist"); } } else if (!isCreationEnabled()) { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Can't destroy row[" + rowOid + "] : " + "RowStatus = destroy but creation is disabled"); } checkRowStatusFail(req,SnmpStatusException.snmpRspNoAccess); } checkRemoveTableRow(req,rowOid,depth); break; case EnumRowStatus.active: case EnumRowStatus.notInService: if (isnew) { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Can't switch state of row[" + rowOid + "] : specified RowStatus = active | " + "notInService but row does not exist"); } checkRowStatusFail(req, SnmpStatusException.snmpRspInconsistentValue); } checkRowStatusChange(req,rowOid,depth,action); break; case EnumRowStatus.notReady: default: if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "beginRowAction", "Invalid RowStatus value for row[" + rowOid + "] : specified RowStatus = " + action); } checkRowStatusFail(req, SnmpStatusException.snmpRspInconsistentValue); } } /** * This method takes care of final RowStatus handling during the * set() phase of a SET request. * * In particular it will: * <ul><li>either call <code>setRowStatus(<i>active</i>)</code> * (<code> rowAction = <i>createAndGo</i> or <i>active</i> * </code>),</li> * <li>or call <code>setRowStatus(<i>notInService</i> or <i> * notReady</i>)</code> depending on the result of <code> * isRowReady()</code> (<code>rowAction = <i>createAndWait</i> * </code>),</li> * <li>or call <code>setRowStatus(<i>notInService</i>)</code> * (<code> rowAction = <i>notInService</i></code>), * <li>or call <code>removeTableRow()</code> (<code> * rowAction = <i>destroy</i></code>),</li> * <li>or generate a SnmpStatusException if the passed <code> * rowAction</code> is not correct. This should be avoided * since it would break SET request atomicity</li> * </ul> * <p> * In principle, you should not need to redefine this method. * <p> * <code>endRowAction()</code> is called during the set() phase * of a SET request, after the actual set() on the varbind list * has been performed. The varbind containing the control variable * is updated with the value returned by setRowStatus() (if it is * not <code>null</code>). * * <p> * @param req The sub-request that must be handled by this node. * * @param rowOid The <CODE>SnmpOid</CODE> identifying the table * row involved in the operation. * * @param depth The depth reached in the OID tree. * * @param rowAction The requested action as returned by <code> * getRowAction()</code>: one of the RowStatus codes defined in * {@link com.sun.jmx.snmp.EnumRowStatus}. These codes * correspond to RowStatus codes as defined in RFC 2579, * plus the <i>unspecified</i> value which is SNMP Runtime specific. * * @exception SnmpStatusException if the specified <code>rowAction</code> * is not valid. * * @see com.sun.jmx.snmp.EnumRowStatus **/ protected void endRowAction(SnmpMibSubRequest req, SnmpOid rowOid, int depth, int rowAction) throws SnmpStatusException { final boolean isnew = req.isNewEntry(); final SnmpOid oid = rowOid; final int action = rowAction; final Object data = req.getUserData(); SnmpValue value = null; switch (action) { case EnumRowStatus.unspecified: break; case EnumRowStatus.createAndGo: if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Setting RowStatus to 'active' " + "for row[" + rowOid + "] : requested RowStatus = " + "createAndGo"); } value = setRowStatus(oid,EnumRowStatus.active,data); break; case EnumRowStatus.createAndWait: if (isRowReady(oid,data)) { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Setting RowStatus to 'notInService' for row[" + rowOid + "] : requested RowStatus = createAndWait"); } value = setRowStatus(oid,EnumRowStatus.notInService,data); } else { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Setting RowStatus to 'notReady' " + "for row[" + rowOid + "] : requested RowStatus = " + "createAndWait"); } value = setRowStatus(oid,EnumRowStatus.notReady,data); } break; case EnumRowStatus.destroy: if (isnew) { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Warning: requested RowStatus = destroy, " + "but row[" + rowOid + "] does not exist"); } } else { if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Destroying row[" + rowOid + "] : requested RowStatus = destroy"); } } removeTableRow(req,oid,depth); break; case EnumRowStatus.active: if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Setting RowStatus to 'active' for row[" + rowOid + "] : requested RowStatus = active"); } value = setRowStatus(oid,EnumRowStatus.active,data); break; case EnumRowStatus.notInService: if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Setting RowStatus to 'notInService' for row[" + rowOid + "] : requested RowStatus = notInService"); } value = setRowStatus(oid,EnumRowStatus.notInService,data); break; case EnumRowStatus.notReady: default: if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, SnmpMibTable.class.getName(), "endRowAction", "Invalid RowStatus value for row[" + rowOid + "] : specified RowStatus = " + action); } setRowStatusFail(req, SnmpStatusException.snmpRspInconsistentValue); } if (value != null) { final SnmpVarBind vb = req.getRowStatusVarBind(); if (vb != null) vb.value = value; } } // ------------------------------------------------------------------- // PROTECTED METHODS - get next // ------------------------------------------------------------------- /** * Return the next OID arc corresponding to a readable columnar * object in the underlying entry OBJECT-TYPE, possibly skipping over * those objects that must not or cannot be returned. * Calls {@link * #getNextVarEntryId(com.sun.jmx.snmp.SnmpOid,long,java.lang.Object)}, * until * {@link #skipEntryVariable(com.sun.jmx.snmp.SnmpOid,long, * java.lang.Object,int)} returns false. * * * @param rowOid The OID index of the row involved in the operation. * * @param var Id of the variable we start from, looking for the next. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @param pduVersion Protocol version of the original request PDU. * * @return The next columnar object id which can be returned using * the given PDU's protocol version. * * @exception SnmpStatusException If no id is found after the given id. * **/ protected long getNextVarEntryId(SnmpOid rowOid, long var, Object userData, int pduVersion) throws SnmpStatusException { long varid=var; do { varid = getNextVarEntryId(rowOid,varid,userData); } while (skipEntryVariable(rowOid,varid,userData,pduVersion)); return varid; } /** * Hook for subclasses. * The default implementation of this method is to always return * false. Subclasses should redefine this method so that it returns * true when: * <ul><li>the variable is a leaf that is not instantiated,</li> * <li>or the variable is a leaf whose type cannot be returned by that * version of the protocol (e.g. an Counter64 with SNMPv1).</li> * </ul> * * @param rowOid The OID index of the row involved in the operation. * * @param var Id of the variable we start from, looking for the next. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @param pduVersion Protocol version of the original request PDU. * * @return true if the variable must be skipped by the get-next * algorithm. */ protected boolean skipEntryVariable(SnmpOid rowOid, long var, Object userData, int pduVersion) { return false; } /** * Get the <CODE>SnmpOid</CODE> index of the row that follows * the given <CODE>oid</CODE> in the table. The given <CODE> * oid</CODE> does not need to be a valid row OID index. * * <p> * @param oid The OID from which the search will begin. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return The next <CODE>SnmpOid</CODE> index. * * @exception SnmpStatusException There is no index following the * specified <CODE>oid</CODE> in the table. */ protected SnmpOid getNextOid(SnmpOid oid, Object userData) throws SnmpStatusException { if (size == 0) throw noSuchInstanceException; final SnmpOid resOid = oid; // Just a simple check to speed up retrieval of last element ... // // XX SnmpOid last= (SnmpOid) oids.lastElement(); SnmpOid last= tableoids[tablecount-1]; if (last.equals(resOid)) { // Last element of the table ... // throw noSuchInstanceException; } // First find the oid. This will allow to speed up retrieval process // during smart discovery of table (using the getNext) as the // management station will use the valid index returned during a // previous getNext ... // // Returns the position following the position at which resOid // is found, or the position at which resOid should be inserted. // final int newPos = getInsertionPoint(resOid,false); // If the position returned is not out of bound, we will find // the next element in the array. // if (newPos > -1 && newPos < size) { try { // XX last = (SnmpOid) oids.elementAt(newPos); last = tableoids[newPos]; } catch(ArrayIndexOutOfBoundsException e) { throw noSuchInstanceException; } } else { // We are dealing with the last element of the table .. // throw noSuchInstanceException; } return last; } /** * Return the first entry OID registered in the table. * * <p> * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return The <CODE>SnmpOid</CODE> of the first entry in the table. * * @exception SnmpStatusException If the table is empty. */ protected SnmpOid getNextOid(Object userData) throws SnmpStatusException { if (size == 0) throw noSuchInstanceException; // XX return (SnmpOid) oids.firstElement(); return tableoids[0]; } // ------------------------------------------------------------------- // Abstract Protected Methods // ------------------------------------------------------------------- /** * This method is used internally and is implemented by the * <CODE>SnmpMibTable</CODE> subclasses generated by <CODE>mibgen</CODE>. * * <p> Return the next OID arc corresponding to a readable columnar * object in the underlying entry OBJECT-TYPE.</p> * * <p> * @param rowOid The OID index of the row involved in the operation. * * @param var Id of the variable we start from, looking for the next. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return The next columnar object id. * * @exception SnmpStatusException If no id is found after the given id. * **/ abstract protected long getNextVarEntryId(SnmpOid rowOid, long var, Object userData) throws SnmpStatusException; /** * This method is used internally and is implemented by the * <CODE>SnmpMibTable</CODE> subclasses generated by <CODE>mibgen</CODE>. * * <p> * @param rowOid The OID index of the row involved in the operation. * * @param var The var we want to validate. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @exception SnmpStatusException If this id is not valid. * */ abstract protected void validateVarEntryId(SnmpOid rowOid, long var, Object userData) throws SnmpStatusException; /** * * This method is used internally and is implemented by the * <CODE>SnmpMibTable</CODE> subclasses generated by <CODE>mibgen</CODE>. * * <p> * @param rowOid The OID index of the row involved in the operation. * * @param var The OID arc. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @exception SnmpStatusException If this id is not valid. * */ abstract protected boolean isReadableEntryId(SnmpOid rowOid, long var, Object userData) throws SnmpStatusException; /** * This method is used internally and is implemented by the * <CODE>SnmpMibTable</CODE> subclasses generated by <CODE>mibgen</CODE>. */ abstract protected void get(SnmpMibSubRequest req, SnmpOid rowOid, int depth) throws SnmpStatusException; /** * This method is used internally and is implemented by the * <CODE>SnmpMibTable</CODE> subclasses generated by <CODE>mibgen</CODE>. */ abstract protected void check(SnmpMibSubRequest req, SnmpOid rowOid, int depth) throws SnmpStatusException; /** * This method is used internally and is implemented by the * <CODE>SnmpMibTable</CODE> subclasses generated by <CODE>mibgen</CODE>. */ abstract protected void set(SnmpMibSubRequest req, SnmpOid rowOid, int depth) throws SnmpStatusException; // ---------------------------------------------------------------------- // PACKAGE METHODS // ---------------------------------------------------------------------- /** * Get the <CODE>SnmpOid</CODE> index of the row that follows the * index extracted from the specified OID array. * Builds the SnmpOid corresponding to the row OID and calls * <code>getNextOid(oid,userData)</code>; * * <p> * @param oid The OID array. * * @param pos The position in the OID array at which the index starts. * * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return The next <CODE>SnmpOid</CODE>. * * @exception SnmpStatusException There is no index following the * specified one in the table. */ SnmpOid getNextOid(long[] oid, int pos, Object userData) throws SnmpStatusException { // Construct the sub-oid starting at pos. // This sub-oid correspond to the oid part just after the entry // variable oid. // final SnmpOid resOid = new SnmpEntryOid(oid,pos); return getNextOid(resOid,userData); } // --------------------------------------------------------------------- // // Register an exception when checking the RowStatus variable // // --------------------------------------------------------------------- static void checkRowStatusFail(SnmpMibSubRequest req, int errorStatus) throws SnmpStatusException { final SnmpVarBind statusvb = req.getRowStatusVarBind(); final SnmpStatusException x = new SnmpStatusException(errorStatus); req.registerCheckException(statusvb,x); } // --------------------------------------------------------------------- // // Register an exception when checking the RowStatus variable // // --------------------------------------------------------------------- static void setRowStatusFail(SnmpMibSubRequest req, int errorStatus) throws SnmpStatusException { final SnmpVarBind statusvb = req.getRowStatusVarBind(); final SnmpStatusException x = new SnmpStatusException(errorStatus); req.registerSetException(statusvb,x); } // --------------------------------------------------------------------- // // Implements the method defined in SnmpMibNode. // // --------------------------------------------------------------------- @Override final synchronized void findHandlingNode(SnmpVarBind varbind, long[] oid, int depth, SnmpRequestTree handlers) throws SnmpStatusException { final int length = oid.length; if (handlers == null) throw new SnmpStatusException(SnmpStatusException.snmpRspGenErr); if (depth >= length) throw new SnmpStatusException(SnmpStatusException.noAccess); if (oid[depth] != nodeId) throw new SnmpStatusException(SnmpStatusException.noAccess); if (depth+2 >= length) throw new SnmpStatusException(SnmpStatusException.noAccess); // Checks that the oid is valid // validateOid(oid,depth); // Gets the part of the OID that identifies the entry final SnmpOid entryoid = new SnmpEntryOid(oid, depth+2); // Finds the entry: false means that the entry does not exists final Object data = handlers.getUserData(); final boolean hasEntry = contains(entryoid, data); // Fails if the entry is not found and the table does not // not support creation. // We know that the entry does not exists if (isentry == false). if (!hasEntry) { if (!handlers.isCreationAllowed()) // we're not doing a set throw noSuchInstanceException; else if (!isCreationEnabled()) // we're doing a set but creation is disabled. throw new SnmpStatusException(SnmpStatusException.snmpRspNoAccess); } final long var = oid[depth+1]; // Validate the entry id if (hasEntry) { // The entry already exists - validate the id validateVarEntryId(entryoid,var,data); } // Registers this node for the identified entry. // if (handlers.isSetRequest() && isRowStatus(entryoid,var,data)) // We only try to identify the RowStatus for SET operations // handlers.add(this,depth,entryoid,varbind,(!hasEntry),varbind); else handlers.add(this,depth,entryoid,varbind,(!hasEntry)); } // --------------------------------------------------------------------- // // Implements the method defined in SnmpMibNode. The algorithm is very // largely inspired from the original getNext() method. // // --------------------------------------------------------------------- @Override final synchronized long[] findNextHandlingNode(SnmpVarBind varbind, long[] oid, int pos, int depth, SnmpRequestTree handlers, AcmChecker checker) throws SnmpStatusException { int length = oid.length; if (handlers == null) // This should be considered as a genErr, but we do not want to // abort the whole request, so we're going to throw // a noSuchObject... // throw noSuchObjectException; final Object data = handlers.getUserData(); final int pduVersion = handlers.getRequestPduVersion(); long var= -1; // If the querried oid contains less arcs than the OID of the // xxxEntry object, we must return the first leaf under the // first columnar object: the best way to do that is to reset // the queried oid: // oid[0] = nodeId (arc of the xxxEntry object) // pos = 0 (points to the arc of the xxxEntry object) // then we just have to proceed... // if (pos >= length) { // this will have the side effect to set // oid[pos] = nodeId // and // (pos+1) = length // so we won't fall into the "else if" cases below - // so using "else if" rather than "if ..." is guaranteed // to be safe. // oid = new long[1]; oid[0] = nodeId; pos = 0; length = 1; } else if (oid[pos] > nodeId) { // oid[pos] is expected to be the id of the xxxEntry ... // The id requested is greater than the id of the xxxEntry, // so we won't find the next element in this table... (any // element in this table will have a smaller OID) // throw noSuchObjectException; } else if (oid[pos] < nodeId) { // we must return the first leaf under the first columnar // object, so we are back to our first case where pos was // out of bounds... => reset the oid to contain only the // arc of the xxxEntry object. // oid = new long[1]; oid[0] = nodeId; pos = 0; length = 0; } else if ((pos + 1) < length) { // The arc at the position "pos+1" is the id of the columnar // object (ie: the id of the variable in the table entry) // var = oid[pos+1]; } // Now that we've got everything right we can begin. SnmpOid entryoid; if (pos == (length - 1)) { // pos points to the last arc in the oid, and this arc is // guaranteed to be the xxxEntry id (we have handled all // the other possibilities before) // // We must therefore return the first leaf below the first // columnar object in the table. // // Get the first index. If an exception is raised, // then it means that the table is empty. We thus do not // have to catch the exception - we let it propagate to // the caller. // entryoid = getNextOid(data); var = getNextVarEntryId(entryoid,var,data,pduVersion); } else if ( pos == (length-2)) { // In that case we have (pos+1) = (length-1), so pos // points to the arc of the querried variable (columnar object). // Since the requested oid stops there, it means we have // to return the first leaf under this columnar object. // // So we first get the first index: // Note: if this raises an exception, this means that the table // is empty, so we can let the exception propagate to the caller. // entryoid = getNextOid(data); // XXX revisit: not exactly perfect: // a specific row could be empty.. But we don't know // how to make the difference! => tradeoff holes // in tables can't be properly supported (all rows // must have the same holes) // if (skipEntryVariable(entryoid,var,data,pduVersion)) { var = getNextVarEntryId(entryoid,var,data,pduVersion); } } else { // So now there remain one last case, namely: some part of the // index is provided by the oid... // We build a possibly incomplete and invalid index from // the OID. // The piece of index provided should begin at pos+2 // oid[pos] = id of the xxxEntry object, // oid[pos+1] = id of the columnar object, // oid[pos+2] ... oid[length-1] = piece of index. // // We get the next index following the provided index. // If this raises an exception, then it means that we have // reached the last index in the table, and we must then // try with the next columnar object. // // Bug fix 4269251 // The SnmpIndex is defined to contain a valid oid: // this is not an SNMP requirement for the getNext request. // So we no more use the SnmpIndex but directly the SnmpOid. // try { entryoid = getNextOid(oid, pos + 2, data); // If the variable must ne skipped, fall through... // // XXX revisit: not exactly perfect: // a specific row could be empty.. But we don't know // how to make the difference! => tradeoff holes // in tables can't be properly supported (all rows // must have the same holes) // if (skipEntryVariable(entryoid,var,data,pduVersion)) throw noSuchObjectException; } catch(SnmpStatusException se) { entryoid = getNextOid(data); var = getNextVarEntryId(entryoid,var,data,pduVersion); } } return findNextAccessibleOid(entryoid, varbind, oid, depth, handlers, checker, data, var); } private long[] findNextAccessibleOid(SnmpOid entryoid, SnmpVarBind varbind,long[] oid, int depth, SnmpRequestTree handlers, AcmChecker checker, Object data, long var) throws SnmpStatusException { final int pduVersion = handlers.getRequestPduVersion(); // Loop on each var (column) while(true) { // This should not happen. If it happens, (bug, or customized // methods returning garbage instead of raising an exception), // it probably means that there is nothing to return anyway. // So we throw the exception. // => will skip to next node in the MIB tree. // if (entryoid == null || var == -1 ) throw noSuchObjectException; // So here we know both the row (entryoid) and the column (var) // try { // Raising an exception here will make the catch() clause // switch to the next variable. If `var' is not readable // for this specific entry, it is not readable for any // other entry => skip to next column. // if (!isReadableEntryId(entryoid,var,data)) throw noSuchObjectException; // Prepare the result and the ACM checker. // final long[] etable = entryoid.longValue(false); final int elength = etable.length; final long[] result = new long[depth + 2 + elength]; result[0] = -1 ; // Bug detector! // Copy the entryOid at the end of `result' // java.lang.System.arraycopy(etable, 0, result, depth+2, elength); // Set the node Id and var Id in result. // result[depth] = nodeId; result[depth+1] = var; // Append nodeId.varId.<rowOid> to ACM checker. // checker.add(depth,result,depth,elength+2); // No we're going to ACM check our OID. try { checker.checkCurrentOid(); // No exception thrown by checker => this is all OK! // we have it: register the handler and return the // result. // handlers.add(this,depth,entryoid,varbind,false); return result; } catch(SnmpStatusException e) { // Skip to the next entry. If an exception is // thrown, will be catch by enclosing catch // and a skip is done to the next var. // entryoid = getNextOid(entryoid, data); } finally { // Clean the checker. // checker.remove(depth,elength+2); } } catch(SnmpStatusException e) { // Catching an exception here means we have to skip to the // next column. // // Back to the first row. entryoid = getNextOid(data); // Find out the next column. // var = getNextVarEntryId(entryoid,var,data,pduVersion); } // This should not happen. If it happens, (bug, or customized // methods returning garbage instead of raising an exception), // it probably means that there is nothing to return anyway. // No need to continue, we throw an exception. // => will skip to next node in the MIB tree. // if (entryoid == null || var == -1 ) throw noSuchObjectException; } } /** * Validate the specified OID. * * <p> * @param oid The OID array. * * @param pos The position in the array. * * @exception SnmpStatusException If the validation fails. */ final void validateOid(long[] oid, int pos) throws SnmpStatusException { final int length= oid.length; // Control the length of the oid // if (pos +2 >= length) throw noSuchInstanceException; // Check that the entry identifier is specified // if (oid[pos] != nodeId) throw noSuchObjectException; } // ---------------------------------------------------------------------- // PRIVATE METHODS // ---------------------------------------------------------------------- /** * Enable this <CODE>SnmpMibTable</CODE> to send a notification. * * <p> * @param notification The notification to send. */ private synchronized void sendNotification(Notification notification) { // loop on listener // for(java.util.Enumeration<NotificationListener> k = handbackTable.keys(); k.hasMoreElements(); ) { NotificationListener listener = k.nextElement(); // Get the associated handback list and the associated filter list // java.util.Vector<?> handbackList = handbackTable.get(listener) ; java.util.Vector<NotificationFilter> filterList = filterTable.get(listener) ; // loop on handback // java.util.Enumeration<NotificationFilter> f = filterList.elements(); for(java.util.Enumeration<?> h = handbackList.elements(); h.hasMoreElements(); ) { Object handback = h.nextElement(); NotificationFilter filter = f.nextElement(); if ((filter == null) || (filter.isNotificationEnabled(notification))) { listener.handleNotification(notification,handback) ; } } } } /** * This method is used by the SnmpMibTable to create and send a table * entry notification to all the listeners registered for this kind of * notification. * * <p> * @param type The notification type. * * @param timeStamp The notification emission date. * * @param entry The entry object. */ private void sendNotification(String type, long timeStamp, Object entry, ObjectName name) { synchronized(this) { sequenceNumber = sequenceNumber + 1; } SnmpTableEntryNotification notif = new SnmpTableEntryNotification(type, this, sequenceNumber, timeStamp, entry, name); this.sendNotification(notif) ; } /** * Return true if the entry identified by the given OID index * is contained in this table. * <p> * <b>Do not call this method directly</b>. * <p> * This method is provided has a hook for subclasses. * It is called when a get/set request is received in order to * determine whether the specified entry is contained in the table. * You may want to override this method if you need to perform e.g. * lazy evaluation of tables (you need to update the table when a * request is received) or if your table is virtual. * <p> * Note that this method is called by the Runtime from within a * synchronized block. * * @param oid The index part of the OID we're looking for. * @param userData A contextual object containing user-data. * This object is allocated through the <code> * {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}</code> * for each incoming SNMP request. * * @return <code>true</code> if the entry is found, <code>false</code> * otherwise. * * @since 1.5 **/ protected boolean contains(SnmpOid oid, Object userData) { return (findObject(oid) > -1); } /** * Look for the given oid in the OID table (tableoids) and returns * its position. * * <p> * @param oid The OID we're looking for. * * @return The position of the OID in the table. -1 if the given * OID was not found. * **/ private int findObject(SnmpOid oid) { int low= 0; int max= size - 1; SnmpOid pos; int comp; int curr= low + (max-low)/2; //System.out.println("Try to retrieve: " + oid.toString()); while (low <= max) { // XX pos = (SnmpOid) oids.elementAt(curr); pos = tableoids[curr]; //System.out.println("Compare with" + pos.toString()); // never know ...we might find something ... // comp = oid.compareTo(pos); if (comp == 0) return curr; if (oid.equals(pos) == true) { return curr; } if (comp > 0) { low = curr + 1; } else { max = curr - 1; } curr = low + (max-low)/2; } return -1; } /** * Search the position at which the given oid should be inserted * in the OID table (tableoids). * * <p> * @param oid The OID we would like to insert. * * @param fail Tells whether a SnmpStatusException must be generated * if the given OID is already present in the table. * * @return The position at which the OID should be inserted in * the table. When the OID is found, it returns the next * position. Note that it is not valid to insert twice the * same OID. This feature is only an optimization to improve * the getNextOid() behaviour. * * @exception SnmpStatusException if the OID is already present in the * table and <code>fail</code> is <code>true</code>. * **/ private int getInsertionPoint(SnmpOid oid, boolean fail) throws SnmpStatusException { final int failStatus = SnmpStatusException.snmpRspNotWritable; int low= 0; int max= size - 1; SnmpOid pos; int comp; int curr= low + (max-low)/2; while (low <= max) { // XX pos= (SnmpOid) oids.elementAt(curr); pos= tableoids[curr]; // never know ...we might find something ... // comp= oid.compareTo(pos); if (comp == 0) { if (fail) throw new SnmpStatusException(failStatus,curr); else return curr+1; } if (comp>0) { low= curr +1; } else { max= curr -1; } curr= low + (max-low)/2; } return curr; } /** * Remove the OID located at the given position. * * <p> * @param pos The position at which the OID to be removed is located. * **/ private void removeOid(int pos) { if (pos >= tablecount) return; if (pos < 0) return; final int l1 = --tablecount-pos; tableoids[pos] = null; if (l1 > 0) java.lang.System.arraycopy(tableoids,pos+1,tableoids,pos,l1); tableoids[tablecount] = null; } /** * Insert an OID at the given position. * * <p> * @param oid The OID to be inserted in the table * @param pos The position at which the OID to be added is located. * **/ private void insertOid(int pos, SnmpOid oid) { if (pos >= tablesize || tablecount == tablesize) { // Vector must be enlarged // Save old vector final SnmpOid[] olde = tableoids; // Allocate larger vectors tablesize += Delta; tableoids = new SnmpOid[tablesize]; // Check pos validity if (pos > tablecount) pos = tablecount; if (pos < 0) pos = 0; final int l1 = pos; final int l2 = tablecount - pos; // Copy original vector up to `pos' if (l1 > 0) java.lang.System.arraycopy(olde,0,tableoids,0,l1); // Copy original vector from `pos' to end, leaving // an empty room at `pos' in the new vector. if (l2 > 0) java.lang.System.arraycopy(olde,l1,tableoids, l1+1,l2); } else if (pos < tablecount) { // Vector is large enough to accomodate one additional // entry. // // Shift vector, making an empty room at `pos' java.lang.System.arraycopy(tableoids,pos,tableoids, pos+1,tablecount-pos); } // Fill the gap at `pos' tableoids[pos] = oid; tablecount++; } // ---------------------------------------------------------------------- // PROTECTED VARIABLES // ---------------------------------------------------------------------- /** * The id of the contained entry object. * @serial */ protected int nodeId=1; /** * The MIB to which the metadata is linked. * @serial */ protected SnmpMib theMib; /** * <CODE>true</CODE> if remote creation of entries via SET operations * is enabled. * [default value is <CODE>false</CODE>] * @serial */ protected boolean creationEnabled = false; /** * The entry factory */ protected SnmpTableEntryFactory factory = null; // ---------------------------------------------------------------------- // PRIVATE VARIABLES // ---------------------------------------------------------------------- /** * The number of elements in the table. * @serial */ private int size=0; /** * The list of indexes. * @serial */ // private Vector indexes= new Vector(); /** * The list of OIDs. * @serial */ // private Vector oids= new Vector(); private final static int Delta = 16; private int tablecount = 0; private int tablesize = Delta; private SnmpOid tableoids[] = new SnmpOid[tablesize]; /** * The list of entries. * @serial */ private final Vector<Object> entries= new Vector<>(); /** * The list of object names. * @serial */ private final Vector<ObjectName> entrynames= new Vector<>(); /** * Callback handlers */ // final Vector callbacks = new Vector(); /** * Listener hashtable containing the hand-back objects. */ private Hashtable<NotificationListener, Vector<Object>> handbackTable = new Hashtable<>(); /** * Listener hashtable containing the filter objects. */ private Hashtable<NotificationListener, Vector<NotificationFilter>> filterTable = new Hashtable<>(); // PACKAGE VARIABLES //------------------ /** * SNMP table sequence number. * The default value is set to 0. */ transient long sequenceNumber = 0; }