Using a Jaxb unmarshaller, I cannot achieve to load a XML content as a string.
Here is a running example of what I am trying to achieve.
public static class BarAdapter extends XmlAdapter<Object, String> {
#Override
public Object marshal(String v) throws Exception {
return null;
}
#Override
public String unmarshal(Object v) throws Exception {
return null; // what to do with the ElementNsImpl??
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class Container implements Serializable {
#XmlAnyElement
#XmlJavaTypeAdapter(BarAdapter.class)
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Container.class);
String xml = "<foo><bar><name>Barry</name><surName>White</surName></bar></foo>";
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream is = new ByteArrayInputStream(xml.getBytes());
JAXBElement<Container> barWrapperElement = unmarshaller.unmarshal(new StreamSource(is), Container.class);
Container container = barWrapperElement.getValue();
System.out.println(container.getBar());
}
I would like to have into bar : <bar><name>Barry</name><surName>White</surName></bar>
I've tried to use the #XmlAnyElement but it gives a ElementNsImpl and I need a String.
If you have a better solution, please post. I am feeling that I am not doing it right.
I can transform ElementNsImpl into a String.
So :
#Override
public String unmarshal(Object obj) throws Exception {
// Be careful, affect a new string writer has to be done within your
// unmarshaller. If you do this here, it will partially unmarshall.
// I can povide more code upon request
StringWriter stringWriter = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
Document document = ((Element) obj).getOwnerDocument();
transformer .transform(new DOMSource(document), new StreamResult(stringWriter));
return stringWriter.toString();
}
Related
I'm currently working on a project requesting web services using CXF framework.
For some reasons, I started to receive invalid XML SOAP(missing element with ID referenced from ) response that results in throwing exception during unmarshalling to POJO instance.
Example:
XML excerpt where attribute ref references to an element with identifier Person1 that does not exist in XML.
<ext:Applicant s:ref="Person1"/>
where ref is IDREF type in XSD schema
<attribute name="ref" type="IDREF"/>
Exception thrown by JAXB
javax.xml.ws.soap.SOAPFaultException: Unmarshalling Error: Undefined ID "Person1". ] with root cause
javax.xml.bind.UnmarshalException: Undefined ID "Person1".
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:744) ~[jaxb-impl-2.3.1.jar!/:2.3.1]
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.errorUnresolvedIDREF(UnmarshallingContext.java:795) ~[jaxb-impl-2.3.1.jar!/:2.3.1]
at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$IDREFTransducedAccessorImpl$1.run(TransducedAccessor.java:330) ~[jaxb-impl-2.3.1.jar!/:2.3.1]
at...
Is there a way how to make JAXB ignore missing references and unmarshall incoming XML response without throwing exception?
The XML object is not provided in the request to discuss more specifically. There is a handy workaround to create CustomAdapter for JAX-B objects to determine how to marshal and unmarshal the object.
As an example, the below CustomAdapter could be implemeted:
public static class CustomAdapter extends XmlAdapter<Object, Person1> {
#Override
public Object marshal(Person1 value) {
// your implementation to marshal
}
#Override
public Person1 unmarshal(Object value) {
// your implementation to unmarshal
}
}
XmlAdapter is in javax.xml.bind.annotation.adapters.XmlAdapter package.
If you are using Person1 as a field in other objects (composition), you could make it annotated with #XmlJavaTypeAdapter as below:
#XmlJavaTypeAdapter(CustomAdapter.class)
private Person1 person1;
Please let me know if it helps out.
Possible solution could be creating a decorator for XMLEventReader, which will filter out the IDREF attributes (or other attributes if needed):
public class IdRefFilteringReader implements XMLEventReader {
/**
* QName of the attribute to be removed
*/
private final static QName QNAME = new QName("http://www.w3.org/2001/XMLSchema", "ref");
/**
* Delegate XML event reader
*/
private final XMLEventReader delegate;
/**
* XML event factory
*/
private final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
/**
* Constructor injects delegate
*/
public IdRefFilteringReader(XMLEventReader delegate) {
this.delegate = delegate;
}
/**
* Remove attributes with matching QName
*/
#Override
public XMLEvent nextEvent() throws XMLStreamException {
XMLEvent event = delegate.nextEvent();
if (event.isStartElement()) {
StartElement startElement = event.asStartElement();
Attribute attr = startElement.getAttributeByName(QNAME);
// if attribute is present, create new XMLEvent with same
// prefix, namespace, name and other attributes except one
// which should be removed
if(attr != null) {
String prefix = startElement.getName().getPrefix();
String uri = startElement.getName().getNamespaceURI();
String localname = startElement.getName().getLocalPart();
List<Attribute> attributes = new ArrayList<>();
startElement.getAttributes().forEachRemaining(a -> {
if(!a.getName().equals(attr.getName())) {
attributes.add(a);
}
});
return eventFactory.createStartElement(
prefix,
uri,
localname,
attributes.iterator(),
startElement.getNamespaces()
);
}
}
return event;
}
#Override
public boolean hasNext() {
return delegate.hasNext();
}
#Override
public XMLEvent peek() throws XMLStreamException {
return delegate.peek();
}
#Override
public String getElementText() throws XMLStreamException {
return delegate.getElementText();
}
#Override
public XMLEvent nextTag() throws XMLStreamException {
return delegate.nextTag();
}
#Override
public Object getProperty(String name) throws IllegalArgumentException {
return delegate.getProperty(name);
}
#Override
public void close() throws XMLStreamException {
delegate.close();
}
#Override
public Object next() {
return delegate.next();
}
}
To use this reader it's needed to pass it to unmarshaller instance, e.g.:
// create unmarshaller
JAXBContext ctx = JAXBContext.newInstance(Applicant.class);
Unmarshaller unmarshaller = ctx.createUnmarshaller();
// create regular XML event reader and filtered XML event reader
XMLInputFactory xif = XMLInputFactory.newInstance();
XMLEventReader reader = xif.createXMLEventReader(new StreamSource(new StringReader(xml)));
XMLEventReader filteringReader = new IdRefFilteringReader(reader);
// unmarshall XML using filtering reader
Applicant applicant = unmarshaller.unmarshal(filteringReader, Applicant.class);
I've got this POJO, encapsulating a dynamic, non-nested element of an Atom entry:
public class SimpleElement {
private Namespace namespace;
private String tagName;
private String value;
private Collection<Attribute> attributes;
/* getters/setters/... */
And for completeness, Attribute
public class Attribute {
private String name;
private String value;
private Namespace namespace;
/* getters/setters/... */
And Namespace:
public class Namespace {
private final String uri;
private final String prefix;
/* getters/setters/... */
SimpleElementAdapter serializes a SimpleElement into its org.w3c.dom.Element counterpart.
The only problem with this approach is that namespaces always end up at element level, never at document root.
Is there a way to dynamically declare namespaces at document root?
MY RECOMMENDATION
My recommendation is to let the JAXB implementation write the namespace declarations as it sees fit. As long as the elements are properly namespace qualified it does not really matter where the namespace declarations occur.
If you ignore my recommendation, below is an approach you can use.
ORIGINAL ANSWER
Specify the Namespaces to Include on Root Element
You can use the NamespacePrefixMapper extension to add extra namespace declarations to the root element (see: https://jaxb.java.net/nonav/2.2.11/docs/ch05.html#prefixmapper). You will need to derive from your own object model what namespaces should be declared at the root.
Note: NamespacePrefixMapper is in the com.sun.xml.bind.marshaller package. This means you will need the JAXB refereince implementation jar on your classpath (see: https://jaxb.java.net/).
import com.sun.xml.bind.marshaller.*;
public class MyNamespacePrefixMapper extends NamespacePrefixMapper {
#Override
public String getPreferredPrefix(String arg0, String arg1, boolean arg2) {
return null;
}
#Override
public String[] getPreDeclaredNamespaceUris2() {
return new String[] {"ns1", "http://www.example.com/FOO", "ns2", "http://www.example.com/BAR"};
}
}
Specify the NamespacePrefixMapper on the Marshaller
The com.sun.xml.bind.namespacePrefixMapper property is used to specify the NamespacePrefixMapper on the Marshaller.
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());
Demo Code
Java Model (Foo)
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Foo {
private Object object;
#XmlAnyElement
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
Demo
import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.Element;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Foo foo = new Foo();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://www.example.com/FOO", "ns1:foo");
foo.setObject(element);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());
marshaller.marshal(foo, System.out);
}
}
Output
Below is sample output that will be produced:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:ns1="http://www.example.com/FOO" xmlns:ns2="http://www.example.com/BAR">
<ns1:foo/>
</foo>
UPDATE
Clear answer, thanks. However, I need access to the NSMapper from
SimpleElementAdapter. What do you suggest? The only way I see right
now is making the NSMapper a mutable singleton so that
SimpleElementAdapter can add namespaces if needed.
I forgot about your XmlAdapter.
Java Model
Below is a more complicated iteration of the model, where instead of Foo holding an instance of a DOM element, it holds and instance of Bar that gets adapted into an instance of a DOM element.
Foo
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Foo {
private Bar bar;
#XmlAnyElement
#XmlJavaTypeAdapter(BarAdapter.class)
public Bar getBar() {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
}
Bar
public class Bar {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
BarAdapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class BarAdapter extends XmlAdapter<Object, Bar>{
#Override
public Object marshal(Bar bar) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://www.example.com/BAR", "ns:bar");
element.setTextContent(bar.getValue());
return element;
}
#Override
public Bar unmarshal(Object arg0) throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Grab Namespace Declarations
Since your object model does not hold the DOM elements directly you can't traverse it to get the namespace declarations. Instead we could do a marshal to a ContentHandler to collect them. Below are the reasons for marshalling to a ContentHandler:
It gives us an easy event which we can use to collection the namespace declarations.
It doesn't actually produce anything so it is the lightest marshal target we can use.
NsContentHandler contentHandler = new NsContentHandler();
marshaller.marshal(foo, contentHandler);
NsContentHandler
The implementation of ContentHandler will look something like:
import java.util.*;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class NsContentHandler extends DefaultHandler {
private Map<String, String> namespaces = new TreeMap<String, String>();
#Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
if(!namespaces.containsKey(prefix)) {
namespaces.put(prefix, uri);
}
}
public Map<String, String> getNamespaces() {
return namespaces;
}
}
Specify the Namespaces to Include on Root Element
The implementation of MyNamespacePrefixMapper changes a little to use the namrespaces captured from our ContentHandler.
import java.util.Map;
import java.util.Map.Entry;
import com.sun.xml.bind.marshaller.*;
public class MyNamespacePrefixMapper extends NamespacePrefixMapper {
private String[] namespaces;
public MyNamespacePrefixMapper(Map<String, String> namespaces) {
this.namespaces = new String[namespaces.size() * 2];
int index = 0;
for(Entry<String, String> entry : namespaces.entrySet()) {
this.namespaces[index++] = entry.getKey();
this.namespaces[index++] = entry.getValue();
}
}
#Override
public String getPreferredPrefix(String arg0, String arg1, boolean arg2) {
return null;
}
#Override
public String[] getPreDeclaredNamespaceUris2() {
return namespaces;
}
}
Demo Code
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Bar bar = new Bar();
bar.setValue("Hello World");
Foo foo = new Foo();
foo.setBar(bar);
Marshaller marshaller = jc.createMarshaller();
// Marshal First Time to Get Namespace Declarations
NsContentHandler contentHandler = new NsContentHandler();
marshaller.marshal(foo, contentHandler);
// Marshal Second Time for Real
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper(contentHandler.getNamespaces()));
marshaller.marshal(foo, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:ns="http://www.example.com/BAR">
<ns:bar>Hello World</ns:bar>
</foo>
I have a simpe XML that I want to unmarshall into a model class. I have annotated the class with JAXB annotations for defining the access type (FIELD):
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
#XmlAccessorType(XmlAccessType.FIELD)
public class DtoTest {
private String name;
public DtoTest() {}
public DtoTest(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "DtoTest [name=" + name + "]";
}
}
This is my main class where I run an unmarshal method against a simple XML saved in a String variable:
public class Test {
public static void main(String[] args) throws Exception {
Object obj = new DtoTest();
String testXML = "<dtoTest><name>example</name></dtoTest>";
obj = unmarshal(obj, testXML);
System.out.println(obj);
}
/* This is a generic unmarshall method which I've already used with success with other XML*/
public static <T> T unmarshal(T obj, String xml) throws Exception {
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));
Class<? extends Object> type = obj.getClass();
JAXBContext jc = JAXBContext.newInstance(type);
Unmarshaller unmarshaller = jc.createUnmarshaller();
obj = (T)unmarshaller.unmarshal(xsr, type).getValue();
xsr.close();
return obj;
}
}
Whenever I run the code I get the same output:
DtoTest [name=null]
I don't understand what I'm doing wrong.
I've just run your code on jdk1.7.0_67 and it works.
DtoTest [name=example]
Maybe you have some problem with included libraries? I've run it with just plain java.
What you have in your question runs perfectly fine for me. One optimization you could make to it is to create an StreamSource instead of an XMLStreamReader.
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;
public class Test {
public static void main(String[] args) throws Exception {
Object obj = new DtoTest();
String testXML = "<dtoTest><name>example</name></dtoTest>";
obj = unmarshal(obj, testXML);
System.out.println(obj);
}
public static <T> T unmarshal(T obj, String xml) throws Exception {
StreamSource source = new StreamSource(new StringReader(xml));
Class<? extends Object> type = obj.getClass();
JAXBContext jc = JAXBContext.newInstance(type);
Unmarshaller unmarshaller = jc.createUnmarshaller();
obj = (T)unmarshaller.unmarshal(source, type).getValue();
return obj;
}
}
Debugging Tip
When unmarshalling is not working as expected, populate your JAXB model and marshal it to XML to see what the expected XML looks like.
I have a Maven & Spring based Java web application
In src/main/resources, I have one XML file.
sourceconfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<sourceConfig area="Defects">
<adapterObject>jAdapter</adapterObject>
<resultObject>jsonObject</resultObject>
</sourceConfig>
In I have a POJO for this SourceConfig.java
#XmlRootElement
public class SourceConfig {
String area;
String adapterObject;
String resultObject;
public String getArea() {
return area;
}
#XmlAttribute
public void setArea(String area) {
this.area = area;
}
public String getAdapterObject() {
return adapterObject;
}
#XmlElement
public void setAdapterObject(String adapterObject) {
this.adapterObject = adapterObject;
}
public String getResultObject() {
return resultObject;
}
#XmlElement
public void setResultObject(String resultObject) {
this.resultObject = resultObject;
}
}
I am able to parse the xml to object.
public class SourceAdapterConfig {
public SourceConfig getConfigObject() throws JAXBException, IOException {
JAXBContext jaxbContext = JAXBContext.newInstance(SourceConfig.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Resource resource=new ClassPathResource("sourceconfig.xml");
File file=resource.getFile();
SourceConfig sourceConfig = (SourceConfig) jaxbUnmarshaller.unmarshal(file);
return sourceConfig;
}
}
It is working fine.
But all are String. Some I want as object. For example, In XML I have mentioned <resultObject>jsonObject</resultObject>
I have a class com.myapp.config.JsonObject.java
So, instead of <resultObject>jsonObject</resultObject> If I mention class like this
<resultObject class="com.myapp.config.JsonObject">jsonObject</resultObject>
or some other way to mention class, I should be able to get a JsonObject object in my SourceConfig How can I do that?
use java reflection
Class theClass = Class.forName("com.example.Test");
Test testObject = (Test)theClass.newInstance();
This will create an instance of com.example.Test.
In your context,
public class SourceAdapterConfig {
private SourceConfig config;
private SourceConfig getConfigObject() throws JAXBException, IOException {
JAXBContext jaxbContext = JAXBContext.newInstance(SourceConfig.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Resource resource=new ClassPathResource("sourceconfig.xml");
File file=resource.getFile();
SourceConfig sourceConfig = (SourceConfig) jaxbUnmarshaller.unmarshal(file);
return sourceConfig;
}
public SourceAdapterConfig(){
config = getConfigObject();
}
public Object getAdapterObject(){
String adapterClassName = config.getAdapterObject();
Class theClass = Class.forName(adapterClassName);
return theClass.newInstance();
}
}
Usage:
SourceAdapterConfig config = new SourceAdapterConfig();
Object adapterObject = config.getAdapterObject();
in my program I need to store object in XML. But I dont want all properties to be serialized to xml. How should I do that ?
public class Car implements ICar{
//all variables has their own setters and getters
private String manufacturer;
private String plate;
private DateTime dateOfManufacture;
private int mileage;
private int ownerId;
private Owner owner; // will not be serialized to xml
.....
}
//code for serialize to xml
public static String serialize(Object obj)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XMLEncoder encoder = new XMLEncoder(baos);
encoder.writeObject(obj);
encoder.close();
return baos.toString();
}
Check out this link. Here's an updated example.
BeanInfo info = Introspector.getBeanInfo(Car.class);
PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; ++i) {
PropertyDescriptor pd = propertyDescriptors[i];
if (pd.getName().equals("dateOfManufacture")) {
pd.setValue("transient", Boolean.TRUE);
}
}
I choose to use JAXB serialization with annotations. It was the best and easiest option. Thank all of you for your help.
public static String serialize(Object obj) throws JAXBException
{
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller m = context.createMarshaller();
m.marshal(obj, writer);
return writer.toString();
}
public static Object deserialize(String xml, Object obj) throws JAXBException
{
StringBuffer xmlStr = new StringBuffer(xml);
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Unmarshaller um = context.createUnmarshaller();
return um.unmarshal(new StreamSource(new StringReader(xmlStr.toString())));
}