Skip to main content

Managing configuration files with JAXB and the Visitor Pattern

Posted by fabriziogiudici on May 18, 2011 at 2:34 AM PDT

Complicated year for me and I've not blogged something with decent technical stuff for a while. This is the n+1 restart that I'm trying... let's see if I can resume with a constant flow of useful stuff.

 

The subjects of this post are two: first, a design topic (how to avoid anemic object when handling the processing of configurations) and second some extensions of JAXB to accomplish the design task (en passant, I'll also show you how JAXB is used with Maven)

The context is my BlueBook mojo, a Maven plugin that allows to extract chunks of code from real projects (available in a Maven repository) to be used as examples in a book written with DocBook (actually, I'm writing Exercises of Design in this way). 

The first release of my BlueBook mojo was really raw and relied upon the standard Maven configuration for getting all the required parameters, configuring multiple executions of the plugin. While this works, it leads to a very verbose configuration, also because you can't design your own schema - an approach that could benefit of some specific optimization derived by a specific structure of the configuration file.

The obvious solution is to use an external configuration file, with a given format. I don't have any particular hate to XML, which I choose it for this purpose. JAXB is a good technology to make it easier the process of parsing the XML, since it allows to automatically generate both the code that handles the data and the parser itself starting from a XSD schema. It sounds the right approach to me, as it pushes you to start by clearly defining the structure of your XML.

This is the XSD that defines the BlueBook configuration schema:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="configuration" type="ConfigurationType"/>

    <xsd:complexType name="ConfigurationType">
        <xsd:sequence>
            <xsd:element name="skipHeader"        type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipBlockComments" type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipPackage"       type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipImports"       type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="artifactGroup"     type="ArtifactGroupType" maxOccurs="unbounded" />
        </xsd:sequence>
    </xsd:complexType>

    <xsd:complexType name="ArtifactGroupType">
        <xsd:sequence>
            <xsd:element name="outputDirectory"   type="xsd:string"   minOccurs="1" maxOccurs="1"/>
            <xsd:element name="skipHeader"        type="xsd:boolean"  minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipBlockComments" type="xsd:boolean"  minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipPackage"       type="xsd:boolean"  minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipImports"       type="xsd:boolean"  minOccurs="0" maxOccurs="1"/>
            <xsd:element name="artifact"          type="ArtifactType" maxOccurs="unbounded" />
        </xsd:sequence>
    </xsd:complexType>

    <xsd:complexType name="ArtifactType">
        <xsd:sequence>
            <xsd:element name="groupId"           type="xsd:string"  minOccurs="1" maxOccurs="1"/>
            <xsd:element name="artifactId"        type="xsd:string"  minOccurs="1" maxOccurs="1"/>
            <xsd:element name="version"           type="xsd:string"  minOccurs="1" maxOccurs="1"/>
            <xsd:element name="classifier"        type="xsd:string"  minOccurs="1" maxOccurs="1"/>
            <xsd:element name="type"              type="xsd:string"  minOccurs="1" maxOccurs="1"/>
            <xsd:element name="file"              type="FileType"    maxOccurs="unbounded" />
        </xsd:sequence>
    </xsd:complexType>

    <xsd:complexType name="FileType">
        <xsd:sequence>
            <xsd:element name="fileName"          type="xsd:string"  minOccurs="1" maxOccurs="1"/>
            <xsd:element name="outputFile"        type="xsd:string"  minOccurs="1" maxOccurs="1"/>
            <xsd:element name="skipHeader"        type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipBlockComments" type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipPackage"       type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="skipImports"       type="xsd:boolean" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="includedFragment"  type="xsd:string"  minOccurs="0"/>
            <xsd:element name="excludedFragment"  type="xsd:string"  minOccurs="0"/>
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>

For instance, it allows to write a configuration such as

<configuration>
    <!-- ======== Finder Examples ================================================================================== -->
    <artifactGroup>
        <skipPackage>false</skipPackage>
        <skipImports>false</skipImports>
        <outputDirectory>Basic Concepts/Fluent Interfaces</outputDirectory>

        <artifact>
            <groupId>it.tidalwave.thesefoolishthings</groupId>
            <artifactId>it-tidalwave-util</artifactId>
            <file>
                <fileName>it/tidalwave/util/Finder.java</fileName>
                <excludedFragment>other</excludedFragment>
            </file>
            <file>
                <fileName>it/tidalwave/util/spi/SimpleFinderSupport.java</fileName>
                <excludedFragment>other</excludedFragment>
            </file>                        
            ...
        </artifact>

        <artifact>
            <groupId>it.tidalwave.thesefoolishthings</groupId>
            <artifactId>it-tidalwave-util</artifactId>
            <classifier>javadoc</classifier>
            <file>
                <fileName>it/tidalwave/util/Finder.png</fileName>
            </file>
        </artifact>
    </artifactGroup>

    <!-- ======== Finder Example 1 ================================================================================= -->
    <artifactGroup>
       ...
    </artifactGroup>
</configuration>
This configuration chunk tells BlueBook to search for the source artifacts of the project it.tidalwave.thesefoolishthings:it-tidalwave-util and to extract portions of the Finder.java and SimpleFinderSupport.java to the working directory, properly formatted for the inclusion in DocBook.
Using JAXB with Maven is a breeze. You just need to declare the proper dependencies to the JAXB libraries and tools:
    <dependencies>
        <dependency>
            &lt;groupId&gt;j<strong>avax.xml.bind</strong>&lt;/groupId&gt;
            &lt;artifactId&gt;<strong>jaxb-api</strong>&lt;/artifactId&gt;
            &lt;version&gt;2.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;<strong>com.sun.xml.bind</strong>&lt;/groupId&gt;
            &lt;artifactId&gt;<strong>jaxb-impl</strong>&lt;/artifactId&gt;
            &lt;version&gt;2.1.3&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
and then bind the Maven JAXB plugin xjc goal to the generate-sources phase in order to have the xjc compiler kicking in during the build of the project:
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;
                &lt;artifactId&gt;jaxb2-maven-plugin&lt;/artifactId&gt;
                &lt;executions&gt;
                    &lt;execution&gt;
                        &lt;goals&gt;
                            &lt;goal&gt;<strong>xjc</strong>&lt;/goal&gt;
                        &lt;/goals&gt;
                        &lt;phase&gt;<strong>generate-sources</strong>&lt;/phase&gt;
                    &lt;/execution&gt;
                &lt;/executions&gt;
                &lt;configuration&gt;
                    &lt;packageName&gt;it.tidalwave.bluebook.configuration.jaxb&lt;/packageName&gt;
                    &lt;schemaDirectory&gt;${basedir}/src/main/xsd&lt;/schemaDirectory&gt;
                    &lt;extension&gt;true&lt;/extension&gt;
                &lt;/configuration&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;

Fine. What's next?

 

Fight anemic objects!

What I don't like is that, by default, JAXB generates anemic objects. An anemic object is one that doesn't have any behaviour, but only state, in Java typically exposed by means of getter/setter methods, à la JavaBeans standard. Since status without behaviour is useless, an anemic object always needs an active counterpart which manages it. According to the good practices of properly allocating roles and responsibilities to classes in your design, this is a mistake since you're splitting a single role (handle a configuration file) into two classes (or two set of classes). This often leads to violations of the DRY principle (Don't Repeat Yourself) as I'm going to show below.

These are reasons for which an anemic object shouldn't be considered a true object at all, but a sort of degraded one.

These are the objects that JAXB created for me: a class for each complexType of the schema and an ObjectFactory for creating them (thìs factory is also used by the unmarshaller provided by the JAXB runtime that automatically parses a XML file adhering to the given schema and returns a tree of properly populated instances of these classes);

Design patterns help us in improving this situation. Since my configuration data structure is in form of a tree, the Visitor Pattern is specially helpful. Forget JAXB and its generated classes for a moment (so, instead of the ConfigurationType or FileType classes, let's talk of Configuration and File). The idea is about a class named ConfigurationVisitor where you can set all the properties for a single processing step and exposes methods for running the processing step (at this stage of BlueBook, the single exposed method is extractFile(), others will come in future). Now all the classes representing a chunk of configuration need only to expose an accept() method to receive the visitor:

 

 

Each class locally copies all the properties it manages into the visitor and then passes the visitor to its children. For instance:

public class Configuration implements ConfigurationElement
  {
    @Override
    public void accept (final @Nonnull ConfigurationVisitor visitor)
      throws Exception
      {
        visitor.setSkipHeader(isSkipHeader());
        visitor.setSkipBlockComments(isSkipBlockComments());
        visitor.setSkipPackage(isSkipPackage());
        visitor.setSkipImports(isSkipImports());

        acceptInChildren(visitor, getArtifactGroup());
      }
  }

Configuration elements such as <file>, which actually perform an operation, calls their related method on the visitor, such as extractFile():

public class File implements ConfigurationElement
  {
    @Override
    public void accept (final @Nonnull ConfigurationVisitor visitor)
      throws Exception
      {
        visitor.setFileName(getFileName());
        visitor.setOutputFile(getOutputFile());
        visitor.setSkipHeader(isSkipHeader());
        visitor.setSkipBlockComments(isSkipBlockComments());
        visitor.setSkipPackage(isSkipPackage());
        visitor.setSkipImports(isSkipImports());
        visitor.setExcludedFragment(getExcludedFragment());
        visitor.setIncludedFragment(getIncludedFragment());

        visitor.extractFile();
      }
  }

This approach is better than the one with anemic objects, since the latter ones would need a single configuration processor class that knows the whole schema of the configuration file - for instance, that <configuration> has got <artifactGroup> children, that <artifactGroup> has got <artifact> children an so on; and which properties each class exposes. This would be an unnecessary replication of information (violation of the DRY principle), since the information about children and properties is already in the JAXB generated classes - in other words, it's the class Configuration that already knows that it has children of type ArtifactGroup and which properties to set on the visitor.

So, all the mojo has to do now is to get an instance of Configuration and have it to accept a ConfigurationVisitor:

final Configuration configuration = new Configuration();
final ConfigurationVisitor visitor = new ConfigurationVisitor();

configuration.accept(visitor);&nbsp;

Any future change in the configuration schema will be locally dealt with code fixes in single classes representing the XML elements; such changes will be completely opaque to the mojo, that will still just need to kick in the Configuration as shown (actually, if you look in the real code, things are just a bit more complicated, but only for specific Maven stuff that it's not in the scope of the current discussion).

So, now the big question: is it possible to transform the anemic classes generated by JAXB in the new design based on the Visitor Pattern? 

Of course, yes, it's possible.

 

Using non anemic objects with JAXB with a custom ObjectFactory

The first step is easy: I just have my participants in the Visitor Pattern to extend the JAXB classes - so, for instance, Configuration extends ConfigurationType, File extends FileType, and so on:

But at this point, though, JAXB still unmarshalles instances of the original ConfigurationType, FileType, etc... and not my classes. JAXB provides an extension point on this purpose allowing the subclassing of the ObjectFactory:

@XmlRegistry
public class ObjectFactory extends it.tidalwave.bluebook.configuration.jaxb.ObjectFactory
  {
    @Override @Nonnull
    public ConfigurationType createConfigurationType()
      {
        return new Configuration();
      }

    @Override @Nonnull
    public ArtifactType createArtifactType()
      {
        return new Artifact();
      }

    @Override @Nonnull
    public FileType createFileType()
      {
        return new File();
      }

    @Override @Nonnull
    public ArtifactGroupType createArtifactGroupType()
      {
        return new ArtifactGroup();
      }
  }

The custom ObjectFactory can be set to override the default one by just setting a property in the JAXB unmarshaller:

        final JAXBContext jc = JAXBContext.newInstance("it.tidalwave.bluebook.configuration.jaxb");
        final Unmarshaller u = jc.createUnmarshaller();
        u.setProperty("com.sun.xml.bind.ObjectFactory", new ObjectFactory()); // using my own ObjectFactory

        final File file = ...;
        final Configuration configuration = ((JAXBElement&lt;Configuration&gt;)u.unmarshal(file)).getValue();

This would probably be enough and I could consider the job accomplished. But...

 

Making JAXB generate classes implementing / extending a common interface / class

But I have another need: the behaviour of passing a visitor to all the children can be expressed as a common behaviour inherited by all the configuration classes, for instance subclassing a common superclass:

public class ConfigurationElementSupport implements ConfigurationElement
  {
    protected void acceptInChildren (final @Nonnull ConfigurationVisitor visitor,
                                     final Collection&lt;? extends ConfigurationElement&gt; elements)
      throws Exception
      {
        for (final ConfigurationElement element : elements)
          {
            element.accept(visitor.clone());
          }
      }
  }

You might argue that the acceptInChildren() method could be static and just placed in a separate facility class. Still, being a common behaviour, that could be eventually refined, it's correct to put it in a superclass and, in future, I might find more common behaviours to implement there. In any case, it gives me the opportunity of showing how JAXB can be customized to generate its classes as subclasses of a base class. We just need to add this chunk at the beginning of the XSD schema that is fed to the xjc compiler:

&lt;xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
  jaxb:version="1.0" jaxb:extensionBindingPrefixes="xjc"&gt;

    &lt;xsd:annotation&gt;
        &lt;xsd:appinfo&gt;
            &lt;jaxb:globalBindings&gt;
                &lt;xjc:superClass name="it.tidalwave.bluebook.configuration.ConfigurationElementSupport"/&gt;
            &lt;/jaxb:globalBindings&gt;
        &lt;/xsd:appinfo&gt;
    &lt;/xsd:annotation&gt;
    ...

XSD annotations are just a place where to write metadata not related with the XML schema, but intended to be processed by a tool. In our specific case, we're using JAXB extensions (identified by their own namespace) that allow to define a common superclass.

And now, we're ready to enjoy our non anemic, JAXB generated configuration.