Question

I've got a typical web service using JAX-RS and JAXB, and upon unmarshalling I would like to know which setters were explicitly called by JAXB. This effectively lets me know which elements were included in the document provided by the caller.

I know I can probably solve this with an XmlAdapter, but I have a lot of classes in a number of different packages, and I don't want to create adapters for each and every one of them. Nor do I want to put hooks into each and every setter. I would like a general solution if possible. Note that all of my classes are setup to use getters and setters; none of them use fields for the access type.

My service uses Jersey 2.4, Spring 3.2, and MOXy 2.5.1, so if there's anything that can be leveraged from any of those, that's all the better. Our original thought was we could dynamically create a factory class (akin to what @XmlType supports) that would return a proxy object that would intercept the setters. We thought we could make this happen using the MetadataSource concept in MOXy, but that does not seem to be possible.

Anyone have any ideas?

Was it helpful?

Solution

My service uses Jersey 2.4, Spring 3.2, and MOXy 2.5.1, so if there's anything that can be leveraged from any of those, that's all the better.

Create your own EclipseLink AttributeAccessor

MOXy (which is a component of EclipseLink) leverages a class called AttributeAccessor to do operations with fields and properties. You could wrap this class to capture all the information that you need.

import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.mappings.AttributeAccessor;

public class MyAttributeAccessor extends AttributeAccessor {

    private AttributeAccessor attributeAccessor;

    public MyAttributeAccessor(AttributeAccessor attributeAccessor) {
        this.attributeAccessor = attributeAccessor;
    }

    @Override
    public Object getAttributeValueFromObject(Object domainObject)
            throws DescriptorException {
        return attributeAccessor.getAttributeValueFromObject(domainObject);
    }

    @Override
    public void setAttributeValueInObject(Object domainObject, Object value)
            throws DescriptorException {
        System.out.println("Thread: " + Thread.currentThread().getId() + " - Set value:  " + value + " on property: " + attributeAccessor.getAttributeName() + " for object: " + domainObject);
        attributeAccessor.setAttributeValueInObject(domainObject, value);
    }

}

Tell MOXy to use your AttributeAccessor

We can leverage a SessionEventListener to access the underlying metadata to specify your implementation of AttributeAccessor. This is passed in as a property when creating the JAXBContext.

    Map<String, Object> properties = new HashMap<String, Object>(1);
    properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() {

        @Override
        public void postLogin(SessionEvent event) {
            Project project = event.getSession().getProject();
            for(ClassDescriptor descriptor : project.getOrderedDescriptors()) {
                for(DatabaseMapping mapping : descriptor.getMappings()) {
                    mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor()));
                }
            }
            super.preLogin(event);
        }

    });

    JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);

Leverage a JAX-RS ContextResolver when Creating the JAXBContext

Since you are in a JAX-RS environment you can leverage a ContextResolver to control how the JAXBContext is created.


Standalone Example

Java Model (Foo)

Below is a sample class where we will use field access (no setters).

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {

    private String bar;
    private String baz;

}

Demo

import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.sessions.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(1);
        properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, new SessionEventAdapter() {

            @Override
            public void postLogin(SessionEvent event) {
                Project project = event.getSession().getProject();
                for(ClassDescriptor descriptor : project.getOrderedDescriptors()) {
                    for(DatabaseMapping mapping : descriptor.getMappings()) {
                        mapping.setAttributeAccessor(new MyAttributeAccessor(mapping.getAttributeAccessor()));
                    }
                }
                super.preLogin(event);
            }

        });

        JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StringReader xml = new StringReader("<foo><bar>Hello World</bar></foo>");
        Foo foo = (Foo) unmarshaller.unmarshal(xml);
    }

}

Output

Thread: 1 - Set value:  Hello World on property: bar for object: forum21044956.Foo@37e47e38

UPDATE

So this works, but I have a few issues. First, the domainObject is always logging as 0 in my system. Not sure why that's occurring.

I have not idea why that is occuring, may need to check the toString() for the object you are logging.

Second, I am not able to tell if the property in question is on the top-level item that is being unmarshalled or on a sub-element. That's actually quite annoying.

You will need to beef up the logic here. Based on the objects being set you should be able to do what you want.

Third, your solution is per JAXBContext, but I don't know if I really want to create a new context for every request. Isn't that bad from an overhead perspective?

You can cache the created JAXBContext to prevent rebuilding it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top