JBoss Community Archive (Read Only)

JBoss AS 7.2

Design and define the model structure

The following example xml contains a valid subsystem configuration, we will see how to plug this in to JBoss AS 7 later in this tutorial.

<subsystem xmlns="urn:com.acme.corp.tracker:1.0">
   <deployment-types>
      <deployment-type suffix="sar" tick="10000"/>
      <deployment-type suffix="war" tick="10000"/>
   </deployment-types>
</subsystem>

Now when designing our model, we can either do a one to one mapping between the schema and the model or come up with something slightly or very different. To keep things simple, let us stay pretty true to the schema so that when executing a :read-resource(recursive=true) against our subsystem we'll see something like:

{
    "outcome" => "success",
    "result" => {"type" => {
        "sar" => {"tick" => "10000"},
        "war" => {"tick" => "10000"}
    }}
}
Each deployment-type in the xml becomes in the model a child resource of the subsystem's root resource. The child resource's child-type is type, and it is indexed by its suffix. Each type resource then contains the tick attribute.

We also need a name for our subsystem, to do that change com.acme.corp.tracker.extension.SubsystemExtension:

public class SubsystemExtension implements Extension {
    ...
    /** The name of our subsystem within the model. */
    public static final String SUBSYSTEM_NAME = "tracker";
    ...
Once we are finished our subsystem will be available under /subsystem=tracker.

The SubsystemExtension.initialize() method defines the model, currently it sets up the basics to add our subsystem to the model:

@Override
    public void initialize(ExtensionContext context) {
        //register subsystem with its model version
        final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, 1, 0);
        //register subsystem model with subsystem definition that defines all attributes and operations
        final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(SubsystemDefinition.INSTANCE);
        //register describe operation, note that this can be also registered in SubsystemDefinition
        registration.registerOperationHandler(DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);
        //we can register additional submodels here
        //
        subsystem.registerXMLElementWriter(parser);
    }
The registerSubsystem() call registers our subsystem with the extension context. At the end of the method we register our parser with the returned SubsystemRegistration to be able to marshal our subsystem's model back to the main configuration file when it is modified. We will add more functionality to this method later.

Registering the core subsystem model

Next we obtain a ManagementResourceRegistration by registering the subsystem model. This is a compulsory step for every new subsystem.

final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(SubsystemDefinition.INSTANCE);

It's parameter is an implementation of the ResourceDefinition interface, which means that when you call /subsystem=tracker:read-resource-description the information you see comes from model that is defined by SubsystemDefinition.INSTANCE.

public class SubsystemDefinition extends SimpleResourceDefinition {
    public static final SubsystemDefinition INSTANCE = new SubsystemDefinition();

    private SubsystemDefinition() {
        super(SubsystemExtension.SUBSYSTEM_PATH,
                SubsystemExtension.getResourceDescriptionResolver(null),
                //We always need to add an 'add' operation
                SubsystemAdd.INSTANCE,
                //Every resource that is added, normally needs a remove operation
                SubsystemRemove.INSTANCE);
    }

    @Override
    public void registerOperations(ManagementResourceRegistration resourceRegistration) {
        super.registerOperations(resourceRegistration);
        //you can register aditional operations here
    }

    @Override
    public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
        //you can register attributes here
    }
}

Since we need child resource type we need to add new ResourceDefinition,

The ManagementResourceRegistration obtained in SubsystemExtension.initialize() is then used to add additional operations or to register submodels to the /subsystem=tracker address. Every subsystem and resource must have an ADD method which can be achieved by the following line inside registerOperations in your ResourceDefinition or by providing it in constructor of your SimpleResourceDefinition just as we did in example above.

//We always need to add an 'add' operation
        resourceRegistration.registerOperationHandler(ADD, SubsystemAdd.INSTANCE, new DefaultResourceAddDescriptionProvider(resourceRegistration,descriptionResolver), false);

The parameters when registering an operation handler are:

  1. The name - i.e. ADD.

  2. The handler instance - we will talk more about this below

  3. The handler description provider - we will talk more about this below.

  4. Whether this operation handler is inherited - false means that this operation is not inherited, and will only apply to /subsystem=tracker. The content for this operation handler will be provided by 3.

Let us first look at the description provider which is quite simple since this operation takes no parameters. The addition of type children will be handled by another operation handler, as we will see later on.

There are two way to define DescriptionProvider, one is by defining it by hand using ModelNode, but as this has show to be very error prone there are lots of helper methods to help you automatically describe the model. Flowing example is done by manually defining Description provider for ADD operation handler

