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);
Related
I have such code
public class Xml {
public static void main(String[] args) throws JsonProcessingException {
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
XmlMapper xmlMapper = new XmlMapper();
PlainPassword plainPassword = xmlMapper.readValue(xmlString, PlainPassword.class);
System.out.println(plainPassword.getPlainPassword());
}
#JacksonXmlRootElement(localName = "password")
public static class PlainPassword {
public String getPlainPassword() {
return this.plainPassword;
}
public void setPlainPassword(String plainPassword) {
this.plainPassword = plainPassword;
}
private String plainPassword;
}
}
It works fine, but in xmlString I can use any root tag name and my code still will work.
For example String xmlString = "<x><plainPassword>12345</plainPassword></x>"; where I use x as root element also works.
But is it possible to say xmlMapper that it could correctly deserialize only strings with "password" root element?
Unfortunately, the behavior you described is the one supported by Jackson as indicated in this Github open issue.
With JSON content and ObjectMapper you can enable the UNWRAP_ROOT_VALUE deserialization feature, and maybe it could be of help for this purpose, although I am not quite sure if this feature is or not correctly supported by XmlMapper.
One possible solution could be the implementation of a custom deserializer.
Given your PlainPassword class:
#JacksonXmlRootElement(localName = "password")
public class PlainPassword {
public String getPlainPassword() {
return this.plainPassword;
}
public void setPlainPassword(String plainPassword) {
this.plainPassword = plainPassword;
}
private String plainPassword;
}
Consider the following main method:
public static void main(String[] args) throws JsonProcessingException {
String xmlString = "<x><plainPassword>12345</plainPassword></x>";
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(new SimpleModule().setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
Class<?> beanClass = beanDesc.getBeanClass();
JacksonXmlRootElement annotation = beanClass.getAnnotation(JacksonXmlRootElement.class);
String requiredLocalName = null;
if (annotation != null) {
requiredLocalName = annotation.localName();
}
if (requiredLocalName != null) {
return new EnforceXmlElementNameDeserializer<>(deserializer, beanDesc.getBeanClass(), requiredLocalName);
}
return deserializer;
}
}));
PlainPassword plainPassword = xmlMapper.readValue(xmlString, PlainPassword.class);
System.out.println(plainPassword.getPlainPassword());
}
Where the custom deserializer looks like:
public class EnforceXmlElementNameDeserializer<T> extends StdDeserializer<T> implements ResolvableDeserializer {
private final JsonDeserializer<?> defaultDeserializer;
private final String requiredLocalName;
public EnforceXmlElementNameDeserializer(JsonDeserializer<?> defaultDeserializer, Class<?> beanClass, String requiredLocalName) {
super(beanClass);
this.defaultDeserializer = defaultDeserializer;
this.requiredLocalName = requiredLocalName;
}
#Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
String rootName = ((FromXmlParser)p).getStaxReader().getLocalName();
if (!this.requiredLocalName.equals(rootName)) {
throw new IllegalArgumentException(
String.format("Root name '%s' does not match required element name '%s'", rootName, this.requiredLocalName)
);
}
#SuppressWarnings("unchecked")
T itemObj = (T) defaultDeserializer.deserialize(p, ctxt);
return itemObj;
}
#Override public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}
You have to implement ResolvableDeserializer when modifying BeanDeserializer, otherwise deserializing throws exception.
The code is based in this excellent SO answer.
The test should raise IllegalArgumentException with the corresponding message:
Root name 'x' does not match required element name 'password'
Please, modify the exception type as appropriate.
If, instead, you use:
String xmlString = "<password><plainPassword>12345</plainPassword></password>";
in your main method, it should run without problem.
You can change your name of root class to everything, for example : #JacksonXmlRootElement(localName = "xyz") and it works.
Based on Java documentation JacksonXmlRootElement is used to define name of root element used for the root-level object when serialized (not for deserialized mapping), which normally uses name of the type (class).
I'd approach this differently. Grab an XPath implementation, select all nodes that match //plainPassword, then get a list of contents of each node.
If you need to, you can also get the name of the parent node; when in context of a found node use .. to get the parent node.
Check XPath examples and try it out for yourself. Note that your code may differ depending on language and XPath implementation.
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();
}
I try make a very 'abstract' method to convert any type of Object to an XML-String and vise versa using JAXB (javax.xml.bind.*).
I get a very strange error which I don't know the meaning of.
javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"Incident"). Expected elements are (none)
I have searched for numerous solutions on google and stackoverflow, yet their solution don't seem t help. I'm facing a dead end here.
My converter method
public Object convertXmlToObject(String string, Class c) throws ConversionException {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(c.getClass());
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
InputStream stream = new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8));
Object converted = jaxbUnmarshaller.unmarshal(stream);
return converted;
} catch (JAXBException e) {
e.printStackTrace();
throw new ConversionException("Could not convert the message to an Object", e);
}
}
where I call the method
public void generateIncidentReport(Incident incident) throws RepositoryException, ConversionException {
ConversionTool conversionTool = new Converter();
String xmlMessage = conversionTool.convertObjectToXml(incident);
//...
}
My Incident class(which has al the needed annotations)
#XmlRootElement(name = "Incident")
#XmlAccessorType(XmlAccessType.FIELD)
public class Incident {
#XmlElement(name = "shipId")
private int shipID;
#XmlElement(name = "incidentType")
private String type;
#XmlElement(name = "action")
private String action;
#XmlElement(name = "centraleID")
private String centraleID;
#XmlElement(name = "Ship")
private Ship ship;
public Incident() {
}
//getters and setters
}
and last the XML String
<Incident><incidentType>Medisch noodgeval</incidentType><shipId>1234567</shipId></Incident>
You write
JAXBContext jaxbContext = JAXBContext.newInstance(c.getClass());
with c already being a class, therefore creating a context for java.lang.Class. What you need is
JAXBContext jaxbContext = JAXBContext.newInstance(c);
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.