JAXB empty element unmarshalling - java

The problem is in the following :
I get the soap response with empty element inside (e.g. ... <someDate /> ... )
and as a result exception is being throwed when JAXB wants to parse this element
instead to set the appropriate field with null value.
How to configure JAXB to treat empty elements as null ?
Can we do this with JAXB only (not using some third-party workarounds)

Base Problem
Empty String is not a valid value for the xsd:date type. To be valid with the XML schema an optional element should be represented as an absent node.,
Why the Base Problem is Impacting You
All JAXB implementations will recognize that empty String is not a valid value for xsd:date. They do this by reporting it to an instance of ValidationEventHandler. You can see this yourself by doing the following:
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setEventHandler(new ValidationEventHandler() {
#Override
public boolean handleEvent(ValidationEvent event) {
System.out.println(event);
return true;
}
});
The implementation of JAX-WS you are using, leverages EclipseLink MOXy as the JAXB provider. And in the version you are using MOXy will by default throw an exception when a ValidationEvent of severity ERROR is encountered instead of FATAL_ERROR like the reference implementation. This has since been fixed in the following bug:
http://bugs.eclipse.org/369994
Work Around
If you are using the JAXB APIs directly you could simply override the default ValidationEventHandler. In a JAX-WS environment a XmlAdapter can be used to provide custom conversion logic. We will leverage an XmlAdapter to override how the conversion to/from Date is handled.
XmlAdapter (DateAdapter)
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date>{
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
#Override
public Date unmarshal(String v) throws Exception {
if(v.length() == 0) {
return null;
}
return dateFormat.parse(v);
}
#Override
public String marshal(Date v) throws Exception {
if(null == v) {
return null;
}
return dateFormat.format(v);
}
}
Java Model (Root)
The XmlAdapter is referenced using the #XmlJavaTypeAdapter annotation. If you wish this XmlAdapter to apply to all instances of Date you can register it at the package level (see: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).
import java.util.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlSchemaType(name = "date")
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
private Date abc;
#XmlSchemaType(name="date")
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
private Date qwe;
}
Demo Code
Below is a standalone example you can run to see that everything works.
jaxb.properties
In a standalone example to use MOXy as your JAXB provider you need to include a file called jaxb.propeties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<abc></abc>
<qwe>2013-09-05</qwe>
</root>
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum18617998/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Note that in the marshalled XML the Date field that was null was marshalled as an absent element (see: http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html).
<?xml version="1.0" encoding="UTF-8"?>
<root>
<qwe>2013-09-05</qwe>
</root>

Related

JAXB #XmlAdapter for arbitrary XML

I have a org.w3c.dom.Element that I'm returning from my XmlAdapter for a custom #XmlElement and I'd like to include it as part of a JAXB object as arbitrary XML (I'm aware I'll have to hand-craft the XSD). However, JAXB complains with
org.w3c.dom.Element is an interface, and JAXB can't handle interfaces.
Apparently the w3c XML types are not supported as Java types, which is a shame. But further than this, I get the same error when I use javax.xml.transform.Result which is apparently supported.
How can I include arbitrary XML elements as elements in JAXB?
Note: as per https://forums.oracle.com/thread/1668210 I've also tried
MessageFactory factory = MessageFactory.newInstance();
message = factory.createMessage();
SOAPElement element = message.getSOAPBody().addDocument(doc);
but that is also giving the same error.
TL;DR
You can have an XmlAdapter that converts you domain object to an instance of org.w3c.dom.Element as long as you specify the value type as Object (not Element).
Below is a full example.
XmlAdapter
A field/property of type java.lang.Object will keep unknown content as DOM nodes. You can leverage this in your use case by specifying the value type in your XmlAdapter as Object. You will need to ensure that the root element returned from the marshal method matches the field/property as defined by the #XmlElement annotation.
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class BarAdapter extends XmlAdapter<Object, Bar>{
private DocumentBuilder documentBuilder;
public BarAdapter() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
documentBuilder = dbf.newDocumentBuilder();
} catch(Exception e) {
// TODO - Handle Exception
}
}
#Override
public Bar unmarshal(Object v) throws Exception {
Bar bar = new Bar();
Element element = (Element) v;
bar.value = element.getTextContent();
return bar;
}
#Override
public Object marshal(Bar v) throws Exception {
Document document = documentBuilder.newDocument();
Element root = document.createElement("bar");
root.setTextContent(v.value);
return root;
}
}
Java Model
Foo
The #XmlJavaTypeAdapter annotation is used to reference the XmlAdapter.
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
#XmlJavaTypeAdapter(BarAdapter.class)
private Bar bar;
}
Bar
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Bar {
String value;
}
Demo Code
Demo
Since there is a cost to creating the DocumentBuilderFactory we can leverage JAXB's ability to handle stateful instances of XmlAdapter by setting an instance on the Marshaller.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum18272059/input.xml");
Foo foo = (Foo) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(new BarAdapter());
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar>Hello World</bar>
</foo>