/**
     * Used to create the description of the subsystem add method
     */
    public static DescriptionProvider SUBSYSTEM_ADD = new DescriptionProvider() {
        public ModelNode getModelDescription(Locale locale) {
            //The locale is passed in so you can internationalize the strings used in the descriptions

            final ModelNode subsystem = new ModelNode();
            subsystem.get(OPERATION_NAME).set(ADD);
            subsystem.get(DESCRIPTION).set("Adds the tracker subsystem");

            return subsystem;
        }
    };

Or you can use API that helps you do that for you. For Add and Remove methods there are classes DefaultResourceAddDescriptionProvider and DefaultResourceRemoveDescriptionProvider that do work for you. In case you use SimpleResourceDefinition even that part is hidden from you.

resourceRegistration.registerOperationHandler(ADD, SubsystemAdd.INSTANCE, new DefaultResourceAddDescriptionProvider(resourceRegistration,descriptionResolver), false);
resourceRegistration.registerOperationHandler(REMOVE, SubsystemRemove.INSTANCE, new DefaultResourceRemoveDescriptionProvider(resourceRegistration,descriptionResolver), false);

For other operation handlers that are not add/remove you can use DefaultOperationDescriptionProvider that takes additional parameter of what is the name of operation and optional array of parameters/attributes operation takes. This is an example to register operation "add-mime" with two parameters:

container.registerOperationHandler("add-mime",
                MimeMappingAdd.INSTANCE,
                new DefaultOperationDescriptionProvider("add-mime", Extension.getResourceDescriptionResolver("container.mime-mapping"), MIME_NAME, MIME_VALUE));

When descriping an operation its description provider's OPERATION_NAME must match the name used when calling ManagementResourceRegistration.registerOperationHandler()

Next we have the actual operation handler instance, note that we have changed its populateModel() method to initialize the type child of the model.

class SubsystemAdd extends AbstractBoottimeAddStepHandler {

    static final SubsystemAdd INSTANCE = new SubsystemAdd();

    private SubsystemAdd() {
    }

    /** {@inheritDoc} */
    @Override
    protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
        log.info("Populating the model");
        //Initialize the 'type' child node
        model.get("type").setEmptyObject();
    }
    ....

SubsystemAdd also has a performBoottime() method which is used for initializing the deployer chain associated with this subsystem. We will talk about the deployers later on. However, the basic idea for all operation handlers is that we do any model updates before changing the actual runtime state.

The rule of thumb is that every thing that can be added, can also be removed so we have a remove handler for the subsystem registered
in SubsystemDefinition.registerOperations or just provide the operation handler in constructor.

//Every resource that is added, normally needs a remove operation
        registration.registerOperationHandler(REMOVE, SubsystemRemove.INSTANCE, DefaultResourceRemoveDescriptionProvider(resourceRegistration,descriptionResolver) , false);

SubsystemRemove extends AbstractRemoveStepHandler which takes care of removing the resource from the model so we don't need to override its performRemove() operation, also the add handler did not install any services (services will be discussed later) so we can delete the performRuntime() method generated by the archetype.

class SubsystemRemove extends AbstractRemoveStepHandler {

    static final SubsystemRemove INSTANCE = new SubsystemRemove();

    private final Logger log = Logger.getLogger(SubsystemRemove.class);

    private SubsystemRemove() {
    }
}

The description provider for the remove operation is simple and quite similar to that of the add handler where just name of the method changes.

Registering the subsystem child

The type child does not exist in our skeleton project so we need to implement the operations to add and remove them from the model.

First we need an add operation to add the type child, create a class called com.acme.corp.tracker.extension.TypeAddHandler. In this case we extend the org.jboss.as.controller.AbstractAddStepHandler class and implement the org.jboss.as.controller.descriptions.DescriptionProvider interface. org.jboss.as.controller.OperationStepHandler is the main interface for the operation handlers, and AbstractAddStepHandler is an implementation of that which does the plumbing work for adding a resource to the model.

class TypeAddHandler extends AbstractAddStepHandler implements DescriptionProvider {

    public static final TypeAddHandler INSTANCE = new TypeAddHandler();

    private TypeAddHandler() {
    }

Then we define subsystem model. Lets call it TypeDefinition and for ease of use let it extend SimpleResourceDefinition instead just implement ResourceDefinition.

public class TypeDefinition extends SimpleResourceDefinition {

