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:
$ java -cp bsh-2.0b4.jar:jboss-dmr-1.0.0.Final.jar bsh.Interpreter BeanShell 2.0b4 - by Pat Niemeyer ([email protected])
bsh % import org.jboss.dmr.*;
bsh %
Next, create a ModelNode and use the beanshell print function to output what type it is:
bsh % ModelNode node = new ModelNode();
bsh % print(node.getType());
UNDEFINED
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:
bsh % node.set(1);
bsh % print(node.getType());
INT
bsh % node.set(true);
bsh % print(node.getType());
BOOLEAN
bsh % node.set("Hello, world");
bsh % print(node.getType());
STRING
Use one of the asXXX() methods to retrieve the value:
bsh % node.set(2);
bsh % print(node.asInt());
2
bsh % node.set("A string");
bsh % print(node.asString());
A string
will attempt to perform type conversions when you invoke the methods:
ModelNode asXXX
bsh % node.set(1);
Not all type conversions are possible:
bsh % node.set("A string");
bsh % print(node.asInt());
// Error: // Uncaught Exception: Method Invocation node.asInt : at Line: 20 : in file: <unknown file> : node .asInt ( )
Target exception: java.lang.NumberFormatException: For input string: "A string"
java.lang.NumberFormatException: For input string: "A string"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
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:
bsh % node.set("A string");
bsh % ModelNode another = new ModelNode();
bsh % another.set(node);
A ModelNode can be cloned. Again, there is no shared state between the original node and its clone:
bsh % ModelNode clone = another.clone();
bsh % print(clone.asString());
A string
bsh % another.set(42);
bsh % print(another.asString());
42
bsh % print(clone.asString());
A string
Use the protect() method to make a ModelNode immutable:
bsh % clone.protect();
bsh % clone.set("A different string");
// Error: // Uncaught Exception: Method Invocation clone.set : at Line: 15 : in file: <unknown file> : clone .set ( "A different string" )
Target exception: java.lang.UnsupportedOperationException
java.lang.UnsupportedOperationException
at org.jboss.dmr.ModelNode.checkProtect(ModelNode.java:1441) at org.jboss.dmr.ModelNode.set(ModelNode.java:351)
....
Lists
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:
bsh % ModelNode list = new ModelNode();
bsh % list.add(5);
bsh % list.add(10);
bsh % print(list.getType());
LIST
Use asInt() to find the size of the list:
bsh % print(list.asInt());
2
Use the overloaded get method variant that takes an int param to retrieve an item. The item is returned as a :
ModelNode
bsh % ModelNode child = list.get(1);
bsh % print(child.asInt());
10
Elements in a list need not all be of the same type:
bsh % list.add("A string");
bsh % print(list.get(1).getType());
INT
bsh % print(list.get(2).getType());
STRING
Here's one of the trickiest things about jboss-dmr: The get methods actually mutate state; they are not . For example, calling with an index that does not exist yet in the list will actually create a
"read-only" get
child of type ModelType.UNDEFINED at that index (and will create UNDEFINED children for any intervening indices.)
bsh % ModelNode four = list.get(4);
bsh % print(four.getType());
UNDEFINED
bsh % print(list.asInt());
6
Since the get call always returns a ModelNode and never null it is safe to manipulate the return value:
bsh % list.get(5).set(30);
bsh % print(list.get(5).asInt());
30
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:
bsh % bsh % for (ModelNode element : list.asList()) { print(element.getType());
} INT INT STRING UNDEFINED UNDEFINED INT
bsh % print(list.asString());
[5,10,"A string",undefined,undefined,30]
bsh % print(list.toString());
[ 5, 10,
"A string", undefined, undefined, 30
]
Finally, if you've previously used set to assign a node's value to some non-list type, you cannot use the add method:
bsh % node.add(5);
// Error: // Uncaught Exception: Method Invocation node.add : at Line: 18 : in file: <unknown file> : node .add ( 5 )
Target exception: java.lang.IllegalArgumentException
java.lang.IllegalArgumentException
at org.jboss.dmr.ModelValue.addChild(ModelValue.java:120) at org.jboss.dmr.ModelNode.add(ModelNode.java:1007) at org.jboss.dmr.ModelNode.add(ModelNode.java:761) ...
You can, however, use the setEmptyList() method to change the node's type, and then use add:
bsh % node.setEmptyList();
bsh % node.add(5);
bsh % print(node.toString());
[5]
Properties
The third public class in the jboss-dmr library is org.jboss.dmr.Property. A Property is a String =>
tuple.
ModelNode
bsh % Property prop = new Property("stuff", list);
bsh % print(prop.toString());
org.jboss.dmr.Property@79a5f739 bsh % print(prop.getName());
stuff
bsh % print(prop.getValue());
[ 5, 10,
"A string", undefined, undefined, 30
]
The property can be passed to ModelNode.set:
bsh % node.set(prop);
bsh % print(node.getType());
PROPERTY
The text format for a node of ModelType.PROPERTY is:
bsh % print(node.toString());
("stuff" => [ 5,
10,
"A string", undefined, undefined, 30
])
Directly instantiating a Property via its constructor is not common. More typically one of the two argument or variants is used. The first argument is the property name:
ModelNode.add ModelNode.set
bsh % ModelNode simpleProp = new ModelNode();
bsh % ModelNode propList = new ModelNode();
bsh % propList.add("min", 1);
The asPropertyList() method provides easy access to a List<Property>:
bsh % for (Property prop : propList.asPropertyList()) { print(prop.getName() + " = " + prop.getValue());
} min = 1 max = 10
ModelType.OBJECT
The most powerful and most commonly used complex value type in jboss-dmr is ModelType.OBJECT. A
whose value is internally maintains a .
ModelNode ModelType.OBJECT 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:
bsh % ModelNode range = new ModelNode();
bsh % ModelNode min = range.get("min");
bsh % print(range.toString());
{"min" => undefined}
bsh % min.set(2);
bsh % print(range.toString());
{"min" => 2}
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:
bsh % range.get("max").set(10);
bsh % print(range.toString());
{
"min" => 2, "max" => 10 }
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:
bsh % print(min == range.get("min"));
true
Multiple parameters can be passed to get. This is a simple way to traverse a tree made up of
nodes. Again, may mutate the node on which it is invoked; e.g. it will actually
ModelType.OBJECT get
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:
bsh % String[] varargs = { "US", "Missouri", "St. Louis" };
bsh % salesTerritories.get(varargs).set("Brian");
bsh % print(salesTerritories.toString());
{"US" => {"Missouri" => {"St. Louis" => "Brian"}}}
The normal syntax would be:
salesTerritories.get("US", "Missouri", "St. Louis").set("Brian");
The key/value pairs in the map can be accessed as a List<Property:
bsh % for (Property prop : range.asPropertyList()) { print(prop.getName() + " = " + prop.getValue());
} min = 2
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:
bsh % print(range.has("unit"));
false
bsh % print(range.has("min"));
true
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:
bsh % print(range.hasDefined("unit"));
false
bsh % // Establish an undefined child 'unit';
bsh % range.get("unit");
bsh % print(range.toString());
{
"min" => 2, "max" => 10, "unit" => undefined }
bsh % print(range.hasDefined("unit"));
false
bsh % range.get("unit").set("meters");
bsh % print(range.hasDefined("unit"));
true
ModelType.EXPRESSION
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.
[<prefix>][${<system-property-name>[:<default-value>]}][<suffix>]*
For example:
${queue.length}
http://${host}
http://${host:localhost}:${port:8080}/index.html
Use the setExpression method to set a node's value to type expression:
bsh % ModelNode expression = new ModelNode();
bsh % expression.setExpression("${queue.length}");
bsh % print(expression.getType());
EXPRESSION
bsh % print(expression.asString());
${queue.length}
However, calling toString() tells you that this node's value is not of ModelType.STRING:
bsh % print(expression.toString());
expression "${queue.length}"
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:
bsh % System.setProperty("queue.length", "10");
bsh % ModelNode resolved = expression.resolve();
bsh % print(resolved.asInt());
10
Note that the type of the ModelNode returned by resolve() is ModelType.STRING:
bsh % print(resolved.getType());
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:
bsh % resolved = expression.resolve();
bsh % print(resolved.toString());
"10"
bsh % print(expression.toString());
expression "${queue.length}"
If an expression cannot be resolved, resolve just uses the original string. The string can include more than one system property substitution:
bsh % expression.setExpression("http://${host}:${port}/index.html");
bsh % resolved = expression.resolve();
bsh % print(resolved.asString());
http://${host}:${port}/index.html
The expression can optionally include a default value, separated from the name of the system property by a
bsh % expression.setExpression("http://${host:localhost}:${port:8080}/index.html");
bsh % resolved = expression.resolve();
bsh % print(resolved.asString());
http://localhost:8080/index.html
Actually including a system property substitution in the expression is not required:
bsh % expression.setExpression("no system property");
bsh % resolved = expression.resolve();
bsh % print(resolved.asString());
no system property
bsh % print(expression.toString());
expression "no system property"
The resolve method works on nodes of other types as well; it returns a copy without attempting any real resolution:
bsh % ModelNode basic = new ModelNode();
bsh % basic.set(10);
bsh % resolved = basic.resolve();
bsh % print(resolved.getType());
INT
bsh % resolved.set(5);
bsh % print(resolved.asInt());
5
bsh % print(basic.asInt());
10
ModelType.TYPE
You can also pass one of the values of the ModelType enum to set:
bsh % ModelNode type = new ModelNode();
bsh % type.set(ModelType.LIST);
bsh % print(type.getType());
TYPE
bsh % print(type.toString());
LIST
This is useful when using a ModelNode data structure to describe another ModelNode data structure.