Unmarshalling an XML using Xpath expression and jaxb - java

I am new to JAXB and would like to know if there is a way by which I can unmarshall an XML to my response object but using xpath expressions. The issue is that I am calling a third party webservice and the response which I receive has a lot of details. I do not wish to map all the details in the XML to my response object. I just wish to map few details from the xml using which I can get using specific XPath expressions and map those to my response object. Is there an annotation which can help me achieve this?
For example consider the following response
<root>
<record>
<id>1</id>
<name>Ian</name>
<AddressDetails>
<street> M G Road </street>
</AddressDetails>
</record>
</root>
I am only intrested in retrieving the street name so I want to use xpath expression to get value of street using 'root/record/AddressDetails/street' and map it to my response object
public class Response{
// How do i map this in jaxb, I do not wish to map record,id or name elements
String street;
//getter and setters
....
}
Thanks

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JST-222) expert group.
You could use MOXy's #XmlPath extension for this use case.
Response
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name="root")
#XmlAccessorType(XmlAccessType.FIELD)
public class Response{
#XmlPath("record/AddressDetails/street/text()")
String street;
//getter and setters
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties 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
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/forum17141154/input.xml");
Response response = (Response) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<record>
<AddressDetails>
<street> M G Road </street>
</AddressDetails>
</record>
</root>
For More Information
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

If all you want is the street name, just use an XPath expression to get it as a string, and forget about JAXB - the complex JAXB machinery is not adding any value.
import javax.xml.xpath.*;
import org.xml.sax.InputSource;
public class XPathDemo {
public static void main(String[] args) throws Exception {
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
InputSource xml = new InputSource("src/forum17141154/input.xml");
String result = (String) xpath.evaluate("/root/record/AddressDetails/street", xml, XPathConstants.STRING);
System.out.println(result);
}
}

If you want to map to the middle of an XML document you could do the following:
Demo Code
You could use a StAX parser to advance to the part of the document you wish to unmarshal, and then unmarshal the XMLStreamReader from there,.
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Response.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
StreamSource xml = new StreamSource("src/forum17141154/input.xml");
XMLStreamReader xsr = xif.createXMLStreamReader(xml);
while(!(xsr.isStartElement() && xsr.getLocalName().equals("AddressDetails"))) {
xsr.next();
}
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement<Response> response = unmarshaller.unmarshal(xsr, Response.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
}
Response
The mappings on the domain object are made relative to the fragment of the XML document you are mapping to.
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Response{
String street;
//getter and setters
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<AddressDetails>
<street> M G Road </street>
</AddressDetails>
For More Information
http://blog.bdoughan.com/2012/08/handle-middle-of-xml-document-with-jaxb.html

#Xpath of Moxy works like a charm. But I got only null values in the beginning. Seems that it works a bit different with java 11 and above:
For me this post was helpful: #XmlPath has no impact during the JAXB Marshalling .
It finally worked with using these dependencies:
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.0.1</version>
</dependency>
This in de jaxb.properties file (notice 'jakarta', this is different for older versions of java: jakarta.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Put this jaxb.properties file in the exact same location as the class files
In my pom.xml also the underneath, to copy de jaxb.properties file to the exact same location. (check in you target folder if it worked, the properties file should be in the exact same folder.
org.apache.maven.plugins
maven-compiler-plugin
src/main/java
**/*.java
Check in your imports that for jakarta is used for the Unmarshaller etc.
So your import statements should be like this:
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;
Good luck!

Related

Create POJO from XML with JAXB

I am trying to convert a Pojo to XML using the JAXB library.
I need the end result to look something like this:
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<!--other stuff-->
</soap:Body>
</soap:Envelope>
I have tried a few different ways but so far I have no success, here is my latest attempt.
#XmlRootElement(name = "soap:Envelope")
public class Envelope {
private SoapBody soapBody;
public String toString() {
return "ClassPojo [SoapBody = " + soapBody + "]";
}
public SoapBody getSoapBody() {
return soapBody;
}
#XmlElement(name = "soap:Body")
public void setSoapBody(SoapBody soapBody) {
this.soapBody = soapBody;
}
}
this converts to the following result (but it's missing the XMLNS lines):
<soap:Envelope>
<soap:Body>
<!--Other stuff-->
</soap:Body>
</soap:Envelope>
I have tried adding a namespace tag to the declaration:
#XmlRootElement(name = "soap:Envelope", namespace = "soap")
but it just made the line convert to this <ns2:soap:Envelope xmlns:ns2="soap">
Edit:
OutputStream os = connection.getOutputStream();
JAXBContext jaxbContext =
JAXBContext.newInstance(MyOtherStuffObject.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(myObject, os);
os.flush();
I have tried adding a namespace tag to the declaration:
#XmlRootElement(name = "soap:Envelope", namespace = "soap")
but it just made the line convert to this
you are in one step out of what you need...
soap namespace is http://schemas.xmlsoap.org/soap/envelope/ not soap so... what if it will be like that?
#XmlRootElement(name = "soap:Envelope", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
BTW. but do you really need to create SOAP Envelope manually? actually standard package javax.xml.soap has everything to work with SOAP where you can wrap your "other stuff" into SOAP Envelope and do not care about building it by your-self?
UPDATED:
I strongly recommend to use normal frameworks when working with SOAP web services, like Apache CXF or such, instead of manipulating SOAP on that low level.
But it can be done with standard JDK classes.
Example code:
package com.foo.tests;
import java.io.ByteArrayOutputStream;
import java.util.Calendar;
import java.util.UUID;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPMessage;
import org.w3c.dom.Document;
public class TestSOAPMessage {
static MessageFactory factory;
static DocumentBuilderFactory documentFactory;
static JAXBContext jaxbCtx;
static com.foo.tests.pojo.ObjectFactory myStuffFactory = new com.foo.tests.pojo.ObjectFactory();
static {
try {
factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
documentFactory = DocumentBuilderFactory.newInstance();
jaxbCtx = JAXBContext.newInstance(com.foo.tests.pojo.MyStuffPojo.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String... args) {
try {
// prepare test MyStuff JAXB POJO
com.foo.tests.pojo.MyStuffPojo myStuff = myStuffFactory.createMyStuffPojo();
// populate myStuff Pojo
myStuff.setMyPropertyA("property A");
myStuff.setTimestamp(Calendar.getInstance());
myStuff.setMessageId(UUID.randomUUID().toString());
//---
// marshal JAXB Pojo to DOM Document
Document myStuffDoc = documentFactory.newDocumentBuilder().newDocument();
//*** myStuff has #XmlRootElement annotation
jaxbCtx.createMarshaller().marshal(myStuff, myStuffDoc);
//*** myStuff does not have #XmlRootElement annotation wrap it and use JAXBElement instead
// JAXBElement<com.foo.tests.pojo.MyStuffPojo myStuff> jaxbWrapper = myStuffFactory.createMyStuffPojo(myStuff);
// jaxbCtx.createMarshaller().marshal(jaxbWrapper, myStuffDoc);
//marshal JAXB Pojo to DOM Document
Document myStuffDoc = documentFactory.newDocumentBuilder().newDocument();
jaxbCtx.createMarshaller().marshal(jaxbWrapper, myStuffDoc);
//Create SOAPMessage
SOAPMessage myMessage = factory.createMessage();
//Optional if we'd like to set those properties...
myMessage.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true");
myMessage.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, "utf-8");
// set myStuff into SOAPBody
myMessage.getSOAPBody().addDocument(myStuffDoc);
//All done. Save changes
myMessage.saveChanges();
// Just for test: print message
ByteArrayOutputStream finalBos = new ByteArrayOutputStream();
myMessage.writeTo(finalBos);
System.out.println("my Message: \r\n" + new String(finalBos.toByteArray()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
What about adding the namespace attribute in your java bean? or
JAXB also provides the #XMLSchema annotation, which we can use to generate namespace.
You can do as below.
#javax.xml.bind.annotation.XmlSchema(namespace="http://www.springframework.org/schema/beans" , elementFormDefault=javax.xml.bind.annotation.XmlNsForm.QUALIFIED,
xmlns={ #javax.xml.bind.annotation.XmlNs(namespaceURI="http://www.w3.org/2001/XMLSchema-instance", prefix="xsi"),
#javax.xml.bind.annotation.XmlNs(namespaceURI="http://schemas.xmlsoap.org/soap/envelope/", prefix="soap")
}
)
Have also a look on this

Can't unmarshal marshalled document when default namespace used

I set up a repo which shows my problem: https://github.com/Waxolunist/stackoverflow.34392476
I try to unmarshal a simple xml document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<for:document xmlns:for="http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx">
<Export xmlns="urn:adcubum:Syrius">
<ExportInhalt/>
<ExportKopf>
<Quelle>lokal</Quelle>
</ExportKopf>
<SchemaVersion>bec811a9807a8c8da403d70b9b5e22ad</SchemaVersion>
</Export>
</for:document>
This is the document I get from following code:
Document document = new Document();
Export export = new Export();
ExportKopf exportKopf = new ExportKopf();
exportKopf.setQuelle("lokal");
export.setExportKopf(exportKopf);
ExportInhalt exportInhalt = new ExportInhalt();
export.setExportInhalt(exportInhalt);
export.setSchemaVersion("bec811a9807a8c8da403d70b9b5e22ad");
document.setExport(export);
JAXBContext jaxbContext = JAXBContext.newInstance(Document.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(document, System.out);
Document looks as follows:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "document", namespace = "http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx")
public class Document {
#XmlElement(name = "Export", namespace = "urn:adcubum:Syrius")
private vo.dom.common_service.modul_bl.syrius.Export export;
}
package-info.java
#XmlSchema(
namespace = "urn:adcubum:Syrius",
xmlns = {
#XmlNs(prefix = "for", namespaceURI = "http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx"),
#XmlNs(prefix = "", namespaceURI = "urn:adcubum:Syrius")
},
elementFormDefault = XmlNsForm.UNQUALIFIED)
When I try to unmarshal it, I don't get the data mapped:
JAXBContext jaxbContext = JAXBContext.newInstance(Document.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream is = this.getClass().getResourceAsStream("/requests/document_simple3.xml");
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader xmlsr = factory.createXMLStreamReader(is);
Document document = unmarshaller.unmarshal(xmlsr, Document.class).getValue();
ExportKopf and ExportInhalt are returning null.
Instead following xml works. The only difference is the namespace prefix:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<for:document xmlns:for="http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx">
<ns3:Export xmlns:ns3="urn:adcubum:Syrius">
<ExportInhalt/>
<ExportKopf>
<Quelle>lokal</Quelle>
</ExportKopf>
<SchemaVersion>bec811a9807a8c8da403d70b9b5e22ad</SchemaVersion>
</ns3:Export>
</for:document>
I am using eclipselink moxy.
What do I have to change, so that unmarshaling the marshaled document works.
I think it's always a good idea to see the actual XSD schema of your mapping whenever something strange like this is happening in JAXB. You could easily do that with the following code.
JAXBContext jaxbContext = JAXBContext.newInstance(Document.class);
jaxbContext.generateSchema(new SchemaOutputResolver() {
#Override
public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
StreamResult streamResult = new StreamResult(new PrintWriter(System.err) {
#Override
public void close() {
}
});
streamResult.setSystemId(suggestedFileName);
return streamResult;
}
});
That will print the schema(s) that should reflect your JAXB model (you can use another writer to write them to a file). The XSD files are usually very revealing about these kind of issues. I think the problem in your case is the #XmlSchema mapping. You should try to use elementFormDefault = XmlNsForm.QUALIFIED instead. It's always a good idea to use QUALIFIED whenever you're working with multiple namespaces in your mapping.
EDIT: while the main problem with your JAXB mapping was the wrong and/or missing value for the elementFormDefault there were other things that had to be fixed for the code in your repo to work.
the Export element in the Document was missing a namespace declaration (from your example, the Document and Export elements are parts of different namespaces)
missing elementFormDefault = XmlNsForm.QUALIFIED from the export package
wrong namespace value for your main package #XmlSchema annotation (was urn:stackoverflow:exportnamespace instead of urn:stackoverflow:documentnamespace in which the Document element should be)
wrong import for jdk_jaxb/UnmarshallerTest.java - it was importing the model.eclipselink.Document instead of model.sun.Document
EDIT: Here's the updated code https://github.com/MojoJojo/stackoverflow.34392476
Okay, here's the working version with all moxy_jaxb test passing. Because you said you are using moxy, I left out the changes for model.sun.* packages. If you understand the concept below, you should be able to fix it easily on your own.
First, I cleaned up namespace declarations in your mode.* packages. Most of the times, the declarations and bindings inside package-info.java suffice. Declaring them over and over again on package, entities, fields will add to complexity and unwanted behavior. Check out this link for details. There is no need to re-declare them on the individual Models/Entities themselves, unless there is a strong reason to do otherwise. Next, the test xml itself was a little broken. Fixed the test xml with proper prefixes wherever necessary:
First, model.eclipselink.Document.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "document")
public class Document {
#XmlElement(name = "Export", namespace="urn:adcubum:Syrius")
private Export export;
public Export getExport() {
return export;
}
public void setExport(Export export) {
this.export = export;
}
}
model.eclipselink.package-info.java:
#XmlSchema(namespace = "http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx",
elementFormDefault = XmlNsForm.QUALIFIED)
package model.eclipselink;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
Similar refactoring on model.eclipselink.export.packageinfo.java:
#XmlSchema(namespace = "urn:adcubum:Syrius",
elementFormDefault = XmlNsForm.QUALIFIED)
package model.eclipselink.export;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.Xml
And on Export.java:
package model.eclipselink.export;
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.XmlType;
import org.eclipse.persistence.oxm.annotations.XmlElementNillable;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Export")
#XmlType(name = "Export", propOrder = {
"exportInhalt",
"exportKopf",
"schemaVersion"
})
public class Export {
#XmlElement(name = "ExportKopf", required = true)
private ExportKopf exportKopf;
#XmlElement(name = "ExportInhalt", required = true)
private ExportInhalt exportInhalt;
#XmlElement(name = "SchemaVersion", required = true)
private String schemaVersion;
public ExportKopf getExportKopf() {
return exportKopf;
}
public void setExportKopf(ExportKopf exportKopf) {
this.exportKopf = exportKopf;
}
public ExportInhalt getExportInhalt() {
return exportInhalt;
}
public void setExportInhalt(ExportInhalt exportInhalt) {
this.exportInhalt = exportInhalt;
}
public String getSchemaVersion() {
return schemaVersion;
}
public void setSchemaVersion(String schemaVersion) {
this.schemaVersion = schemaVersion;
}
}
And the few tweaks to your xml files for prefixes.Here's document_prefix.xml
<?xml version="1.0" encoding="UTF-8"?>
<for:document xmlns:for="http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx">
<ns1:Export xmlns:ns1="urn:adcubum:Syrius">
<ns1:ExportKopf>
<ns1:Quelle>lokal</ns1:Quelle>
</ns1:ExportKopf>
<ns1:ExportInhalt/>
<ns1:SchemaVersion>bec811a9807a8c8da403d70b9b5e22ad</ns1:SchemaVersion>
</ns1:Export>
</for:document>
document.xml:
<?xml version="1.0" encoding="UTF-8"?>
<for:document
xmlns:for="http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx" xmlns="urn:adcubum:Syrius">
<Export>
<ExportKopf>
<Quelle>lokal</Quelle>
</ExportKopf>
<ExportInhalt />
<SchemaVersion>bec811a9807a8c8da403d70b9b5e22ad</SchemaVersion>
</Export>
</for:document>
and document_realnamespace.xml (Not sure what the purpose of this file is):
<?xml version="1.0" encoding="UTF-8"?>
<for:document xmlns:ns1="urn:adcubum:Syrius" xmlns:for="http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrenderer/forwktbx">
<ns1:Export>
<ns1:ExportKopf>
<ns1:Quelle>lokal</ns1:Quelle>
</ns1:ExportKopf>
<ns1:ExportInhalt/>
<ns1:SchemaVersion>bec811a9807a8c8da403d70b9b5e22ad</ns1:SchemaVersion>
</ns1:Export>
</for:document>
And you run mvn clean test:
Running moxy_jaxb.MarshallerTest
Context class: class org.eclipse.persistence.jaxb.JAXBContext
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://www.adcubum.com/wsdl/global/callout/syrius/modul_bl/doc/service/documentrend
erer/forwktbx" xmlns:ns0="urn:adcubum:Syrius">
<ns0:Export>
<ns0:ExportInhalt/>
<ns0:ExportKopf>
<ns0:Quelle>lokal</ns0:Quelle>
</ns0:ExportKopf>
<ns0:SchemaVersion>bec811a9807a8c8da403d70b9b5e22ad</ns0:SchemaVersion>
</ns0:Export>
</document>
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 sec
Running moxy_jaxb.UnmarshallerTest
Context class: class org.eclipse.persistence.jaxb.JAXBContext
lokal
Context class: class org.eclipse.persistence.jaxb.JAXBContext
lokal
Context class: class org.eclipse.persistence.jaxb.JAXBContext
lokal
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.033 sec

JAXB unmarshall element children as plain text [duplicate]

Consider this example -
I have a class called Report that has a field of type Message. The Message class has a field called "body" which is a string. "body" can be any string, but sometimes it contains properly formatted XML content. How can I ensure that when the "body" contains XML content, the serialization takes the form of an XML structure rather than what it gives at present?
Here is the code with the output -
Report class
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement(name = "Report")
#XmlType(propOrder = { "message"})
public class Report
{
private Message message;
public Message getMessage() { return message; }
public void setMessage(Message m) { message = m; }
}
Message class
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
#XmlType(propOrder = { "body" })
public class Message
{
private String body;
public String getBody() { return body; }
#XmlElement
public void setBody(String body) { this.body = body; }
}
Main
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
public class SerializationTest
{
public static void main(String args[]) throws Exception
{
JAXBContext jaxbContext = JAXBContext.newInstance(Report.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Report report = new Report();
Message message = new Message();
message.setBody("Sample report message.");
report.setMessage(message);
jaxbMarshaller.marshal(report, System.out);
message.setBody("<rootTag><body>All systems online.</body></rootTag>");
report.setMessage(message);
jaxbMarshaller.marshal(report, System.out);
}
}
The output is as follows -
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Report>
<message>
<body>Sample report message.</body>
</message>
</Report>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Report>
<message>
<body><rootTag><body>All systems online.</body></rootTag></body>
</message>
</Report>
As you can see in the above output, for the second instance of "body", the serialization produced
<body><rootTag><body>All systems online.</body></rootTag></body>
instead of
<body><rootTag><body>All systems online.</body></rootTag></body>
How to solve this problem?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
This use case is mapped using the #XmlAnyElement annotation and specifying a DOMHandler. There appears to be bug when doing this with the JAXB RI, but the following use case works with EclipseLink JAXB (MOXy).
BodyDomHandler
By default a JAXB impleemntation will represent unmapped content as a DOM node. You can leverage a DomHandler to an alternate representation of the DOM, In this case we will represent the DOM as a String.
import java.io.*;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.DomHandler;
import javax.xml.transform.Source;
import javax.xml.transform.stream.*;
public class BodyDomHandler implements DomHandler<String, StreamResult> {
private static final String BODY_START_TAG = "<body>";
private static final String BODY_END_TAG = "</body>";
private StringWriter xmlWriter = new StringWriter();
public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
return new StreamResult(xmlWriter);
}
public String getElement(StreamResult rt) {
String xml = rt.getWriter().toString();
int beginIndex = xml.indexOf(BODY_START_TAG) + BODY_START_TAG.length();
int endIndex = xml.indexOf(BODY_END_TAG);
return xml.substring(beginIndex, endIndex);
}
public Source marshal(String n, ValidationEventHandler errorHandler) {
try {
String xml = BODY_START_TAG + n.trim() + BODY_END_TAG;
StringReader xmlReader = new StringReader(xml);
return new StreamSource(xmlReader);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
Message
Below is how you would specify the #XmlAnyElement annotation on your Message class.
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlType;
#XmlType(propOrder = { "body" })
public class Message
{
private String body;
public String getBody() { return body; }
#XmlAnyElement(BodyDomHandler.class)
public void setBody(String body) { this.body = body; }
}
Output
Below is the output from running your SerialziationTest:
<?xml version="1.0" encoding="UTF-8"?>
<Report>
<message>
<body>Sample report message.</body>
</message>
</Report>
<?xml version="1.0" encoding="UTF-8"?>
<Report>
<message>
<body>
<rootTag>
<body>All systems online.</body>
</rootTag>
</body>
</message>
</Report>
For More Information
http://blog.bdoughan.com/2011/04/xmlanyelement-and-non-dom-properties.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
NOTE - Bug in JAXB RI
There appears to be a bug in the JAXB reference implementation, and the example code will result in a stack trace like the following:
Exception in thread "main" javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.internal.SAXException2: unable to marshal type "java.lang.String" as an element because it is missing an #XmlRootElement annotation]
at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317)
at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
at forum12428727.SerializationTest.main(SerializationTest.java:20)
Caused by: com.sun.istack.internal.SAXException2: unable to marshal type "java.lang.String" as an element because it is missing an #XmlRootElement annotation
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:216)
at com.sun.xml.internal.bind.v2.runtime.LeafBeanInfoImpl.serializeRoot(LeafBeanInfoImpl.java:126)
at com.sun.xml.internal.bind.v2.runtime.property.SingleReferenceNodeProperty.serializeBody(SingleReferenceNodeProperty.java:100)
at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:664)
at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:141)
at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561)
at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
... 3 more
If its only for Marshalling, and to ignore the < and >,
We can use the following:
marshaller.setProperty("com.sun.xml.bind.marshaller.CharacterEscapeHandler",
new CharacterEscapeHandler() {
#Override
public void escape(char[] ac, int i, int j, boolean flag,
Writer writer) throws IOException {
writer.write(ac, i, j);
}
});
3 different solutions 1), 2) 3), here below :
1) Following post is a the description of your solution Loresh :
http://anna-safronova.livejournal.com/2524.html?thread=9180
This is still missing limitations details.
With embeeded html, we need a <![CDATA block
JAXB's dependancy
com.sun.xml.bind.marshaller.CharacterEscapeHandler
Needs import jaxb-impl for compilation / and may be required for excution, e.g.
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.4</version>
Limitation : this solution is Container-specific and may not run because of class-loading policy.
2) Another similar approach is JDK's rt.jar dependancy
com.sun.xml.internal.bind.CharacterEscapeHandler
http://theopentutorials.com/tutorials/java/jaxb/jaxb-marshalling-and-unmarshalling-cdata-block/
Same limitation / dependends on target JDK, and some tweaks on Eclipse/Maven are necessary (bad alternative / My opinion)
3) Finally, the best solution was found on another post of Reg Whitton :
https://stackoverflow.com/a/12637295/560410
and this is the detailed reciepe :
http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html
Worked perfect for me !

java xml annotation get field with namespace, <aaa:bbb>value</aaa:bbb>

I'm working on a project that has no schema and I have to parsing the xml response manually.
My problem is i can't get some value using the xml annotation.
For example , the xml is like:
<?xml version='1.0' encoding='UTF-8' ?>
<autnresponse>
<action>QUERY</action>
<response>SUCCESS</response>
<responsedata>
<autn:numhits>7</autn:numhits>
</responsedata>
</autnresponse>
And the java class is :
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "autnresponse")
public class AutonomyResponse {
private String action;
private String response;
private ResponseData responsedata;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "responsedata")
public class ResponseData {
#XmlElement(name = "numhits",namespace = "autn")
private String numhits;
#XmlElement(name = "totalhits")
private String totalhits;
}
I can get the action and the response, but can't get the numhits in the responsedata,
Can anyone tell me how to handle the <autn:numhits> using annotation?
Too much Thanks !!!
Another issue is : I have multi <autn:numhits> in the responsedata....how can i get all the value in the Java code.
--> I solve this multi same tags, just set List and the annotation will automatically generate the list
The fact is autn - is only prefix, not namespace. For correct processing of the XML document, namespace must be declared.
Right namespace declaration:
<?xml version='1.0' encoding='UTF-8' ?>
<autnresponse xmlns:autn="http://namespace.here">
<action>QUERY</action>
<response>SUCCESS</response>
<responsedata>
<autn:numhits>7</autn:numhits>
</responsedata>
</autnresponse>
You also need to change the annotation:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "responsedata")
public class ResponseData {
#XmlElement(name = "numhits",namespace = "http://namespace.here")
private String numhits;
#XmlElement(name = "totalhits")
private String totalhits;
}
And finnaly advice for you. If you have a xsd scheme for this xml document, use the XJC utilit for java code generation.
http://docs.oracle.com/javaee/5/tutorial/doc/bnbah.html
JAXB and other XML processors that are capable of processing XML Schema are going to treat everything before a : as a namespace prefix. If the colon is then you can do the following.
Java Model
You need to specify that your element name contains the : character.
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class ResponseData {
#XmlElement(name = "autn:numhits")
private String numhits;
private String totalhits;
}
Demo
import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.xml.sax.XMLReader;
public class Demo {
public static void main(String[] args) throws Exception {
// Create a SAXParser that is not namespace aware
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
// Create the JAXBContext
JAXBContext jc = JAXBContext.newInstance(AutonomyResponse.class);
// Instead of Unmarshaller we will use an UnmarshallerHandler
Unmarshaller unmarshaller = jc.createUnmarshaller();
UnmarshallerHandler unmarshallerHandler = unmarshaller.getUnmarshallerHandler();
// Do a SAX parse with the UnmarshallerHanlder as the ContentHandler
xr.setContentHandler(unmarshallerHandler);
xr.parse("src/forum20062536/input.xml");
// Get the result of the unmarshal
AutonomyResponse autonomyResponse = (AutonomyResponse) unmarshallerHandler.getResult();
// Marshal the object back to XML
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(autonomyResponse, System.out);
}
}

Parsing xml data in java

i have one requirement to get the data from the xml.
String res;
the data will be in the string res as follows.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
<id>QZhx_w1eEJ</id>
<first-name>pratap</first-name>
<last-name>murukutla</last-name>
</person>
i have to get the id and the first-name and last-name from this data and has to be stored in the variables id,first-name,last-name
how to access the xml to get those details.
You could use JAXB (JSR-222) and do the following. An implementation is included in Java SE 6.
Demo
package forum10520757;
import java.io.StringReader;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader xml = new StringReader("<person><id>QZhx_w1eEJ</id><first-name>pratap</first-name><last-name>murukutla</last-name></person>");
Person person = (Person) unmarshaller.unmarshal(xml);
System.out.println(person.id);
System.out.println(person.firstName);
System.out.println(person.lastName);
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
static class Person {
String id;
#XmlElement(name="first-name")
String firstName;
#XmlElement(name="last-name")
String lastName;
}
}
Output
QZhx_w1eEJ
pratap
murukutla
You can start with:
ByteArrayInputStream inputStream =
new ByteArrayInputStream(response.getBody().getBytes("UTF-8"));
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
Document doc = builder.parse(new InputSource(inputStream));
You can see an example in http://www.java2s.com/Code/Java/XML/XMLDocumentinformationbyDOM.htm
Use a SAX or DOM parser that's built into Java. Parse the String into a DOM tree, walk the tree, get your values.
http://java.sun.com/xml/tutorial_intro.html

Categories