The following example xml contains a valid subsystem configuration, we will see how to plug this in to WildFly 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 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.
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.
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.
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
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:
|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.
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.
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.
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.
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 define subsystem model. Lets call it TypeDefinition and for ease of use let it extend SimpleResourceDefinition instead just implement ResourceDefinition.
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.
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 WildFly. (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 WildFly 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.
Next we have a utility method to create the ServiceName which is used to register the service in the controller.
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 TypeDefinitnion to registerAttribute
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 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.
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.|