[[Example_subsystem]] = 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]] == Create the skeleton project To make your life easier we have provided a maven archetype which will create a skeleton project for implementing subsystems. [source, bash] ---- mvn archetype:generate \ -DarchetypeArtifactId=wildfly-subsystem \ -DarchetypeGroupId=org.wildfly.archetypes \ -DarchetypeVersion=8.0.0.Final \ -DarchetypeRepository=http://repository.jboss.org/nexus/content/groups/public ---- Maven will download the archetype and it's dependencies, and ask you some questions: [source, bash] ---- $ mvn archetype:generate \ -DarchetypeArtifactId=wildfly-subsystem \ -DarchetypeGroupId=org.wildfly.archetypes \ -DarchetypeVersion=8.0.0.Final \ -DarchetypeRepository=http://repository.jboss.org/nexus/content/groups/public [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Stub Project (No POM) 1 [INFO] ------------------------------------------------------------------------ [INFO]   .........   Define value for property 'groupId': : com.acme.corp Define value for property 'artifactId': : acme-subsystem Define value for property 'version': 1.0-SNAPSHOT: : Define value for property 'package': com.acme.corp: : com.acme.corp.tracker Define value for property 'module': : com.acme.corp.tracker [INFO] Using property: name = WildFly subsystem project Confirm properties configuration: groupId: com.acme.corp artifactId: acme-subsystem version: 1.0-SNAPSHOT package: com.acme.corp.tracker module: com.acme.corp.tracker name: WildFly subsystem project Y: : Y [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1:42.563s [INFO] Finished at: Fri Jul 08 14:30:09 BST 2011 [INFO] Final Memory: 7M/81M [INFO] ------------------------------------------------------------------------ $ ---- [cols=",",options="header"] |======================================================================= | |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. |======================================================================= 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 link:downloads/acme-subsystem.zip[acme-subsystem.zip]. If you do a `mvn install` in the created project, you will see some tests being run [source,bash] ---- $mvn install [INFO] Scanning for projects... [...] [INFO] Surefire report directory: /Users/kabir/sourcecontrol/temp/archetype-test/acme-subsystem/target/surefire-reports   ------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.acme.corp.tracker.extension.SubsystemBaseParsingTestCase Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.424 sec Running com.acme.corp.tracker.extension.SubsystemParsingTestCase Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.074 sec   Results :   Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [...] ---- We will talk about these later in the <> section. [[create-the-schema]] == 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 [source, xml] ----   ---- 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. [source, java] ---- public class SubsystemExtension implements Extension {   /** The name space used for the {@code substystem} element */ public static final String NAMESPACE = "urn:com.acme.corp.tracker:1.0"; ... ---- [[design-and-define-the-model-structure]] == 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. [source,xml] ---- ---- 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: [source, ruby] ---- { "outcome" => "success", "result" => {"type" => { "sar" => {"tick" => "10000"}, "war" => {"tick" => "10000"} }} } ---- 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`: [source, java] ---- public class SubsystemExtension implements Extension { ... /** The name of our subsystem within the model. */ public static final String SUBSYSTEM_NAME = "tracker"; ... ---- 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: [source, java] ---- @Override public void initialize(ExtensionContext context) { //register subsystem with its model version   final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, 1, 0); //register subsystem model with subsystem definition that defines all attributes and operations         final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(SubsystemDefinition.INSTANCE); //register describe operation, note that this can be also registered in SubsystemDefinition registration.registerOperationHandler(DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);   //we can register additional submodels here // subsystem.registerXMLElementWriter(parser); } ---- 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]] === Registering the core subsystem model Next we obtain a `ManagementResourceRegistration` by registering the subsystem model. This is a *compulsory* step for every new subsystem. [source, java] ---- final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(SubsystemDefinition.INSTANCE); ---- Its 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`. [source, java] ---- public class SubsystemDefinition extends SimpleResourceDefinition {     public 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);         //you can register aditional operations here     }       @Override     public void registerAttributes(ManagementResourceRegistration resourceRegistration) {         //you can register attributes here     } } ---- 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. [source, java] ---- //We always need to add an 'add' operation resourceRegistration.registerOperationHandler(ADD, SubsystemAdd.INSTANCE, new DefaultResourceAddDescriptionProvider(resourceRegistration,descriptionResolver), false); ---- 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. Following example is done by manually defining Description provider for ADD operation handler [source, java] ---- /** * Used to create the description of the subsystem add method */ public static DescriptionProvider SUBSYSTEM_ADD = new DescriptionProvider() { public ModelNode getModelDescription(Locale locale) { //The locale is passed in so you can internationalize the strings used in the descriptions   final ModelNode subsystem = new ModelNode(); subsystem.get(OPERATION_NAME).set(ADD); subsystem.get(DESCRIPTION).set("Adds the tracker subsystem");   return subsystem; } }; ---- 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. [source, java] ---- 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: [source, java] ---- container.registerOperationHandler("add-mime",                 MimeMappingAdd.INSTANCE,                 new DefaultOperationDescriptionProvider("add-mime", Extension.getResourceDescriptionResolver("container.mime-mapping"), MIME_NAME, MIME_VALUE)); ---- [NOTE] 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. [source, java] ---- class SubsystemAdd extends AbstractBoottimeAddStepHandler {   static final SubsystemAdd INSTANCE = new SubsystemAdd();   private SubsystemAdd() { }   /** {@inheritDoc} */ @Override protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { log.info("Populating the model"); //Initialize the 'type' child node model.get("type").setEmptyObject(); } .... ---- `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. [source, java] ---- //Every resource that is added, normally needs a remove operation registration.registerOperationHandler(REMOVE, SubsystemRemove.INSTANCE, DefaultResourceRemoveDescriptionProvider(resourceRegistration,descriptionResolver) , false); ---- `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. [source, java] ---- class SubsystemRemove extends AbstractRemoveStepHandler {   static final SubsystemRemove INSTANCE = new SubsystemRemove();   private final Logger log = Logger.getLogger(SubsystemRemove.class);   private SubsystemRemove() { } } ---- 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]] === 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. [source, java] ---- class TypeAddHandler extends AbstractAddStepHandler implements DescriptionProvider {   public static final TypeAddHandler INSTANCE = new TypeAddHandler();   private TypeAddHandler() { } ---- Then we define subsystem model. Lets call it `TypeDefinition` and for ease of use let it extend `SimpleResourceDefinition` instead just implement `ResourceDefinition`. [source, java] ---- public class TypeDefinition extends SimpleResourceDefinition {    public static final TypeDefinition INSTANCE = new TypeDefinition();    //we define attribute named tick protected static final SimpleAttributeDefinition TICK = new SimpleAttributeDefinitionBuilder(TrackerExtension.TICK, ModelType.LONG) .setAllowExpression(true) .setXmlName(TrackerExtension.TICK) .setFlags(AttributeAccess.Flag.RESTART_ALL_SERVICES) .setDefaultValue(new ModelNode(1000)) .setAllowNull(false) .build();   private TypeDefinition(){   super(TYPE_PATH, TrackerExtension.getResourceDescriptionResolver(TYPE),TypeAdd.INSTANCE,TypeRemove.INSTANCE); }   @Override public void registerAttributes(ManagementResourceRegistration resourceRegistration){ resourceRegistration.registerReadWriteAttribute(TICK, null, TrackerTickHandler.INSTANCE); }   } ---- 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. [source, java] ---- @Override protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { TICK.validateAndSet(operation,model); } ---- 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. [source, java] ---- @Override protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { String suffix = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.ADDRESS)).getLastElement().getValue(); long tick = TICK.resolveModelAttribute(context,model).asLong(); TrackerService service = new TrackerService(suffix, tick); ServiceName name = TrackerService.createServiceName(suffix); ServiceController controller = context.getServiceTarget() .addService(name, service) .addListener(verificationHandler) .setInitialMode(Mode.ACTIVE) .install(); newControllers.add(controller); } } ---- 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. [source, java] ---- public class TrackerService implements Service{ ---- We then have some fields to keep the tick count and a thread which when run outputs all the deployments registered with our service. [source, java] ---- private AtomicLong tick = new AtomicLong(10000);   private Set deployments = Collections.synchronizedSet(new HashSet()); private Set coolDeployments = Collections.synchronizedSet(new HashSet()); private final String suffix;   private Thread OUTPUT = new Thread() { @Override public void run() { while (true) { try { Thread.sleep(tick.get()); System.out.println("Current deployments deployed while " + suffix + " tracking active:\n" + deployments + "\nCool: " + coolDeployments.size()); } catch (InterruptedException e) { interrupted(); break; } } } };   public TrackerService(String suffix, long tick) { this.suffix = suffix; this.tick.set(tick); } ---- 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. [source, java] ---- @Override public TrackerService getValue() throws IllegalStateException, IllegalArgumentException { return this; }   @Override public void start(StartContext context) throws StartException { OUTPUT.start(); }   @Override public void stop(StopContext context) { OUTPUT.interrupt(); } ---- Next we have a utility method to create the `ServiceName` which is used to register the service in the controller. [source, java] ---- public static ServiceName createServiceName(String suffix) { return ServiceName.JBOSS.append("tracker", suffix); } ---- Finally we have some methods to add and remove deployments, and to set and read the `tick`. The 'cool' deployments will be explained later. [source, java] ---- public void addDeployment(String name) { deployments.add(name); }   public void addCoolDeployment(String name) { coolDeployments.add(name); }   public void removeDeployment(String name) { deployments.remove(name); coolDeployments.remove(name); }   void setTick(long tick) { this.tick.set(tick); }   public long getTick() { return this.tick.get(); } }//TrackerService - end ---- 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. [source, java] ---- public class TypeRemoveHandler extends AbstractRemoveStepHandler {   public static final TypeRemoveHandler INSTANCE = new TypeRemoveHandler();   private TypeRemoveHandler() { }     @Override protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { String suffix = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.ADDRESS)).getLastElement().getValue(); ServiceName name = TrackerService.createServiceName(suffix); context.removeService(name); }   } ---- We then need a description provider for the `type` part of the model itself, so we modify TypeDefinitnion to registerAttribute [source, java] ---- class TypeDefinition{ ... @Override public void registerAttributes(ManagementResourceRegistration resourceRegistration){ resourceRegistration.registerReadWriteAttribute(TICK, null, TrackerTickHandler.INSTANCE); }   } ---- 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. [source, java] ---- @Override public void initialize(ExtensionContext context) { final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, 1, 0); final ManagementResourceRegistration registration = subsystem.registerSubsystemModel(TrackerSubsystemDefinition.INSTANCE); //Add the type child ManagementResourceRegistration typeChild = registration.registerSubModel(TypeDefinition.INSTANCE); subsystem.registerXMLElementWriter(parser); } ---- 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. [source, java] ---- class TrackerTickHandler extends AbstractWriteAttributeHandler {   public static final TrackerTickHandler INSTANCE = new TrackerTickHandler();   private TrackerTickHandler() { super(TypeDefinition.TICK); }   protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder handbackHolder) throws OperationFailedException {   modifyTick(context, operation, resolvedValue.asLong());   return false; }   protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, Void handback){ modifyTick(context, operation, valueToRestore.asLong()); }   private void modifyTick(OperationContext context, ModelNode operation, long value) throws OperationFailedException {   final String suffix = PathAddress.pathAddress(operation.get(ModelDescriptionConstants.ADDRESS)).getLastElement().getValue(); TrackerService service = (TrackerService) context.getServiceRegistry(true).getRequiredService(TrackerService.createServiceName(suffix)).getValue(); service.setTick(value); }   } ---- 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. [NOTE] When implementing `execute()`, you *must* call `context.completeStep()` when you are done. [[parsing-and-marshalling-of-the-subsystem-xml]] == Parsing and marshalling of the subsystem xml WildFly uses the Stax API to parse the xml files. This is initialized in `SubsystemExtension` by mapping our parser onto our namespace: [source, java] ---- public class SubsystemExtension implements Extension {   /** The name space used for the {@code subsystem} element */ public static final String NAMESPACE = "urn:com.acme.corp.tracker:1.0"; ... protected static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);     protected static final PathElement TYPE_PATH = PathElement.pathElement(TYPE);    /** The parser used for parsing our subsystem */ private final SubsystemParser parser = new SubsystemParser();    @Override public void initializeParsers(ExtensionParsingContext context) { context.setSubsystemXmlMapping(NAMESPACE, parser); } ... ---- 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. [source, java] ---- public class SubsystemExtension implements Extension {   /** * The subsystem parser, which uses stax to read and write to and from xml */ private static class SubsystemParser implements XMLStreamConstants, XMLElementReader>, XMLElementWriter {   /** {@inheritDoc} */ @Override public void readElement(XMLExtendedStreamReader reader, List list) throws XMLStreamException { // Require no attributes ParseUtils.requireNoAttributes(reader);   //Add the main subsystem 'add' operation final ModelNode subsystem = new ModelNode();             subsystem.get(OP).set(ADD);             subsystem.get(OP_ADDR).set(PathAddress.pathAddress(SUBSYSTEM_PATH).toModelNode());             list.add(subsystem);   //Read the children while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { if (!reader.getLocalName().equals("deployment-types")) { throw ParseUtils.unexpectedElement(reader); } while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { if (reader.isStartElement()) { readDeploymentType(reader, list); } } } }   private void readDeploymentType(XMLExtendedStreamReader reader, List list) throws XMLStreamException {             if (!reader.getLocalName().equals("deployment-type")) {                 throw ParseUtils.unexpectedElement(reader);             }             ModelNode addTypeOperation = new ModelNode();             addTypeOperation.get(OP).set(ModelDescriptionConstants.ADD);               String suffix = null;             for (int i = 0; i < reader.getAttributeCount(); i++) {                 String attr = reader.getAttributeLocalName(i);                 String value = reader.getAttributeValue(i);                 if (attr.equals("tick")) {                     TypeDefinition.TICK.parseAndSetParameter(value, addTypeOperation, reader);                 } else if (attr.equals("suffix")) {                     suffix = value;                 } else {                     throw ParseUtils.unexpectedAttribute(reader, i);                 }             }             ParseUtils.requireNoContent(reader);             if (suffix == null) {                 throw ParseUtils.missingRequiredElement(reader, Collections.singleton("suffix"));             }               //Add the 'add' operation for each 'type' child             PathAddress addr = PathAddress.pathAddress(SUBSYSTEM_PATH, PathElement.pathElement(TYPE, suffix));             addTypeOperation.get(OP_ADDR).set(addr.toModelNode());             list.add(addTypeOperation);         } ... ---- 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: [source, java] ---- private static class SubsystemParser implements XMLStreamConstants, XMLElementReader>, XMLElementWriter { ... /** {@inheritDoc} */ @Override public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {             //Write out the main subsystem element             context.startSubsystemElement(TrackerExtension.NAMESPACE, false);             writer.writeStartElement("deployment-types");             ModelNode node = context.getModelNode();             ModelNode type = node.get(TYPE);             for (Property property : type.asPropertyList()) {                   //write each child element to xml                 writer.writeStartElement("deployment-type");                 writer.writeAttribute("suffix", property.getName());                 ModelNode entry = property.getValue();                 TypeDefinition.TICK.marshallAsAttribute(entry, true, writer);                 writer.writeEndElement();             }             //End deployment-types             writer.writeEndElement();             //End subsystem             writer.writeEndElement();         } } ---- 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. [source, java] ---- private static class SubsystemDescribeHandler implements OperationStepHandler, DescriptionProvider { static final SubsystemDescribeHandler INSTANCE = new SubsystemDescribeHandler();   public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { //Add the main operation context.getResult().add(createAddSubsystemOperation());   //Add the operations to create each child   ModelNode node = context.readModel(PathAddress.EMPTY_ADDRESS); for (Property property : node.get("type").asPropertyList()) {   ModelNode addType = new ModelNode(); addType.get(OP).set(ModelDescriptionConstants.ADD); PathAddress addr = PathAddress.pathAddress(SUBSYSTEM_PATH, PathElement.pathElement("type", property.getName())); addType.get(OP_ADDR).set(addr.toModelNode()); if (property.getValue().hasDefined("tick")) {  TypeDefinition.TICK.validateAndSet(property,addType); } context.getResult().add(addType); } context.completeStep(); }     } ---- [[testing-the-parsers]] === Testing the parsers Changes to tests between 7.0.0 and 7.0.1 [NOTE] 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 [source,xml] ---- @Test public void testParseSubsystem() throws Exception { //Parse the subsystem xml into operations String subsystemXml = "" + " " + " " + " " + ""; List operations = super.parse(subsystemXml); ---- There should be one operation for adding the subsystem itself and an operation for adding the `deployment-type`, so check we got two operations [source, java] ---- ///Check that we have the expected number of operations Assert.assertEquals(2, operations.size()); ---- Now check that the first operation is `add` for the address `/subsystem=tracker`: [source, java] ---- //Check that each operation has the correct content //The add subsystem operation will happen first ModelNode addSubsystem = operations.get(0); Assert.assertEquals(ADD, addSubsystem.get(OP).asString()); PathAddress addr = PathAddress.pathAddress(addSubsystem.get(OP_ADDR)); Assert.assertEquals(1, addr.size()); PathElement element = addr.getElement(0); Assert.assertEquals(SUBSYSTEM, element.getKey()); Assert.assertEquals(SubsystemExtension.SUBSYSTEM_NAME, element.getValue()); ---- 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: [source, java] ---- //Then we will get the add type operation ModelNode addType = operations.get(1); Assert.assertEquals(ADD, addType.get(OP).asString()); Assert.assertEquals(12345, addType.get("tick").asLong()); addr = PathAddress.pathAddress(addType.get(OP_ADDR)); Assert.assertEquals(2, addr.size()); element = addr.getElement(0); Assert.assertEquals(SUBSYSTEM, element.getKey()); Assert.assertEquals(SubsystemExtension.SUBSYSTEM_NAME, element.getValue()); element = addr.getElement(1); Assert.assertEquals("type", element.getKey()); Assert.assertEquals("tst", element.getValue()); } ---- 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 [source, java] ---- @Test public void testInstallIntoController() throws Exception { //Parse the subsystem xml and install into the controller String subsystemXml = "" + " " + " " + " " + ""; KernelServices services = super.installInController(subsystemXml); ---- The returned `KernelServices` allow us to execute operations on the controller, and to read the whole model. [source, java] ---- //Read the whole model and make sure it looks as expected ModelNode model = services.readWholeModel(); //Useful for debugging :-) //System.out.println(model); ---- Now we make sure that the structure of the model within the controller has the expected format and values [source, java] ---- Assert.assertTrue(model.get(SUBSYSTEM).hasDefined(SubsystemExtension.SUBSYSTEM_NAME)); Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME).hasDefined("type")); Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type").hasDefined("tst")); Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type", "tst").hasDefined("tick")); Assert.assertEquals(12345, model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type", "tst", "tick").asLong()); } ---- 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 [source, java] ---- @Test public void testParseAndMarshalModel() throws Exception { //Parse the subsystem xml and install into the first controller String subsystemXml = "" + " " + " " + " " + ""; KernelServices servicesA = super.installInController(subsystemXml); ---- Now we read the model and the xml that was persisted from the first controller, and use that xml to start a second controller [source, java] ---- //Get the model and the persisted xml from the first controller ModelNode modelA = servicesA.readWholeModel(); String marshalled = servicesA.getPersistedSubsystemXml();   //Install the persisted xml from the first controller into a second controller KernelServices servicesB = super.installInController(marshalled); ---- Finally we read the model from the second controller, and make sure that the models are identical by calling `compare()` on the test superclass. [source, java] ---- ModelNode modelB = servicesB.readWholeModel();   //Make sure the models from the two controllers are identical super.compare(modelA, modelB); } ---- 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 [source, java] ---- @Test public void testDescribeHandler() throws Exception { //Parse the subsystem xml and install into the first controller String subsystemXml = "" + ""; KernelServices servicesA = super.installInController(subsystemXml); ---- We then call `/subsystem=tracker:describe` which outputs the subsystem as operations needed to reach the current state (Done by our `SubsystemDescribeHandler`) [source, java] ---- //Get the model and the describe operations from the first controller ModelNode modelA = servicesA.readWholeModel(); ModelNode describeOp = new ModelNode(); describeOp.get(OP).set(DESCRIBE); describeOp.get(OP_ADDR).set( PathAddress.pathAddress( PathElement.pathElement(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME)).toModelNode()); List operations = super.checkResultAndGetContents(servicesA.executeOperation(describeOp)).asList(); ---- Then we create a new controller using those operations [source, java] ---- //Install the describe options from the first controller into a second controller KernelServices servicesB = super.installInController(operations); ---- And then we read the model from the second controller and make sure that the two subsystems are identical + ModelNode modelB = servicesB.readWholeModel(); [source, java] ---- //Make sure the models from the two controllers are identical super.compare(modelA, modelB);   } ---- To test the removal of the the subsystem and child resources we modify the `testSubsystemRemoval()` test provided by the archetype: [source, java] ---- /** * Tests that the subsystem can be removed */ @Test public void testSubsystemRemoval() throws Exception { //Parse the subsystem xml and install into the first controller ---- We provide xml for the subsystem installing a child, which in turn installs a TrackerService [source, java] ---- String subsystemXml = "" + " " + " " + " " + ""; KernelServices services = super.installInController(subsystemXml); ---- Having installed the xml into the controller we make sure the TrackerService is there [source, java] ---- //Sanity check to test the service for 'tst' was there services.getContainer().getRequiredService(TrackerService.createServiceName("tst")); ---- 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. [source, java] ---- //Checks that the subsystem was removed from the model super.assertRemoveSubsystemResources(services); ---- Finally we check that all the services were removed by the remove handlers [source, java] ---- //Check that any services that were installed were removed here try { services.getContainer().getRequiredService(TrackerService.createServiceName("tst")); Assert.fail("Should have removed services"); } catch (Exception expected) { } } ---- 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 [source, java] ---- @Test public void testExecuteOperations() throws Exception { String subsystemXml = "" + " " + " " + " " + ""; KernelServices services = super.installInController(subsystemXml); ---- Now create an operation which does the same as the following CLI command `/subsystem=tracker/type=foo:add(tick=1000)` [source, java] ---- //Add another type PathAddress fooTypeAddr = PathAddress.pathAddress( PathElement.pathElement(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME), PathElement.pathElement("type", "foo")); ModelNode addOp = new ModelNode(); addOp.get(OP).set(ADD); addOp.get(OP_ADDR).set(fooTypeAddr.toModelNode()); addOp.get("tick").set(1000); ---- Execute the operation and make sure it was successful [source, java] ---- ModelNode result = services.executeOperation(addOp); Assert.assertEquals(SUCCESS, result.get(OUTCOME).asString()); ---- Read the whole model and make sure that the original data is still there (i.e. the same as what was done by `testInstallIntoController()` [source, java] ---- ModelNode model = services.readWholeModel(); Assert.assertTrue(model.get(SUBSYSTEM).hasDefined(SubsystemExtension.SUBSYSTEM_NAME)); Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME).hasDefined("type")); Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type").hasDefined("tst")); Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type", "tst").hasDefined("tick")); Assert.assertEquals(12345, model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type", "tst", "tick").asLong()); ---- Then make sure our new `type` has been added: [source, java] ---- Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type").hasDefined("foo")); Assert.assertTrue(model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type", "foo").hasDefined("tick")); Assert.assertEquals(1000, model.get(SUBSYSTEM, SubsystemExtension.SUBSYSTEM_NAME, "type", "foo", "tick").asLong()); ---- Then we call `write-attribute` to change the `tick` value of `/subsystem=tracker/type=foo`: [source, java] ---- //Call write-attribute ModelNode writeOp = new ModelNode(); writeOp.get(OP).set(WRITE_ATTRIBUTE_OPERATION); writeOp.get(OP_ADDR).set(fooTypeAddr.toModelNode()); writeOp.get(NAME).set("tick"); writeOp.get(VALUE).set(3456); result = services.executeOperation(writeOp); Assert.assertEquals(SUCCESS, result.get(OUTCOME).asString()); ---- 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. [source, java] ---- //Check that write attribute took effect, this time by calling read-attribute instead of reading the whole model ModelNode readOp = new ModelNode(); readOp.get(OP).set(READ_ATTRIBUTE_OPERATION); readOp.get(OP_ADDR).set(fooTypeAddr.toModelNode()); readOp.get(NAME).set("tick"); result = services.executeOperation(readOp); Assert.assertEquals(3456, checkResultAndGetContents(result).asLong()); ---- 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 [source, java] ---- TrackerService service = (TrackerService)services.getContainer().getService(TrackerService.createServiceName("foo")).getValue(); Assert.assertEquals(3456, service.getTick()); } ---- TypeDefinition.TICK. [[add-the-deployers]] == Add the deployers When discussing `SubsystemAddHandler` we did not mention the work done to install the deployers, which is done in the following method: [source, java] ---- @Override public void performBoottime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException {   log.info("Populating the model");   //Add deployment processors here //Remove this if you don't need to hook into the deployers, or you can add as many as you like //see SubDeploymentProcessor for explanation of the phases context.addStep(new AbstractDeploymentChainStep() { public void execute(DeploymentProcessorTarget processorTarget) { processorTarget.addDeploymentProcessor(SubsystemDeploymentProcessor.PHASE, SubsystemDeploymentProcessor.priority, new SubsystemDeploymentProcessor());   } }, OperationContext.Stage.RUNTIME);   } ---- 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: [source, java] ---- public class SubsystemDeploymentProcessor implements DeploymentUnitProcessor { ...   @Override public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { String name = phaseContext.getDeploymentUnit().getName(); TrackerService service = getTrackerService(phaseContext.getServiceRegistry(), name); if (service != null) { ResourceRoot root = phaseContext.getDeploymentUnit().getAttachment(Attachments.DEPLOYMENT_ROOT); VirtualFile cool = root.getRoot().getChild("META-INF/cool.txt"); service.addDeployment(name); if (cool.exists()) { service.addCoolDeployment(name); } } }   @Override public void undeploy(DeploymentUnit context) { context.getServiceRegistry(); String name = context.getName(); TrackerService service = getTrackerService(context.getServiceRegistry(), name); if (service != null) { service.removeDeployment(name); } }   private TrackerService getTrackerService(ServiceRegistry registry, String name) { int last = name.lastIndexOf("."); String suffix = name.substring(last + 1); ServiceController container = registry.getService(TrackerService.createServiceName(suffix)); if (container != null) { TrackerService service = (TrackerService)container.getValue(); return service; } return null; } } ---- 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]] === 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]] ==== STRUCTURE The deployment unit processors in this phase determine the structure of a deployment, and looks for sub deployments and metadata files. [[parse]] ==== 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]] ==== 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]] ==== 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]] ==== 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 Install new services coming from the deployment. [[cleanup]] ==== CLEANUP Attachments put in place earlier in the deployment unit processor chain may be removed here. [[integrate-with-wildfly]] == Integrate with WildFly Now that we have all the code needed for our subsystem, we can build our project by running `mvn install` [source, bash] ---- [kabir ~/sourcecontrol/temp/archetype-test/acme-subsystem] $mvn install [INFO] Scanning for projects... [...] main: [delete] Deleting: /Users/kabir/sourcecontrol/temp/archetype-test/acme-subsystem/null1004283288 [delete] Deleting directory /Users/kabir/sourcecontrol/temp/archetype-test/acme-subsystem/target/module [copy] Copying 1 file to /Users/kabir/sourcecontrol/temp/archetype-test/acme-subsystem/target/module/com/acme/corp/tracker/main [copy] Copying 1 file to /Users/kabir/sourcecontrol/temp/archetype-test/acme-subsystem/target/module/com/acme/corp/tracker/main [echo] Module com.acme.corp.tracker has been created in the target/module directory. Copy to your JBoss AS 7 installation. [INFO] Executed tasks [INFO] [INFO] --- maven-install-plugin:2.3.1:install (default-install) @ acme-subsystem --- [INFO] Installing /Users/kabir/sourcecontrol/temp/archetype-test/acme-subsystem/target/acme-subsystem.jar to /Users/kabir/.m2/repository/com/acme/corp/acme-subsystem/1.0-SNAPSHOT/acme-subsystem-1.0-SNAPSHOT.jar [INFO] Installing /Users/kabir/sourcecontrol/temp/archetype-test/acme-subsystem/pom.xml to /Users/kabir/.m2/repository/com/acme/corp/acme-subsystem/1.0-SNAPSHOT/acme-subsystem-1.0-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 5.851s [INFO] Finished at: Mon Jul 11 23:24:58 BST 2011 [INFO] Final Memory: 7M/81M [INFO] ------------------------------------------------------------------------ ---- This will have built our project and assembled a module for us that can be used for installing it into WildFly. If you go to the `target/module` folder where you built the project you will see the module [source, bash] ---- $ls target/module/com/acme/corp/tracker/main/ acme-subsystem.jar module.xml ---- 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`: [source, xml] ---- ---- 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. [source, xml] ---- ---- 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 `` section: [source, java] ---- ... ---- And then we have to add our subsystem to the `` section: [source, xml] ---- ...   ... ---- Adding this to a managed domain works exactly the same apart from in this case you need to modify `$WFLY/domain/configuration/domain.xml`. Now start up WildFly 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: .... 15:27:33,838 INFO [org.jboss.as] (Controller Boot Thread) JBoss AS 7.0.0.Final "Lightning" started in 2861ms - Started 94 of 149 services (55 services are passive or on-demand) 15:27:42,966 INFO [stdout] (Thread-8) Current deployments deployed while sar tracking active: 15:27:42,966 INFO [stdout] (Thread-8) [] 15:27:42,967 INFO [stdout] (Thread-8) Cool: 0 15:27:42,967 INFO [stdout] (Thread-9) Current deployments deployed while war tracking active: 15:27:42,967 INFO [stdout] (Thread-9) [] 15:27:42,967 INFO [stdout] (Thread-9) Cool: 0 15:27:52,967 INFO [stdout] (Thread-8) Current deployments deployed while sar tracking active: 15:27:52,967 INFO [stdout] (Thread-8) [] 15:27:52,967 INFO [stdout] (Thread-8) Cool: 0 .... If you run the command line interface you can execute some commands to see more about the subsystem. For example [source, ruby] ---- [standalone@localhost:9999 /] /subsystem=tracker/:read-resource-description(recursive=true, operations=true) ---- will return a lot of information, including what we provided in the `DescriptionProvider`s we created to document our subsystem. To see the current subsystem state you can execute [source, ruby] ---- [standalone@localhost:9999 /] /subsystem=tracker/:read-resource(recursive=true) { "outcome" => "success", "result" => {"type" => { "war" => {"tick" => 10000L}, "sar" => {"tick" => 10000L} }} } ---- We can remove both the deployment types which removes them from the model: [source, ruby] ---- [standalone@localhost:9999 /] /subsystem=tracker/type=sar:remove {"outcome" => "success"} [standalone@localhost:9999 /] /subsystem=tracker/type=war:remove {"outcome" => "success"} [standalone@localhost:9999 /] /subsystem=tracker/:read-resource(recursive=true) { "outcome" => "success", "result" => {"type" => undefined} } ---- You should now see the output from the `TrackerService` instances having stopped. Now, let's add the war tracker again: [source, ruby] ---- [standalone@localhost:9999 /] /subsystem=tracker/type=war:add {"outcome" => "success"} [standalone@localhost:9999 /] /subsystem=tracker/:read-resource(recursive=true) { "outcome" => "success", "result" => {"type" => {"war" => {"tick" => 10000L}}} } ---- and the WildFly 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 link:downloads/test1.zip[test1.zip] and link:downloads/test2.zip[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: [source, java] ---- [standalone@localhost:9999 /] deploy ~/Downloads/test1/target/test1.war 'test1.war' deployed successfully. ---- And you should now see the output from the war `TrackerService` list the deployments: .... 15:35:03,712 INFO [org.jboss.as.server.deployment] (MSC service thread 1-2) Starting deployment of "test1.war" 15:35:03,988 INFO [org.jboss.web] (MSC service thread 1-1) registering web context: /test1 15:35:03,996 INFO [org.jboss.as.server.controller] (pool-2-thread-9) Deployed "test1.war" 15:35:13,056 INFO [stdout] (Thread-9) Current deployments deployed while war tracking active: 15:35:13,056 INFO [stdout] (Thread-9) [test1.war] 15:35:13,057 INFO [stdout] (Thread-9) Cool: 1 .... So our `test1.war` got picked up as a 'cool' deployment. Now if we deploy `test2.war` [source, ruby] ---- [standalone@localhost:9999 /] deploy ~/sourcecontrol/temp/archetype-test/test2/target/test2.war 'test2.war' deployed successfully. ---- 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: .... 15:37:05,634 INFO [org.jboss.as.server.deployment] (MSC service thread 1-4) Starting deployment of "test2.war" 15:37:05,699 INFO [org.jboss.web] (MSC service thread 1-1) registering web context: /test2 15:37:05,982 INFO [org.jboss.as.server.controller] (pool-2-thread-15) Deployed "test2.war" 15:37:13,075 INFO [stdout] (Thread-9) Current deployments deployed while war tracking active: 15:37:13,075 INFO [stdout] (Thread-9) [test1.war, test2.war] 15:37:13,076 INFO [stdout] (Thread-9) Cool: 1 .... An undeploy [source, java] ---- [standalone@localhost:9999 /] undeploy test1.war Successfully undeployed test1.war. ---- is also reflected in the `TrackerService` output: .... 15:38:47,901 INFO [org.jboss.as.server.controller] (pool-2-thread-21) Undeployed "test1.war" 15:38:47,934 INFO [org.jboss.as.server.deployment] (MSC service thread 1-3) Stopped deployment test1.war in 40ms 15:38:53,091 INFO [stdout] (Thread-9) Current deployments deployed while war tracking active: 15:38:53,092 INFO [stdout] (Thread-9) [test2.war] 15:38:53,092 INFO [stdout] (Thread-9) Cool: 0 .... Finally, we registered a write attribute handler for the `tick` property of the `type` so we can change the frequency [source, ruby] ---- [standalone@localhost:9999 /] /subsystem=tracker/type=war:write-attribute(name=tick,value=1000) {"outcome" => "success"} ---- You should now see the output from the `TrackerService` happen every second .... 15:39:43,100 INFO [stdout] (Thread-9) Current deployments deployed while war tracking active: 15:39:43,100 INFO [stdout] (Thread-9) [test2.war] 15:39:43,101 INFO [stdout] (Thread-9) Cool: 0 15:39:44,101 INFO [stdout] (Thread-9) Current deployments deployed while war tracking active: 15:39:44,102 INFO [stdout] (Thread-9) [test2.war] 15:39:44,105 INFO [stdout] (Thread-9) Cool: 0 15:39:45,106 INFO [stdout] (Thread-9) Current deployments deployed while war tracking active: 15:39:45,106 INFO [stdout] (Thread-9) [test2.war] .... If you open `$WFLY/standalone/configuration/standalone.xml` you can see that our subsystem entry reflects the current state of the subsystem: [source, xml] ---- ---- [[expressions]] == 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 .... ${jboss.bind.address.management:127.0.0.1} .... 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]] === 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 .... ${expression_to_resolve} .... 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]] === 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. [source, java] ---- SimpleAttributeDefinition MY_ATTRIBUTE =             new SimpleAttributeDefinitionBuilder("my-attribute", ModelType.INT, true)                     .setAllowExpression(true)                     .setFlags(AttributeAccess.Flag.RESTART_ALL_SERVICES)                     .setDefaultValue(new ModelNode(1))                     .build(); ---- 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. [source, java] ---- ....       String attr = reader.getAttributeLocalName(i);       String value = reader.getAttributeValue(i);       if (attr.equals("my-attribute")) {           MY_ATTRIBUTE.parseAndSetParameter(value, operation, reader);       } else if (attr.equals("suffix")) { ..... ---- 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. [source, java] ---- @Override  protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {         MY_ATTRIBUTE.validateAndSet(operation,model);  } ---- 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 [source, java] ---- protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException {        ....         final int attributeValue = MY_ATTRIBUTE.resolveModelAttribute(context, model).asInt();              ...       } ---- 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: [source, java] ---- public void writeContent(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {     ....       MY_ATTRIBUTE.marshallAsAttribute(sessionData, writer);       MY_OTHER_ATTRIBUTE.marshallAsElement(sessionData, false, writer);     ... } ----