 public static final TypeDefinition INSTANCE = new TypeDefinition();

 //we define attribute named tick
protected static final SimpleAttributeDefinition TICK =
new SimpleAttributeDefinitionBuilder(TrackerExtension.TICK, ModelType.LONG)
  .setAllowExpression(true)
  .setXmlName(TrackerExtension.TICK)
  .setFlags(AttributeAccess.Flag.RESTART_ALL_SERVICES)
  .setDefaultValue(new ModelNode(1000))
  .setAllowNull(false)
  .build();

private TypeDefinition(){
   super(TYPE_PATH, TrackerExtension.getResourceDescriptionResolver(TYPE),TypeAdd.INSTANCE,TypeRemove.INSTANCE);
}

@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration){
   resourceRegistration.registerReadWriteAttribute(TICK, null, TrackerTickHandler.INSTANCE);
}

}
Which will take care of describing the model for us. As you can see in example above we define SimpleAttributeDefinition named TICK, this is a mechanism to define Attributes in more type safe way and to add more common API to manipulate attributes. As you can see here we define default value of 1000 as also other constraints and capabilities. There could be other properties set such as validators, alternate names, xml name, flags for marking it attribute allows expressions and more.

Then we do the work of updating the model by implementing the populateModel() method from the AbstractAddStepHandler, which populates the model's attribute from the operation parameters. First we get hold of the model relative to the address of this operation (we will see later that we will register it against /subsystem=tracker/type=*), so we just specify an empty relative address, and we then populate our model with the parameters from the operation. There is operation validateAndSet on AttributeDefinition that helps us validate and set the model based on definition of the attribute.

@Override
    protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
         TICK.validateAndSet(operation,model);
    }

We then override the performRuntime() method to perform our runtime changes, which in this case involves installing a service into the controller at the heart of JBoss AS 7. (AbstractAddStepHandler.performRuntime() is similar to AbstractBoottimeAddStepHandler.performBoottime() in that the model is updated before runtime changes are made.

@Override
    protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model,
            ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers)
            throws OperationFailedException {
        String suffix = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.ADDRESS)).getLastElement().getValue();
        long tick = TICK.resolveModelAttribute(context,model).asLong();
        TrackerService service = new TrackerService(suffix, tick);
        ServiceName name = TrackerService.createServiceName(suffix);
        ServiceController<TrackerService> controller = context.getServiceTarget()
                .addService(name, service)
                .addListener(verificationHandler)
                .setInitialMode(Mode.ACTIVE)
                .install();
        newControllers.add(controller);
    }
}

Since the add methods will be of the format /subsystem=tracker/suffix=war:add(tick=1234), we look for the last element of the operation address, which is war in the example just given and use that as our suffix. We then create an instance of TrackerService and install that into the service target of the context and add the created service controller to the newControllers list.

The tracker service is quite simple. All services installed into JBoss AS 7 must implement the org.jboss.msc.service.Service interface.

public class TrackerService implements Service<TrackerService>{

We then have some fields to keep the tick count and a thread which when run outputs all the deployments registered with our service.

private AtomicLong tick = new AtomicLong(10000);

    private Set<String> deployments = Collections.synchronizedSet(new HashSet<String>());
    private Set<String> coolDeployments = Collections.synchronizedSet(new HashSet<String>());
    private final String suffix;

    private Thread OUTPUT = new Thread() {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(tick.get());
                    System.out.println("Current deployments deployed while " + suffix + " tracking active:\n" + deployments
                       + "\nCool: " + coolDeployments.size());
                } catch (InterruptedException e) {
                    interrupted();
                    break;
                }
            }
        }
    };

    public TrackerService(String suffix, long tick) {
        this.suffix = suffix;
        this.tick.set(tick);
    }

Next we have three methods which come from the Service interface. getValue() returns this service, start() is called when the service is started by the controller, stop is called when the service is stopped by the controller, and they start and stop the thread outputting the deployments.

@Override
    public TrackerService getValue() throws IllegalStateException, IllegalArgumentException {
        return this;
    }

    @Override
    public void start(StartContext context) throws StartException {
        OUTPUT.start();
    }

    @Override
    public void stop(StopContext context) {
        OUTPUT.interrupt();
    }

Next we have a utility method to create the ServiceName which is used to register the service in the controller.

public static ServiceName createServiceName(String suffix) {
        return ServiceName.JBOSS.append("tracker", suffix);
}

