JAXB: Intercept during unmarshalling? - java

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?

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.
http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html
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.

Related

Extending the ObjectFactory in EclipseLink MOXy

I have a JAXB model generated from MOXy's version of XJC. The xjc:superclass tag was used in the binding's file so all the objects extend a common class.
package my.package
//Base.java
#XmlTransient
public class Base {
//...
}
//MyTag.java (generated from XJC)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {/*...*/})
#XmlRootElement(name = "myTag")
public class MyTag extends Base {
//...
}
The model nor the schema can be changed, but I can change the Base class.
I need to extend MyTag, as well as some other classes in the model, so I can customize its method behaviors from the Base class. So I extended MyTag and the other domain classes that needed custom behavior, as well as the ObjectFactory. These classes exist in a separate Java package.
package my.extended.package
//MyTagExtended.java
public class MyTagExtended extends MyTag {
//...
}
//CustomObjectFactory.java
public class CustomObjectFactory extends ObjectFactory {
//...
#Override
public MyTagExtended createMyTag() {
return new MyTagExtended();
}
//...
}
Application code:
package application
//Application.java
System.setProperty("org.eclipse.persistence.moxy.annotation.xml-value-extension", "true");
JAXBContext jc = (JAXBContext) JAXBContext.newInstance(XPSObjectFactory.class);
JAXBUnmarshaller u = jc.createUnmarshaller();
return u.unmarshal(xmlFile);
The problem I'm having is MOXy seems to randomly decide whether to call the CustomObjectFactory's or ObjectFactory's methods.
In the CustomObjectFactory class, if I have only one overridden method, that method is always called. However, when I put in more, MOXy seems to randomly decide whether or not to call ObjectFactory's method's or CustomObjectFactory's.
When I use the same sort of setup with the Oracle's JAXB implementation that's supplied with the JDK, it works fine. The CustomObjectFactory's methods are always called.
Is there a configuration that needs to be set? How can I configure MOXy to always use my CustomObjectFactory's methods?
EDIT:
To clarify, here's how I made the same scenario work with Oracle's JAXB implementation:
JAXBContext jc = JAXBContext.newInstance("my.package");
Unmarshaller u = jc.createUnmarshaller();
u.setProperty("com.sun.xml.internal.bind.ObjectFactory", new CustomObjectFactory());
I tried to set the ObjectFactory property on the MOXy unmarshaller, but it threw an exception.
I found a workaround for MOXy. I found some information on XmlClassExtractor, which can be used to specify which class to instantiate. Since I can't modify the domain objects, an XML file was used instead.
I kept the extended classes the same, but modified Application.java and wrote a binding.xml file and a ClassExtractor:
package application
//Application.java
System.setProperty("org.eclipse.persistence.moxy.annotation.xml-value-extension", "true");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "classExtractor.xml");
JAXBContext jc = (JAXBContext) JAXBContextFactory.createContext(new Class[]{CustomObjectFactory.class}, properties);
JAXBUnmarshaller u = jc.createUnmarshaller();
return u.unmarshal(xmlFile);
classExtractor.xml:
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="my.package"
version="2.3">
<java-types>
<java-type name="MyTag">
<xml-class-extractor class="my.extended.package.MyTagClassExtractor"/>
</java-type>
</java-types>
</xml-bindings>
Class extractor:
package my.extended.package
//MyTagExtractor.java
public class MyTagExtractor extends ClassExtractor {
#Override
public Class<? extends Base> extractClassFromRow(Record databaseRow, Session session) {
return MyTagExtended.class;
}
}
This seems to work, but it's quirky and prone to programmer errors since multiple source files have to be edited to get the desired functionality. Oracle's ObjectFactory property on the unmarshaller was very easy and streamlined. Does anyone have a better answer?

java.beans.ConstructorProperties in Jackson 2