How does EclipseLink/MOXy create the property name?

I am writing a code to set XmlTransient at runtime using MOXy. Here is the part of the code which is adapted from the example on http://blog.bdoughan.com/2011/06/moxy-extensible-models-refresh-example.html
public void setXmlTransient(Class<?> domainClass, String propertyName) {
XmlTransient xmlTransient = new XmlTransient();
xmlTransient.setJavaAttribute(propertyName);
JavaType javaType = getJavaType(domainClass);
javaType.getJavaAttributes().getJavaAttribute().add(objectFactory.createXmlTransient(xmlTransient));
}
Since I am doing this programmatically, I need to be able to create the propertyName exactly the same way as MOXy does. For most getter method names, like getOrder, the property name is done by removing get from the method name and change upper-case O to lower-case o, i.e. property name is order. However, I am hitting the case which my getter method is getXInA but xInA doesn't seem to be a valid property name. MOXy throws a warning like
Ignoring attribute [xInA] on class [Atom] as no Property was generated for it.
Does anyone know what the rules are used by MOXy for creating the property name from getters? or know where I can find out about this without reading the MOXy source code?
SHORT ANSWER
Because there are two capital letters in a row the property name is going to be XInA.
LONG ANSWER
Domain Model (Foo)
Below is a sample Java class with the property from your question.
package forum14945664;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Foo {
private String x;
public String getXInA() {
return x;
}
public void setXInA(String x) {
this.x = x;
}
}
MetadataSource (ExampleMetadataSource)
MetadataSource is a programmatic way to provide MOXy with the mapping metadata.
package forum14945664;
import java.util.*;
import org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter;
import org.eclipse.persistence.jaxb.xmlmodel.*;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType.*;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings.*;
public class ExampleMetadataSource extends MetadataSourceAdapter {
private ObjectFactory objectFactory;
private Map<Class<?>, JavaType> javaTypes;
private XmlBindings xmlBindings;
public ExampleMetadataSource() {
objectFactory = new ObjectFactory();
javaTypes = new HashMap<Class<?>, JavaType>();
xmlBindings = new XmlBindings();
xmlBindings.setPackageName("forum14945664");
xmlBindings.setJavaTypes(new JavaTypes());
}
#Override
public XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader) {
return xmlBindings;
}
public JavaType getJavaType(Class<?> clazz) {
JavaType javaType = javaTypes.get(clazz);
if(null == javaType) {
javaType = new JavaType();
javaType.setName(clazz.getSimpleName());
javaType.setJavaAttributes(new JavaAttributes());
xmlBindings.getJavaTypes().getJavaType().add(javaType);
javaTypes.put(clazz, javaType);
}
return javaType;
}
public void setXmlTransient(Class<?> domainClass, String propertyName) {
XmlTransient xmlTransient = new XmlTransient();
xmlTransient.setJavaAttribute(propertyName);
JavaType javaType = getJavaType(domainClass);
javaType.getJavaAttributes().getJavaAttribute().add(objectFactory.createXmlTransient(xmlTransient));
}
}
Specify MOXy as JAXB Provider (jaxb.properties)
To specify MOXy as the JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
In the demo code below we will create a JAXBContext based on the domain model and we will marshal an instance to XML. Then we will use the MetadataSource to make the property transient, refresh the JAXBContext and marshal the instance again.
package forum14945664;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.jaxb.JAXBHelper;
public class Demo {
public static void main(String[] args) throws Exception {
ExampleMetadataSource metadata = new ExampleMetadataSource();
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, metadata);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Foo foo = new Foo();
foo.setXInA("Hello World");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
metadata.setXmlTransient(Foo.class, "XInA");
JAXBHelper.getJAXBContext(jc).refreshMetadata();
marshaller.marshal(foo, System.out);
}
}
Output
First we see the XInA property marshalled, then after we make it transient we see that it is not in the XML from the second marshal operation.
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<XInA>Hello World</XInA>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<foo/>

What JAXB bean corresponds to this JSON?