Finally we have some methods to add and remove deployments, and to set and read the tick. The 'cool' deployments will be explained later.

public void addDeployment(String name) {
        deployments.add(name);
    }

    public void addCoolDeployment(String name) {
        coolDeployments.add(name);
    }

    public void removeDeployment(String name) {
        deployments.remove(name);
        coolDeployments.remove(name);
    }

    void setTick(long tick) {
        this.tick.set(tick);
    }

    public long getTick() {
        return this.tick.get();
    }
}//TrackerService - end

Since we are able to add type children, we need a way to be able to remove them, so we create a com.acme.corp.tracker.extension.TypeRemoveHandler. In this case we extend AbstractRemoveStepHandler which takes care of removing the resource from the model so we don't need to override its performRemove() operationa. But we need to implement the DescriptionProvider method to provide the model description, and since the add handler installs the TrackerService, we need to remove that in the performRuntime() method.

public class TypeRemoveHandler extends AbstractRemoveStepHandler {

    public static final TypeRemoveHandler INSTANCE = new TypeRemoveHandler();

    private TypeRemoveHandler() {
    }


    @Override
    protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
        String suffix = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.ADDRESS)).getLastElement().getValue();
        ServiceName name = TrackerService.createServiceName(suffix);
        context.removeService(name);
    }

}

We then need a description provider for the type part of the model itself, so we modify TypeDefinitnion to registerAttribute

class TypeDefinition{
...
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration){
    resourceRegistration.registerReadWriteAttribute(TICK, null, TrackerTickHandler.INSTANCE);
}

}

Then finally we need to specify that our new type child and associated handlers go under /subsystem=tracker/type=* in the model by adding registering it with the model in SubsystemExtension.initialize(). So we add the following just before the end of the method.

@Override
public void initialize(ExtensionContext context)
{
 final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, 1, 0);
 final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(TrackerSubsystemDefinition.INSTANCE);
 //Add the type child
 ManagementResourceRegistration typeChild = registration.registerSubModel(TypeDefinition.INSTANCE);
 subsystem.registerXMLElementWriter(parser);
}

The above first creates a child of our main subsystem registration for the relative address type=*, and gets the typeChild registration.
To this we add the TypeAddHandler and TypeRemoveHandler.
The add variety is added under the name add and the remove handler under the name remove, and for each registered operation handler we use the handler singleton instance as both the handler parameter and as the DescriptionProvider.

Finally, we register tick as a read/write attribute, the null parameter means we don't do anything special with regards to reading it, for the write handler we supply it with an operation handler called TrackerTickHandler.
Registering it as a read/write attribute means we can use the :write-attribute operation to modify the value of the parameter, and it will be handled by TrackerTickHandler.

Not registering a write attribute handler makes the attribute read only.

TrackerTickHandler extends AbstractWriteAttributeHandler
directly, and so must implement its applyUpdateToRuntime and revertUpdateToRuntime method.
This takes care of model manipulation (validation, setting) but leaves us to do just to deal with what we need to do.

class TrackerTickHandler extends AbstractWriteAttributeHandler<Void> {

    public static final TrackerTickHandler INSTANCE = new TrackerTickHandler();

    private TrackerTickHandler() {
        super(TypeDefinition.TICK);
    }

    protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
              ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<Void> handbackHolder) throws OperationFailedException {

        modifyTick(context, operation, resolvedValue.asLong());

        return false;
    }

    protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, Void handback){
        modifyTick(context, operation, valueToRestore.asLong());
    }

    private void modifyTick(OperationContext context, ModelNode operation, long value) throws OperationFailedException {

        final String suffix = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.ADDRESS)).getLastElement().getValue();
        TrackerService service = (TrackerService) context.getServiceRegistry(true).getRequiredService(TrackerService.createServiceName(suffix)).getValue();
        service.setTick(value);
    }

}

The operation used to execute this will be of the form /subsystem=tracker/type=war:write-attribute(name=tick,value=12345) so we first get the suffix from the operation address, and the tick value from the operation parameter's resolvedValue parameter, and use that to update the model.

We then add a new step associated with the RUNTIME stage to update the tick of the TrackerService for our suffix. This is essential since the call to context.getServiceRegistry() will fail unless the step accessing it belongs to the RUNTIME stage.

When implementing execute(), you must call context.completeStep() when you are done.

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-13 13:29:27 UTC, last content change 2012-11-27 12:00:12 UTC.