JAX London 2014: A retrospective
Not a hitch in sight

A new approach to XML – Part 3: Seamless usage of namespaces and easy projector setup

SvenEwald
seamless

All you need to know for handling name spaces, configuring the data projector, and separating XPath expressions from the Java code.

In the third part of this series we are going to demonstrate the proper way to handle name spaces, configure the data projector, and separate XPath expressions from the Java code. If you want to make the most of this tutorial, we also recommend reading ‘Part 1: Data projection with XMLBeam’ and ‘Part 2: Features of the XMLBeam Library

The combination of XML-namespaces with XPath has several stumbling blocks, and may well have caused you problems in the past. In the document defined prefixes do not automatically apply to XPath expressions. Whilst default namespaces do apply to sub-elements, they do not apply to attributes unlike expectations. According to the XPath 1.0 specification, an XPath selector without a prefix is always an element without namespace – even though there is an element with a default namespace.

To make matters worse, processing namespaces in the JDK’ s XML-parser is disabled initially, and the XPath engine throws an exception on selecting missing prefixes. To use namespaces in a flexible and comfortable way XMLBeam offers three different approaches in terms of so-called namespace-philosophies (see DefaultXMLFactoriesConfig.NamespacePhilosophy). A namespace philosophy is a set of defaults applied to the underlaying XML-tools.

The first option is ignoring namespaces completely, which comes closest to the JDK defaults. The XML parser and the XPath evaluator just does not see any attribute or element with a namespace. The used DocumentBuilder is configured to ignore namespaces via the method “setNamespaceAware(false)“. Thus, the enum for this namespace philosophy is called: “NIHILISTIC“.

The second option is delegating decisions about namespace handling to the underlaying factory methods. The DocumentBuilder, XPath instance, and Transformer are used without touching their configuration. The user himself can (and must) decide on namespace handling and install a prefix mapping. XMLBeam does not decide whether namespaces exist in the document and therefore the enumeration for this philosophy is called “AGNOSTIC”.

The third option is the default of the class XBProjector. XMLBeam takes existing prefix mappings from the document and forwards them to the XPathFactory. Nonexistent namespaces don’t break the XPath evaluation anymore, but are treated as nonexistent elements. Finally, elements with default namespace become selectable by an automatically predefined prefix “xbdefaultns:”. These defaults should ease most namespace related tasks, thus the enum for this philosophy is called “HEDONISTIC”.

Choosing from namespace philosophies is one purpose of the class DefaultXMLFactoriesConfig. It represents the default settings for the class XBProjector. The API is designed to support multiple implementations of the interface XMLFactoriesCofig, which is used to control the creation of DocumentBuilder-, XPath-, and Transformer instances.

The class DefaultXMLFactoriesConfig provides several useful settings. It’s possible to choose whether the resulting XML document is formatted to a readable representation (aka. “pretty printing”) or whether the header contains an XML declaration (see [2]).

Access to the current projector configuration is done via calling projector.config(), which with a little syntax sugar helps to avoid manual casting to the implementation type.

Methods of the default configuration are accessible directly via projector.config().as(DefaultXMLFactoriesConfig.class).*. The cast is hidden in the fluent interface which saves some brackets for readability. By the way, method chaining is supported by all projections too. Setters and deleters are allowed to declare the projection type as return type instead of void. In this way these methods may be invoked one after another directly.

The projector itself has two more flags to control the behaviour of projections. The flag “SYNCHRONIZE_ON_DOCUMENTS” can be used to make the projector create all projections in a synchronized version. The synchronisation is done on the root of the DOM-tree. This ensures thread-safety by preventing concurrency on all projection methods in all projections and sub-projections.

The second flag “TO_STRING_RENDERS_XML” changes the rendering of the toString() method.

Sometimes it’s useful to make projections render their XML content directly via toString(). This has to be enabled explicitly, because large documents might have side effects when debugging in the IDE.

The default implementation of the toString() method renders a short, debug-friendly projection description. The next part of this series is going to explain how to implement your own version of the toString() method.

A further configuration option affects the origin of the XPath expressions. In the previous examples, the XPath expressions are located in the @XBRead and @XBWrite annotations beside the projection methods. Via projector.config().setExternalizer(…) you may choose a different way. The interface Externalizer is responsible to assign a XPath expression to a projection method. How this assignment is done, is left to the user’s implementation. One example, the PropertyFileExternalizer is included in the library offering the opportunity to store the XPath expressions outside the Java code and even to change them during runtime. One further option is to derive the expressions from the projection methods names. This is demonstrated in the next example.

Simple example structure for a projection without XPath

<Department>
<Users>
<Name>John Doe</Name>
<Name>Tommy Atkins</Name>
</Users>
</Department>

Projection interface without declaring XPath

@XBDocURL(“resource://“)
public interface ExampleProjection {
@XBRead
List<String> getDepartmentUsersName();
}

Example code with user defined Externalizer

XBProjector projector = new XBProjector();
projector.config().setExternalizer(new ExternalizerAdapter() {
@Override
public String resolveXPath(String annotationValue, Method method, Object[] args) {
// Simplest conversion of camel case getter to XPath expression.
return method.getName().substring(3).replaceAll("[A-Z]","/$0");
}
});
List<Sting> departmentUsers = projector.io().fromURLAnnotation(ExampleProjection.class).getDepartmentUsersName();

In the fourth and last part of this series we are going to address building complex XML-documents, explain how to enrich projections with own method implementations, and demonstrate how the type converter can be extended.

About the author

Sven Ewald (Twitter handle:@Cfx) is the author of the library XMLBeam (@XMLBeam). He has been creating Java solutions for 15 years with no end in sight. Currently he works in the field of domain-specific languages for the automotive industry.

Links & Literature

[1] http://xmlbeam.org

[2] http://www.w3.org/TR/REC-xml/#NT-XMLDecl

 

 




Author
Comments
comments powered by Disqus