The JSON is:
{"list": [1,2,3,4,5,6,7,8,9,10]}
Here's how I implement the JAXB bean:
package com.anon.sortweb.jaxb;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlElement;
#XmlRootElement
public class JsonBean {
#XmlElement(name="list")
private int[] list;
public JsonBean() {}
public void setList(int[] list) {
this.list = list;
}
public int[] getList() {
return list;
}
}
My web application works fine (I'm able to successfully access other resources) but this one resource (that I pass my JSON to) returns a 415 Media Type Unsupported exception.
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces("text/html")
public String sortAndReturnHtml(JsonBean listBean) { ... }
How do I correctly write my JAXB bean to encapsulate a list of integers?
Thanks in advance!
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Your JAXB bean is a perfectly reasonable representation of your JSON data. The JAXB (JSR-222) spec does not cover JSON binding so the answer ultimately comes down to how/if your JAX-RS implementation interprets JAXB metadata to produce/consume JSON.
Demo
Below is how it would work with MOXy.
package forum13648734;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {JsonBean.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum13648734/input.json");
JsonBean jsonBean = unmarshaller.unmarshal(json, JsonBean.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(jsonBean, System.out);
}
}
input.json/Output
{"list":[1,2,3,4,5,6,7,8,9,10]}
For More Information
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html

Marshalling null values with a custom type used with an xml adapter

I'm using a POJO object with XmlType for a custom XML adapter I built for marshaling a map of strings. I'm having issues however, with getting it to allow me to use null values properly. I was able to get it to work, but I'm not happy with the XML it is generating.
This is what I'm currently using that I would like to work, but as you can see in an sample XML result, it is not including the proper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" value
#XmlType(name="element")
public class RestMapElements {
#XmlAttribute(name="name")
public String key;
#XmlValue
public String value;
public RestMapElements(String key, String value) {
this.key = key;
this.value = value;
}
}
The resulting XML (slimmed to relevant data).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
...
<element-list>
<item name="activated_date">2012-03-29 11:34:14.323</item>
<item name="some_null_value"/>
</element-list>
...
However, I was able to get it to work with this, I'm just not happy with the XML having to add an additional "value" tag inside of the item tag to get it to work. (side note, why is it naming it item instead of element like I tried to specify in the XmlType name declaration?)
#XmlType(name="element")
public class RestMapElements {
#XmlAttribute(name="name")
public String key;
#XmlElement(nillable = true)
public String value;
public RestMapElements(String key, String value) {
this.key = key;
this.value = value;
}
}
Again, the resulting XML (slimmed to relevant data).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
...
<element-list>
<item name="activated_date"><value>2012-03-29 11:34:14.323</value></item>
<item name="some_null_value"><value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/></item>
</element-list>
...
Really, I can use the second as it works to solve my issue. I'm just wanting to use this as a learning experience to see if JAXB using annotations will allow be to bend this to what I'm looking for without having to add that additional value tag underneath an item tag just so I can support null values. Right now, when it unmarshals in the first example, I end up getting empty strings instead of null. In the second example, I get the null value I was expecting.
FYI: I'm currently using Jersey 1.11
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
You could use MOXy's #XmlNullPolicy extension to map this use case:
RestMapElements
package forum10415075;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#XmlType(name="element")
public class RestMapElements {
#XmlAttribute(name="name")
public String key;
#XmlValue
#XmlNullPolicy(nullRepresentationForXml=XmlMarshalNullRepresentation.XSI_NIL)
public String value;
}
Root
package forum10415075;
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
#XmlElementWrapper(name="element-list")
#XmlElement(name="item")
public List<RestMapElements> items = new ArrayList<RestMapElements>();
}
jaxb.properties
To use MOXy as your JAXB (JSR-222) provider you need to add a file called jaxb.properties in the same package as your domain model with the following content:
javax.xml.bind.context.factory = org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum10415075;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum10415075/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<element-list>
<item name="activated_date">2012-03-29 11:34:14.323</item>
<item name="some_null_value" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</element-list>
</root>
Note
If you are using Jersey in GlassFish 3.1.2 MOXy or WebLogic 12.1.1 is already included:
http://blog.bdoughan.com/2012/02/glassfish-312-is-full-of-moxy.html
http://blog.bdoughan.com/2011/12/eclipselink-moxy-is-jaxb-provider-in.html
I see your problem. An item element with xsi:nil="true" will be generated only by setting the corresponding RestMapElements entry in your ArrayList (or whatever) to null, losing the attribute. I think there isn't much of a solution to this.
One option would be to use your marshalling from the beginning of your post and unmarshal using the following:
If you're doing something like this:
#XmlElementWrapper(name="element-list")
#XmlElement(name="item")
public ArrayList<RestMapElements> list;
You can use a XmlAdapter to check if the value is an empty String and set it to null if it is:
#XmlElementWrapper(name="element-list")
#XmlElement(name="item")
#XmlJavaTypeAdapter(ItemAdapter.class)
public ArrayList<RestMapElements> list;
And ItemAdapter:
public class ItemAdapter extends XmlAdapter<RestMapElements, RestMapElements> {
#Override
public RestMapElements unmarshal(RestMapElements v) throws Exception {
if (v.value.equals("")) v.value = null;
return v;
}
}
Although this is still inelegant imho.
If you want to generate the proper xsi:nil="true" item elements, this is obviously not what you want though.
Good luck.

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