Writing Deployment Templates for the DeploymentService
To create a new deployment template it is necessary to create a new
subdirectory under the directory where templates are stored
(./conf/templates by default), then create a template-config.xml
file into that subdirectory. The deployment template takes its name from the
subdirectory name, so ./conf/templates/jms-queue/template-config.xml
yields the name: jms-queue. template-config.xml must be according
to the XML Schema defined at
Generated modules go to the UndeployDir and there are 2 possible
module outcomes from "applying" a deployment configuration:
Generate a deployment that consists of a single descriptor, for example,
my-mbean-service.xml
Generate a deployment that consists potentially of many descriptors and/or
other files, e.g. jars. In this case the generated deployment corresponds
to an unpacked directory module, for example, my-module.sar/,
my-webapp.war/, etc.
The "Hello World" of deployment templates
The simplest template configuration is something like the following:
<template-config template="vm/some-descriptor.vm" extension="-service.xml"></template-config>
Remember that when a client calls createModule() on DeploymentService,
it will pass as parameters a module name, a template name, and
a HashMap of key/value pairs. For example:
createModule("my-queue", "jms-queue", properties);
The template name picks up a template configuration (e.g. jms-queue).
The module name suggest a proposed name (e.g. my-queue) for the generated
descriptor and the HashMap contains properties that are specific to a
template configuration.
In the above example createModule() will cause the rendering of the velocity
template ./conf/templates/jms-queue/vm/some-descriptor.vm. All parameters
from the HashMap will be copied over to the VelocityContext
(which is another HashMap more or less) and they will be accessible
inside the velocity template using the $ (or simply $key) syntax.
For example, assume that a required QueueName and an optional
DestinationManager are needed by this particular template that generates
an MBean descriptor for a simple JMS Queue:
<server>
<mbean code="org.jboss.mq.server.jmx.Queue"
name="jboss.mq.destination:service=Queue,name=$QueueName">
#if($DestinationManager)
<depends optional-attribute-name="DestinationManager">$DestinationManager</depends>
#end
</mbean>
</server>
Notice the usage of the velocity if/end directive that can be
use for conditional inclusion of a template part. For a detailed
description of the velocity features read the velocity
reference guide.
The rendered template will be output to file my-queue-service.xml
which is the resulting module name after applying the "-service.xml"
extension. If the extension is in the module name already, it won't
be applied again. Also, the extension is an optional attribute of
the template-config.xml descriptor, but is a useful one to make
sure that the resulting descriptor will be deployable.
Specifying Properties
The term property here is interpreted as any Serializable java class.
In the above example any property inside the HashMap of createModule()
will be passed over to the velocity template where it will be accesible
though its key.
In the common case, all properties will be simple java.lang.String instances,
and infact, when the property value is accessed in the template using the
$ syntax, the value of key will be substituted by implicitly calling
toString() on the instance. But it is possible to use any java type
(e.g. a JavaBean) in order to pass more complex values and still be able
to access them in a convenient way (e.g. $mybean.getPerson().getName()),
while having some basic validation (e.g. a java.lang.Boolean can only
have a valid true/false value).
In addition to the above, we can enumerate the properties in the
template-config.xml descriptor. This allows the DeploymentService
to:
check if the number and type of the values passed at runtime are correct
apply default values if necessary
provide metadata to generic tools that want to discover the properties
supported by a particular deployment template at runtime and present to
the user service creation forms, and the like.
So the template.config.xml could be enhanced as follows:
<template-config template="vm/jms-queue.xml.vm" extension="-service.xml"> <property-list> <!-- if type not specified, java.lang.String is assumed --> <!-- a value must be supplied at runtime, or an exception will be thrown --> <property name="QueueName"></property> <!-- if a value is not supplied at runtime, the default will be used --> <property name="DestinationManager" type="javax.management.ObjectName"> <description> The ObjectName of the DestinationManager factory to be used <default-value>jboss.mq:service=DestinationManager</default-value> </property> </template-config>
A property element support the following attributes:
name - the name of the property (required)
type - the java type of the property, if not specified java.lang.String is assumed.
Arrays are allowed, as well (e.g.
[Ljava.lang.String;
).
optional - set it to true to indicate it is ok for the property to be missing.
It is false by default so if the property is absent an exception will
be thrown by the DeploymentService.
And the following sub-elements:
description - An optional free-text description to accompany the property.
default-value - If the property is required, this value will be substituted
whenever the property is missing. If the property is marked as optional
the default-value will not be used. A PropertyEditor for the chosen
type must exist, for this to work.
Creating complex deployments
A complex deployment is an unpacked directory structure containing virtually
anything. On top of this structure one or more velocity templates can be
rendered.
You choose the complex deployment mode by omitting the template attribute
from the template-config element, and using instead the template-list
sub-element. For example:
<template-config copydir="files"> <template-list> <template input="vm/jboss-service.xml.vm" output="console-mgr.sar/META-INF/jboss-service.xml"></template> <template input="vm/jboss-web.xml.vm" output="web-console.war/WEB-INF/jboss-web.xml"></template> </template-list> </template-config>
This example can be used to produce the ./deploy/management deployment that
includes the web-console.war and the console-mgr.sar. The copydir attribute
indicates that the file structure under ./conf/templates/template-name/files/
will be deep copied verbatim to the the output module (e.g. ./undeploy/module-x),
then the two specified velocity templates (specified with the input attribute)
will be rendered using the same HashMap and written to the files specified
by the corresponding output attributes. Simple?
Handling errors
The velocity templates can perform basic property validation (e.g. making
sure an integer is within certain limits, or a string follows a certain format).
For example:
... #if($PoolSize <= 0 || $PoolSize > 10) #set($template-error="Pool size must be between 1 and 10") #else <attribute name="PoolSize">$PoolSize</attribute> #end
By assigning a non-null value to the variable template-error we
tell the DeploymentService to throw an Exception back to the caller
using the variable text as the exception message. In addition,
any intermediate result from the generated module will be removed.
The VelocityEngine withing DeploymentService has been configured
to output log to the jboss logger under the category name
org.jboss.services.deployment.DeploymentService.VelocityEngine,
so it maybe possible to derive useful information when debugging
a template.
Velocity, macros and more.
Velocity is a very simple scripting language and the reason it was
chosen for the DeploymentService is because a template look very
much like the end-result, so a jboss deployment descriptor template
looks a lot like a jboss deployment descriptor.
In addition, since the template is basically free text it can
be used to generate deployment descriptors of all kinds
(e.g. jboss-service.xml, web.xml, ejb-jar.xml, etc.)
or even property files, or test data.
On the other hand you can access the objects included in the velocity
context like you would in Java, so assuming you have included
a java bean, you can call methods on the instance:
$mybean.getAddress().getZipCode().
Another useful primitive that you may consider using is foreach.
For example, having defined the following property as an array
of ObjectName instances:
<property name="DependsList" type="[Ljavax.management.ObjectName;" optional=true"></property>
we could write in the template:
#if($DependsList) #foreach($objectName in $DependsList) <depends>$objectName</depends> #end #end
To avoid repeating velocity constructs and to clean-up the template
you can create velocity macros that are shared by all templates.
Those are stored at ./conf/template/VM_global_library.vm. An
example macro is shown below:
## -------------------------------------------------------- ## ifDefReplace ## ## If $substr is contained in $string then produce $replace ## ## $string and $substr must be Strings ## -------------------------------------------------------- #macro(ifDefReplace $string $substr $replace) #if($string.indexOf($substr) >= 0) $replace#end #end
Using the macro is then easy:
#ifDefReplace($role "read" 'read="true"')
Comments/Suggestions
Please, post your comments on the
forum.
Referenced by:
Comments