Creating a new subsystem in WildFly made simple

In this tutorial we will learn how to extend WildFly by adding a new subsystem. Although this operation involves several steps, by using the Maven wildfly-subsystem archetype you will get soon up to speed. . Below, I’ve outlined all the steps in a basic tutorial to get you started. Note that this tutorial assumes you have some familiarity with Java and WildFly administration.

A Maven archetype to extend WildFly

In the context of WildFly, a subsystem is a modular component that provides a specific set of functionalities or services within the application server. At first sight, adding a new subsystem is an intricate task as you can see from the documentation: https://docs.wildfly.org/28/Extending_WildFly.html

On the other hand, the usage of a Maven archetype greatly simplifies this process and especially the most tedious part which is creating the overall infrastructure we need both for the subsystem implementation and for the XML definition.

Let’s kickstart our first subsystem with the wildfly-subsystem archetype:

mvn archetype:generate -DgroupId=com.acme -DartifactId=example-subsystem -Dversion=1.0-SNAPSHOT -Dmodule=org.test.subsystem -Dpackage=com.acme.example -DarchetypeGroupId=org.wildfly.archetype -DarchetypeArtifactId=wildfly-subsystem -DarchetypeVersion=30.0.0.Final-SNAPSHOT

Here is a quick overview of the Maven project we have:

src
├── main
│   ├── java
│   │   └── com
│   │       └── acme
│   │           └── example
│   │               ├── deployment
│   │               │   └── SubsystemDeploymentProcessor.java
│   │               ├── SubsystemAdd.java
│   │               ├── SubsystemDefinition.java
│   │               ├── SubsystemExtension.java
│   │               └── SubsystemRemove.java
│   ├── resources
│   │   ├── com
│   │   │   └── acme
│   │   │       └── example
│   │   │           └── LocalDescriptions.properties
│   │   ├── META-INF
│   │   │   └── services
│   │   │       └── org.jboss.as.controller.Extension
│   │   ├── module
│   │   │   └── main
│   │   │       └── module.xml
│   │   └── schema
│   │       └── mysubsystem.xsd

The Java project includes a set of components such as:

  • SubsystemAdd is an Handler responsible for adding the subsystem resource to the model
  • SubsystemRemove is an Handler responsible for removing the subsystem resource from the model
  • SubsystemDefinition contains the subsystem definition and the operations associated with it
  • SubsystemExtension -that reads and parses the XML subsystem configuration
  • SubsystemDeploymentProcessor is a processor of new deployment resources

Additionally, the project also contains a Bundle (LocalDescription.properties) with a descriptions of operations, the module.xml definition and the XSD schema for the subsystem (mysubsystem.xsd).

Next, let’s dig into some of these Classes to include some simple operations that our subsystem will execute.

Our custom subsystem

Firstly, let’s check the Extension Class. This Class allows registering the subsystem Extension. For the purpose of this tutorial, we will focus on the two static Class variables that contain the subsystem namespace and name:

public class SubsystemExtension implements Extension {

    /**
     * The name space used for the {@code substystem} element
     */
    public static final String NAMESPACE = "urn:mycompany:mysubsystem:1.0";

    /**
     * The name of our subsystem within the model.
     */
    public static final String SUBSYSTEM_NAME = "mysubsystem";
    // Rest of the Class
}

From the Extension Class, we understand that the subsystem will be installed as follows in the server configuration:

<subsystem xmlns="urn:mycompany:mysubsystem:1.0"/>

Then, what happens when we add the subsystem in the Server configuration? Put it simply, WildFly Core will execute the SubsystemAdd Handler:

class SubsystemAdd extends AbstractBoottimeAddStepHandler {

    static final SubsystemAdd INSTANCE = new SubsystemAdd();

    private SubsystemAdd() {
        super(SubsystemDefinition.ATTRIBUTES);
    }

    @Override
    public void performBoottime(OperationContext context, ModelNode operation, Resource resource)
            throws OperationFailedException {

        installServices(context, operation, resource.getModel());

        context.addStep(new AbstractDeploymentChainStep() {
            public void execute(DeploymentProcessorTarget processorTarget) {
                processorTarget.addDeploymentProcessor(SubsystemExtension.SUBSYSTEM_NAME, SubsystemDeploymentProcessor.PHASE, SubsystemDeploymentProcessor.PRIORITY, new SubsystemDeploymentProcessor());

            }
        }, OperationContext.Stage.RUNTIME);

    }

    static void installServices(OperationContext context, ModelNode operation, ModelNode model) {
        // ADDED Service
        context.getServiceTarget().addService(SimpleService.NAME, new SimpleService()).install();
    }
}

Within the installService method we have added a new Service, the SimpleService. In a nutshell, when the subsystem starts, so will do the SimpleService.

Next, to compile our project, we need to add the SimpleService Class. Here is a minimal Service implementation that logs a message upon service start and stop:

public class SimpleService implements Service<SimpleService> {

    public static final ServiceName NAME = ServiceName.of("test", "service", "simple");

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