My domain Objects are enhanced using lombok, which generates the java.beans #ConstructorProperties annotation for the constructors of immutable objects.
Now in my frontend artifact, I'd like to serialize these objects to JSON using Jackson 2.
For Jackson 1, this could be done using Jackson Extensions. Is there such a solution for Jackson 2 as well or do I have to write it myself?
My main problem is that I want to keep my domain Objects frontend agnostic, so I wouldn't like to pollute them with Jackson annotations.
And no: Java 8 parameter names is not an option, as I am stuck with Java 7 for the time being.
Sean Patrick Floyd has already written a solution, but I am posting my solution because his is proprietary. This is a Jackson module that uses an AnnotationIntrospector to make a constructor annotated with #ConstructorProperties a jackson #JsonCreator.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
public class ConstructorPropertiesModule extends SimpleModule {
public ConstructorPropertiesModule() {
super(PackageVersion.VERSION);
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new ConstructorPropertiesAnnotationIntrospector());
}
public static class ConstructorPropertiesAnnotationIntrospector extends NopAnnotationIntrospector {
#Override
public boolean hasCreatorAnnotation(Annotated a) {
if (!(a instanceof AnnotatedConstructor)) {
return false;
}
AnnotatedConstructor ac = (AnnotatedConstructor) a;
Constructor<?> c = ac.getAnnotated();
ConstructorProperties properties = c.getAnnotation(ConstructorProperties.class);
if (properties == null) {
return false;
}
for (int i = 0; i < ac.getParameterCount(); i++) {
final String name = properties.value()[i];
final int index = i;
JsonProperty jsonProperty = new JsonProperty() {
#Override
public String value() {
return name;
}
#Override
public boolean required() {
return false;
}
#Override
public Class<? extends Annotation> annotationType() {
return JsonProperty.class;
}
#Override
public int index() {
return index;
}
};
ac.getParameter(i).addOrOverride(jsonProperty);
}
return true;
}
}
}
The module can then be registered to an object mapper to deserialize JSON using the #ConstructorProperties annotation:
ObjectMapper m = new ObjectMapper();
m.registerModules(new ConstructorPropertiesModule());
As others stated Jackson now supports #ConstructorProperties - unfortunatelly. Because it messed up things.
The logic Jackson applies is quite unfortunate. If multiple #ConstructorProperties annotated constructor are present it will create the object via the one with most parameters. Ops. This is problem especially with Lombok which annotates all constructors with #ConstructorProperties. But anyway, this annotation is not there solely for Jackson. It makes sense to annotate every single constructor for any code inspection tool which may use this information. Lombok is right here.
Imagine following object:
#Data
#Builder
#NoArgsConstructor // for Jackson
#AllArgsConstructor // for builder
public class MyDto {
private Type1 value1 = Type1.NONE;
private Type2 value2;
}
Here Jackson will always use the all-args constructor because it is annotated with #ConstructorProperties and has most parameters.
This also means that if you set only value2 in your JSON object the value1 becomes null. Not what you would expect.
Conclusion: the current behaviour (when used with Lombok or annotate more than one constructor) doesn't allow for the easy class-level default values.
Workaround: #AllArgsConstructor(suppressConstructorProperties=true) - but this is claimed to be deprecated soon as it's present just for java 1.5 compatibility purposes.
This issue has finally been resolved in Jackson 2.7 and #ConstructorProperties are now supported out-of-the-box.
See https://github.com/FasterXML/jackson-databind/issues/905
I'm afraid you will have to write a similar wrapper for Jackson2 yourself.

Spring REST with both JSON and XML

