Cyclic references in JAXB

In this post I present a solution for marshalling and unmarshalling cyclic references in JAXB.

Problem

The originating problem was the migration of legacy webservices to Apache CXF from Axis 1, where cyclic object references are supported. There are some proposals on the web for solving this problem using the JAXB databinding, but none of these solutions worked for me. Most solutions assume that you have a parent element and children with a backwards reference towards the parent. This parent reference is then stored as ID and restored by inserting the reference again after unmarshalling.
But what if you have a cyclic object graph where you need to transfer a child element as root element, and its parent must also be marshalled including all its (other) children? Then these solutions do not work properly.

Example object graph:

Object graph

Marshalling of the parent would work, while marshalling child 1 would not work or at least not preserve the parent node and its child 2.

Solution

This is a general-purpose-solution, which allows for cyclic reference marshalling and unmarshalling for any kind of object graph and dependency. JAXB provides the Interface CycleRecoverable, which allows for an Object to break the cycle by returning a substitute object. Using this, you can generate a replacement object with a unique id, which can later be used to identify this object during the unmarshalling process.

Marshalling

For the marshalling, I added an abstract class CycleRecoverableEx to derive from, and a type adapter CycleAdapter to do the replacement.

@XmlTransient
public abstract class CycleRecoverableEx implements CycleRecoverable
{
	@XmlAttribute
	String cycId = UUID.randomUUID().toString();

	@Override
	public Object onCycleDetected(Context context)
	{
		try
		{
			final CycleRecoverableEx newObj = getClass().getConstructor().newInstance();
			newObj.cycId = cycId;
			return newObj;
		}
		catch (final Exception e)
		{
			throw new RuntimeException(e);
		}
	}
}

Here is a example class for testing:

@XmlRootElement
@XmlJavaTypeAdapter(CycleAdapter.class)
public class Node extends CycleRecoverableEx
{
	private String name;
	private List<Node> children = new ArrayList<Node>();
	private Node parent;

	public Node()
	{
	}

	public Node(String name)
	// ...

	@XmlAttribute
	public String getName()
	// ...
	
	public void setName(String name)
	// ...

	@XmlElementWrapper(name = "children")
	@XmlElement(name = "node")
	public List<Node> getChildren()
	// ...

	public void setChildren(List<Node> children)
	// ...

	public Node getParent()
	// ...

	public void setParent(Node parent)
	// ...
}

The marshalling output for an example looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<node name="Node2" cycId="98a92c19-cd5a-47ac-9f99-7bbcf7197021">
   <children>
      <node xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="node" name="Node4" cycId="6f6882a2-ba27-4572-afd4-57a409150fb3">
         <children />
         <parent xsi:type="node" cycId="98a92c19-cd5a-47ac-9f99-7bbcf7197021">
            <children />
         </parent>
      </node>
   </children>
   <parent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="node" name="Node1" cycId="8a10f58f-4e33-4775-95e5-9014f4eaa2a2">
      <children>
         <node xsi:type="node" cycId="98a92c19-cd5a-47ac-9f99-7bbcf7197021">
            <children />
         </node>
         <node xsi:type="node" name="Node3" cycId="4205a587-f977-413c-ae82-c8577e3c9f82">
            <children />
            <parent xsi:type="node" cycId="8a10f58f-4e33-4775-95e5-9014f4eaa2a2">
               <children />
            </parent>
         </node>
      </children>
   </parent>
</node>

All the second occurrences of a node are replaced by an empty node with only the cycle ID set. Note that the root node „Node2“ is also referencered as a child of its parent node.

Unmarshalling

But how to unmarshal this again? For this, you need an unmarshalling listener, which keeps track of all already unmarshalled objects and replaces references with known ids.

@XmlRootElement
@XmlJavaTypeAdapter(CycleAdapter.class)
public class CycleRestoreListener extends Listener
{
	@Override
	public void afterUnmarshal(Object target, Object parent)
	{
		if (parent == null)
		{
			ThreadLocalObjectList.removeInstance();
		}
	}

	@Override
	public void beforeUnmarshal(Object target, Object parent)
	{
		if (target instanceof CycleRecoverableEx)
		{
			ThreadLocalObjectList.getInstance().put((CycleRecoverableEx) target);
		}
	}
}

The tricky part is how to add this unmarshal listener for different JAXB configurations. If you directly use a Unmarshaller, you can use

final Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setListener(new CycleRestoreListener());

If you a are using JAXB as databinding of an underlying framework like CXF, you can use this on client/server side:

JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
		
JAXBDataBinding jaxbDataBinding = new JAXBDataBinding();
jaxBDataBinding.setUnmarshallerListener(new CycleRestoreListener());
factory.setDataBinding(jaxbDataBinding);
<jaxws:endpoint ...>
	<jaxws:dataBinding>
		<bean class="org.apache.cxf.jaxb.JAXBDataBinding">
		<property name="unmarshallerListener">
			<bean class="de.cronn.jaxbcycle.CycleRestoreListener" />
		</property>
		</bean>
	</jaxws:dataBinding>
</jaxws:endpoint>

Source code

The whole source code is hosted at github.com/cronn-de/jaxbcycles.

Leave a Reply