Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » EclipseLink » Moxy JAXB annotations for closure table relationship
Moxy JAXB annotations for closure table relationship [message #540667] Wed, 16 June 2010 18:37 Go to next message
Fericit Bostan is currently offline 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 11: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 12:06 Go to previous messageGo to next message
Blaise Doughan is currently offline Blaise Doughan
Messages: 163
Registered: July 2009
Senior Member

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 #541157 is a reply to message #540927] Fri, 18 June 2010 09:35 Go to previous messageGo to next message
Fericit Bostan is currently offline Fericit Bostan
Messages: 68
Registered: June 2010
Member
Thanks for the help. It seems odd that we are adding Organizations as children of the parent organization and it doesn't seem like the correct data model.

The other thing I'm concerned about is how will the client JAXB libraries handle this? My intent is to return these objects to the client via a WebService. First, I'm wondering will JAXB respect the annotations of these objects when I return them from my WebService method invocation? Second, will the client understand the XML returned and be able to reconstruct the objects?

This seems like a large amount of work that I will need to implement for all of my classes that represent these types of relationships? Am I going to need to do this for each object that contains an @XmlIDREF annotation?

Thanks again...
Re: Moxy JAXB annotations for closure table relationship [message #541209 is a reply to message #540667] Fri, 18 June 2010 13:40 Go to previous messageGo to next message
Blaise Doughan is currently offline Blaise Doughan
Messages: 163
Registered: July 2009
Senior Member

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 #541218 is a reply to message #541209] Fri, 18 June 2010 14:00 Go to previous messageGo to next message
Fericit Bostan is currently offline Fericit Bostan
Messages: 68
Registered: June 2010
Member
I haven't gotten as far as generating a schema as of yet. I was going to delegate that to the Metro libraries, which build the wsdl and schema dynamically. This was just my first step - seeing what the data structure might look like when marshalled. That's why I asked the question.

The idea of having a containment property makes sense. I figured as much so I was planning on creating a high level container for my entities to be encapsulated within. But based upon your example, I'm going to need a lot more than just that one class. I guess I was a bit naive about this as I was hoping that most of the work would be don't based upon the annotations. Now it looks like I might need to create a large number of classes, an adapter, a customizer, a policy and a listener) for each complex relationship that contains @XmlIDREF annotations. (Is that correct?)

Re: Moxy JAXB annotations for closure table relationship [message #541527 is a reply to message #540667] Mon, 21 June 2010 09:19 Go to previous messageGo to next message
Blaise Doughan is currently offline Blaise Doughan
Messages: 163
Registered: July 2009
Senior Member

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 #543168 is a reply to message #541527] Mon, 28 June 2010 11:29 Go to previous messageGo to next message
Fericit Bostan is currently offline Fericit Bostan
Messages: 68
Registered: June 2010
Member
The example that you posted, which object would I return from a WebService call? The AdaptedOrganization? And how would Metro know about the OrganizationMarshalListener?

I know the example works because I've run it but it is still unclear to me now Metro would know about most of these objects.

Thanks....
Re: Moxy JAXB annotations for closure table relationship [message #543541 is a reply to message #540667] Tue, 29 June 2010 13:47 Go to previous messageGo to next message
Blaise Doughan is currently offline Blaise Doughan
Messages: 163
Registered: July 2009
Senior Member

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

}
Re: Moxy JAXB annotations for closure table relationship [message #543543 is a reply to message #543541] Tue, 29 June 2010 13:50 Go to previous messageGo to next message
Fericit Bostan is currently offline Fericit Bostan
Messages: 68
Registered: June 2010
Member
That makes things easier and cleaner. Would I also need to implement the unmarshal method for when receiving an Organization from the client?
Re: Moxy JAXB annotations for closure table relationship [message #543545 is a reply to message #540667] Tue, 29 June 2010 14:00 Go to previous message
Blaise Doughan is currently offline Blaise Doughan
Messages: 163
Registered: July 2009
Senior Member

You won't need any special logic on the unmarshal side. The only reason for this special logic on the marshal side is to auto populate a virtual containment property, alternatively you could manually populate that property.
Previous Topic:cannotAddElementWithoutKeyToMap
Next Topic:cached sql insert statement executed during query exection
Goto Forum:
  


Current Time: Mon Apr 21 11:43:20 EDT 2014

Powered by FUDForum. Page generated in 0.08567 seconds