4
\$\begingroup\$

This is related to a question I posted on Stack Overflow. The consensus was to use JAXB, which is what I did.

One of the requirements was that it needed to work with legacy XML configuration files. The code needed to be able to start reading new configuration settings from XML files already used to configure pre-existing applications.

In trying to make the code reusable, I added the generic type parameter. However, this causes some yuckiness for the user, as it requires user to pass in the class type. (It shouldn't be necessary - but due to Java implementation of generics, there's no easy way around that).

I had to throw this together very quickly, so I would welcome any suggestions on improvement (or finding errors). I wasn't very skilled with JAXB, so perhaps there is a better solution I overlooked.

package com.eztech.config;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.eztech.xml.XmlUtils;

public class JAXBConfigurator<T> {

    private String filePath;
    private Class<T> clazz;
    public JAXBConfigurator(Class<T> toConfigure, String xmlFilePath) {
        this.clazz = toConfigure;
        this.filePath = xmlFilePath;
    }

    /**
     * Parses Xml and reads configuration from Document element. Using this method 
     * assumes that the configuration xml starts at the top of the xml document.
     * @return 
     * @throws Exception
     */ 
    public T createAndConfigure() throws Exception {
        return createAndConfigure(null);
    }

    /**
     * Selects specified element from the parsed Xml document to use as the base
     * for reading the configuration.
     * 
     * @param tagName
     * @return
     */
    public T createAndConfigure(String tagName) throws Exception {
        Document doc = XmlUtils.parse(filePath);
        Node startNode;
        if (tagName == null) {
            startNode = doc;
        } else {
            startNode = XmlUtils.findFirstElement(doc, tagName);
        }

        return readConfigFromNode(startNode);
    }

    private T readConfigFromNode(Node startNode) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        JAXBElement<T> configElement = unmarshaller.unmarshal(startNode, clazz);
        return configElement.getValue();
    }

}

How to use it:

JAXBConfigurator<MyConfig> configurator = 
    new JAXBConfigurator<Config>(MyConfig.class, xmlfilePath);
instance = configurator.createAndConfigure("MyXmlStartTag");
\$\endgroup\$
4
\$\begingroup\$

Two things which are good to know about JAXB:

  • You can annotate private methods and fields, JAXB can call/modify them. It could help hiding internal data structure from clients.
  • Marshaller as well as Unmarshaller has event callbacks. They can help handing deprecated or compound fields.

Just two small notes about the code:

  1. I would call the class JaxbConfigurator. Lowercase letters are easier to read. (Effective Java, Second Edition, Item 56: Adhere to generally accepted naming conventions)

  2. It would be worth checking input parameters in the constructor to avoid latter mysterious NullPonterExceptions:

    import static com.google.common.base.Preconditions.*;
    
    ...
    
    public JaxbConfigurator(final Class<T> toConfigure, final String xmlFilePath) {
        this.clazz = checkNotNull(toConfigure, "toConfigure cannot be null");
        this.filePath = checkNotNull(xmlFilePath, "xmlFilePath cannot be null");
    }
    
\$\endgroup\$
  • \$\begingroup\$ Thanks for the comment. The null check is definitely critical. I didn't understand one thing you wrote: "You can annotate private methods and fields, JAXB can call/modify them. It could help hiding internal data structure from clients." Does this somehow affect the code above? (I was thinking that the annotations are entirely internal to the client class and JAXB, but this utility doesn't need any knowledge of that.) \$\endgroup\$ – Sam Goldberg Dec 19 '11 at 15:42
  • \$\begingroup\$ "Does this somehow affect the code above?" - No, it doesn't but it could help if your client (config) classes have to handle more than one XML schema (because of XML schema backward compatibility reasons). \$\endgroup\$ – palacsint Dec 19 '11 at 21:45
2
\$\begingroup\$

Finals and Validation

The fields

private String filePath;
private Class<T> clazz;

seem to be constant references, why not transforming them to finals: private final String etc?

To validate them, a very short way is

import java.util.Objects;
...
// in the constructor:
Objects.requireNonNull(toConfigure);

Avoid Throwing Exception

Throwing Exception in any of the methods forces the user of your class to catch (Exception), which is not very comfortable and brings some doubts about the API. Please try to replace Exception with a more concrete type, which specifies the kind of exceptional condition that the user should be aware of. E.g. throws JAXBException is OK.

\$\endgroup\$
  • \$\begingroup\$ I agree with both you points. Actually, I configured Eclipse to automatically convert private variables to final private, so I don't have to think about this anymore. Throwing a more specific Exception is also better design to give users the option to handle it specifically or not. \$\endgroup\$ – Sam Goldberg Sep 8 '15 at 12:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.