Using JAXB to cross reference XmlIDs from two XML files
This can be done with an XmlAdapter. The trick is the XmlAdapter will need to be initialized with all of the Nodes from Network.xml and passed to the Unmarshaller used with 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);
}
}
The trick is to map the toNode property on Flow with an 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;
}
}
The adapter will look like the following. The trick is that we will pass a configured XmlAdapter that knows about all the Nodes to the unmarshaller:
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class NodeAdapter extends XmlAdapter<String, Node>{
private Map<String, Node> nodes = new HashMap<String, Node>();
public Map<String, Node> 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();
}
}
jaxb reference xmlID between xml files
The problem arises from class Company that corresponds to an XML document containing employees and departments. However, you've got two separate documents. Apparently you want one final class containing both lists.
(1) You could define a class EmployeeList for employees only, similar to the one for departments (DepartmentList). This will still let you write an application class Company into which you set the references for both lists.
(2) Change the annotation for Company.departments
@XmlTransient
private List<Department> departments;
marshal like you do now, and set the List with the reference you have from unmarshalling the corresponding XML into the returned object.
How to force JAXB marshaller to use xlink references?
You can implement an XLink approach in JAXB using an XmlAdapter
. Below are links to various similar answers.
- Serialize a JAXB object via its ID?
- Using JAXB to cross reference XmlIDs from two XML files
- Can JAXB marshal by containment at first then marshal by @XmlIDREF for subsequent references?
I lead the EclipseLink JAXB (MOXy) implementation, and we have the @XmlInverseReference
extension for mapping bidirectional relationship that you may be interested in:
- http://blog.bdoughan.com/2010/07/jpa-entities-to-xml-bidirectional.html
JaxB reference resolving
To better illustrate the point I have modified the question to fit his answer. There is now a generic base class Person and I am trying to use it as per Can generic XmlAdapter be written
I solved the issue of being able to actually make sure the Adapters are used by writing specific derived Classes and using them with @XmlJavaTypeAdapter. I preregister the adapters using:
JAXBContext context = JAXBContext.newInstance(type);
Unmarshaller u = context.createUnmarshaller();
u.setAdapter(Worker.WorkerAdapter.class,new Worker.WorkerAdapter());
u.setAdapter(Trainer.TrainerAdapter.class,new Trainer.TrainerAdapter());
and then unmarshalling twice. The debug shows that the Adapter instance for both passes is the same. Still the lookup somehow seemed to fail ... The reason was the way the @XmlJavaTypeAdapter annotation works see:
What package-info do I annotate with XmlJavaTypeAdapters?
There seem to be multiple modes for @XmlJavaTypeAdapter:
- it can be an annotation for a class
- it can be an annotation for a field (getter)
- it can be used in a package-info.java file to annotate a whole package
At this point I am using all three annotations and now have to debug which ones are necessary. I assume the global annotations (class,package) are not working as expected. The reason might be the type= usage in the @XmlElementWrapper which explicitly calls for a type. Personally I do not understand what is going on yet. At least things are now working as expected.
the local field annotation is now e.g.:
@XmlElementWrapper(name="workers")
@XmlElement(name="Worker", type=Worker.class)
@XmlJavaTypeAdapter(WorkerAdapter.class)
the package-info.java annotation is:
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(value=WorkerAdapter.class,type=Worker.class),
@XmlJavaTypeAdapter(value=TrainerAdapter.class,type=Trainer.class),
})
package com.bitplan.jaxb.refidtest;
import javax.xml.bind.annotation.adapters.*;
the class annotation is:
@XmlJavaTypeAdapter(Worker.WorkerAdapter.class)
public class Worker extends Person {
...
/**
* Worker Adapter
* @author wf
*
*/
public static class WorkerAdapter extends PersonAdapter<Worker>{
@Override
public Worker marshal(Worker me)
throws Exception {
return super.marshal(me);
}
@Override
public Worker unmarshal(Worker me) throws Exception {
return super.unmarshal(me);
}
}
/**
* https://stackoverflow.com/questions/7587095/can-jaxb-marshal-by-containment-at-first-then-marshal-by-xmlidref-for-subsequen/7587727#7587727
* @author wf
*
*/
public class PersonAdapter<T extends Person> extends XmlAdapter<T, T>{
public boolean debug=true;
/**
* keep track of the elements already seen
*/
public Map<String,T> lookup=new HashMap<String,T>();
@Override
public T marshal(T me)
throws Exception {
return me;
}
/**
* show debug information
* @param title
* @param key
* @param me
* @param found
*/
public void showDebug(String title,String key,T me, T found) {
String deref="?";
if (found!=null)
deref="->"+found.getId()+"("+found.getClass().getSimpleName()+")";
if (debug)
System.err.println(title+": "+key+"("+me.getClass().getSimpleName()+")"+deref+" - "+this);
}
@Override
public T unmarshal(T me) throws Exception {
if (me.getId()!=null) {
showDebug("id",me.getId(),me,null);
lookup.put(me.getId(), me);
return me;
}
if (me.getRef()!=null) {
if (lookup.containsKey(me.getRef())) {
T meRef=lookup.get(me.getRef());
showDebug("ref",me.getRef(),me,meRef);
me.setRef(null);
return meRef;
} else {
if (debug)
showDebug("ref",me.getRef(),me,null);
}
}
return me;
}
}
Related Topics
Servlet Seems to Handle Multiple Concurrent Browser Requests Synchronously
Multiple Runnable Classes Inside Jar, How to Run Them
Run Command Prompt as Administrator
How to Convert a Java Object (Bean) to Key-Value Pairs (And Vice Versa)
Parsing JSON with Gson, Object Sometimes Contains List Sometimes Contains Object
How to Do a Fractional Power on Bigdecimal in Java
How to Use Yamlpropertiesfactorybean to Load Yaml Files Using Spring Framework 4.1
Running a Java Program from Another Java Program
Sending the Same But Modifed Object Over Objectoutputstream
How to Make a Deep Copy of Java Arraylist
What Does Biginteger Having No Limit Mean
How to Get Unique Values from Array
Java: How to Indent Xml Generated by Transformer