XML Configuration File Processor

Introduction

It is currently common to build a number of releases from a single code base. For example, a development release, a QA release, a production release and perhaps customer-specific releases. However, these releases seem to differ mostly in the contents of their XML configuration files, and then only very little. Maintaining all these slightly different configuration files is a real nuisance.

XConf was created to simplify this maintenance. Its fundamental premise is that a single development-release (or production-release) configuration file is created and maintained, and is processed by XConf at either build or deployment time into an appropriate release by applying one or more XML-based scripts. Each script contains only the differences required to create the appropriate release, thus removing the need for the mass duplication of configuration files.

This is not really a new solution, since XSLT has been used in the past to do this quite successfully, but XPath can get a little arcane, and maintaining transformation scripts using XSLT can become really complex very quickly. XConf uses a very simple and compact method of specifying elements that need to be processed, and provides some very useful constructs to make transformations painless.

UPDATE: XConf is now able to process properties configuration files in addition to XML files. Check out usage and requirements and properties file processing below for more information.


Project Links


Usage

XConf can be invoked from the command line or shell scripts using:
    java -jar xconf-standalone.jar [arguments]
      -config <file>           (Required) full path to configuration file
      -in <file>               (Required) full path to input file to be processed
      -out <file>              (Required) full path to processed output file
      -tokens <file>           (Optional) full path to properties file containing replacement tokens
      -properties              (Optional) process config, in & out as properties files
      -validate                (Optional) validate input XML file
XML Validation
By default, XConf does not validate the input XML file, and if a Xerces SAX parser is present in the classpath, XConf completely ignores any DTDs. All it really requires is a well-formed XML file. You can force XConf to validate any input XML files it processes by adding the optional -validate argument to the invocation command line.
XML File Processing
By default, XConf assumes that both the configuration and input files are XML. You can direct it to process them as properties files by adding the optional -properties to the invocation command line. In that case, the configuration file should be a properties file in the format described by properties file processing, and the input file should also be a valid properties file.
There is also an Ant task to invoke XConf within Ant build scripts:
    <target name="create-production-config">
        <taskdef name="xconf" classname="net.sourceforge.xconf.AntTask">
            <classpath>
                <pathelement location="lib/xconf-standalone.jar"/>
            </classpath>
        </taskdef>
        <xconf config="etc/production.xml" input="war/WEB-INF/web.xml" output="${dist.dir}/web.xml">
            <token name="sessionTimeout" value="30" />
        </xconf>
    </target>

xconf

Attribute Description Required
config Absolute or relative path to a processing script file Yes
input Absolute or relative path to a file to process Yes
output Absolute or relative path to output the processed file Yes
validate Validate the input XML file against its declared DTD. Default is "false". No
properties Treat the config, input and output files as properties and not XML. Default is "false". No
tokens Use replacement tokens from the given properties file. No

token

Each xconf task can contain token elements that represent replacement tokens for processing directives.
These are usually supplied to the command line instance from a properties file referenced by the -tokens optional flag. These token elements will replace any conflicting values provided via the tokens attribute.

Attribute Description Required
name ${token} to be replaced Yes
value replacement value Yes

XML Script Example

During development, a web application has a 30 minute timeout, but for production the timeout should be set to 5 minutes.
<web-app>
    ...
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    ...
</web-app>
The following script can be applied using XConf to change the session timeout during a production build.
<?xml version="1.0"?>
<xconf>
    <setText path="web-app/session-config/session-timeout">
       <text>5</text>
    </setText>
</xconf>
Resulting in the following production release web.xml.
<web-app>
    ...
    <session-config>
        <session-timeout>5</session-timeout>
    </session-config>
    ...
</web-app>

Processing Actions

NOTE: All processing actions require a path attribute to define the XML element(s) in the input file that will be altered/processed. See the Path Syntax section below for a description of the contents of this attribute.
1. setText
- sets the text content of an XML element
- uses a <text> child element to define the new text content
- the text element's content can contain replacement tokens.
- Example: Set the session timeout of a web application to 20 minutes:
   <setText path="web-app/session-config/session-timeout">
       <text>20</text>
   </setText>
- As of release 1.9.6 the text child element is optional, so you could rewrite the above as:
   <setText path="web-app/session-config/session-timeout">20</setText>
2. removeText
- removes the text content of an XML element
- Example: Remove the title from an XHTML document:
   <removeText path="html/head/title"/>
3. setAttribute
- sets and adds attributes to an XML element
- requires <attribute> child elements to provide the name and value for each new attribute
- attribute name and value can contain replacement tokens.
- Example: Set the path of all tiles definitions:
   <setAttribute path="tiles-definitions/definition">
       <attribute name="path" value="/WEB-INF/vm/layout/layout.vm"/>
   </setAttribute>
4. removeAttribute
- removes attributes from an XML element
- requires <attribute> child elements to provide the name of the attributes to be removed
- Example: Remove the singleton attribute from all spring beans:
   <removeAttribute path="beans/bean">
       <attribute name="singleton"/>
   </removeAttribute>
5. addElement
- adds new child element(s) to an existing XML element
- requires 1 or more child elements as prototypes for the new element or elements
- Example: Add a new put element to all tiles definitions:
   <addElement path="tiles-definitions/definition">
       <put name="header" value="/WEB-INF/vm/layout/header.vm"/>
   </addElement>
