< Previous | Front page | Next >
Skip to end of metadata
Go to start of metadata

Target Audience

This document is intended for people who want to extend WildFly to introduce new capabilities.

Prerequisites

You should know how to download, install and run WildFly. If not please consult the Getting Started Guide. You should also be familiar with the management concepts from the Admin Guide, particularly the Core management concepts section and you need Java development experience to follow the example in this guide.

Examples in this guide

Most of the examples in this guide are being expressed as excerpts of the XML configuration files or by using a representation of the de-typed management model.

Overview

In this document we provide an example of how to extend the core functionality of WildFly via an extension and the subsystem it installs. The WildFly core is very simple and lightweight; most of the capabilities people associate with an application server are provided via extensions and their subsystems. The WildFly distribution includes many extensions and subsystems; the webserver integration is via a subsystem; the transaction manager integration is via a subsystem, the EJB container integration is via a subsystem, etc.

This document is divided into two main sections. The first is focused on learning by doing. This section will walk you through the steps needed to create your own subsystem, and will touch on most of the concepts discussed elsewhere in this guide. The second focuses on a conceptual overview of the key interfaces and classes described in the example. Readers should feel free to start with the second section if that better fits their learning style. Jumping back and forth between the sections is also a good strategy.

Example subsystem

Our example subsystem will keep track of all deployments of certain types containing a special marker file, and expose operations to see how long these deployments have been deployed.

Create the skeleton project

To make your life easier we have provided a maven archetype which will create a skeleton project for implementing subsystems.

Maven will download the archetype and it's dependencies, and ask you some questions:

  Instruction
1 Enter the groupId you wish to use
2 Enter the artifactId you wish to use
3 Enter the version you wish to use, or just hit Enter if you wish to accept the default 1.0-SNAPSHOT
4 Enter the java package you wish to use, or just hit Enter if you wish to accept the default (which is copied from groupId).
5 Enter the module name you wish to use for your extension.
6 Finally, if you are happy with your choices, hit Enter and Maven will generate the project for you.

You can also do this in Eclipse, see Creating your own application for more details. We now have a skeleton project that you can use to implement a subsystem. Import the acme-subsystem project into your favourite IDE. A nice side-effect of running this in the IDE is that you can see the javadoc of WildFly classes and interfaces imported by the skeleton code. If you do a mvn install in the project it will work if we plug it into WildFly, but before doing that we will change it to do something more useful.

The rest of this section modifies the skeleton project created by the archetype to do something more useful, and the full code can be found in acme-subsystem.zip.

If you do a mvn install in the created project, you will see some tests being run

We will talk about these later in the Testing the parsers section.

Create the schema

First, let us define the schema for our subsystem. Rename src/main/resources/schema/mysubsystem.xsd to src/main/resources/schema/acme.xsd. Then open acme.xsd and modify it to the following

Note that we modified the xmlns and targetNamespace values to urn.com.acme.corp.tracker:1.0. Our new subsystem element has a child called deployment-types, which in turn can have zero or more children called deployment-type. Each deployment-type has a required suffix attribute, and a tick attribute which defaults to true.

Now modify the com.acme.corp.tracker.extension.SubsystemExtension class to contain the new namespace.

Design and define the model structure

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:

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:

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:

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.

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:

  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

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.

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.

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

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.

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.

Parsing and marshalling of the subsystem xml

JBoss AS 7 uses the Stax API to parse the xml files. This is initialized in SubsystemExtension by mapping our parser onto our namespace:

We then need to write the parser. The contract is that we read our subsystem's xml and create the operations that will populate the model with the state contained in the xml. These operations will then be executed on our behalf as part of the parsing process. The entry point is the readElement() method.

So in the above we always create the add operation for our subsystem. Due to its address /subsystem=tracker defined by SUBSYSTEM_PATH this will trigger the SubsystemAddHandler we created earlier when we invoke /subsystem=tracker:add. We then parse the child elements and create an add operation for the child address for each type child. Since the address will for example be /subsystem=tracker/type=sar (defined by TYPE_PATH ) and TypeAddHandler is registered for all type subaddresses the TypeAddHandler will get invoked for those operations. Note that when we are parsing attribute tick we are using definition of attribute that we defined in TypeDefintion to parse attribute value and apply all rules that we specified for this attribute, this also enables us to property support expressions on attributes.

The parser is also used to marshal the model to xml whenever something modifies the model, for which the entry point is the writeContent() method:

