I would like to add a processing instruction whenever a collection/array property is serialized to get something like
<alice>
<? array bob ?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Is this possible with JAXB? Or at least with some specific JAXB implementation?
You could leverage an XMLStreamWriter and an XmlAdapter to do this:
BobAdapter
Things to note about the XmlAdapter:
It's stateful and references an XMLStreamWriter. We will later leverage JAXB's ability to set a stateful XmlAdapter on a Marshaller.
It converts from a List<String> to a List<String>, we're just using an XmlAdapter here to get an event.
The marshal method is where we call writeProcessingInstruction on the XMLStreamWriter:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.stream.XMLStreamWriter;
public class BobAdapter extends XmlAdapter<List<String>, List<String>> {
private boolean first = true;
private XMLStreamWriter xmlStreamWriter;
public BobAdapter() {
}
public BobAdapter(XMLStreamWriter xmlStreamWriter) {
this();
this.xmlStreamWriter = xmlStreamWriter;
}
#Override
public List<String> marshal(List<String> stringList) throws Exception {
if(first) {
xmlStreamWriter.writeProcessingInstruction("array", "bob");
first = false;
}
return stringList;
}
#Override
public List<String> unmarshal(List<String> stringList) throws Exception {
return stringList;
}
}
Alice
The #XmlJavaTypeAdapter annotation is used to link the XmlAdapter with the field/property:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Alice {
private List<String> bob;
#XmlJavaTypeAdapter(BobAdapter.class)
public List<String> getBob() {
return bob;
}
public void setBob(List<String> bob) {
this.bob = bob;
}
}
Demo
package forum6931520;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Alice.class);
File xml = new File("src/forum6931520/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Alice alice = (Alice) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
XMLOutputFactory xof = XMLOutputFactory.newFactory();
XMLStreamWriter xmlStreamWriter = xof.createXMLStreamWriter(System.out);
marshaller.setAdapter(new BobAdapter(xmlStreamWriter));
marshaller.marshal(alice, xmlStreamWriter);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<alice>
<?array bob?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Output
<?xml version='1.0' encoding='UTF-8'?><alice><?array bob?><bob>edgar</bob><bob>david</bob></alice>
Note
This example needs to be run with EclipseLink JAXB (MOXy) as the JAXB RI will throw the following exception (I'm the MOXy lead):
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
java.util.List is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
java.util.List does not have a no-arg default constructor.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
For More Information
http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
UPDATE
I have entered an enhancement request (https://bugs.eclipse.org/354286) to add native support for processing instructions. This would eventually allow you to specify the following on your property:
#XmlProcessingInstruction(target="array", value="bob")
public List<String> getBob() {
return bob;
}
Related
I have to create Java Objects from an xml file (that I can not edit before conversion), so I thought that it would probably be the best to create an XSD and then Unmarshall the file via JAXB.
The problem is, there are some elements in that XML that are formatted like this:
<parent-element>
<id-00001>...</id-00001>
<id-00002>...</id-00002>
...
</parent-element>
So basically I have a list of xml-elements, with the "name" of the element being the order inside of the list. There is a potentially unlimited number of elements in each of those lists.
Is it possible to unmarshall this with JAXB or do I HAVE TO use an XML-Reader and loop through the elements? The "id-XXXXX" elements are all formatted in the same way internally, so if it would be formatted like "" it would be possible without any issues.
Thanks in advance!
I guess that your XML payload looks like below:
<root>
<parent-element>
<id-00001>value 1</id-00001>
<id-00002>value 2</id-00002>
</parent-element>
</root>
To read nodes with dynamic names we need to write custom adapter (javax.xml.bind.annotation.adapters.XmlAdapter). Assume that model looks like below:
#XmlRootElement(name = "root")
#XmlAccessorType(XmlAccessType.FIELD)
class Root {
#XmlElement(name = "parent-element")
#XmlJavaTypeAdapter(IdsXmlAdapter.class)
private MultiItemList list;
// getters, setters, toString
}
class MultiItemList {
private List<String> ids;
// getters, setters, toString
}
For above XML and POJO model custom adapter could look like below:
class IdsXmlAdapter extends XmlAdapter<Object, MultiItemList> {
#Override
public MultiItemList unmarshal(Object v) {
Element element = (Element) v;
NodeList childNodes = element.getChildNodes();
List<String> ids = new ArrayList<>(childNodes.getLength());
for (int i = 0; i < childNodes.getLength(); i++) {
Node item = childNodes.item(i);
if (item.getNodeName().equals("#text")) {
continue;
}
ids.add(item.getTextContent());
}
MultiItemList multiItemList = new MultiItemList();
multiItemList.setIds(ids);
return multiItemList;
}
#Override
public Object marshal(MultiItemList v) throws Exception {
return null; // Implement if needed
}
}
Example usage:
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
public class JaxbApp {
public static void main(String[] args) throws Exception {
File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(new FileReader(xmlFile));
System.out.println(unmarshal);
}
}
prints:
Root{list=Root{ids=[value 1, value 2]}}
Notice, that you can not implement directly XmlAdapter<Element, MultiItemList> adapter because of exception:
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
org.w3c.dom.Element is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at org.w3c.dom.Element
See also:
Read XML body element having multiple values based on different requests
Since your xml has potentially an unlimited number of "id-elements", it is tricky to create matching POJO structures. You can use #XmlAnyElement and an adapter. #XmlAnyElement will read the varying elements as dom nodes and you can process them in your adapter.
For example, with the xml given below:
<parent-element>
<id-00001>value 1</id-00001>
<id-00002>value 2</id-00002>
</parent-element>
Your root could look like this:
#XmlRootElement(name = "parent-element")
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlAnyElement
#XmlJavaTypeAdapter(MyAdapter.class)
private List<String> varyingElements;
}
and your adapter like this:
public class MyAdapter extends XmlAdapter<Element, String> {
#Override
public Element marshal(String arg0) throws Exception {
// do what you must
return null;
}
#Override
public String unmarshal(Element element) throws Exception {
return element.getChildNodes().item(0).getNodeValue();
}
}
Element as in org.w3c.dom.Element
Is it possible to directly unmarshal a list of wrapped elements into a List<String> in JAXB?
For example I have a XML like:
<TEST>
<MIME_INFO>
<MIME>
<MIME_SOURCE>foo.png</MIME_SOURCE>
<MIME_PURPOSE>normal</MIME_PURPOSE>
</MIME>
<MIME>
<MIME_SOURCE>bar.png</MIME_SOURCE>
<MIME_PURPOSE>normal</MIME_PURPOSE>
</MIME>
</MIME_INFO>
</TEST>
So would it be possible to directly unmarshal this XML file into a List<String> only containing { "foo.png", "bar.png" }?
I know that you could create a class hierarchy with the correct annotations to perform this unmarshalling but i would like to have a code like:
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "TEST")
#XmlAccessorType (XmlAccessType.FIELD)
public class Info {
// -> what annotation to put here?
private List<String> infos;
public List<String> getInfos() {
return infos;
}
}
And the main file:
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class UnmarshallingJAXB {
public static void main(String[] args) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(Info.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Info info = (Info) jaxbUnmarshaller.unmarshal(new File("test.xml"));
// should output foo.png and bar.png
info.getInfos().forEach(System.out::println);
}
}
Is there any way to do that?
Apologies for answering so late, but this is the first hit on Google if search for related problems so it might help anyway.
Yes, it is possible, using
#XmlElementWrapper
in combination
#XmlElement
See this example for more detailed info.
Let's say I have class Example:
class Example{
String myField;
}
I want to unmarshal it in this way:
<Example>
<myField value="someValue" />
</Example>
Is it possible to unmarshal object in such way using JAXB XJC? ( I know about XmlPath in EclipseLink, but can't use it).
You could leverage an XmlAdapter for this use case. In that XmlAdapter you will convert a String to/from an object that has one property mapped to an XML attribute.
XmlAdapter
package forum12914382;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class MyFieldAdapter extends XmlAdapter<MyFieldAdapter.AdaptedMyField, String> {
#Override
public String unmarshal(AdaptedMyField v) throws Exception {
return v.value;
}
#Override
public AdaptedMyField marshal(String v) throws Exception {
AdaptedMyField amf = new AdaptedMyField();
amf.value = v;
return amf;
}
public static class AdaptedMyField {
#XmlAttribute
public String value;
}
}
Example
package forum12914382;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="Example")
#XmlAccessorType(XmlAccessType.FIELD)
class Example{
#XmlJavaTypeAdapter(MyFieldAdapter.class)
String myField;
}
Demo
package forum12914382;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Example.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum12914382/input.xml");
Example example = (Example) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(example, System.out);
}
}
input.xml/Output
<Example>
<myField value="someValue" />
</Example>
Related Example
JAXB Element mapping
Yes, manually add the #XmlAttribute-Annotation or generate the classes from an XSD.
I have some objects being unmarshalled from an XML file by JAXB. Is it possible to have JAXB tell me or somehow find out where in the XML file (line and column) each object comes from?
This information is available at some point, because JAXB gives it to me during schema validation errors. But I would like to have it available for validated objects too.
You could do this in JAXB by leveraging an XMLStreamReader and an Unmarshaller.Listener:
Demo
package forum383861;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.Unmarshaller.Listener;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
FileInputStream xml = new FileInputStream("src/forum383861/input.xml");
XMLStreamReader xsr = xif.createXMLStreamReader(xml);
Unmarshaller unmarshaller = jc.createUnmarshaller();
LocationListener ll = new LocationListener(xsr);
unmarshaller.setListener(ll);
Customer customer = (Customer) unmarshaller.unmarshal(xsr);
System.out.println(ll.getLocation(customer));
System.out.println(ll.getLocation(customer.getAddress()));
}
private static class LocationListener extends Listener {
private XMLStreamReader xsr;
private Map<Object, Location> locations;
public LocationListener(XMLStreamReader xsr) {
this.xsr = xsr;
this.locations = new HashMap<Object, Location>();
}
#Override
public void beforeUnmarshal(Object target, Object parent) {
locations.put(target, xsr.getLocation());
}
public Location getLocation(Object o) {
return locations.get(o);
}
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<customer>
<address/>
</customer>
Output
[row,col {unknown-source}]: [2,1]
[row,col {unknown-source}]: [3,5]
Customer
package forum383861;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Customer {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address
package forum383861;
public class Address {
}
For More Information
http://blog.bdoughan.com/2011/08/using-unmarshallerlistener-to-capture.html
I'm afraid not. JAXB builds on top of a XML parser, this one will have built up a logical representation of your XML document forgetting the original string representation of your document.
The validation step is done while your string is still read in, so your parser is able to give you an error message telling you the position of the error. JAXB will only bypass that error message. But as soon as the XML is validated and parsed, only the logical representation will exist.
I have a java bean without any annotations. And I have a class inherited from this bean with JAXB annotations.
Jersey (JAX-RS) serialize the second class to JSON. And inherited properties occur in JSON twice: with name from XmlElement annotation and with 'camel-case' name of java-bean property. Here is a code which illustrates this:
class MyBean {
private Integer beanField;
public Integer getBeanField() { return beanField; }
public void setBeanField(Integer value) { this.beanField = value; }
}
#XmlRootElement
class AnnotatedBean extends MyBean {
#Override
#XmlElement(name="field")
public Integer getBeanField() { return super.getBeanField(); }
}
}
After serialization I get the next JSON:
{
"field" : 5,
"beanField" : 5
}
(while I want it to contain only one field with name field).
I investigated JAXB marshaller implementation and found that it marshals properties from all superclasses of the given class (and that means that it's impossible to get rid of the odd beanField property in my example).
But I still hope that I could miss something. Is there a way to serialize only annotated properties?
To get only the annotated properties use XmlAccessType.NONE:
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement
class AnnotatedBean extends MyBean {
...
}
Mapping the 3rd Party Class Using Externalized Metadata
You could use the external metadata extension in EclipseLink JAXB (MOXy), I'm the tech lead. It allows you to provide metadata for 3rd party classes. For this example the metadata will look like:
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="third.party.package">
<java-types>
<java-type name="MyBean" xml-transient="true"/>
</java-types>
</xml-bindings>
To use MOXy you need to add a file named jaxb.properties in with your model classes with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
The following article has instructions on configuring MOXy to work with Jersery:
http://bdoughan.blogspot.com/2010/08/creating-restful-web-service-part-35.html
Context Resolver - Leveraging the Metadata
You would need to use a ContextResolver to get your JAXBContext to leverage the external bindings file. The metadata is specified through a property when the JAXBContext is instantiated:
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
#Provider
#Produces({"application/xml", "application/json"})
public class AnnotatedBeanContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext jaxbContext;
public PurchaseOrderContextResolver() {
try {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, new File("src/blog/bindingfile/binding.xml"));
jaxbContext = JAXBContext.newInstance(new Class[] {AnnotatedBean.class}, properties);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public JAXBContext getContext(Class<?> clazz) {
if(AnnotatedBean.class == clazz) {
return jaxbContext;
}
return null;
}
}