Architecture - Scalars

FriendlySNMP API presents scalars declared in MIBs as org.friendlysnmp.FScalar class objects.

How and where scalars are created
The following discussion uses a scalar imageSize declared in DEMO-SCALAR-RW-MIB from a demo application as an example. This scalar is defined in the MIB as follows:

imageSize OBJECT-TYPE
    SYNTAX      DisplayString
    MAX-ACCESS  read-write
    STATUS      current
    DESCRIPTION "Image size defined by MIB browser. Valid format is NNNxMMM"
    ::= { demoMIBObjects 3 }

Tools discussed in MIB-to-Java generation convert the MIB file DEMO-SCALAR-RW-MIB into Java class DemoScalarRwMibFriend. This class has the following structure:

public class DemoScalarRwMibFriend extends BaseMib {
    private FScalar imageSize;
    . . .
    public FScalar getImageSize() {
        return imageSize;
    }
}
The scalar imageSize is initialized when object of the class DemoScalarRwMibFriend added to the SNMP agent.

How to access scalar object
There are two techniques to get access to FScalar objects.

Technique 1. Direct access from the MIB object using getters generated for each scalar. This technique is used most of the time:

DemoScalarRwMibFriend mib = new DemoScalarRwMibFriend();
. . .
FScalar scalar = mib.getImageSize();

Technique 2. Indirect access from the agent object using scalar OID. This technique is used very rear, usually when a MIB object is hidden, for example when MIB is internal to the agent and is not visible from the outside:

import org.snmp4j.mp.SnmpConstants;
. . .
FriendlyAgent agent = new FriendlyAgent();
. . .
FScalar scalar = agent.getScalar(SnmpConstants.sysDescr);

Scalar internals
The FScalar class is a wrapper which hides SNMP4J org.snmp4j.agent.mo.MOScalar managed object. The class provides persistency layer and mechanism to add / remove listeners. Each scalar object is associated with immutable identifier org.friendlysnmp.FID. See also FID. The scalar FID could be accessed but cannot be modified.

Scalar syntax and value
The FScalar class provides transparent scalar values access with conversion basic Java objects and primitives to / from SNMP4J specific org.snmp4j.smi.Variable. The conversion is driven by scalar syntax declared in the MIB and may throw an exception.

There are two techniques to set scalar value:

// Technique 1.
try {
    scalar.setValue(..);
} catch (FException e) {
    // handle the problem 
}

// Technique 2.
scalar.setValueEx(..); // report the problem

The Technique 1 requires exception handling. Exception is usually thrown when the new value cannot be properly converted into internal presentation. Propagating the exception in many cases is not possible and code has only an option to log the exception. Code becomes polluted with useless try/catch blocks annoying a developer. The debugged code throws exceptions of this kind in very rare cases.

The Technique 2 does not require exception handling. Exception is propagated to the SNMP agent. The application has an option to register listener to handle exceptions of this kind in the SNMP agent. This option is preferable. It allows central handling of the exception. For example, a GUI application may show popup window with the problem. See section Events and Listeners for details.

Scalar value could be retrieved with the FScalar.getValue() method which returns generic Object.

Scalar persistency
Scalar persistency makes sense only for scalars declared with read-write access in a MIB. The scalar persistent value is saved into an external to the application persistent storage and the scalar value restored from it after the application startup. The scalar value becomes persistent only after successful explicit SNMP SET request from a MIB browser for non-volatile scalars. See also Persistency.

The scalar is volatile by default and its value is not stored in the persistent storage. Explicit call scalar.setVolatile(false) is required to enable scalar persistency. This call also loads scalar persistent value from external storage if scalar persistent value exists. This call does nothing for read-only scalar.

private DemoScalarRwMibFriend mib = new DemoScalarRwMibFriend();
. . .
FScalar scalar = mib.getImageSize();
scalar.setVolatile(false); // loads persistent value (if exist)
if (scalar.isPersistLoaded()) {
    ... use persistent value ...
} else {
    ... use default value ...
}

Scalar GET event and listener
The FScalar class supports listening GET events which are triggered by MIB browser GET requests. The following methods in the class allow adding and removing GET listeners:

public void addGetListener(FScalarGetListener l)
public void removeGetListener(FScalarGetListener l)