Then we have to implement the SubsystemDescribeHandler which translates the current state of the model into operations similar to the ones created by the parser. The SubsystemDescribeHandler is only used when running in a managed domain, and is used when the host controller queries the domain controller for the configuration of the profile used to start up each server. In our case the SubsystemDescribeHandler adds the operation to add the subsystem and then adds the operation to add each type child. Since we are using ResourceDefinitinon for defining subsystem all that is generated for us, but if you want to customize that you can do it by implementing it like this.

Testing the parsers

Changes to tests between 7.0.0 and 7.0.1
The testing framework was moved from the archetype into the core JBoss AS 7 sources between JBoss AS 7.0.0 and JBoss AS 7.0.1, and has been improved upon and is used internally for testing JBoss AS 7's subsystems. The differences between the two versions is that in 7.0.0.Final the testing framework is bundled with the code generated by the archetype (in a sub-package of the package specified for your subsystem, e.g. com.acme.corp.tracker.support), and the test extends the AbstractParsingTest class.

From 7.0.1 the testing framework is now brought in via the org.jboss.as:jboss-as-subsystem-test maven artifact, and the test's superclass is org.jboss.as.subsystem.test.AbstractSubsystemTest. The concepts are the same but more and more functionality will be available as JBoss AS 7 is developed.

Now that we have modified our parsers we need to update our tests to reflect the new model. There are currently three tests testing the basic functionality, something which is a lot easier to debug from your IDE before you plug it into the application server. We will talk about these tests in turn and they all live in com.acme.corp.tracker.extension.SubsystemParsingTestCase. SubsystemParsingTestCase extends AbstractSubsystemTest which does a lot of the setup for you and contains utility methods for verifying things from your test. See the javadoc of that class for more information about the functionality available to you. And by all means feel free to add more tests for your subsystem, here we are only testing for the best case scenario while you will probably want to throw in a few tests for edge cases.

The first test we need to modify is testParseSubsystem(). It tests that the parsed xml becomes the expected operations that will be parsed into the server, so let us tweak this test to match our subsystem. First we tell the test to parse the xml into operations

There should be one operation for adding the subsystem itself and an operation for adding the deployment-type, so check we got two operations

Now check that the first operation is add for the address /subsystem=tracker:

Then check that the second operation is add for the address /subsystem=tracker, and that 12345 was picked up for the value of the tick parameter:

The second test we need to modify is testInstallIntoController() which tests that the xml installs properly into the controller. In other words we are making sure that the add operations we created earlier work properly. First we create the xml and install it into the controller. Behind the scenes this will parse the xml into operations as we saw in the last test, but it will also create a new controller and boot that up using the created operations

The returned KernelServices allow us to execute operations on the controller, and to read the whole model.

Now we make sure that the structure of the model within the controller has the expected format and values

The last test provided is called testParseAndMarshalModel(). It's main purpose is to make sure that our SubsystemParser.writeContent() works as expected. This is achieved by starting a controller in the same way as before

Now we read the model and the xml that was persisted from the first controller, and use that xml to start a second controller

Finally we read the model from the second controller, and make sure that the models are identical by calling compare() on the test superclass.

We then have a test that needs no changing from what the archetype provides us with. As we have seen before we start a controller

We then call /subsystem=tracker:describe which outputs the subsystem as operations needed to reach the current state (Done by our SubsystemDescribeHandler)

Then we create a new controller using those operations

And then we read the model from the second controller and make sure that the two subsystems are identical
ModelNode modelB = servicesB.readWholeModel();

To test the removal of the the subsystem and child resources we modify the testSubsystemRemoval() test provided by the archetype:

We provide xml for the subsystem installing a child, which in turn installs a TrackerService

Having installed the xml into the controller we make sure the TrackerService is there

This call from the subsystem test harness will call remove for each level in our subsystem, children first and validate
that the subsystem model is empty at the end.

Finally we check that all the services were removed by the remove handlers

For good measure let us throw in another test which adds a deployment-type and also changes its attribute at runtime. So first of all boot up the controller with the same xml we have been using so far

Now create an operation which does the same as the following CLI command /subsystem=tracker/type=foo:add(tick=1000)

Execute the operation and make sure it was successful

