Using XML files for configuration

EasyConf handles XML configuration files by converting the XML data to Java objects (POJOs). There is no need to work with complex DOM objects or alike. The XML structure is converted to POJOs that already exist in your project or to ones you create to hold the configuration. Those objects have no dependency on EasyConf or its XML source and they can have additional business logic if wanted.

The first step you should take is to design the XML definition that you want to use (if you don't already have one) and fill a sample XML file. Then you should define the POJOs that will hold the configuration. You can design them completly independent of each other but you'll later see that following some conventions makes the mapping file a lot simpler.

Once you have an XML file and the necesary POJOs, the second step is to write a mapping file. By having to write by hand a mapping file EasyConf gives up a little ease of use in favor of flexibility. We believe this is a worthy trade off because it makes the library available for much broader uses, such as when the configuration files already exist and you cannot change it.

The mapping file follows the markup language of digester rules. The example of the next sections will show it is not difficult to use at all.

Once the mapping file has been written the resulting object which contains the configuration can be obtained using the getConfigurationObject() method of ComponentConfiguration. For example:

            EasyConf.getConfiguration("calculator").getConfigurationObject();
         

In this example the resulting configuration object will be created by reading the contents of the file calculator.xml and converting it to java objects according to the rules defined in the file calculator.digesterRules.xml

Quick introduction to Digester

The best way to learn to use Digester is by going through an example. But before we go into the details you have to know some concepts of how Digester works: When parsing an XML file Digester reads the XML structure as if it were a tree each time it finds an element or an attribute it issues an event. The user can program or configure rules which catch those events and perform some action. Digester comes with several default rules which are enough for most uses. These rules work with a stack to build the object representation of the XML file. As the XML structure is visited objects are filled and pushed into the stack. The actions of the next rules will operate on the last introduces objects of the stack. During the example we'll represent the stack state as follows:

            |       |
            |       |
            |object1|
            |object2|
            ---------
         

Where object1 is the last object introduced into the stack.

Explanation by example

We'll use as an example the configuration file of a Content Management System. It supports several content types, each with its own set of fields and properties. The XML file which describes the content types is the following:

             
 <cms>
     <contentTypes>
         <contentType
             id="0"
             directory="news-notes"
             name="cms.news.notes.name"
             dataClass="com.foo.bar.cvro.model.NewsNoteData"
             iconFile="ictipo_apunte.gif"
             include="true"
             creationFieldSet="0">

             <field id="contentData.title"
                            name="cms.field.title"
                            type="text"
                            maxlength="256"
                            defaultValue=""
                            width="90"
                            height=""
                            depends="required"
                            options="">
              </field>
                       ........
         </contentTypes>
 </cms>
             
         

This XML file is converted to several POJOs. First there is a class called ContentTypeDefinitions which holds a List of the available content types. Each content type is represented by the following class:

             
public class ContentTypeDefinition {

    private String name;
    private Long id;

    private String directory;
    private String dataClass;
    private String createMapping;
    private String include;
    private String iconFile;
    private String creationFieldSet;

    private List fields;

         // ................ Getters and setters .................
}            
         

This class has a list of fields. Each of them is represented using the class FieldDefinition:

             
public class FieldDefinition {

    private String id;
    private String name;
    private String type;
    private String maxlength;
    private String defaultValue;
    private String width;
    private String height;
    private String depends;
    private String options;



    // ................ Getters and setters .................
}
            
         

The mapping file used to convert the XML file to these objects is:

            
<?xml version="1.0"?>

<digester-rules>
    <pattern value="cms/contentTypes">
        <object-create-rule classname="com.foo.bar.cms.ContentTypeDefinitions"/>
        <pattern value="contentType">
            <object-create-rule classname="com.foo.bar.cms.ContentTypeDefinition"/>
            <set-properties-rule/>
            <set-next-rule methodname="addContentTypeDefinition"
                           paramtype="com.foo.bar.cms.ContentTypeDefinition"/>
            <pattern value="field">
                 <object-create-rule classname="com.foo.bar.cms.FieldDefinition"/>
                 <set-properties-rule/>
                 <set-next-rule methodname="addFieldDefinition"
                            paramtype="com.foo.bar.cms.FieldDefinition"/>
             </pattern>
        </pattern>
    </pattern>
</digester-rules>
               
            

Let's go over it. The pattern elements set the rules into a context. the first pattern, <pattern value="cms/contentTypes"> states that we only care about the contents inside the contentTypes element. Inside this pattern we find the first rule object-create-rule. This rule will be fired when the Digester parser finds an element called contentTypes that is inside an element called cms.

Digester comes with many rules but only 3 or 4 are used frequently when using an XML file for configuration purposes. The most common rules and its most important attributes are:

  • object-create-rule: creates a new object and pushes it onto the object stack. It has one parameter, classname which has the name of the class of the object to be created. (More information)
  • set-properties-rule: sets properties on the object at the top of the stack, based on attributes with corresponding names. No attribute is needed, but you can filter the properties read by using the attributes attributeNames and propertyNames. (More information)
  • set-next-rule: calls a method on the (top-1) (parent) object, passing the top object (child) as an argument. The attribute methodname is used to specify the method which should be called on the parent object to set the child object. the attribute paramtype specifies the type of the child object. (More information)
  • call-method-rule: calls a method on an object on the stack (normally the top/parent object), passing arguments collected from subsequent CallParamRule rules or from the body of this element. The methodName attribute is used to specify the method that will be called on the stack object. The paramCount attribute specifies the number of parameters. The value of those parameters values will be specified by this number of call-param-rule rules. (More information)
  • call-param-rule: saves a parameter for use by a surrounding CallMethodRule. There are several ways to specify the value of the parameter. (More information)

All other rules are documented in the Digester Javadocs. You can also create your own rules as described in the section Including programmatically-created rules.

Now that you know what the rules do, let's continue with the rules of our example. After firing the first object-create-rule the content of the stack is:

            |                      |
            |                      |
            |                      |
            |contentTypeDefinitions|
            -----------------------
         

Let's review the rules that we had left

            
        ...
        <pattern value="contentType">
            <object-create-rule classname="com.foo.bar.cms.ContentTypeDefinition"/>
            <set-properties-rule/>
            <set-next-rule methodname="addContentTypeDefinition"
                           paramtype="com.foo.bar.cms.ContentTypeDefinition"/>
            <pattern value="field">
                 <object-create-rule classname="com.foo.bar.cms.FieldDefinition"/>
                 <set-properties-rule/>
                 <set-next-rule methodname="addFieldDefinition"
                            paramtype="com.foo.bar.cms.FieldDefinition"/>
             </pattern>
        </pattern>
        ...
               
            

Next we find another pattern whose value is contentType. It tells digester to fire several rules each time a contentType element is found in the configuration file. The first rule (object-create-rule) creates an instance of ContentTypeDefinition and pushes it into the stack. The second (set-properties-rule) populates its properties. The third one (set-next-rule) calls the method addContentType of contentTypeDefinitions to add the definition to the list. The same process is followed to read the fields and add them to the list of fields of the content type currently being read. When the parsing is inside a field the content of the stack is:

            |                      |
            |fieldDefinition       |
            |contentTypeDefinition |
            |contentTypeDefinitions|
            -----------------------
         

We have seen how the object-create-rule pushes objects into the stack but, who takes them out? Digester does it automatically. In our example, when a contentType XML element is entered (the tag <element> is found) the object-create-rule is fired and an object is pushed into the stack. But when the contentType XML element finishes (the tag </element> is found) all pushed objects are popped. That's why the set-next-rule is necessary, so that the previous object in the stack keeps a reference to the object before it is popped out.

Getting to know digester in great detail takes some time. Hopefully this example has shown enough to get you starting. You can learn more reading some of the following articles:

When reading this articles remember that with EasyConf the interface with Digester is only through the mapping file, you cannot access it's API directly (and shouldn't need it).

XML tips

  • Map elements to classes and attributes to fields of those classes where possible. This simple rule will simplify your mapping files.
  • When adding a list of elements to a parent object with a set-next-rule remember that you can use any structure to hold the items. For simple cases lists are ok, but for others it's better to use Hashtables or more complex structures.

Configuring an application for an ASP

An ASP or Application Service Provider offers applications to several customers from a single installation. In this environment a single running application may need to have different configurations depending on the customer that is accessing the application. EasyConf is prepared to configure applications which are developed to work in this environment.

In this situation the application must ask EasyConf for the configuration providing both the component name and a company id. The company id will represent the unique customer id which is currently accesing the application:

         Easyconf.getConfiguration("companyA", "calculator").getConfigurationObject();
      

In this scenario EasyConf will search for a file called calculator-companyA.xml and load the configuration from it (as specified in the mapping file calculator.digesterRules.xml). Only if this file does not exist the configuration will be read from calculator.xml.