July 22, 2011, 10:21 a.m.
posted by factorial
JDOM and Factories
As noted earlier in this chapter, the ability to have some form of factories allows greater flexibility in how your XML is modeled in Java. Take a look at the simple subclass of JDOM's Element class shown in Figure.
Subclassing the JDOM Element class
This is about as simple a subclass as you could come up with. It is somewhat similar to the NamespaceFilter class from Chapter 4 in that it disregards whatever namespace is actually supplied to the element (even if there isn't a namespace supplied!), and sets the element's namespace defined by the URI http://www.oreilly.com with the prefix ora. This is a simple case, but it gives you an idea of what is possible, and serves as a good example for this section.
Creating a Factory
Once you've got a custom subclass, the next step is actually using it. As I already mentioned, JDOM considers having to create all objects with factories a bit over the top. Simple element creation in JDOM works like this:
// Create a new Element Element element = new Element("guitar");
Things remain equally simple with a custom subclass:
// Create a new Element, typed as an ORAElement Element oraElement = new ORAElement("guitar");
The element is dropped into the O'Reilly namespace because of the custom subclass. Additionally, this method is more self-documenting than using a factory. It is clear at any point exactly what classes are being used to create objects. Compare that to this code fragment:
// Create an element: what type is created? Element someElement = doc.createElement("guitar");
It's not clear if the object created is an Element instance, an ORAElement instance, or something else entirely. For these reasons, the custom class approach serves JDOM well. For object creation, you can simply instantiate your custom subclass directly. However, the need for factories arises when you are building a document:
// Build from an input source SAXBuilder builder = new SAXBuilder( ); Document doc = builder.build(someInputStream);
Obviously, here you were unable to specify custom classes through the building process. I suppose you could be really bold and modify the SAXBuilder class (and the related org.jdom.input.SAXHandler class), but that's a little ridiculous. So, to facilitate this, the JDOMFactory interface, in the org.jdom package, was introduced. This interface defines methods for every type of object creation. For example, there are four methods for element creation, which match up to the four constructors for the Element class:
public Element element(String name); public Element element(String name, Namespace ns); public Element element(String name, String uri); public Element element(String name, String prefix, String uri);
You will find similar methods for Document, Attribute, CDATA, and all the rest. By default, JDOM uses the org.jdom.DefaultJDOMFactory , which simply returns all of the core JDOM classes within these methods. However, you can easily subclass this implementation and provide your own factory methods. Look at Figure, which defines a custom factory.
A customJDOMFactory implementation
This is a simple implementation; it doesn't need to be very complex. It overrides each of the element( ) methods and returns an instance of the custom subclass, ORAElement, instead of the default JDOM Element class. At this point, any builder that uses this factory will end up with ORAElement instances in the created JDOM Document object, rather than the default Element instances you would normally see. All that's left is to let the build process know about this custom factory.
Building with Custom Classes
Once you have a valid implementation of JDOMFactory, let your builders know to use it by invoking the setFactory( ) method and passing in a factory instance. This method is available on both of the current JDOM builders, SAXBuilder and DOMBuilder. To see it in action, check out Figure. This simple class takes in an XML document and builds it using the ORAElement class and CustomJDOMFactory from Examples 9-12 and 9-13. It then writes the document back out to a supplied output filename, so you can see the effect of the custom classes.
Building with custom classes using a custom factory
I ran this on the contents.xml file used throughout the first several chapters:
[email protected] $ java javaxml3.ElementChanger contents.xml newContents.xml
This hummed along for a second, and then gave me a new document (newContents.xml). A portion of that new document is shown in Figure.
Output fragment from contents.xml afterElementChanger
Each element is now in the O'Reilly namespace, prefixed and referencing the URI specified in the ORAElement class.
Obviously, you can take this subclassing to a much higher degree of complexity. Common examples include adding specific attributes, or even child elements, to every element that comes through. Many developers have existing business interfaces, and define custom JDOM classes that extend the core JDOM classes and also implement these business-specific interfaces. Other developers have built "lightweight" subclasses that discard namespace information and maintain only the bare essentials, keeping documents small (albeit not XML-compliant in some cases). The only limitations are your own ideas in subclassing. Just remember to set up your own factory before building documents, so your new functionality is included.
JDOM includes an implementation of the JDOMFactory interface that warrants some attention: org.jdom.UncheckedJDOMFactory. When you create JDOM objects through the default factory or the new keyword, the object constructors do some basic checks to ensure that the objects follow XML standards. For example, trying to create a new Element with an invalid name produces an org.jdom.IllegalNameException:
Element el = new Element("0name");
This is definitely a good thing and worth the extra processing needed to do it, but when parsing a document with a modern parser, it is unnecessary. If you had this element in a file:
<book> <0name>Java & XML</0name> </book>
And tried to parse it, a good SAX parser will throw org.xml.sax.SAXParseException. If you are confident your parser will do this, you can skip JDOM's checks by using UncheckedJDOMFactory, as in:
SAXBuilder builder = new SAXBuilder( ); builder.setFactory(new UncheckedJDOMFactory( )); // parse away