Read the whole model and make sure that the original data is still there (i.e. the same as what was done by testInstallIntoController()

Then make sure our new type has been added:

Then we call write-attribute to change the tick value of /subsystem=tracker/type=foo:

To give you exposure to other ways of doing things, now instead of reading the whole model to check the attribute, we call read-attribute instead, and make sure it has the value we set it to.

Since each type installs its own copy of TrackerService, we get the TrackerService for type=foo from the service container exposed by the kernel services and make sure it has the right value

TypeDefinition.TICK.

Add the deployers

When discussing SubsystemAddHandler we did not mention the work done to install the deployers, which is done in the following method:

This adds an extra step which is responsible for installing deployment processors. You can add as many as you like, or avoid adding any all together depending on your needs. Each processor has a Phase and a priority. Phases are sequential, and a deployment passes through each phases deployment processors. The priority specifies where within a phase the processor appears. See org.jboss.as.server.deployment.Phase for more information about phases.

In our case we are keeping it simple and staying with one deployment processor with the phase and priority created for us by the maven archetype. The phases will be explained in the next section. The deployment processor is as follows:

The deploy() method is called when a deployment is being deployed. In this case we look for the TrackerService instance for the service name created from the deployment's suffix. If there is one it means that we are meant to be tracking deployments with this suffix (i.e. TypeAddHandler was called for this suffix), and if we find one we add the deployment's name to it. Similarly undeploy() is called when a deployment is being undeployed, and if there is a TrackerService instance for the deployment's suffix, we remove the deployment's name from it.

Deployment phases and attachments

The code in the SubsystemDeploymentProcessor uses an attachment, which is the means of communication between the individual deployment processors. A deployment processor belonging to a phase may create an attachment which is then read further along the chain of deployment unit processors. In the above example we look for the Attachments.DEPLOYMENT_ROOT attachment, which is a view of the file structure of the deployment unit put in place before the chain of deployment unit processors is invoked.

As mentioned above, the deployment unit processors are organized in phases, and have a relative order within each phase. A deployment unit passes through all the deployment unit processors in that order. A deployment unit processor may choose to take action or not depending on what attachments are available. Let's take a quick look at what the deployment unit processors for in the phases described in org.jboss.as.server.deployment.Phase.

STRUCTURE

The deployment unit processors in this phase determine the structure of a deployment, and looks for sub deployments and metadata files.

PARSE

In this phase the deployment unit processors parse the deployment descriptors and build up the annotation index. Class-Path entries from the META-INF/MANIFEST.MF are added.

DEPENDENCIES

Extra class path dependencies are added. For example if deploying a war file, the commonly needed dependencies for a web application are added.

CONFIGURE_MODULE

In this phase the modular class loader for the deployment is created. No attempt should be made loading classes from the deployment until after this phase.

POST_MODULE

Now that our class loader has been constructed we have access to the classes. In this stage deployment processors may use the Attachments.REFLECTION_INDEX attachment which is a deployment index used to obtain members of classes in the deployment, and to invoke upon them, bypassing the inefficiencies of using java.lang.reflect directly.

INSTALL

Install new services coming from the deployment.

CLEANUP

Attachments put in place earlier in the deployment unit processor chain may be removed here.

Integrate with WildFly

Now that we have all the code needed for our subsystem, we can build our project by running mvn install

This will have built our project and assembled a module for us that can be used for installing it into WildFly 8. If you go to the target/module folder where you built the project you will see the module

The module.xml comes from src/main/resources/module/main/module.xml and is used to define your module. It says that it contains the acme-subsystem.jar:

And has a default set of dependencies needed by every subsystem created. If your subsystem requires additional module dependencies you can add them here before building and installing.

Note that the name of the module corresponds to the directory structure containing it. Now copy the target/module/com/acme/corp/tracker/main/ directory and its contents to $WFLY/modules/com/acme/corp/tracker/main/ (where $WFLY is the root of your WildFly install).

Next we need to modify $WFLY/standalone/configuration/standalone.xml. First we need to add our new module to the <extensions> section:

And then we have to add our subsystem to the <profile> section:

Adding this to a managed domain works exactly the same apart from in this case you need to modify $AS7/domain/configuration/domain.xml.

Now start up WildFly 8 by running $WFLY/bin/standalone.sh and you should see messages like these after the server has started, which means our subsystem has been added and our TrackerService is working:

If you run the command line interface you can execute some commands to see more about the subsystem. For example

will return a lot of information, including what we provided in the DescriptionProviders we created to document our subsystem.

To see the current subsystem state you can execute

We can remove both the deployment types which removes them from the model:

You should now see the output from the TrackerService instances having stopped.

Now, let's add the war tracker again:

and the WildFly 8 console should show the messages coming from the war TrackerService again.

Now let us deploy something. You can find two maven projects for test wars already built at test1.zip and test2.zip. If you download them and extract them to /Downloads/test1 and /Downloads/test2, you can see that /Downloads/test1/target/test1.war contains a META-INF/cool.txt while /Downloads/test2/target/test2.war does not contain that file. From CLI deploy test1.war first:

And you should now see the output from the war TrackerService list the deployments:

So our test1.war got picked up as a 'cool' deployment. Now if we deploy test2.war

You will see that deployment get picked up as well but since there is no META-INF/cool.txt it is not marked as a 'cool' deployment:

An undeploy

is also reflected in the TrackerService output:

Finally, we registered a write attribute handler for the tick property of the type so we can change the frequency

You should now see the output from the TrackerService happen every second

If you open $WFLY/standalone/configuration/standalone.xml you can see that our subsystem entry reflects the current state of the subsystem:

Expressions

Expressions are mechanism that enables you to support variables in your attributes, for instance when you want the value of attribute to be resolved using system / environment properties.

An example expression is

which means that the value should be taken from a system property named jboss.bind.address.management and if it is not defined use 127.0.0.1.

What expression types are supported

  • System properties, which are resolved using java.lang.System.getProperty(String key)
  • Environment properties, which are resolved using java.lang.System.getEnv(String name).
  • Security vault expressions, resolved against the security vault configured for the server or Host Controller that needs to resolve the expression.

In all cases, the syntax for the expression is

For an expression meant to be resolved against environment properties, the expression_to_resolve must be prefixed with env.. The portion after env. will be the name passed to java.lang.System.getEnv(String name).

Security vault expressions do not support default values (i.e. the 127.0.0.1 in the jboss.bind.address.management:127.0.0.1 example above.)

How to support expressions in subsystems

The easiest way is by using AttributeDefinition, which provides support for expressions just by using it correctly.

When we create an AttributeDefinition all we need to do is mark that is allows expressions. Here is an example how to define an attribute that allows expressions to be used.

Then later when you are parsing the xml configuration you should use the MY_ATTRIBUTE attribute definition to set the value to the management operation ModelNode you are creating.

Note that this just helps you to properly set the value to the model node you are working on, so no need to additionally set anything to the model for this attribute. Method parseAndSetParameter parses the value that was read from xml for possible expressions in it and if it finds any it creates special model node that defines that node is of type ModelType.EXPRESSION.

Later in your operation handlers where you implement populateModel and have to store the value from the operation to the configuration model you also use this MY_ATTRIBUTE attribute definition.

This will make sure that the attribute that is stored from the operation to the model is valid and nothing is lost. It also checks the value stored in the operation ModelNode, and if it isn't already ModelType.EXPRESSION, it checks if the value is a string that contains the expression syntax. If so, the value stored in the model will be of type ModelType.EXPRESSION. Doing this ensures that expressions are properly handled when they appear in operations that weren't created by the subsystem parser, but are instead passed in from CLI or admin console users.

As last step we need to use the value of the attribute. This is usually needed inside of the performRuntime method

As you can see resolving of attribute's value is not done until it is needed for use in the subsystem's runtime services. The resolved value is not stored in the configuration model, the unresolved expression is. That way we do not lose any information in the model and can assure that also marshalling is done properly, where we must marshall back the unresolved value.

Attribute definitinon also helps you with that:


Working with WildFly Capabilities

An extension to WildFly will likely want to make use of services provided by the WildFly kernel, may want to make use of services provided by other subsystems, and may wish to make functionality available to other extensions. Each of these cases involves integration between different parts of the system. In releases prior to WildFly 10, this kind of integration was done on an ad-hoc basis, resulting in overly tight coupling between different parts of the system and overly weak integration contracts. For example, a service installed by subsystem A might depend on a service installed by subsystem B, and to record that dependency A's authors copy a ServiceName from B's code, or even refer to a constant or static method from B's code. The result is B's code cannot evolve without risking breaking A. And the authors of B may not even intend for other subsystems to use its services. There is no proper integration contract between the two subsystems.

Beginning with WildFly Core 2 and WildFly 10 the WildFly kernel's management layer provides a mechanism for allowing different parts of the system to integrate with each other in a loosely coupled manner. This is done via WildFly Capabilities. Use of capabilities provides the following benefits:

  1. A standard way for system components to define integration contracts for their use by other system components.
  2. A standard way for system components to access integration contracts provided by other system components.
  3. A mechanism for configuration model referential integrity checking, such that if one component's configuration has an attribute that refers to an other component (e.g. a socket-binding attribute in a subsystem that opens a socket referring to that socket's configuration), the validity of that reference can be checked when validating the configuration model.

