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.
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:
We also need a name for our subsystem, to do that change com.acme.corp.tracker.extension.SubsystemExtension:
The SubsystemExtension.initialize() method defines the model, currently it sets up the basics to add our subsystem to the model:
Next we obtain a ManagementResourceRegistration by registering the subsystem model. This is a compulsory step for every new 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:
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.
The parameters when registering an operation handler are:
- The name - i.e. ADD.
- The handler instance - we will talk more about this below
- The handler description provider - we will talk more about this below.
- 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.
Next we have the actual operation handler instance, note that we have changed its populateModel() method to initialize the type child of the model.
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 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.
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
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.
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.
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.
We then have some fields to keep the tick count and a thread which when run outputs all the deployments registered with our service.
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.
Finally we have some methods to add and remove deployments, and to set and read the tick. The 'cool' deployments will be explained later.
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.
We then need a description provider for the type part of the model itself, so we modify SubsystemProviders to add the description provider.
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.
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.
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.|