Version 4

    WARNING: This article is out of date as of AS 7.0.0.CR1. See the Extending JBoss AS 7 section of the official JBoss AS 7 documentation for correct information.

     

    This article is a quick description of the steps needed to add a new handler to an AS7 subsystem for handling a management operation.

     

    This is as of the AS 7.0.0.Beta1 release. Developers are encouraged to look at the latest code as a better source for understanding the process. Looking at the handlers in the org.jboss.as.web package in the AS 7 source tree's web/ module is a good approach.

     

    Handling the Operation

     

    1) The handler must implement the org.jboss.as.controller.OperationHandler interface or one of its subinterfaces.

     

    /**
     * Execute the operation, passing the result to {@code resultHandler}.  The submodel is not available
     * unless one of the subtypes of this interface is implemented.  This method <b>must</b> invoke one of
     * the completion methods on {@code resultHandler} regardless of the outcome of the operation.
     *
     *
     * @param context the context for this operation
     * @param operation the operation being executed
     * @param resultHandler the result handler to invoke when the operation is complete
     * @return a handle which may be used to asynchronously cancel this operation or retrieve the compensating model operation
     * @throws OperationFailedException If the operation fails to execute correctly
     */
    OperationResult execute(OperationContext context, ModelNode operation, ResultHandler resultHandler) throws OperationFailedException;
    

     

    2) Handlers that need to read or write the persistent management model (i.e. ones that don't solely deal with their subsystem's runtime services) must implement one of the following marker interfaces:

     

    • ModelQueryOperationHandler -- for handlers that only read the persistent management model
    • ModelAddOperationHandler -- for handlers that add a new addressable resource to the model. (Inserting an attribute value for an existing resource is different.) Typically all handlers that implement this interface would be for an operation named "add".
    • ModelRemoveOperationHandler -- for handlers that remove a addressable resource from the model. (Removing an attribute value for an existing resource to is different.) Typically all handlers that implement this interface would be for an operation named "remove".
    • ModelUpdateOperationHandler -- for handlers that update existing resources in the persistent management model.

     

    (Note: these marker interfaces may be replaced with a diferent mechanism for providing this information before AS 7.0.0.CR1)

     

    3) Handlers are encouraged to perform some validation on the parameters contained in the passed in operation ModelNode. See the org.jboss.as.controller.operations.validation package for some general purpose helpers.  If any parameter is invalid, the handler should throw OperationFailedException. OFE is the preferred exception for pretty much any failure condition.

     

    4) If the handler needs to read or write the persistent management model, they can get a reference to the portion of the model indicated by the operation's address by calling context.getSubModel() on the passed in OperationContext.

     

    5) If the operation doesn't throw an exception but the effect of the operation can be reversed via another operation, the operation should create a ModelNode that represents that "compensating operation" (E.g. an operation that sets an attribute to a new value would create a ModelNode that represents an operation to set the attribute back to the old value.) Pass this compensating operation out by calling

     

    return new BasicOperationResult(compensatingOperation)

     

    If there is no valid compensating operation (e.g. for a read-only operation) then

     

    return new BasicOperationResult();

     

    6) Handlers that seek to alter the runtime state are encouraged to do so asynchronously (i.e. after the call to OperationHandler.execute() returns . This is done by calling context.getRuntimeContext() on the passed in OperationContext.

     

    • If that method returns null, the controller that called the handler has determined that it's currently inappropriate to execute runtime operations. The handler should not attempt to alter runtime state.
    • Otherwise, the handler should create an object that implements the org.jboss.as.controller.RuntimeTask interface. This typical idiom is to create an anonymous inner class inside the OperationHandler.execute() method. This RuntimeTask should be passed to the calling context via a call to context.getRuntimeContext().setRuntimeTask(theTask)

     

    The ModelController that called OperationHandler.execute() will ensure that either the runtime task is run or the changes made by the handler to the model are discarded.

     

    The RuntimeTask must ensure that one (and only one) of the following methods is invoked on the passed in RuntimeHandler:

     

    • handleResultComplete() -- if the runtime task is able to perform its changes successfully
    • handleResultFailed(ModelNode failureDescription) -- if some problem occurs. The failureDescription should describe the problem.

     

    The RuntimeTask need not invoke the ResultHandler directly. A common idiom is to install or remove an MSC Service in the RuntimeTask and to add a listener that invokes the RuntimeHandler when the service is started, stopped or fails. See ResultHandler.ServiceStartListener and ResultHandler.ServiceRemoveListener for ready-to-use listener implementations.

     

    When the RuntimeTask executes, it is provided a RuntimeTaskContext from which it can get access to an MSC ServiceTarget and ServiceRegistry.

     

    7) All handlers must do one (and only one) of the following in the execute method:

     

    • Throw an exception (preferably OperationFailedException)
    • On the passed in ResultHandler, invoke resultHandler.handleResultComplete()
    • Register a RuntimeTask as described in 6) above.

     

    8) Handlers must be thread safe. The handler can assume that any object passed in is thread safe (including the model made available via OperationContext.getSubModel()).

     

    Describing the Operation

     

    All operations except "write-attribute" handlers (see below) must have a description that management API callers can access. The description is registered with the core management system along with the OperationHandler (see "Registering the Operation" below). The description must come from an implementation of the org.jboss.as.controller.descriptions.DescriptionProvider interface.

     

    /**
     * Gets the descriptive information (human-friendly description, list of attributes,
     * list of children) describing a single model node or operation.
     * <p>
     * The implementation must assume that the caller intends to modify the
     * returned {@code ModelNode} so it should not hand out a reference to any internal data structures.
     *
     * @param locale the locale to use to generate any localized text used in the description.
     *               May be {@code null}, in which case {@link Locale#getDefault()} should be used
     *
     * @return {@link ModelNode} describing the model node's structure
     */
    ModelNode getModelDescription(Locale locale);
    

     

    It is recommended that the handler implement the org.jboss.as.controller.descriptions.DescriptionProvider interface. When the OperationHandler implementation is registered, a DescriptionProvider impl must be registered with it, and having the same class implement both interfaces works well.

     

    See http://community.jboss.org/docs/DOC-16317 for details on what the DescriptionProvider should return.

     

    All free-form text output from a DescriptionProvider must be externalized into a ResourceBundle.

     

    It is strongly encouraged that DescriptionProvider implementations have minimal code, and instead invoke a static method in a separate utility class. A typical idiom is to call that utility class XYZSubsystemDescriptions. The goal here is to improve boot time performance by not loading all the often-verbose code needed to generate description when the DescriptionProvider implemenation is loaded. Instead the code is loaded when the DescriptionProvider is invoked, which in many cases (e.g. embedded testing) will never occur.

     

    Handlers for the "write-attribute" Operation

     

    The system includes a special operation called "write-attribute". This operation exists for every resource and takes two single parameters:

     

    • name -- the name of the attribute that is being written
    • value -- the new value of the attribute

     

    This operation is logically equivalent to a JMX setAttribute call.

     

    Calls to "write-attribute" will only be accepted for attributes that have had an OperationHandler registered (see below). The handler must know how to access the "name" and "value" parameters.

     

    There is no need to write a DescriptionProvider to associate with the OperationHandler for a "write-attribute" operation.

     

    Registering the Operation

     

    Your OperationHandler will only be invoked if the core management system knows about it. This is done by registering the handler as part of the subsystem's implementation of the org.jboss.as.controller.Extension interface, in the implementation of the Extension.initialize(ExtensionContext context) method.

     

    Assume that the resource to which your operation applies had been registered as follows:

     

    public void initialize(ExtensionContext context) { 
      final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME);
      final ModelNodeRegistration resource = subsystem.registerSubsystemModel(getTheRootResourceDescription());
    

     

    If your OperationHandler is a handler for the "write-attribute" operation, e.g. for the "foo" attribute, it would generally be registered as follows:

     

    resource.registerReadWriteAttribute("foo", null, handler, AttributeAccess.Storage.CONFIGURATION);
    

     

    The second parameter is usually null. It could be an OperationHandler that is able to handle the "read-attribute" operation for the "foo" attribute. If null, a default handler is used that reads the current attribute value from the persistent configuration model.

     

    The final parameter is either AttributeAccess.Storage.CONFIGURATION or AttributeAccess.Storage.RUNTIME. This indicates where the attribute value is stored. CONFIGURATION means it is stored in the persistent configuration; RUNTIME means it is stored in runtime memory only and the value will be lost on server restart.

     

    For all other operations, the OperationHandler is registered as follows:

     

    resource.registerOperationHandler("my-operation-name", handler, descriptionProvider);
    

     

    where "my-operation-name" is the name of the operation the end user would invoke to trigger your handler.

     

    A common idiom is to have the same class implement OperationHandler and DescriptionProvider, to make that class a singleton, and to declare the operation name as a constant on the handler class:

     

    resource.registerOperationHandler(MyHandler.OPERATION_NAME, MyHandler.INSTANCE, MyHandler.INSTANCE);
    

     

     

    There are some overloaded variants of ModelNodeRegistration.registerOperationHandler() that may be useful in some unusual situations. See the javadoc.