I want to provide one comprehensive REST API with support for both JSON and XML.
The domain model is of complex type and we note that to produce friendly JSON and XML on the same model using MappingJacksonHttpMessageConverter and JaxbMarshaller respectively tends to give either readable XML or readable JSON 1).
What's the best way to proceed?
1) Due to how objects such as maps, root tags and relations are modelled differently in json than in xml, the objects to serialize needs to be designed differently to get both tidy json and tidy xml. Utilities such as jaxb annotations only goes that far.
I can think of a few candidates
1) Create both a json and xml controller/model
public class Controller {
public Foo foo() {
return new Foo();
}
}
public class XmlController extends Controller {
#Override
public XmlFoo foo() {
return new new XmlFoo(super.foo());
}
}
public class JsonController extends Controller {
#Override
public JsonFoo foo() {
return new JsonFoo(super.foo());
}
}
Given a model object Foo create a JsonFoo and XmlFoo
2) Write a custom message converter
I tried this and it turned out to be a bit complicated since the view must know how to resolve e.g., a Foo to a JsonFoo to be able to serialize it into a readable format.
3) Let each model object serialize itself, e.g.,
public class Foo {
public String serialize(Serializer s) {
return s.serialize(this);
}
}
Based on some arbitration parameter let the controller inject the correct serializer
new Foo(new FooJsonSerializer());
new Foo(new FooXmlSerializer());
I'm doing this in a current project without using a ContentNegotiatingViewResolver. For one method in my controller:
#RequestMapping(value = "/test", method = RequestMethod.GET)
#ResponseBody
public HttpEntity<BasicResponse> getBasicResponse() {
return new HttpEntity<BasicResponse>(new BasicResponse());
}
I can receive the following output based on the Accept request header.
Accept: application/xml (requires JAXB2 on the classpath)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<basicResponse>
<errors>
<message>test1</message>
<message>test2</message>
</errors>
</basicResponse>
Accept: application/json (requires Jackson on the classpath)
{
"errors" : ["test1", "test2"]
}
My response object is simple and uses normal annotations:
package org.mypackage.response;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class BasicResponse {
#XmlElementWrapper(name = "errors")
#XmlElement(name = "message")
private List<String> errors = new ArrayList<String>();
public BasicResponse() {
this.errors.add("test1");
this.errors.add("test2");
}
public List<String> getErrors() {
return errors;
}
}
The SpringSource spring-mvc-showcase project is also a helpful resource. I think they separate the conversions for different methods, but I am definitely doing this for one method.
I can't quite tell by your question...but if you're looking to serialize the output more than that, #chrylis is correct in that a custom serializer would be your next move. But everything I've ran into (which can get pretty complex, with nested objects in my response) converts perfectly to valid XML or JSON.
You should use the ContentNegotiatingViewResolver.
There is an issue in that a collection of POJOs are not mapped correctly with some XML marshallers. XStream has solutions for this (Moxy too?).
Here's a place to start:
http://blog.springsource.org/2013/06/03/content-negotiation-using-views/
Basically, you use a MappingJacksonView and a similar one for XML, which is a "fake" view that uses Jackson (or an XML marshaller) to marshall your POJO(s) to the correct format.
The server will send back the correct type based on one of:
the HTTP Accept header
a "filetype extension", such as ".json"
a querystring parameter, such as "format=json"
As far as omitting fields, you cans use annotations #JsonIgnore(for Jackson) and/or #XStreamOmitField(for XStream).
Did you try this:
#RequestMapping(value = "/{id}",
method = RequestMethod.GET,
headers ={"Accept=application/json,application/xml"},
produces={"application/json", "application/xml"})

jaxb exception management in property setters of unmarshalled class