Capabilities

A capability is a piece of functionality used in a WildFly Core based process that is exposed via the WildFly Core management layer. Capabilities may depend on other capabilities, and this interaction between capabilities is mediated by the WildFly Core management layer.

Some capabilities are automatically part of a WildFly Core based process, but in most cases the configuration provided by the end user (i.e. in standalone.xml, domain.xml and host.xml) determines what capabilities are present at runtime. It is the responsibility of the handlers for management operations to register capabilities and to register any requirements those capabilities may have for the presence of other capabilities. This registration is done during the MODEL stage of operation execution

A capability has the following basic characteristics:

  1. It has a name.
  2. It may install an MSC service that can be depended upon by services installed by other capabilities. If it does, it provides a mechanism for discovering the name of that service.
  3. It may expose some other API not based on service dependencies allowing other capabilities to integrate with it at runtime.
  4. It may depend on, or require other capabilities.

During boot of the process, and thereafter whenever a management operation makes a change to the process' configuration, at the end of the MODEL stage of operation execution the kernel management layer will validate that all capabilities required by other capabilities are present, and will fail any management operation step that introduced an unresolvable requirement. This will be done before execution of the management operation proceeds to the RUNTIME stage, where interaction with the process' MSC Service Container is done. As a result, in the RUNTIME stage the handler for an operation can safely assume that the runtime services provided by a capability for which it has registered a requirement are available.

