Moxy JAXB annotations for closure table relationship [message #540667] |
Wed, 16 June 2010 22:37 |
Fericit Bostan Messages: 68 Registered: June 2010 |
Member |
|
|
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 15:54] Report message to a moderator
|
|
|
Re: Moxy JAXB annotations for closure table relationship [message #540927 is a reply to message #540667] |
Thu, 17 June 2010 16:06 |
|
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
|
|
|
|
Re: Moxy JAXB annotations for closure table relationship [message #541209 is a reply to message #540667] |
Fri, 18 June 2010 17:40 |
|
JAXB metadata is used to assign a tree structure to a graph. Starting with the root object, each object must be reachable through a containment property (@Xml Element or unannotated field/property). @XmlIDREF represents a non-containment relationship.
In the example I sent you I created a virtual containment property to handle any "orphan" organizations not referenced through a containment property.
Client JAXB libraries will be built on the schema from your WSDL (which must provide some sort of containment structure). What does the Schema look like for this XML message?
|
|
|
|
Re: Moxy JAXB annotations for closure table relationship [message #541527 is a reply to message #540667] |
Mon, 21 June 2010 13:19 |
|
Creating a high level container is what most people do. Once you have the high level container, if you populate the containment relationship yourself (traversing the organization graph) you don't need any customizations.
The example I posted was a demonstration of how this logic could be handled by the binding layer using some of EclipseLink's extension mechanisms.
|
|
|
|
Re: Moxy JAXB annotations for closure table relationship [message #543541 is a reply to message #540667] |
Tue, 29 June 2010 17:47 |
|
You would return the AdaptedOrganization from the WS call. Instead of setting a MarshalListener you could add the following reserved method to Organization to capture the beforeMarshal event. Still thinking about an easy way for you to grab the shared AdaptedOrganization:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Organization {
public boolean beforeMarshal(Marshaller marshaller) {
AdaptedOrganization adaptedOrganization = // GET ROOT ADAPTED ORGANIZATION
if(this.getAncestors() != null) {
for(Organization ancestorOrganization : this.getAncestors()) {
if(adaptedOrganization.getOrganization() != ancestorOrganization && !adaptedOrganization.getOrphans().contains(ancestorOrganization)) {
adaptedOrganization.getOrphans().add(ancestorOrganization);
}
}
}
if(this.getDescendents() != null) {
for(Organization descendantOrganization : this.getDescendents()) {
if(adaptedOrganization.getOrganization() != descendantOrganization && !adaptedOrganization.getOrphans().contains(descendantOrganization)) {
adaptedOrganization.getOrphans().add(descendantOrganization);
}
}
}
return true;
}
}
|
|
|
|
|
Powered by
FUDForum. Page generated in 0.03452 seconds