When I unmarshal an Xml node to a property, the setMyProperty is called.
The code of the property setter may raise an exception:what happens in this case?
The behaviour that I have observed: if the exception is an unchecked exception, then it is swallowed by jaxb "internals" and that property ignored. If the RuntimeException is changed in a Exception (so checked and added to the throws clause of the property setter) it causes the unmarshal to fail.
The question is: is this a behaviour you can rely on?
thanks in advance
Agostino
PS: Ok "swallowed" is not the correct word, because it actually is managed in the "best possible way", unmarshalling correctly the rest of the document. Nevertheless the unmarshall caller is not notified that an exception has happen.
PS: A simple test case, just in case :-)
package test.jaxb;
import java.io.File;
import javax.xml.bind.*;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class SZL {
public static void main(String [] args) throws JAXBException {
SZL test = new SZL();
test.myProp = "test-value";
test.setMyProp2("test-value-2");
JAXBContext jc = JAXBContext.newInstance(SZL.class);
File f = new File("SZL.xml");
Marshaller m = jc.createMarshaller();
m.marshal(test, f);
test = null;
Unmarshaller um = jc.createUnmarshaller();
test = (SZL)um.unmarshal(f);
System.out.println("The RuntimeException has been swallowed");
System.out.println(test.myProp);
System.out.println(test.myProp2);
}
private String myProp;
public void setMyProp(String myProp) {
throw new RuntimeException();
//this.myProp = myProp;
}
public String getMyProp() {return myProp;}
private String myProp2;
public void setMyProp2(String myProp2){
this.myProp2 = myProp2;
}
public String getMyProp2() {return myProp2;}
}
This behaviour is not guaranteed to be portable across all JAXB implementations (Metro, EclipseLink MOXy, Apache JaxMe, etc).
For example the exception is not swallowed in EclipseLink JAXB (MOXy). Note I'm the MOXy lead, and a member of the JAXB (JSR-222) expert group.

JAXB: How to customize Xml serialization of double fields

I have a legacy class, with a lot of public double fields. All double fields are initialized with Double.MAX_VALUE to indicate that they are empty. (The legacy serialization is coded to ignore the field and not serialize if field is equals to Double.MAX_VALUE).
We are now trying to serialize this class to Xml using JAXB Marshaller. It is working fine, except that we want to prevent generating Xml for fields which equal Double.MAX_VALUE.
We aren't using a separate JAXB schema, just marking up our classes with various javax.xml.bind.annotation Annotations. If a schema is used, you can add a <javaType> element to specify a custom DataType converter. Is there any way to do this using Annotations or programmatically?
After trying approach recommended below, I still can't get XmlAdapter picked up:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value=EmptyDoubleValueHandler.class, type=Double.class), #XmlJavaTypeAdapter(value=EmptyDoubleValueHandler.class, type=double.class)})
package tta.penstock.data.iserver;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
My top level class is: tta.penstock.data.iserver.OrderBlotter, which contains a list of tta.penstock.data.iserver.OrderResponseWrappers which extends com.eztech.OrderResponse. All the double fields are contained in com.eztech.OrderResponse.
My unit test code does the following:
JAXBContext context = JAXBContext.newInstance(new Class[] { OrderBlotter.class, OrderResponseWrapper.class, OrderResponse.class});
Marshaller marshaller = context.createMarshaller();
StringWriter stringWriter = new StringWriter();
marshaller.marshal(blotter, stringWriter);
System.out.println("result xml=\n" + stringWriter.toString());
But the double values still don't get handled by the XmlAdapter. I know I'm missing something basic, but I'm not sure what it is.
You could use an XmlAdapter:
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html
The XmlAdapter
package example;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DoubleAdapter extends XmlAdapter<Double, Double>{
#Override
public Double unmarshal(Double v) throws Exception {
return v;
}
#Override
public Double marshal(Double v) throws Exception {
if(Double.MAX_VALUE == v) {
return null;
} else {
return v;
}
}
}
The Model Object
package example;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Root {
#XmlJavaTypeAdapter(DoubleAdapter.class)
public Double maxDouble = Double.MAX_VALUE;
#XmlJavaTypeAdapter(DoubleAdapter.class)
public Double aDouble = 123d;
}
Demo Code
package example;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(new Root(), System.out);
}
}
UPDATE
StaxMan's suggestion is a good one. If you specify the following package level annotation you can avoid the need of individually annotating all the Double properties
package-info.java
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type=Double.class, value=DoubleAdapter.class)
})
package example;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
Write a getter that returns null, instead of Double.MAX_VALUE? (if type is 'double', need to change it to 'Double' first, to allow nulls).
Since JAXB by default ignores writing out of nulls, that should achieve what you are trying to do. This assumes you can modify legacy class in question.

Categories