GET listener is called when a MIB browser requests a scalar value. The GET request from the MIB browser is received by the SNMP agent in the application. The SNMP agent keeps collection of all scalars as managed objects. The application is responsible to update the scalar value in the listener's code thus making the scalar value correctly reflect the application's current internal value associated with the scalar. This update is required for scalars which represent dynamic application values. Example of the code from demo application to update the scalar:

private JTextField tfImageSize = new JTextField();
. . .
private DemoScalarRwMibFriend mib = new DemoScalarRwMibFriend();
. . .
mib.getImageSize().addGetListener(new FScalarGetListener() {
    public void get(FScalar scalar) {
        scalar.setValueEx(tfImageSize.getText());
    }
});
See also Events and Listeners.

Scalar VALIDATE + SET event and listener
The FScalar class supports listening SET events which are triggered by MIB browser SET requests. Each SET event is prepended with VALIDATION event. Handling VALIDATION event is optional. It allows rejecting invalid SET request.

The following methods in the class allow adding and removing SET and optional VALIDATE listeners:

public void addValidationListener(FScalarValidationListener l)
public void removeValidationListener(FScalarValidationListener l)

public void addSetListener(FScalarSetListener l)
public void removeSetListener(FScalarSetListener l)

VALIDATE listener is called when a MIB browser attempts to SET a new value to the scalar. This listener makes no sense for read-only scalar and it is not called at all for it. Missing VALIDATE listener for read-write scalar allows MIB browser to set any new value.

SET listener is called when a MIB browser attempts to SET a new value and VALIDATION listener is missing or it allows the new value. The application is responsible for updating its internal copy of the value associated with the new scalar value and starting to use it.

Example of the listeners code to VALIDATE and SET scalar value:

private JTextField tfImageSize = new JTextField();
. . .
private DemoScalarRwMibFriend mib = new DemoScalarRwMibFriend();
. . .
// Validate new value
mib.getImageSize().addValidationListener(new FScalarValidationListener() {
    public ValueValidation validate(FScalar scalar, Object objValue) {
        String sValue = objValue.toString();
        if (some condition) {
            return ValueValidation.WRONG_VALUE;
        }
        return ValueValidation.SUCCESS;
    }
});  

// Set new value
mib.getImageSize().addSetListener(new FScalarSetListener() {
    public void get(FScalar scalar) {
        public void set(FScalar scalar) {
            tfImageSize.setText(scalar.getValue().toString());
        }
    }
});

Restore default value
Each read-write scalar over the lifetime has two sources of values: default value which is initially set in the agent and a value which is set from a MIB browser and preserved in a persistency storage. It is very difficult to rollback scalar value to a default after a new value is set from a MIB browser. The FriendlySNMP API provides an option to remove scalar value from persistency storage. The access to the scalars persistency storage is provided in FRIENDLY-SNMP-MIB. The agent is notified about this action via SET operation. It is agent's responsibility to handle this type of events with the following RESTORE DEFAULTS listeners:

public void addRestoreDefaultListener(FRestoreDefaultListener l)
public void removeRestoreDefaultListener(FRestoreDefaultListener l)

Missing RESTORE DEFAULT listeners is not a disaster for the application if the application is restarted to pick up the new persistency storage state.

Example of the listener code to RESTORE DEFAULT scalar value:

private final static String IMAGE_SIZE = "800x600"; 
private JTextField tfImageSize = new JTextField();
. . .
private DemoScalarRwMibFriend mib = new DemoScalarRwMibFriend();
. . .
mib.getImageSize().addRestoreDefaultListener(new FRestoreDefaultListener() {
    public void restoreDefault(FRestoreDefaultEvent e) throws FException {
        FScalar scalar = e.getScalar();
        scalar.setValue(IMAGE_SIZE);
        tfImageSize.setText(scalar.getValue().toString());
    }
});

Static and dynamic scalar
Static scalar has a value which never (or very rare) changes during a lifetime of the application. Usually these types of values are read-only scalars. Examples of such scalars are application name, working directory, startup time, and so on. The value of a static scalar should be set explicitly without using GET listeners overhead. Application may set value of a static scalar at startup and do not keep a working copy of this scalar value. MIB browser GET request is satisfied with a scalar value which is already set in the scalar object.

Dynamic scalar has a value which is changing frequently in the code. Application with read-only scalar should provide GET listener to handle MIB browser request to access this scalar. Application with read-write scalar should provide at minimum GET and SET listeners to handle MIB browser request to access this scalar.