    @Override
    public void start(StartContext context) throws StartException {
        System.out.println("Started Service!");
    }

    @Override
    public void stop(StopContext context) {
        System.out.println("Stopped Service!");
    }

}

In the next section we will show how to install the subsystem on WildFly.

Installing the subsystem

Firstly, build the project with the install goal:

mvn install

Then, we will install the module ( from the target/module folder) into the application server’s module folder:

cp -r target/module/org $JBOSS_HOME/modules/system/layers/base

Finally, start WildFly and connect to it with the CLI. From the CLI, install first the extension and then the subsystem:

[standalone@localhost:9990 /] /extension=org.test.subsystem:add
{"outcome" => "success"}

[standalone@localhost:9990 /] /subsystem=mysubsystem:add
{
    "outcome" => "success",
    "response-headers" => {
        "operation-requires-reload" => true,
        "process-state" => "reload-required"
    }
}

Then, reload the application server configuration and verify that the Service SimpleService executes:

wildfly how to create a new subsystem

As you can see, our subsystem successfully started and triggered the Service SimpleService!

How to add Operations to your subsystem

As it is, our subsystem is an empty shell as it does not include any Operation within it. To add a new Operation, we need to register them in the SubsystemDefinition Class.

Let’s add a simple Operation which delegates to the Class SimpleDateHandler the execution of an Operation:

package com.acme.example;

import java.util.Arrays;
import java.util.Collection;

import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PersistentResourceDefinition;
import org.jboss.as.controller.registry.ManagementResourceRegistration;

 
public class SubsystemDefinition extends PersistentResourceDefinition {

    static final AttributeDefinition[] ATTRIBUTES = { /* you can include attributes here */ };

    static final SubsystemDefinition INSTANCE = new SubsystemDefinition();

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

    @Override
    public void registerOperations(ManagementResourceRegistration resourceRegistration) {
        super.registerOperations(resourceRegistration);
        //ADDED: we are registering a custom operation
        resourceRegistration.registerOperationHandler(SimpleDateHandler.DEFINITION, SimpleDateHandler.INSTANCE);
    }

    @Override
    public Collection<AttributeDefinition> getAttributes() {
        return Arrays.asList(ATTRIBUTES);
    }
}

The resource registration refers to the SimpleDateHandler Class which in turn implements the org.jboss.as.controller.OperationStepHandler Class.

package com.acme.example;

import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationDefinition;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.as.controller.descriptions.ResourceDescriptionResolver;
import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;

import org.jboss.as.server.controller.descriptions.ServerDescriptions;
import org.jboss.dmr.ModelNode;

public class SimpleDateHandler implements OperationStepHandler {

    public static final SimpleDateHandler INSTANCE = new SimpleDateHandler();

    static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder("getTime",
            SubsystemExtension.getResourceDescriptionResolver("time"))
            .setRuntimeOnly()
            .build();

    @Override
    public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {

        context.addStep(new OperationStepHandler() {
            @Override
            public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
                // Modifies state, so communicate that
                context.getServiceRegistry(true);

                System.out.println("Current time: " + new java.util.Date().toString());
                context.completeStep(OperationContext.RollbackHandler.NOOP_ROLLBACK_HANDLER);
            }
        }, OperationContext.Stage.RUNTIME);
    }
}

Within the execute method, we have included a simple print statements that outputs the current Time on the Console.

Mind it, you need also to add a description of the command with its command resolver in the LocalDescriptions.properties file:

mysubsystem.time.getTime=Gets the current time

Then, copy again the subsystem in the modules folder. Then, restart WildFly and execute the operation getTime on the subsystem mysubsystem:

/subsystem=mysubsystem:getTime
{"outcome" => "success"}

Finally, check on the WildFly Console to see the output with the current Time:

step-by-step guide to create a new wildfly subsystem

Processing Deployments

Finally, we will mention that our archetype also includes a Deployment Processor. You can use it for various purposes, such as validating a DeploymentUnit or executing operations upon a new deployment.

The following basic SubsystemDeploymentProcessor merely prints a message when you deploy a new application:

public class SubsystemDeploymentProcessor implements DeploymentUnitProcessor {

    Logger log = Logger.getLogger(SubsystemDeploymentProcessor.class);

    public static final Phase PHASE = Phase.DEPENDENCIES;

    public static final int PRIORITY = 0x4000;

    @Override
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        log.info("Deployed a new application!");
    }

    @Override
    public void undeploy(DeploymentUnit context) {
    }

}

Try deploying an application to see that the sample DeploymentProcessor kicks in:

22:46:02,793 INFO  [com.acme.example.deployment.SubsystemDeploymentProcessor] (MSC service thread 1-3) Deployed a new application!

Conclusion

Extending WildFly with custom subsystems offers an enhanced level of flexibility and adaptability for your Java applications. Whether you need to add specialized functionality, integrate with external systems, or enhance your server’s capabilities, creating a custom subsystem is the way to go.

Source code: https://github.com/fmarchioni/mastertheboss/tree/master/maven-archetype/example-subsystem