Comparison to other concepts

Capabilities vs modules

A JBoss Modules module is the means of making resources available to the classloading system of a WildFly Core based process. To make a capability available, you must package its resources in one or more modules and make them available to the classloading system. But a module is not a capability in and of itself, and simply copying a module to a WildFly installation does not mean a capability is available. Modules can include resources completely unrelated to management capabilities.

Capabilities vs Extensions

An extension is the means by which the WildFly Core management layer is made aware of manageable functionality that is not part of the WildFly Core kernel. The extension registers with the kernel new management resource types and handlers for operations on those resources. One of the things a handler can do is register or unregister a capability and its requirements. An extension may register a single capability, multiple capabilities, or possibly none at all. Further, not all capabilities are registered by extensions; the WildFly Core kernel itself may register a number of different capabilities.

Capability Names

Capability names are simple strings, with the dot character serving as a separator to allow namespacing.

The 'org.wildfly' namespace is reserved for projects associated with the WildFly organization on github (https://github.com/wildfly).

Statically vs Dynamically Named Capabilities

The full name of a capability is either statically known, or it may include a statically known base element and then a dynamic element. The dynamic part of the name is determined at runtime based on the address of the management resource that registers the capability. For example, the management resource at the address '/socket-binding-group=standard-sockets/socket-binding=web' will register a dynamically named capability named 'org.wildlfy.network.socket-binding.web'. The 'org.wildlfy.network.socket-binding' portion is the static part of the name.

All dynamically named capabilities that have the same static portion of their name should provide a consistent feature set and set of requirements.

Service provided by a capability

Typically a capability functions by registering a service with the WildFly process' MSC ServiceContainer, and then dependent capabilities depend on that service. The WildFly Core management layer orchestrates registration of those services and service dependencies by providing a means to discover service names.

Custom integration APIs provided by a capability

Instead of or in addition to providing MSC services, a capability may expose some other API to dependent capabilities. This API must be encapsulated in a single class (although that class can use other non-JRE classes as method parameters or return types).

Capability Requirements

A capability may rely on other capabilities in order to provide its functionality at runtime. The management operation handlers that register capabilities are also required to register their requirements.

There are three basic types of requirements a capability may have:

  • Hard requirements. The required capability must always be present for the dependent capability to function.
  • Optional requirements. Some aspect of the configuration of the dependent capability controls whether the depended on capability is actually necessary. So the requirement cannot be known until the running configuration is analyzed.
  • Runtime-only requirements. The dependent capability will check for the presence of the depended upon capability at runtime, and if present it will utilize it, but if it is not present it will function properly without the capability. There is nothing in the dependent capability's configuration that controls whether the depended on capability must be present. Only capabilities that declare themselves as being suitable for use as a runtime-only requirement should be depended upon in this manner.

Hard and optional requirements may be for either statically named or dynamically named capabilities. Runtime-only requirements can only be for statically named capabilities, as such a requirement cannot be specified via configuration, and without configuration the dynamic part of the required capability name is unknown.

Supporting runtime-only requirements

Not all capabilities are usable as a runtime-only requirement.

Any dynamically named capability is not usable as a runtime-only requirement.

For a capability to support use as a runtime-only requirement, it must guarantee that a configuration change to a running process that removes the capability will not impact currently running capabilities that have a runtime-only requirement for it. This means:

  • A capability that supports runtime-only usage must ensure that it never removes its runtime service except via a full process reload.
  • A capability that exposes a custom integration API generally is not usable as a runtime-only requirement. If such a capability does support use as a runtime-only requirement, it must ensure that any functionality provided via its integration API remains available as long as a full process reload has not occurred.

Capability Contract

A capability provides a stable contract to users of the capability. The contract includes the following:

  • The name of the capability (including whether it is dynamically named).
  • Whether it installs an MSC Service, and if it does, the value type of the service. That value type then becomes a stable API users of the capability can rely upon.
  • Whether it provides a custom integration API, and if it does, the type that represents that API. That type then becomes a stable API users of the capability can rely upon.
  • Whether the capability supports use as a runtime-only requirement.

Developers can learn about available capabilities and the contracts they provide by reading the WildFly capabilty registry.

Capability Registry

The WildFly organization on github maintains a git repo where information about available capabilities is published.

https://github.com/wildfly/wildfly-capabilities

Developers can learn about available capabilities and the contracts they provide by reading the WildFly capabilty registry.

The README.md file at the root of that repo explains the how to find out information about the registry.

Developers of new capabilities are strongly encouraged to document and register their capability by submitting a pull request to the wildfly-capabilities github repo. This both allows others to learn about your capability and helps prevent capability name collisions. Capabilities that are used in the WildFly or WildFly Core code base itself must have a registry entry before the code referencing them will be merged.

External organizations that create capabilities should include an organization-specific namespace as part their capability names to avoid name collisions.

Using Capabilities

Now that all the background information is presented, here are some specifics about how to use WildFly capabilities in your code.

Basics of Using Your Own Capability

Creating your capability

A capability is an instance of the immutable org.jboss.as.controller.capability.RuntimeCapability class. A capability is usually registered by a resource, so the usual way to use one is to store it in constant in the resource's ResourceDefinition. Use a RuntimeCapability.Builder to create one.

That creates a statically named capability named com.example.foo.

If the capability is dynamically named, add the dynamic parameter to state this:

Most capabilities install a service that requiring capabilities can depend on. If your capability does this, you need to declare the service's value type (the type of the object returned by org.jboss.msc.Service.getValue()). For example, if FOO_CAPABILITY provides a Service<javax.sql.DataSource>:

For a dynamic capability:

If the capability provides a custom integration API, you need to instantiate an instance of that API:

and provide it to the builder:

For a dynamic capability:

A capability can provide both a custom integration API and install a service:

Registering and unregistering your capability

Once you have your capability, you need to ensure it gets registered with the WildFly Core kernel when your resource is added. This is easily done simply by providing a reference to the capability to the resource's ResourceDefinition. This assumes your add handler is a subclass of the standard org.jboss.as.controller.SimpleResourceDefinition. SimpleResourceDefinition provides a Parameters class that provides a builder-style API for setting up all the data needed by your definition. This includes a setCapabilities method that can be used to declare the capabilities provided by resources of this type.

Your add handler needs to extend the standard org.jboss.as.controller.AbstractAddStepHandler class or one of its subclasses:

AbstractAddStepHandler's logic will register the capability when it executes.

Your remove handler must also extend of the standard org.jboss.as.controller.AbstractRemoveStepHandler or one of its subclasses.

AbstractRemoveStepHandler's logic will deregister the capability when it executes.

If for some reason you cannot base your ResourceDefinition on SimpleResourceDefinition or your handlers on AbstractAddStepHandler and AbstractRemoveStepHandler then you will need to take responsibility for registering the capability yourself. This is not expected to be a common situation. See the implementation of those classes to see how to do it.

Installing, accessing and removing the service provided by your capability

If your capability installs a service, you should use the RuntimeCapability when you need to determine the service's name. For example in the Stage.RUNTIME handling of your "add" step handler. Here's an example for a statically named capability:

If the capability is dynamically named, get the dynamic part of the name from the OperationContext and use that when getting the service name:

The same patterns should be used when accessing or removing the service in handlers for remove, write-attribute and custom operations.

If you use ServiceRemoveStepHandler for the remove operation, simply provide your RuntimeCapability to the ServiceRemoveStepHandler constructor and it will automatically remove your capability's service when it executes.

Basics of Using Other Capabilities

When a capability needs another capability, it only refers to it by its string name. A capability should not reference the RuntimeCapability object of another capability.

Before a capability can look up the service name for a required capability's service, or access its custom integration API, it must first register a requirement for the capability. This must be done in Stage.MODEL, while service name lookups and accessing the custom integration API is done in Stage.RUNTIME.

Registering a requirement for a capability is simple.

Registering a hard requirement for a static capability

If your capability has a hard requirement for a statically named capability, simply declare that to the builder for your RuntimeCapability. For example, WildFly's JTS capability requires both a basic transaction support capability and IIOP capabilities:

When your capability is registered with the system, the WildFly Core kernel will automatically register any static hard requirements declared this way.

Registering a requirement for a dynamically named capability

If the capability you require is dynamically named, usually your capability's resource will include an attribute whose value is the dynamic part of the required capability's name. You should declare this fact in the AttributeDefinition for the attribute using the SimpleAttributeDefinitionBuilder.setCapabilityReference method.

For example, the WildFly "remoting" subsystem's "org.wildfly.remoting.connector" capability has a requirement for a dynamically named socket-binding capability:

If the "add" operation handler for your resource extends AbstractAddStepHandler and the handler for write-attribute extends AbstractWriteAttributeHandler, the declaration above is sufficient to ensure that the appropriate capability requirement will be registered when the attribute is modified.

Depending upon a service provided by another capability

Once the requirement for the capability is registered, your OperationStepHandler}}s can use the {{OperationContext to discover the name of the service provided by the required capability.

For example, the "add" handler for a remoting connector uses the OperationContext to find the name of the needed {{SocketBinding} service:

That service name is then used to add a dependency on the SocketBinding service to the remoting connector service.

If the required capability isn't dynamically named, OperationContext exposes an overloaded getCapabilityServiceName variant. For example, if a capability requires a remoting Endpoint:

Using a custom integration API provided by another capability

In your Stage.RUNTIME handler, use OperationContext.getCapabilityRuntimeAPI to get a reference to the required capability's custom integration API. Then use it as necessary.

Runtime-only requirements

If your capability has a runtime-only requirement for another capability, that means that if that capability is present in Stage.RUNTIME you'll use it, and if not you won't. There is nothing about the configuration of your capability that triggers the need for the other capability; you'll just use it if it's there.

In this case, use OperationContext.hasOptionalCapability in your Stage.RUNTIME handler to check if the capability is present:

The WildFly Core kernel will not register a requirement for the "com.example.bar" capability, so if a configuration change occurs that means that capability will no longer be present, that change will not be rolled back. Because of this, runtime-only requirements can only be used with capabilities that declare in their contract that they support such use.

Using a capability in a DeploymentUnitProcessor

{{DeploymentUnitProcessor}}s are likely to have a need to interact with capabilities, in order to create service dependencies from a deployment service to a capability provided service or to access some aspect of a capability's custom integration API that relates to deployments.

If a DeploymentUnitProcessor associated with a capability implementation needs to utilize its own capability object, the DeploymentUnitProcessor authors should simply provide it with a reference to the RuntimeCapability instance. Service name lookups or access to the capabilities custom integration API can then be performed by invoking the methods on the RuntimeCapability.

If you need to access service names or a custom integration API associated with a different capability, you will need to use the org.jboss.as.controller.capability.CapabilityServiceSupport object associated with the deployment unit. This can be found as an attachment to the DeploymentPhaseContext:

Once you have the CapabilityServiceSupport you can use it to look up service names:

It's important to note that when you request a service name associated with a capability, the CapabilityServiceSupport will give you one regardless of whether the capability is actually registered with the kernel. If the capability isn't present, any service dependency your DUP creates using that service name will eventually result in a service start failure, due to the missing dependency. This behavior of not failing immediately when the capability service name is requested is deliberate. It allows deployment operations that use the rollback-on-runtime-failure=false header to successfully install (but not start) all of the services related to a deployment. If a subsequent operation adds the missing capability, the missing service dependency problem will then be resolved and the MSC service container will automatically start the deployment services.

You can also use the CapabilityServiceSupport to obtain a reference to the capability's custom integration API:

Note that here, unlike the case with service name lookups, the CapabilityServiceSupport will throw a checked exception if the desired capability is not installed. This is because the kernel has no way to satisfy the request for a custom integration API if the capability is not installed. The DeploymentUnitProcessor will need to catch and handle the exception.

Detailed API

The WildFly Core kernel's API for using capabilities is covered in detail in the javadoc for the RuntimeCapability and RuntimeCapability.Builder classes and the OperationContext and CapabilityServiceSupport interfaces.

Many of the methods in OperationContext related to capabilities have to do with registering capabilities or registering requirements for capabilities. Typically non-kernel developers won't need to worry about these, as the abstract OperationStepHandler implementations provided by the kernel take care of this for you, as described in the preceding sections. If you do find yourself in a situation where you need to use these in an extension, please read the javadoc thoroughly.

Key Interfaces and Classes Relevant to Extension Developers

In the first major section of this guide, we provided an example of how to implement an extension to the AS. The emphasis there was learning by doing. In this section, we'll focus a bit more on the major WildFly interfaces and classes that most are relevant to extension developers. The best way to learn about these interfaces and classes in detail is to look at their javadoc. What we'll try to do here is provide a brief introduction of the key items and how they relate to each other.

Before digging into this section, readers are encouraged to read the "Core Management Concepts" section of the Admin Guide.

Extension Interface

The org.jboss.as.controller.Extension interface is the hook by which your extension to the core AS is able to integrate with the AS. During boot of the AS, when the <extension> element in the AS's xml configuration file naming your extension is parsed, the JBoss Modules module named in the element's name attribute is loaded. The standard JDK java.lang.ServiceLoader mechanism is then used to load your module's implementation of this interface.

The function of an Extension implementation is to register with the core AS the management API, xml parsers and xml marshallers associated with the extension module's subsystems. An Extension can register multiple subsystems, although the usual practice is to register just one per extension.

Once the Extension is loaded, the core AS will make two invocations upon it:

  • void initializeParsers(ExtensionParsingContext context)

When this is invoked, it is the Extension implementation's responsibility to initialize the XML parsers for this extension's subsystems and register them with the given ExtensionParsingContext. The parser's job when it is later called is to create org.jboss.dmr.ModelNode objects representing WildFly management API operations needed make the AS's running configuration match what is described in the xml. Those management operation {{ModelNode}}s are added to a list passed in to the parser.

A parser for each version of the xml schema used by a subsystem should be registered. A well behaved subsystem should be able to parse any version of its schema that it has ever published in a final release.

  • void initialize(ExtensionContext context)

When this is invoked, it is the Extension implementation's responsibility to register with the core AS the management API for its subsystems, and to register the object that is capable of marshalling the subsystem's in-memory configuration back to XML. Only one XML marshaller is registered per subsystem, even though multiple XML parsers can be registered. The subsystem should always write documents that conform to the latest version of its XML schema.

The registration of a subsystem's management API is done via the ManagementResourceRegistration interface. Before discussing that interface in detail, let's describe how it (and the related Resource interface) relate to the notion of managed resources in the AS.

WildFly Managed Resources

Each subsystem is responsible for managing one or more management resources. The conceptual characteristics of a management resource are covered in some detail in the Admin Guide; here we'll just summarize the main points. A management resource has

  • An address consisting of a list of key/value pairs that uniquely identifies a resource
  • Zero or more attributes, the value of which is some sort of org.jboss.dmr.ModelNode
  • Zero or more supported operations. An operation has a string name and zero or more parameters, each of which is a key/value pair where the key is a string naming the parameter and the value is some sort of ModelNode
  • Zero or children, each of which in turn is a managed resource

The implementation of a managed resource is somewhat analogous to the implementation of a Java object. A managed resource will have a "type", which encapsulates API information about that resource and logic used to implement that API. And then there are actual instances of the resource, which primarily store data representing the current state of a particular resource. This is somewhat analogous to the "class" and "object" notions in Java.

A managed resource's type is encapsulated by the org.jboss.as.controller.registry.ManagementResourceRegistration the core AS creates when the type is registered. The data for a particular instance is encapsulated in an implementation of the org.jboss.as.controller.registry.Resource interface.

ManagementResourceRegistration Interface

TODO

ResourceDefinition Interface

TODO

Most commonly used implementation: SimpleResourceDefinition

ResourceDescriptionResolver

TODO

Most commonly used implementation: StandardResourceDescriptionResolver

AttributeDefinition Interface

TODO

Most commmonly used implementation: SimpleAttributeDefinition. Use SimpleAttributeDefinitionBuilder to build.

OperationDefinition and OperationStepHandler Interfaces

TODO

Operation Execution and the OperationContext

TODO

Resource Interface

TODO

DeploymentUnitProcessor Interface

TODO

Useful classes for implementing OperationStepHandler

TODO

 CLI Extensibility for Layered Products

In addition to supporting the ServiceLoader extension mechanism to load command handlers coming from outside of the CLI codebase, starting from wildfly-core-1.0.0.Beta1 release the CLI running in a modular classloading environment can be extended with commands exposed in server extension modules. The CLI will look for and register extension commands when it (re-)connects to the controller by iterating through the registered by that time extensions and using the ServiceLoader mechanism on the extension modules. (Note, that this mechanism will work only for extensions available in the server installation the CLI is launched from.)

Here is an example of a simple command handler and its integration.

The command will simply print a message to the terminal. The next step is to implement the CLI CommandHandlerProvider interface.

The final step is to include META-INF/services/org.jboss.as.cli.CommandHandlerProvider entry into the JAR file containing the classes above with value org.jboss.as.test.cli.extensions.ExtCommandHandlerProvider.

All WildFly documentation

Documentation

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jan 09, 2013

    There has apparently been some refactoring since the article was written? I noticed that nowadays the subsystem registration has the class

    public class TrackerSubsystemDefinition extends SimpleResourceDefinition {

    and it has been simplified a bit.
    public class TrackerSubsystemDefinition extends SimpleResourceDefinition {public class TrackerSubsystemDefinition extends SimpleResourceDefinition {