Simplifying Resin XML With Dynamic Configuration - Part 2
Conditional Configuration Using Functions and Expressions
This second part of a multi-part article on Resin configuration examines how to employ conditional XML statements and EL functions to simplify Resin configuration. When we’re done, you’ll understand how the same configuration could be shared between deployment environments with entirely different Resin operation by using some following concepts:
- Conditional XML with <resin:if>, <resin:when>, <resin:choose>, <resin:otherwise>
- Null checking with “the Elvis Operator†?:
- Regular expression matching with =~
- Resource access with mbean() and jndi()
Background
The first part of this series of articles began with background on the standard Resin configuration, the differences between server configuration and application configuration, and the usage of a registry to tie these two together. It then presented the problem of maintaining configuration across deployment environments, and discussed specific functionality Resin provides to alleviate this problem. The remainder of the article went into more detail on how to use file imports and variable substitution in Resin configuration.
In addition to variable substitution and imports, there are a number of other means an admin can employ to design more powerful and simple Resin configuration. Resin supports a variety of classic conditional programming statements such as if-else, when, and not. As introduced in Part 1, the basis of dynamic configuration is Unified Express Language (EL). However Resin goes beyond standard EL by providing a few custom functions and operators specifically designed for usage in Resin configuration. This part of the series will examine Resin configuration conditionals in combination with configuration appropriate EL operators and functions.
<resin:if> Conditional Configuration
<resin:if> executes part of the configuration file conditionally. Along with <resin:choose>, which is discussed next, these elements are the basis for Resin conditional configuration. <resin:if> can be particularly powerful in combination with the special functions we’ll look at below.
Probably the most common example of conditional configuration with <resin:if> is checking for java command-line properties like -Ddev=true, to enable development mode debugging or elevated logging. <resin:if> takes 1 parameter, “testâ€, an EL expression that must evaluate to a Boolean result of true or false.
<resin:if test="${dev}">
<logger name="com.foo" level="finer"/>
</resin:if>
This example uses EL variables substitution and system properties to increase the logging level for a specific package. Usage of EL variable substitution was described in details in Part 1 of the article. Also, refer to the Resin documentation on EL Variables and Functions for more information.
You may notice that the configuration files packaged with the Resin distribution use <resin:if> frequently in combination with ${resin.professional} to conditionally execute configuration that requires a license. In general, the skipped configuration can be executed safely in Resin Open-Source but will result in license warnings in the logs.
There is no <resin:else>. For anything more complex that a simple if condition, use the <resin:choose> conditional which is discussed next.
<resin:choose> Conditional Configuration
<resin:choose> executes parts of a configuration file conditionally. In combination with <resin:when> and <resin:otherwise>, it implements a traditional if, else-if, and else structure.
<resin:choose> itself takes no parameters. It only serves as a container for one or more <resin:when> statements. <resin:when> executes a section of configuration when the test condition evaluates to Boolean true. Only the statements following the first condition that is found to be true will be executed. All other <resin:when> blocks will be skipped. If none of the <resin:when> conditions match, the catch-all <resin:otherwise> will be executed if present.
Expanding on the example above, we can change –Ddev=true to –Denv=dev, and then match on the value of env, as show here:
<resin:choose>
<resin:when test="${env=='dev'}">
<logger name="com.foo" level="finer"/>
</resin:when>
<resin:when test="${env=='qa'}">
<logger name="com.foo" level="info"/>
</resin:when>
<resin:otherwise>
<logger name="com.foo" level="warning"/>
</resin:otherwise>
</resin:choose>
<resin:message> Configuration Logging
Working with these more complex conditionals naturally elicits the question of how to test and debug. This is where <resin:message> proves useful. <resin:message> simply logs a message to the Resin log file. The content of the XML element is the logged message, which can include evaluation of EL expressions. This provides the ability to log helpful information describing configuration processing as it is occurring.
Consider the following log level example from above, now with <resin:message> statements inserted that describe how logging is being configured:
<resin:choose>
<resin:when test="${env=='dev'}">
<logger name="com.foo" level="finer"/>
<resin:message>com.foo logging set to finer...</resin:message>
</resin:when>
<resin:when test="${env=='qa'}">
<logger name="com.foo" level="info"/>
<resin:message> com.foo logging set to info...</resin:message>
</resin:when>
<resin:otherwise>
<logger name="com.foo" level="warning"/>
<resin:message> com.foo logging set to warning...</resin:message>
</resin:otherwise>
</resin:choose>
<resin:set> Variable Declaration
<resin:set> adds an EL variable to the EL or JNDI context. We’ll discuss working with JNDI at the end of the article, and focus now only on setting an EL variable. This element is useful anywhere declaring a value for later use is appropriate. Usually this is only necessary in more complex situations where you need to indicate some processing occurred earlier in the configuration.
Continuing with the logging example, here it is modified to take advantage of <resin:set>:
<resin:choose>
<resin:when test="${env=='dev'}">
<resin:set var="level" value="finer"/>
</resin:when>
<resin:when test="${env=='qa'}">
<resin:set var="level" value="finer"/>
</resin:when>
<resin:otherwise>
<resin:set var="level" value="finer"/>
</resin:otherwise>
</resin:choose>
<logger name="com.foo" level="${level}"/>
<resin:message>com.foo logging set to ${level}...</resin:message>
?: Null Checking
Recent versions of Resin include the so-called Elvis Operator (?:) as a convenient means to check for null in EL. This is just a shortcut ternary operator that return an alternative value if the condition is null. Resin includes full support for all the other Unified Expression Language operators as well.
?: is used frequently in the Resin distribution configuration in combination with <resin:properties> to provide a default value in case a property value is not set. It works particularly well for configuration, as illustrated in the following example. Here, we’re starting Resin with the logging level set as system property (-Dfoo_level=info), and defaulting to “finer†level just in case Resin is started without the parameter:
<logger name="com.foo" level="${foo_level?:'finer'}"/>
Notice in the example above, ‘finer’ is enclosed in single quotes. This is necessary for EL to interpret the value as a string literal! Without the single quotes, the token “finer†will be interpreted as an EL variable, consequently assigning a null value for the unassigned variable with no warning!
=~ Regular Expression Matching
=~ is another powerful Resin extension to Unified EL. This operator permits inline regular expression matching. It should be a familiar to any Perl programmer. The following example demonstrates how to log a warning if Resin is being executed from any directory containing the string ‘tmp’.
<resin:if test="${resin.root =~ 'tmp'}">
<resin:message>
WARNING: Resin is running from a temp directory: ${resin.root}
</resin:message>
</resin:if>
Regular expression matching works particularly well with the health checking system, in combination with the mbean function discussed next. Consider the following example, which sends an email upon startup if Java’s bootclasspath does not contain a required jar.
<health:SendMail>
<to>admin@yourdomain.com</to>
<health:Not>
<health:IfExpr
test="${mbean('java.lang:type=Runtime').BootClassPath =~ 'resource-16.jar'}"/>
</health:Not>
</health:SendMail>
By convention, the snippet above should be placed in health.xml rather than resin.xml. Much more information on health checking configuration is available in the Resin health checking documentation.
mbean() JMX Access
The mbean() configuration utility function that provides access to any JMX MBean to retrieve attribute values. This can be extremely powerful and convenient, since often information in available in JMX that in not available elsewhere, as part of the system or environment properties.
Consider the following example that sets the thread pool max to a multiple of 2048 based on the number of available processors. An elvis operator is also used in case the MBean attribute is not available:
<server-default>
...
<thread-max>
${(mbean('java.lang:type=OperatingSystem').AvailableProcessors ?: 1) * 2048}
</thread-max>
class_exists() Class Availability Checking
The class_exists function is another handy utility that lets you conditionally execute configuration based on the availability of a Java class. It simply does a “Class.forName†and returns a Boolean result. You can see this used in the Resin distribution file cluster-default.xml, to load jsse-ssl HTTPS support only if JSSE libraries are available:
<resin:if test="${class_exists('sun.security.x509.CertAndKeyGen')}">
<http address="*" port="443">
<jsse-ssl self-signed-certificate-name="resin@localhost"/>
</http>
</resin:if>
In this example, CertAndKeyGen is a class known to exist in the JSSE library, thus if it’s found we can be fairly certain the entire JSSE jar is in the classpath. Another common use-case is loading database resources on a backend server only if the JDBC driver is available. This makes it possible to share configuration between frontend and backend server, simply by not including the database driver library on the front-end server.
<resin:if test="${class_exists('com.mysql.jdbc.Driver')}">
<resin:message>Configuring MySQL database resources...</resin:message>
<database jndi-name="jdbc/mysql">
<driver type="com.mysql.jdbc.Driver">
<url>jdbc:mysql://user:password@192.168.1.1:3306/dbname</url>
</driver>
</database>
</resin:if>
jndi() JNDI Registry Lookup
Any object or value registered in a JNDI context can be accessed using the jndi function. It takes the JNDI name/URL as a parameter. Note, all JNDI access in Resin is assumed to be from the parent context of “java:comp/env/†unless the full URL is specified. So “jdbc/mysql†is simply shorthand for “java:comp/env/jdbc/mysqlâ€.
The value returned by this function is the actual object bound in the registry, so you can even execute functions on it if necessary. Consider the following resin-web.xml example that uses much of what we have already discussed in the article, to conditionally register a servlet filter if the URL of the database is not on the local machine. The object retrieved from JNDI is a database pool registered in the server configuration:
<web-app xmlns="http://caucho.com/ns/resin"
xmlns:ee="urn:java:ee"
xmlns:resin="urn:java:com.caucho.resin">
<resin:if test="${!(jndi('jdbc/mysql').URL =~ '127.0.0.1|localhost')}">
<filter>
<filter-name>db_security</filter-name>
<filter-class>test.MyDbSecurity</filter-class>
</filter>
<filter-mapping>
<filter-name>db_security</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</resin:if>
</web-app>
Note: you may notice in Resin documentation that functions named “jndi_lookup†and “jndi:lookup†are mentioned. These are just aliases for the same function discussed here.
The <resin:set> section earlier in the article mentioned support for JNDI. To supplement the var attribute, <resin:set> also has a “jndi-name†attribute. This provides the convenient ability to bind EL variables in JNDI. The following example demonstrates a slick way to provide the number of CPUs as a JNDI variable:
<resin:set jndi-name="os/cpuCount"
value="${mbean('java.lang:type=OperatingSystem').AvailableProcessors}"/>
As another example, we could use <resin:set> and jndi() rebind a JNDI resource to a different name:
<resin:set jndi-name="jdbc/mysql" value="${jndi('jdbc/wiki')}"/>
Conclusion
Resin provides conditional XML elements and handy utility functions to allow admins to design simple and powerful configuration. The first part of this series of articles examined how to use EL variable substitution imports. This part expanded on that concept by providing examples on conditional configuration with function. Maintaining configuration across deployment environments can be a cumbersome and error-prone without the proper tools. Hopefully after reading to this point you have a better understanding of the tools Resin provides to simply configuration and make job easier.
Tags: =~, configuration, jmx, jndi, mbean, regex, resin:choose, resin:if, resin:when, xml
