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 modelSubsystemRemove
is an Handler responsible for removing the subsystem resource from the modelSubsystemDefinition
contains the subsystem definition and the operations associated with itSubsystemExtension
-that reads and parses the XML subsystem configurationSubsystemDeploymentProcessor
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:
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:
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