Usando JAXB per incrociare XmlIDs di riferimento da due file XML

Sto cercando di eseguire il marshall / unmarshal da due diversi file XML a POJOS. Il primo file XML ha questo aspetto:

--Network.xml--            --------- 

Usando le annotazioni @XmlID e @XmlIDREF, posso compilare correttamente le classi Arc in modo che puntino al Nodo corretto a cui fa riferimento.

Tuttavia, devo anche analizzare questo XML:

 --NetworkInputs.xml--       ------ 

Attualmente, il mio programma seleziona un object Network correttamente, ma non esiste alcuna connessione tra Network e NetworkInputs che consente a JAXB di “vedere” i nodes presenti nella rete. Voglio che i miei oggetti Flow puntino al nodo corretto nella class Network.

Fondamentalmente voglio farlo: http://old.nabble.com/JAXB-Unmarshalling-and-XmlIDREF-using-different-stores-td14035248.html

Ho provato a implementarlo: http://weblogs.java.net/blog/kohsuke/archive/2005/08/pluggable_ididr.html e non funziona, perché non riesco a ottenere i dati del Nodo per la mia rete popolata da un contesto statico.

È ansible fare qualcosa del genere?

Questo può essere fatto con un XmlAdapter. Il trucco è che XmlAdapter dovrà essere inizializzato con tutti i nodes da Network.xml e passato a Unmarshaller utilizzato con NetworkInputs.xml:

 import java.io.File; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Network.class, NetworkInputs.class); File networkXML = new File("Network.xml"); Unmarshaller unmarshaller = jc.createUnmarshaller(); Network network = (Network) unmarshaller.unmarshal(networkXML); File networkInputsXML = new File("NetworkInputs.xml"); Unmarshaller unmarshaller2 = jc.createUnmarshaller(); NodeAdapter nodeAdapter = new NodeAdapter(); for(Node node : network.getNodes()) { nodeAdapter.getNodes().put(node.getId(), node); } unmarshaller2.setAdapter(nodeAdapter); NetworkInputs networkInputs = (NetworkInputs) unmarshaller2.unmarshal(networkInputsXML); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(networkInputs, System.out); } } 

Il trucco è mappare la proprietà toNode su Flow con un XmlAdapter:

 import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; public class Flow { private Node toNode; @XmlAttribute @XmlJavaTypeAdapter(NodeAdapter.class) public Node getToNode() { return toNode; } public void setToNode(Node toNode) { this.toNode = toNode; } } 

L’adattatore sarà simile al seguente. Il trucco è che passeremo un XmlAdapter configurato che conosce tutti i nodes per l’unmarshaller:

 import java.util.HashMap; import java.util.Map; import javax.xml.bind.annotation.adapters.XmlAdapter; public class NodeAdapter extends XmlAdapter{ private Map nodes = new HashMap(); public Map getNodes() { return nodes; } @Override public Node unmarshal(String v) throws Exception { return nodes.get(v); } @Override public String marshal(Node v) throws Exception { return v.getId(); } } 

La mia soluzione: la risoluzione ID è gestita da una (sfortunatamente) class interna (com.sun.xml.internal.bind.IDResolver) che può essere impostata da esterna.

 final Unmarshaller unmarshaller = context.createUnmarshaller(); unmarshaller.setProperty(IDResolver.class.getName(), resolver); 

Dove il resolver poteva essere usato su molte inststance del unmarshaller. Ma il punto è che il resolver non cancellerà se stesso su startDocument come l’implementazione predefinita di com.sun.xml.internal.bind.v2.runtime.unmarshaller.DefaultIDResolver fa:

 import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import org.xml.sax.SAXException; import com.sun.xml.internal.bind.IDResolver; public final class IDResolverExtension extends IDResolver { public static final class CallableImplementation implements Callable { private final Object value; private CallableImplementation(final Object value) { this.value = value; } @Override public Object call() { return value; } } private final Map m = new HashMap(); @SuppressWarnings("rawtypes") @Override public synchronized CallableImplementation resolve(final String key0, final Class clazz) throws SAXException { assert clazz != null; assert key0 != null; final KeyAndClass key = new KeyAndClass(clazz, key0); final Object value = m.get(key); return new CallableImplementation(value); } static class KeyAndClass { public final Class clazz; public final String key; public KeyAndClass(final Class clazz, final String key) { this.clazz = clazz; this.key = key; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + clazz.hashCode(); result = prime * result + key.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final KeyAndClass other = (KeyAndClass) obj; if (!clazz.equals(other.clazz)) { return false; } if (!key.equals(other.key)) { return false; } return true; } } @Override public synchronized void bind(final String key0, final Object value) throws SAXException { assert key0 != null; assert value != null; Class clazz = value.getClass(); assert clazz != null; final KeyAndClass key = new KeyAndClass(clazz, key0); final Object oldValue = m.put(key, value); if (oldValue != null) { final String message = MessageFormat.format("duplicated key ''{0}'' => ''{1}'' - old: ''{2}''", key, value, oldValue); throw new AssertionError(message); } } }