6. removeElement
- removes an XML element from the document
- Example: Remove the session timeout setting from a web application:
   <removeElement path="web-app/session-config/session-timeout"/>
7. replaceElement
- replaces an existing XML element
- requires 1 or more child elements as prototypes for the replacement element or elements
- Example: Redefine an existing tiles root layout:
    <replaceElement path="tiles-definitions/definition[+a|name|rootLayout]">
        <definition name="rootLayout" path="/WEB-INF/vm/layout/layout.vm">
            <put name="title" value=""/>
            <put name="header" value="/WEB-INF/vm/layout/header.vm"/>
            <put name="menu" value="/WEB-INF/vm/layout/menu.vm"/>
            <put name="body" value=""/>
            <put name="footer" value="/WEB-INF/vm/layout/footer.vm"/>
        </definition>
    </replaceElement>

Path Syntax

All these nice processing actions would be pretty pointless without a compact way of describing the elements that need to be processed. If this syntax looks similar to XPath, it is not an accident.

Each element in an XML document tree is defined by a node in the path string.
   path        :=  node/node/node
   node        :=  element OR element[constraint] OR .. (parent element)
   element     :=  name of XML element OR * (all elements)
   constraint  :=  [+-]cType|cName(|cValue)
   [+-]        :=  + (include), - (exclude)
   cType       :=  a (attribute), t (text), c (child)
   cName       :=  name of attribute for (a), normalized text for (t), name of child element for (c)
   cValue      :=  optional, for (a) it is the value of named attribute, and for (c) it is the
                   normalized text content of the child element
So, for example if we want to remove the singleton attribute only from spring beans that have an attribute named "id" with a value of "dataSource", we would create a removeAttribute action with:
   <removeAttribute path="beans/bean[+a|id|dataSource]">
       <attribute name="singleton"/>
   </removeAttribute>
In a more complex example, if we want to change the value of the obfuscatedParams initialisation parameter in a web application filter named RequestTraceFilter, we would create a setText action with:
   <setText path="web-app/filter/filter-name[+t|RequestTraceFilter]/../init-param/param-name[+t|obfuscatedParams]/../param-value">
       <text>password,repeatPassword</text>
   </setText>
Try doing this with XSLT and see how many lines it takes :-)

XML Script Constants

These are optional node and path elements that describe replacement tokens which can be used in the path attributes of processing actions.

A script without constants:
<?xml version="1.0"?>
<xconf>

    <setText path="web-app/session-config/session-timeout">
       <text>20</text>
    </setText>

    <removeElement path="web-app/servlet/servlet-name[+t|velocity]/.." />

    <setText path="web-app/filter/filter-name[+t|RequestTraceFilter]/../init-param/param-name[+t|obfuscatedParams]/../param-value">
       <text>password,repeatPassword</text>
    </setText>

</xconf>
Same script with constants:
<?xml version="1.0"?>
<xconf>

    <node name="requestTraceFilter" value="filter-name[+t|RequestTraceFilter]"/>
    <node name="obfuParamsName" value="param-name[+t|obfuscatedParams]"/>
    <path name="obfuParamsValue" value="init-param/${obfuParamsName}/../param-value"/>

    <setText path="web-app/session-config/session-timeout">
       <text>20</text>
    </setText>

    <removeElement path="web-app/servlet/servlet-name[+t|velocity]/.." />

    <setText path="web-app/filter/${requestTraceFilter}/../${obfuParamsValue}">
       <text>password,repeatPassword</text>
    </setText>

</xconf>
Constant definitions:
1. node
- can be used to define a single path node for reuse in multiple path definitions.
- can also be used to define nodes that contain '/' characters, which will otherwise lead to path attribute parsing errors. For example:
    element[+a|url|http://localhost.com]
- it is referenced in action path attributes by enclosing its name in '${' and '}' characters.
- Example: redefine a Spring DataSource's jndi name:
    <node name="jndiRef" value="value[+t|java:comp/env/jdbc/catalogue]"/>

    <setText path="beans/bean[+a|id|dataSource]/property/${jndiRef}">
        <text>java:comp/env/jdbc/catalogue2</text>
    </setText>
2. path
- can be used to define a complete or partial path for reuse in multiple action path attributes.
- may be composed of previously declared path and node constant references.
- it is referenced in action path attributes by enclosing its name in '${' and '}' characters.
- Example: perform multiple processing actions on a web-app filter definition:
    <path name="requestTraceFilter" value="web-app/filter/filter-name[+t|RequestTraceFilter]/.."/>

    <path name="obfuscatedParams" value="${requestTraceFilter}/init-param/param-name[+t|obfuscatedParams]/.."/>

    <removeElement path="${requestTraceFilter}/init-param"/>

    <setText path="${obfuscatedParams}/param-value">
        <text>password,repeatPassword</text>
    </setText>

Properties File Processing

Processing of properties files is currently done in a very simple manner. At the start of processing, 2 properties files are read by XConf. The first is a config file describing the processing actions to take, and the second is an input file to be processed. The config file is then used to process the input file, creating an output properties file that contains the results of the processing. Please note that the order of properties from the input file is not preserved in the output file, since the java.util.Properties API does not guarantee the order of key/value pairs.

Copyright © 2007-2013 Thomas Czarniecki. All Rights Reserved.