JAXB #XmlAdapter for arbitrary XML - java

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>

Related

any way to convert xml to object in java?

I am trying to use a XML and access to all fields and data on an easy way, so, I decided to use JaxB , but I have no idea how to create all the classes for the objects, I tried manually like this.
#XmlRootElement(name = "Response")
public class Response {
#XmlElement(ns = "SignatureValue")
String signatureValue;
}
But I get an error on #XmlElement saying the annotation is disallowed for this location...
I checked other posts and they work great if I have something like Hellw but doesnt work with more complex formats, an example of first part of mine is like this
<?xml version="1.0" encoding="UTF-8"?><DTE xsi:noNamespaceSchemaLocation="http://www.myurl/.xsd" xmlns:gs1="urn:ean.ucc:pay:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
any idea how to do all this??
Thanks in advance
EDIT:
I forgot to say, the XML is actually a String with the entire XML.
The #XmlElement annotation is valid on a field. If you have a corresponding property then you should annotate the class with #XmlAccessorType(XmlAccessType.FIELD) to avoid a duplicate mapping exception.
Java Model
Annotating the Field
#XmlRootElement(name = "Response")
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
#XmlElement(name = "SignatureValue")
String signatureValue;
public String getSignatureValue() {
return signatureValue;
}
public void setSignatureValue(String signatureValue) {
this.signatureValue = signatureValue;
}
}
Annotating the Property
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "Response")
public class Response {
String signatureValue;
#XmlElement(name = "SignatureValue")
public String getSignatureValue() {
return signatureValue;
}
public void setSignatureValue(String signatureValue) {
this.signatureValue = signatureValue;
}
}
For More Information
http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
Demo Code
Below is some demo code that reads/writes the XML corresponding to your Response class.
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Response.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum19713886/input.xml");
Response response = (Response) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<SignatureValue>Hello World</SignatureValue>
</Response>

JAXB empty element unmarshalling

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>

how do I convert an xml string to an object. I don't know what object ahead of time. Just several possibities

I have a string of that is an XML string and it could correspond to one of several objects that are jaxb generated schema files.
I don't know what object it is ahead of time.
How do convert this XML string to an jaxb xml object? Some type of unmarshalling?
How do I determine which object it is assigned to?
How do I instantiate the object once it is converted from xml string to the object?
You could do something like the following:
Foo
As long as there is a root element associated with your class via an #XmlRootElement or #XmlElementDecl annotation you don't need to specify the type of class that you are unmarshalling (see: http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html).
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Foo {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo
To unmarshal from a String simply wrap the String in an instance of StringReader. The unmarshal operation will convert the XML into an instance of your domain class. If you don't know what class you will have to use instanceof or getClass() to determine what type it is.
import java.io.StringReader;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
String xml = "<foo><bar>Hello World</bar></foo>";
StringReader reader = new StringReader(xml);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Object result = unmarshaller.unmarshal(reader);
if(result instanceof Foo) {
Foo foo = (Foo) result;
System.out.println(foo.getBar());
}
}
}
Output
Hello World
Unmarshaller yourunmarshaller = JAXBContext.NewInstance(yourClass).createUnMarshaller();
JAXBElement<YourType> jaxb = (yourunmarshaller).unmarshal(XMLUtils.getStringSource([your object]), [the class of your object].class);
If you have schema files for the XML objects, which you would if you're using JAXB, run a validate on the XML.
Java XML validation against XSD Schema
If you generate objects from XSD, then JAXB generated an ObjectFactory class in the same package as all the type classes.
JAXBContext jaxbContext = JAXBContext.newInstance("your.package.name");
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Here "your.package.name" stands for the package name of your ObjectFactory class.
The unmarshaller can now convert your XML into objects:
public Object createObjectFromString(String messageBody) throws JAXBException {
return unmarshaller.unmarshal(new StringReader(messageBody));
}
If this is successful, a JAXBElement object will be returned:
try {
JAXBElement jaxbElement= (JAXBElement) createObjectFromString(messageBody);
} catch (JAXBException e) {
// unmarshalling was not successful, take care of the return object
}
If you have a jaxbElement object returned, you can call getValue() for the wrapped object, of getDeclaredType() for it's class.
With this method, you don't need to know the type of the target object in advance.

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/>

Is it possible to programmatically configure JAXB?

Say I have two JavaBeans Person and Address.
If I create a list of Person objects, I'd like to marshal to something like this:
<persons>
<person>...</person>
</persons>
It's possible to use the technique described here:
Using JAXB to unmarshal/marshal a List<String>
By annotating JaxbList with #XmlRootElement(name = "persons") and #XmlElement(name = "person"), then it's possible to marshal to the XML above.
But, it'd be nice to be able to reuse the same JaxbList<T> class to also marshal a list of Address objects. And in reality, I will have many other types of beans. I can go with something like:
<list>
<item xsi:type="person" xmlns:xsi="http://www.w2.org/2001/XmlSchema-instance"></item>
</list>
But, ideally, it'd be nice to have it replace "list" with the plural version of class name and "item" with the class name.
So, is it possible to programmatically configure the JaxbContext or something during runtime and essentially set the value of the name inside #XmlRootElement and #XmlElement?
Or any other way to get this working without having to write a separate implementation of JaxbList for every bean type? Maybe XmlJavaTypeAdapter can achieve this sort of thing?
Update
#Blaise Doughan's solution accepted below works great. For my use case, I needed to go straight from Java object to XML, here's what worked (note this is not my full implementation, it's sort of just pseudo code for demonstration):
//JAXBContext is thread safe and so create it in constructor or
//setter or wherever:
...
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
...
public String marshal(List<T> things, Class clazz) {
//configure JAXB and marshaller
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create wrapper based on generic list of objects
Wrapper<T> wrapper = new Wrapper<T>(things);
JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);
StringWriter result = new StringWriter();
//marshal!
m.marshal(wrapperJAXBElement, result);
return result.toString();
}
You could create a generic Wrapper object like the following:
Wrapper
You could create a generic wrapper class with a List property annotated with #XmlAnyElement(lax=true). The type of the object used to populate this list will be based on its root element (see: http://blog.bdoughan.com/2010/08/using-xmlanyelement-to-build-generic.html).
package forum13272288;
import java.util.*;
import javax.xml.bind.annotation.XmlAnyElement;
public class Wrapper<T> {
private List<T> items = new ArrayList<T>();
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
Address
You will need to annotate the possible contents of the list with #XmlRootElement.
package forum13272288;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Address {
}
Person
package forum13272288;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Person {
}
Demo
The demo code below demonstrates how to use the Wrapper class. Since the root element can be different you will need to specify that you want to unmarshal to the wrapper class. Alternatively you could leverage the #XmlElementDecl annotation to associate multiple root elements with the wrapper class (see: http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html).
package forum13272288;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, Person.class, Address.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StreamSource personsXML = new StreamSource("src/forum13272288/persons.xml");
JAXBElement<Wrapper> wrapper1 = unmarshaller.unmarshal(personsXML, Wrapper.class);
marshaller.marshal(wrapper1, System.out);
StreamSource addressesXML = new StreamSource("src/forum13272288/addresses.xml");
JAXBElement<Wrapper> wrapper2 = unmarshaller.unmarshal(addressesXML, Wrapper.class);
marshaller.marshal(wrapper2, System.out);
}
}
Output
Below is the output from running the demo code. The files persons.xml and addresses.xml look just like there corresponding output.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persons>
<person/>
<person/>
</persons>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addresses>
<address/>
<address/>
</addresses>
For More Information
http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html

Categories