ID/IDREF has been with XML since its very first day. It works nicely with databinding tools, because it's easy to do a type analysis with ID/IDREF In this regard, key/keyref in XML Schema is much worse --- in general it's not even possible to determine how a given key/keyref constraint maps to which field/method of an object.

ID/IDREF is great, but it is sometimes too simple. For example, people often want to:

  1. define distinctive symbol spaces; for example, in Java, methods and fields have different symbol spaces, so you can have a method "foo" and a field "foo" at the same time.
  2. define scoped symbol spaces; for example, in Java, fields are scoped to classes, so you can have a field "foo" in class "X" and field "foo" in class "Y" at the same time.

It's just so convenient if you can tell a databinding implementation to do those for you. That is what I implemented in the JAXB RI 2.0.

In JAXB 2.0, every ID and IDREF fields are marked with @XmlID and @XmlIDREFannotations. For example, if you have a schema that describes a document like this:

             

Then you can have the schema compiler produce the following Java code:

 @XmlRootElement class Apple { @XmlID String id; } @XmlRootElement class AppleRef { @XmlIDREF Object ref; } @XmlRootElement class Orange { @XmlID String id; } @XmlRootElement class OrangeRef { @XmlIDREF Object ref; } class Box { @XmlElementRef List fruits; } 

Supporting Distinctive Symbol Spaces

Suppose you want to define distinctive symbol spaces between apples and oranges. That is, you'd want to allow XML documents like this:

             

Where orangeRefs would only refer to oranges and appleRefs would only refer to apples. (Note that this document no longer validates with the original schema.)

First, you'd have to modify the above classes to make it clear that AppleRef only refers to Apple:

 @XmlRootElement class AppleRef { @XmlIDREF 

Apple

 ref; } @XmlRootElement class OrangeRef { @XmlIDREF 

Orange

 ref; } 

You can then implement a custom IDResolver, like this:

 class MyIDResolver extends IDResolver { Map apples = new HashMap(); Map oranges = new HashMap(); void startDocument() { apples.clear(); oranges.clear(); } public void bind(String id, Object obj) { if(obj instanceof Apple) apples.put(id,(Apple)obj); else oranges.put(id,(Orange)obj); } Callable resolve(final String id, Class targetType) { return new Callable() { public Object call() { if(targetType==Apple.class) return apples.get(id); else return oranges.get(id); } }; } } 

and then set this to the Unmarshaller like this:

 Unmarshaller u = context.createUnmarshaller(); u.setProperty(IDResolver.class.getName(),new MyIDResolver()); u.unmarshal(new File("document.xml")); 

The JAXB RI unmarshaller will delegate all its ID/IDREF works to MyIDResolver. You can see that it's allowing the same ID for apples and oranges. The change we made ealier to AppleRef and OrangeRef allows the JAXB RI to call the resolvemethod with the right target type.

The Callableis used to support forward references.

Supporting Scoped Symbol Spaces

Let's extend this example so that we can handle documents like this:

             

... where references are only allowed within the same box. This can be done by combining IDResolver with a recent addition to the JAXB 2.0 spec, Unmarshaller.Listener.

 class MyIDResolver extends IDResolver { Map apples = new HashMap(); Map oranges = new HashMap(); Unmarshaller.Listener createListener() { return new Unmarshaller.Listener() { void beforeUnmarshal(Object target, Object parent) { if(target instanceof Box) { apples = new HashMap(); oranges = new HashMap(); } } } } void startDocument() { apples.clear(); oranges.clear(); } public void bind(String id, Object obj) { if(obj instanceof Apple) apples.put(id,(Apple)obj); else oranges.put(id,(Orange)obj); } Callable resolve(final String id, Class targetType) { return new Callable() { public Object call() throws Exception { if(targetType==Apple.class) return apples.get(id); else return oranges.get(id); } }; } } 

When registered, a Listener gets notified for each object being unmarshalled. So the idea here is that every time the unmarshalling of a new Box object starts, we create a fresh symbol space. This guarantees that the resolve method always resolve within the same box. Note that we can't do Map.clear because that will cause the forward reference of orangeRefto fail (JAXB RI only process forward references at the very end of the document.)

You can then set up the unmarshaller like this:

 Unmarshaller u = context.createUnmarshaller(); MyIDResolver resolver = new MyIDResolver(); u.setProperty(IDResolver.class.getName(),resolver); u.setListener(resolver.createListener()); u.unmarshal(new File("document.xml")); 

Conclusion

By combining IDResolver and Unmarshaller.Listener, you can do much sophisticated reference integrity processing in the JAXB RI. For more details about the interfaces, see this. You can build the JAXB RI by yourself today, or wait for next Monday's weekly build to play with this.

So Jeremy, I hope you like it, and sorry it took me a month to do this!