Moxy JAXB annotations for closure table relationship [message #540667] |
Wed, 16 June 2010 18:37  |
Eclipse User |
|
|
|
I have an top level object called Organization. I'm using a closure table pattern to allow for parent-child relationships. I'm using EclipseLink JPA to manage these objects and everything works exceptionally well. But now I want to expose my entity beans via a WebService. So I've been following the Moxy tutorials and annotating my entities.
I've managed to get things to work out rather well but my issue is when I marshal my Organization object I only get the current object and the associated Id's. But the other Organization objects do not appear in the XML.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<organization>
<id>6</id>
<name>USA</name>
<level>2</level>
<alias>USA</alias>
<version>2</version>
<ancestors>6</ancestors>
<ancestors>1</ancestors>
<descendents>6</descendents>
<descendents>7</descendents>
</organization>
Java object:
public class Organization {
@Id
@Column(name = "OrganizationId", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@XmlID
private Integer id;
@JoinTable(name = "OrganizationAncestry", joinColumns = { @JoinColumn(name = "Ancestor", referencedColumnName = "OrganizationId", nullable = false) }, inverseJoinColumns = { @JoinColumn(name = "Descendent", referencedColumnName = "OrganizationId", nullable = false) })
@ManyToMany(fetch = FetchType.EAGER)
@XmlIDREF
private Collection<Organization> descendents;
@ManyToMany(mappedBy = "descendents", fetch = FetchType.EAGER)
@XmlIDREF
private Collection<Organization> ancestors;
....
I understand the reason for utilizing the @XmlIDREF annotations, but shouldn't each one of the referenced entities appear once within the XML? From the XML listed above, shouldn't Organization 1 and 7 be marshalled into the XML in addition to 6?
Thanks for the help...
[Updated on: Mon, 28 June 2010 11:54] by Moderator
|
|
|
Re: Moxy JAXB annotations for closure table relationship [message #540927 is a reply to message #540667] |
Thu, 17 June 2010 12:06   |
Eclipse User |
|
|
|
Hello,
JAXB expects that everything mapping with ID/IDREF is also mapped with a containment (@XMLElement). Using EclipseLink JAXB, below is one way you could map this to get XML that looks like:
<?xml version="1.0" encoding="UTF-8"?>
<organization>
<id>1</id>
<descendents>2</descendents>
<organization>
<id>2</id>
<descendents>3</descendents>
<ancestors>1</ancestors>
</organization>
<organization>
<id>3</id>
<ancestors>2</ancestors>
</organization>
</organization>
First we need a stand-in object for Organization that has the necessary containment property. We will call this AdaptedOrganization:
@XmlRootElement(name="organization")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlCustomizer(AdaptedOrganizationCustomizer.class)
public class AdaptedOrganization {
private Organization organization;
@XmlElement(name="organization")
private List<Organization> orphans = new ArrayList<Organization>();
...
}
We will need to customize the mappings for this class so we will leverage the EclipseLink @XmlCustomizer annotation to get at the underlying framework:
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping;
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;
public class AdaptedOrganizationCustomizer implements DescriptorCustomizer {
@Override
public void customize(ClassDescriptor descriptor) throws Exception {
XMLCompositeObjectMapping organizationMapping = (XMLCompositeObjectMapping) descriptor.getMappingForAttributeName("organization");
organizationMapping.setXPath(".");
XMLCompositeCollectionMapping orphansMapping = (XMLCompositeCollectionMapping) descriptor.getMappingForAttributeName("orphans");
orphansMapping.setContainerPolicy(new GrowingListContainerPolicy());
}
}
EclipseLink has something called a ContainerPolicy to handle collection/map types. In this use case we will be modifying the orphans list as we marshal so we need to create a custom ContainerPolicy to handle this. It is associated with the mapping in the above customizer snippet:
import java.util.ArrayList;
import java.util.List;
import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent;
import org.eclipse.persistence.internal.queries.ListContainerPolicy;
public class GrowingListContainerPolicy extends ListContainerPolicy {
@Override
public CollectionChangeEvent createChangeEvent(Object collectionOwner,
String propertyName, Object collectionChanged,
Object elementChanged, int changeType, Integer index) {
return null;
}
@Override
public Class getContainerClass() {
return ArrayList.class;
}
@Override
public boolean hasNext(Object iterator) {
return ((GrowingListIterator) iterator).hasNext();
}
@Override
public Object iteratorFor(Object container) {
return new GrowingListIterator((List<Object>) container);
}
@Override
protected Object next(Object iterator) {
return ((GrowingListIterator) iterator).next();
}
private static class GrowingListIterator {
private int index;
private List<Object> list;
public GrowingListIterator(List<Object> list) {
this.index = 0;
this.list = list;
}
public Object next() {
return list.get(index++);
}
public boolean hasNext() {
return list.size() > index;
}
}
}
We will discover the orphan list as we marshal, we will use a marshal listener to accomplish this:
import javax.xml.bind.Marshaller;
import orphans.Organization;
public class OrganizationMarshalListener extends Marshaller.Listener {
private AdaptedOrganization adaptedOrganization;
public OrganizationMarshalListener(Organization organization) {
this.adaptedOrganization = new AdaptedOrganization();
this.adaptedOrganization.setOrganization(organization);
}
public AdaptedOrganization getAdaptedOrganization() {
return adaptedOrganization;
}
@Override
public void beforeMarshal(Object arg0) {
if(arg0 instanceof Organization) {
Organization organization = (Organization) arg0;
if(organization.getAncestors() != null) {
for(Organization ancestorOrganization : organization.getAncestors()) {
if(adaptedOrganization.getOrganization() != ancestorOrganization && !adaptedOrganization.getOrphans().contains(ancestorOrganization)) {
adaptedOrganization.getOrphans().add(ancestorOrganization);
}
}
}
if(organization.getDescendents() != null) {
for(Organization descendantOrganization : organization.getDescendents()) {
if(adaptedOrganization.getOrganization() != descendantOrganization && !adaptedOrganization.getOrphans().contains(descendantOrganization)) {
adaptedOrganization.getOrphans().add(descendantOrganization);
}
}
}
}
}
}
Pulling it all together, below is how we can marshal a graph of Organization objects:
public class Demo {
public static void main(String[] args) throws Exception {
Organization organization1 = new Organization();
organization1.setId(1);
Organization organization2 = new Organization();
organization2.setId(2);
organization2.getAncestors().add(organization1);
organization1.getDescendents().add(organization2);
Organization organization3 = new Organization();
organization3.setId(3);
organization3.getAncestors().add(organization2);
organization2.getDescendents().add(organization3);
JAXBContext jaxbContext = JAXBContext.newInstance(Organization.class, AdaptedOrganization.class);
System.out.println(jaxbContext);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
OrganizationMarshalListener marshalListener = new OrganizationMarshalListener(organization1);
marshaller.setListener(marshalListener);
marshaller.marshal(marshalListener.getAdaptedOrganization(), System.out);
}
}
I hope this helps, I want to add native support for this use case in an upcoming EclipseLink release.
-Blaise
|
|
|
|
|
|
|
|
|
|
|
Powered by
FUDForum. Page generated in 0.04041 seconds