The management model exposed by WildFly 8 is very large and complex. There are dozens, probably hundreds of logical concepts involved – hosts, server groups, servers, subsystems, datasources, web connectors, and on and on – each of which in a classic objected oriented API design could be represented by a Java type (i.e. a Java class or interface.) However, a primary goal in the development of WildFly's native management API was to ensure that clients built to use the API had as few compile-time and run-time dependencies on JBoss-provided classes as possible, and that the API exposed by those libraries be powerful but also simple and stable. A management client running with the management libraries created for an earlier version of WildFly should still work if used to manage a later version domain. The management client libraries needed to be forward compatible.
It is highly unlikely that an API that consists of hundreds of Java types could be kept forward compatible. Instead, the WildFly 8 management API is a detyped API. A detyped API is like decaffeinated coffee – it still has a little bit of caffeine, but not enough to keep you awake at night. WildFly's management API still has a few Java types in it (it's impossible for a Java library to have no types!) but not enough to keep you (or us) up at night worrying that your management clients won't be forward compatible.
A detyped API works by making it possible to build up arbitrarily complex data structures using a small number of Java types. All of the parameter values and return values in the API are expressed using those few types. Ideally, most of the types are basic JDK types, like java.lang.String, java.lang.Integer, etc. In addition to the basic JDK types, WildFly 8's detyped management API uses a small library called jboss-dmr. The purpose of this section is to provide a basic overview of the jboss-dmr library.
Even if you don't use jboss-dmr directly (probably the case for all but a few users), some of the information in this section may be useful. When you invoke operations using the application server's Command Line Interface, the return values are just the text representation of of a jboss-dmr ModelNode. If your CLI commands require complex parameter values, you may yourself end up writing the text representation of a ModelNode. And if you use the HTTP management API, all response bodies as well as the request body for any POST will be a JSON representation of a ModelNode.
The source code for jboss-dmr is available on Github. The maven coordinates for a jboss-dmr release are org.jboss.jboss-dmr:jboss-dmr.
The public API exposed by jboss-dmr is very simple: just three classes, one of which is an enum!
The primary class is org.jboss.dmr.ModelNode. A ModelNode is essentially just a wrapper around some value; the value is typically some basic JDK type. A ModelNode exposes a getType() method. This method returns a value of type org.jboss.dmr.ModelType, which is an enum of all the valid types of values. And that's 95% of the public API; a class and an enum. (We'll get to the third class, Property, below.)
To illustrate how to work with ModelNode s, we'll use the Beanshell scripting library. We won't get into many details of beanshell here; it's a simple and intuitive tool and hopefully the following examples are as well.
We'll start by launching a beanshell interpreter, with the jboss-dmr library available on the classpath. Then we'll tell beanshell to import all the jboss-dmr classes so they are available for use:
Next, create a ModelNode and use the beanshell print function to output what type it is:
A new ModelNode has no value stored, so its type is ModelType.UNDEFINED.
Use one of the overloaded set method variants to assign a node's value:
Use one of the asXXX() methods to retrieve the value:
ModelNode will attempt to perform type conversions when you invoke the asXXX methods:
Not all type conversions are possible:
The ModelNode.getType() method can be used to ensure a node has an expected value type before attempting a type conversion.
One set variant takes another ModelNode as its argument. The value of the passed in node is copied, so there is no shared state between the two model nodes:
A ModelNode can be cloned. Again, there is no shared state between the original node and its clone:
Use the protect() method to make a ModelNode immutable:
The above examples aren't particularly interesting; if all we can do with a ModelNode is wrap a simple Java primitive, what use is that? However, a ModelNode's value can be more complex than a simple primitive, and using these more complex types we can build complex data structures. The first more complex type is ModelType.LIST.
Use the add methods to initialize a node's value as a list and add to the list:
Use asInt() to find the size of the list:
Use the overloaded get method variant that takes an int param to retrieve an item. The item is returned as a ModelNode:
Elements in a list need not all be of the same type:
Here's one of the trickiest things about jboss-dmr: The get methods actually mutate state; they are not "read-only". For example, calling get with an index that does not exist yet in the list will actually create a child of type ModelType.UNDEFINED at that index (and will create UNDEFINED children for any intervening indices.)
Since the get call always returns a ModelNode and never null it is safe to manipulate the return value:
That's not so interesting in the above example, but later on with node of type ModelType.OBJECT we'll see how that kind of method chaining can let you build up fairly complex data structures with a minimum of code.
Use the asList() method to get a List<ModelNode> of the children:
The asString() and toString() methods provide slightly differently formatted text representations of a ModelType.LIST node:
Finally, if you've previously used set to assign a node's value to some non-list type, you cannot use the add method:
You can, however, use the setEmptyList() method to change the node's type, and then use add:
The third public class in the jboss-dmr library is org.jboss.dmr.Property. A Property is a String => ModelNode tuple.
The property can be passed to ModelNode.set:
The text format for a node of ModelType.PROPERTY is:
Directly instantiating a Property via its constructor is not common. More typically one of the two argument ModelNode.add or ModelNode.set variants is used. The first argument is the property name:
The asPropertyList() method provides easy access to a List<Property>:
The most powerful and most commonly used complex value type in jboss-dmr is ModelType.OBJECT. A ModelNode whose value is ModelType.OBJECT internally maintains a Map<String, ModelNode.
Use the get method variant that takes a string argument to add an entry to the map. If no entry exists under the given name, a new entry is added with a the value being a ModelType.UNDEFINED node. The node is returned:
Again it is important to remember that the get operation may mutate the state of a model node by adding a new entry. It is not a read-only operation.
Since get will never return null, a common pattern is to use method chaining to create the key/value pair:
A call to get passing an already existing key will of course return the same model node as was returned the first time get was called with that key:
Multiple parameters can be passed to get. This is a simple way to traverse a tree made up of ModelType.OBJECT nodes. Again, get may mutate the node on which it is invoked; e.g. it will actually create the tree if nodes do not exist. This next example uses a workaround to get beanshell to handle the overloaded get method that takes a variable number of arguments:
The normal syntax would be:
The key/value pairs in the map can be accessed as a List<Property:
The semantics of the backing map in a node of ModelType.OBJECT are those of a LinkedHashMap. The map remembers the order in which key/value pairs are added. This is relevant when iterating over the pairs after calling asPropertyList() and for controlling the order in which key/value pairs appear in the output from toString().
Since the get method will actually mutate the state of a node if the given key does not exist, ModelNode provides a couple methods to let you check whether the entry is there. The has method simply does that:
Very often, the need is to not only know whether the key/value pair exists, but whether the value is defined (i.e. not ModelType.UNDEFINED. This kind of check is analogous to checking whether a field in a Java class has a null value. The hasDefined lets you do this:
A value of type ModelType.EXPRESSION is stored as a string, but can later be resolved to different value. The string has a special syntax that should be familiar to those who have used the system property substitution feature in previous JBoss AS releases.
Use the setExpression method to set a node's value to type expression:
Calling asString() returns the same string that was input:
However, calling toString() tells you that this node's value is not of ModelType.STRING:
When the resolve operation is called, the string is parsed and any embedded system properties are resolved against the JVM's current system property values. A new ModelNode is returned whose value is the resolved string:
Note that the type of the ModelNode returned by resolve() is ModelType.STRING:
The resolved.asInt() call in the previous example only worked because the string "10" happens to be convertible into the int 10.
Calling resolve() has no effect on the value of the node on which the method is invoked:
If an expression cannot be resolved, resolve just uses the original string. The string can include more than one system property substitution:
The expression can optionally include a default value, separated from the name of the system property by a colon:
Actually including a system property substitution in the expression is not required:
The resolve method works on nodes of other types as well; it returns a copy without attempting any real resolution:
You can also pass one of the values of the ModelType enum to set:
This is useful when using a ModelNode data structure to describe another ModelNode data structure.