Jaxb Mapping Cyclic References to Xml

JAXB Mapping cyclic references to XML

The good thing about using JAXB is that it is a standard runtime with multiple implementations (just like JPA).

If you use EclipseLink JAXB (MOXy) then you have many extensions available to you for handling JPA entities including bi-directional relationships. This is done using the MOXy @XmlInverseReference annotation. It acts similar to @XmlTransient on the marshal and populates the target-to-source relationship on the unmarshal.

http://wiki.eclipse.org/EclipseLink/Examples/MOXy/JPA/Relationships

@Entity 
@XmlRootElement
public class Contact {

@Id
private Long contactId;

@OneToMany(mappedBy = "contact")
private List<ContactAddress> addresses;

...

}

@Entity
@XmlRootElement
public class ContactAddress {

@Id
private Long contactAddressId;

@ManyToOne
@JoinColumn(name = "contact_id")
@XmlInverseReference(mappedBy="addresses")
private Contact contact;

private String address;

...

}

Other extensions are available including support for composite keys & embedded key classes.

To specify the EcliseLink MOXy JAXB implementation you need to include a jaxb.properties file in with your model classes (i.e. Contract) with the following entry:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Resolving cyclic references in jaxb

As you add @XmlTransient on a private property, you should change your XmlAccessType (default to XmlAccessType.PUBLIC_MEMBER) to XmlAccessType.PROPERTY

PUBLIC_MEMBER is the default access type in JAXB. It means that a JAXB implementation will generate bindings for: public fields, annotated fields, properties

code copied from W A :

@WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {

@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
public static class Class1 {

private Class2 class2;

public Class1() {

}

public Class1(Class2 refClass) {
class2 = refClass;
}

@XmlTransient
public Class2 getClass2() {
return class2;
}

public void setClass2(Class2 class2) {
this.class2 = class2;
}

@Override
public String toString() {
return this.getClass().getSimpleName();
}

}

@XmlRootElement
public static class Class2 {

private Class1 class1;

public Class2() {

}

public Class1 getClass1() {
return class1;
}

public void setClass1(Class1 class1) {
this.class1 = class1;
}

@Override
public String toString() {
return this.getClass().getSimpleName();
}
}

public List<Class1> cyclicTest() {
//I create an instance of each class, having them a cyclic reference to the other instance
Class2 class2 = new Class2();
Class1 class1 = new Class1(class2);
class2.setClass1(class1);
return Arrays.asList(class1);
}

public static void main(String[] args) throws JAXBException {

JAXBContext ctx = JAXBContext.newInstance(Class1.class);
Marshaller m = ctx.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
List<Class1> class1s = new MyWs().cyclicTest();

for (Class1 c1 : class1s){
m.marshal(c1, System.out);
}

}

Preventing cyclic references on JAXB XML parsing

You should use @XmlID and @XmlIDREF


@XmlAccessorType(XmlAccessType.FIELD)
public class A{
@XmlAttribute
@XmlID
private String id;

@XmlElement
private B b;

@XmlAccessorType(XmlAccessType.FIELD)
public class B{
@XmlAttribute
@XmlID
private String id;

@XmlIDREF
private A a;

Here an example of the forum of Blaise Doughan

Which Java XML binding framework supports circular/cyclic dependencies?

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

MOXy has the @XmlInverseReference extension for mapping bidirectional relationships.

A

import javax.xml.bind.annotation;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class A {
@XmlElement(name="b")
B refToB;
}

B

import javax.xml.bind.annotation;
import org.eclipse.persistence.oxm.annotations.XmlInverseReference;

@XmlAccessorType(XmlAccessType.FIELD)
public class B {
@XmlInverseReference(mappedBy="refToB")
A refToA;
}

XML

The above classed will map to the following XML

<a>
<b/>
<a>

For More Information

  • http://blog.bdoughan.com/2010/07/jpa-entities-to-xml-bidirectional.html
  • http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

How to map JAXB elements annotated with @XMLSeeAlso using mapStruct?

It seems like you have 3 different problems.

  1. It seems what you are trying to achieve is for MapStruct to detect all possible implementations for Customer (or see @XmlSeeAlso) and use the method you need. This is not possible automatically in MapStruct. See #131 for an existing feature request.
  2. This should happen when you have not defined the property correct. MapStruct actually looks into the getters and setters only (not in the field). So if you getter is getAM then your @Mapping(target = "AMLLineOfBusiness", ignore = true)
  3. This is similar with this question. Maybe you can try Reusing mapping configurations

A possible solution for 1 would be you to an instance of on your side.

@Mapper
public interface CustomerMapper {
PersonalCustomer personcalCustomerToPersonalCustomer(PersonalCustomer pc);

default Customer customerToCustomer(Customer customer) {
if (customer instanceOf PersonalCustomer) {
return personalCustomerToPersonalCustomer((PersonalCustomer) pc);
} else if (customer instanceOf BusinessCustomer) {
return businessCustomerToBusinessCustomer((BusinessCustomer) pc);
}
}
}

The reason for such things is that MapStruct is an annotation processor so it generated code during compilation time. On the other side Dozer is working with runtime information. Dozer can get the class during runtime and pick the right method. MapStruct cannot deduce all the possible implementations.

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();
}

}


Related Topics



Leave a reply



Submit