JBoss Community Archive (Read Only)

JBoss AS 7.0

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) {
        final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME);
        final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(SubsystemProviders.SUBSYSTEM);
        //We always need to add an 'add' operation
        registration.registerOperationHandler(ADD, SubsystemAdd.INSTANCE, SubsystemProviders.SUBSYSTEM_ADD, false);
        //We always need to add a 'describe' operation
        registration.registerOperationHandler(DESCRIBE, SubsystemDescribeHandler.INSTANCE, SubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);

        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(SubsystemProviders.SUBSYSTEM);

It's parameter is an implementation of the DescriptionProvider interface, which means that when you call /subsystem=tracker:read-resource-description the information you see comes from SubsystemProviders.SUBSYSTEM, Let's modify it to reflect our new model:

public class SubsystemProviders {

    /**
     * Used to create the description of the subsystem
     */
    public static DescriptionProvider SUBSYSTEM = 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();
            //Change the description
            subsystem.get(DESCRIPTION).set("This subsystem tracks deployments of a given type");
            subsystem.get(HEAD_COMMENT_ALLOWED).set(true);
            subsystem.get(TAIL_COMMENT_ALLOWED).set(true);
            subsystem.get(NAMESPACE).set(SubsystemExtension.NAMESPACE);

            //Add information about the 'type' child
            subsystem.get(CHILDREN, "type", DESCRIPTION).set("Deployment types that should be tracked");
            subsystem.get(CHILDREN, "type", MIN_OCCURS).set(0);
            subsystem.get(CHILDREN, "type", MAX_OCCURS).set(Integer.MAX_VALUE);
            subsystem.get(CHILDREN, "type", MODEL_DESCRIPTION);

            return subsystem;
        }
    };

    ...

Since our node will have a child called type we have modified the end of the method to reflect this. The constants used are static imports from org.jboss.as.controller.descriptions.ModelDescriptionConstants. The DESCRIPTION and MODEL_DESCRIPTION entries are needed for all children, while the MIN_OCCURS and MAX_OCCURS entries give helpful hints about how many type entries are allowed.

The ManagementResourceRegistration obtained in SubsystemExtension.initialize() is then used to add additional operations to the /subsystem=tracker address. Every subsystem must have an ADD method which is achieved by the following line.

        //We always need to add an 'add' operation
        registration.registerOperationHandler(ADD, SubsystemAdd.INSTANCE, SubsystemProviders.SUBSYSTEM_ADD, 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.

public class SubsystemProviders {
    ...
    /**
     * 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(DESCRIPTION).set("Adds the tracker subsystem");

            return subsystem;
        }
    };
}

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 final Logger log = Logger.getLogger(SubsystemAdd.class);

    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.

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 document the operation in the DescriptionProvider method, in this case we have a boolean property called tick. The attribute is not required, and the default value is 10000

    @Override
    public ModelNode getModelDescription(Locale locale) {
        ModelNode node = new ModelNode();
        node.get(DESCRIPTION).set("Adds a tracked deployment type");
        node.get(REQUEST_PROPERTIES, "tick", DESCRIPTION).set("How often to output information about a tracked deployment");
        node.get(REQUEST_PROPERTIES, "tick", TYPE).set(ModelType.LONG);
        node.get(REQUEST_PROPERTIES, "tick", REQUIRED).set(false);
        node.get(REQUEST_PROPERTIES, "tick", DEFAULT).set(10000);
        return node;
    }

Then we do the work of updating the model by overriding 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.

    @Override
    protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
        //The default value is 10000 if it has not been specified
        long tick = 10000;
        //Read the value from the operation
        if (operation.hasDefined("tick")) {
            tick = operation.get("tick").asLong();
        }
        model.get("tick").set(tick);
    }

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();
        TrackerService service = new TrackerService(suffix, model.get("tick").asLong());
        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 implements DescriptionProvider {

    public static final TypeRemoveHandler INSTANCE = new TypeRemoveHandler();

    private TypeRemoveHandler() {
    }

    @Override
    public ModelNode getModelDescription(Locale locale) {
        ModelNode node = new ModelNode();
        node.get(DESCRIPTION).set("Removes a tracked deployment type");
        return node;
    }

    @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 SubsystemProviders to add the description provider.

class SubsystemProviders {
    ...
    /**
     * Used to create the description of the {@code type}  child
     */
    public static DescriptionProvider TYPE_CHILD = new DescriptionProvider() {
        @Override
        public ModelNode getModelDescription(Locale locale) {
            ModelNode node = new ModelNode();
            node.get(DESCRIPTION).set("Contains information about a tracked deployment type");
            node.get(ATTRIBUTES, "tick", DESCRIPTION).set("How often in milliseconds to output the information about the tracked deployments");
            node.get(ATTRIBUTES, "tick", TYPE).set(ModelType.LONG);
            node.get(ATTRIBUTES, "tick", REQUIRED).set(true);
            node.get(ATTRIBUTES, "tick", DEFAULT).set(10000);
            return node;
        }
    };
}

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);
        final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(SubsystemProviders.SUBSYSTEM);
        ...

        //Add the type child
        ManagementResourceRegistration typeChild = registration.registerSubModel(PathElement.pathElement("type"), SubsystemProviders.TYPE_CHILD);
        typeChild.registerOperationHandler(ModelDescriptionConstants.ADD, TypeAddHandler.INSTANCE, TypeAddHandler.INSTANCE);
        typeChild.registerOperationHandler(ModelDescriptionConstants.REMOVE, TypeRemoveHandler.INSTANCE, TypeRemoveHandler.INSTANCE);
        typeChild.registerReadWriteAttribute("tick", null, TrackerTickHandler.INSTANCE, Storage.CONFIGURATION);

        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 implements OperationStepHandler directly, and so must implement its execute method. This means there is no 'plumbing' taking care of separating the updates to the model from the runtime updates, so we need to do this ourselves.

class TrackerTickHandler implements OperationStepHandler {

    public static final TrackerTickHandler INSTANCE = new TrackerTickHandler();

    private TrackerTickHandler() {
    }

    @Override
    public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
        //Update the model
        final String suffix = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.ADDRESS)).getLastElement().getValue();
        final long tick = operation.require("value").asLong();
        ModelNode node = context.readResourceForUpdate(PathAddress.EMPTY_ADDRESS).getModel();
        node.get("tick").set(tick);

        //Add a step to perform the runtime update
        context.addStep(new OperationStepHandler() {
            @Override
            public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
                TrackerService service = (TrackerService)context.getServiceRegistry(true).getRequiredService(TrackerService.createServiceName(suffix)).getValue();
                service.setTick(tick);
                context.completeStep();
            }
        }, Stage.RUNTIME);
        context.completeStep();
    }
}

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 value 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:20:08 UTC, last content change 2011-09-13 20:33:11 UTC.