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);
Related
I have a large XML file that consists of many events. I would like to unmarshal them. As it's a large file, I would like to unmarshal them one by one so the whole file is not stored in memory. It works for some events but fails for some due to the fact that it's unable to map to a particular class as it's already in the next event.
Note: I am aware of the XMLEventReader but most of them have mentioned it as not very memory efficient so I am trying to use XMLStreamReader and accomplish this.
Following is the sample XML file that contains the events:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<extension>
<extension>
<c>
<name>CName</name>
<age>CAge</age>
</c>
</extension>
</extension>
<extension>
<b>
<name>BName</name>
<age>BAge</age>
</b>
</extension>
<a>
<name>AName</name>
<age>AAge</age>
</a>
<extension>
<b>
<name>BName</name>
<age>BAge</age>
</b>
</extension>
I have 3 classes corresponding to them which will be used for unmarshalling:
#XmlRootElement(name = "a")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "a", propOrder = {"name","age"})
public class A
{
private String name;
private String age;
//Getter, Setter and other constructors
}
#XmlRootElement(name = "extension")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "extension", propOrder = {"name","age"})
public class B
{
#XmlPath("b/name/text()")
private String name;
#XmlPath("b/age/text()")
private String age;
//Getter, Setter and other constructors
}
#XmlRootElement(name = "extension")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "extension", propOrder = {"name","age"})
public class C
{
#XmlPath("extension/c/name/text()")
private String name;
#XmlPath("extension/c/age/text()")
private String age;
//Getter, Setter and other constructors
}
Following is my Main class which will be used for unmarshalling:
public class Main{
private Unmarshaller unmarshaller = null;
private JAXBContext jaxbContext = null;
public void unmarshaller(InputStream xmlStream) throws IOException, XMLStreamException, JAXBException {
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
final XMLStreamReader streamReader = xmlInputFactory.createXMLStreamReader(xmlStream);
//Navigate to next and start of the XML Elements
streamReader.next();
//Read Until the end of the file
while (streamReader.hasNext()) {
//Check if the element is "extension" if so its Class B or C
if (streamReader.isStartElement() && streamReader.getLocalName().equalsIgnoreCase("extension")) {
//Check if the next element also has "extension" if so its Class C
//This is IMPORTANT step for mapping b/w Class B & C which is confusing me
streamReader.next();
if (streamReader.isStartElement() && streamReader.getLocalName().equalsIgnoreCase("extension")) {
//If there is 2 extension tag then its Class C
classSpecifier(C.class);
final C cInfo = unmarshaller.unmarshal(streamReader, C.class).getValue();
System.out.println(cInfo);
}else{
//If there is no "extension" tag then its Class B
//THIS IS WHERE ITS FAILING: IF ITS NOT CLASS C THEN IT WOULD COME HERE BUT SINCE I HAVE
//ALREADY MOVED TO NEXT ELEMENT TO CHECK IF ITS "extension" ITS UNABLE TO MAP THE WHOLE CLASS TO CLASS B
classSpecifier(B.class);
final B bInfo = unmarshaller.unmarshal(streamReader, B.class).getValue();
System.out.println(bInfo);
}
}else if(streamReader.isStartElement() && streamReader.getLocalName().equalsIgnoreCase("a")){
//If there is no "extension" then its class A
classSpecifier(A.class);
final A aInfo = unmarshaller.unmarshal(streamReader, A.class).getValue();
System.out.println(aInfo);
}
}
}
//Method to initialize the JAXBContext and Unmarshaller based on the incoming eventType
private void classSpecifier(Class eventTypeClass) throws JAXBException {
this.jaxbContext = JAXBContext.newInstance(eventTypeClass);
unmarshaller = jaxbContext.createUnmarshaller();
}
public static void main(String args[]){
try{
InputStream xmlStream = Main.class.getClassLoader().getResourceAsStream("InputEPCISEvents.xml");
unmarshaller(xmlStream);
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}
}
The problem I am facing is the differentiating between class B and C.
I need to check if the incoming localName is extension.
If it's extension then I need to check if the next element localName is also extension.
If so then it's class C if not then class B.
Since in Step-2 I have already moved to streamreader.next() and if the element is not extension then its unable to map it to class B as I have already moved to next() element and it does not have the whole class.
I am looking for some solutions where I can do the following:
If the element in the 2nd verification is not extension then go back to the previous element then assign the whole class to class B.
Assign the streamReader to tempreader when making a check so that you will be advancing in tempreader. But this also failing.
Is there a way to go back to the previous element in a stream or else how can I tackle this issue? I hope I was able to provide a complete explanation.
"Going back" in a stream implies some kind of memory, so there is no point in sticking to the most memory-efficient tool.
XMLEventReader can handle this with ease:
public class Main {
public static void main(String args[]) throws Exception {
Unmarshaller aUnmarshaller = JAXBContext.newInstance(A.class).createUnmarshaller();
Unmarshaller bUnmarshaller = JAXBContext.newInstance(B.class).createUnmarshaller();
Unmarshaller cUnmarshaller = JAXBContext.newInstance(C.class).createUnmarshaller();
try (InputStream input = Main.class.getResourceAsStream("InputEPCISEvents.xml")) {
XMLEventReader reader = XMLInputFactory.newInstance().createXMLEventReader(input);
while (reader.hasNext()) {
XMLEvent event = reader.peek();
if (event.isStartElement()) {
switch (event.asStartElement().getName().getLocalPart()) {
case "a" -> System.out.println(aUnmarshaller.unmarshal(reader));
case "b" -> System.out.println(bUnmarshaller.unmarshal(reader));
case "c" -> System.out.println(cUnmarshaller.unmarshal(reader));
}
}
reader.next();
}
}
}
#XmlAccessorType(XmlAccessType.FIELD)
static class ABC {
String name;
String age;
public String toString() {
return getClass().getSimpleName() + "{name='" + name + "', age='" + age + "}";
}
}
#XmlRootElement static class A extends ABC {}
#XmlRootElement static class B extends ABC {}
#XmlRootElement static class C extends ABC {}
}
Output:
C{name='CName', age='CAge}
B{name='BName', age='BAge}
A{name='AName', age='AAge}
B{name='BName', age='BAge}
By the way, your XML needs to be wrapped in a parent element as it contains more than one root element.
I consume a webservice and I'm receiving an XML response with a parent and a child node with the same name. The problem is that the last hierarchie has no values.
From my point of view JAXB should handle a List TestDetails.
Class Envelope:
#XmlRootElement(name="Envelope", namespace="http://schemas.xmlsoap.org/soap/envelope/")
#XmlAccessorType(XmlAccessType.FIELD)
public class Envelope {
#XmlElement(name="Body", namespace="http://schemas.xmlsoap.org/soap/envelope/")
private Body Body;
}
Class Body:
#XmlAccessorType(XmlAccessType.FIELD)
public class Body {
#XmlElement(name="GetTestlistWithConnectionsResponse", namespace="http://tempuri.org/")
private GetTestlistWithConnectionsResponse GetTestlistWithConnectionsResponse;
public Body() {}
}
Class GetTestlistWithConnectionsResponse:
#XmlAccessorType(XmlAccessType.FIELD)
public class GetTestlistWithConnectionsResponse {
public GetTestlistWithConnectionsResponse() {}
#XmlElement(name="GetTestlistWithConnectionsResult",
namespace="http://tempuri.org/")
private GetTestlistWithConnectionsResult GetTestlistWithConnectionsResult;
}
Class GetTestlistWithConnectionsResult:
#XmlAccessorType(XmlAccessType.FIELD)
public class GetTestlistWithConnectionsResult {
public GetTestlistWithConnectionsResult() {}
#XmlElement(name="TestDetails", namespace="http://schemas.datacontract.org/XXX")
private TestDetails TestDetails ;
}
Class TestDetails:
#XmlAccessorType(XmlAccessType.FIELD)
public class TestDetails{
public TestDetails() {}
#XmlElement(name="A", namespace="http://schemas.datacontract.org/XXX")
private String A;
#XmlElement(name="B", namespace="http://schemas.datacontract.org/XXX")
private String B;
#XmlElement(name="TestDetails")
private List<TestDetails> TestDetails = new ArrayList<TestDetails>();
}
XML Structure:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetTestlistWithConnectionsResponse xmlns="http://tempuri.org/">
<GetTestlistWithConnectionsResult xmlns:a="http://schemas.datacontract.org/XXX" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Error i:nil="true"/>
<a:TestDetails>
<a:TestDetails>
<a:A>A</a:A>
<a:B>B</a:B>
</a:TestDetails>
</a:TestDetails>
</GetTestlistWithConnectionsResult>
</GetTestlistWithConnectionsResponse>
</s:Body>
</s:Envelope>
Unmarshall Method:
public Envelope unmarshallFromFile(){
Envelope testDetail= null;
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Envelope.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
InputStream inStream = null;
try {
inStream = new FileInputStream(this.fileLoc);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
flightDetailsSN = (Envelope) jaxbUnmarshaller.unmarshal( inStream );
} catch (JAXBException e) {
e.printStackTrace();
}
return testDetail;
}
When I invoke my unmarshall method I receive an object with a:TestDetails Item with an empty list. I was expecting that the list contains one element with values A and B.
A and B in your XML are elements, not attributes. Try changing
#XmlAttribute
private String A;
private String B;
to
#XmlElement(name = "A")
private String a;
#XmlElement(name = "B")
private String b;
Try out this, by changing sub element or child name if you have no problem. (I think its because of same name for header and sub element.)
<a:TestDetails>
<a:TestDetail>
<a:A>A</a:A>
<a:B>B</a:B>
</a:TestDetail>
</a:TestDetails>
I'm trying to convert the XML text to a Java object, but there is a number in the prQueryStatus XML attribute. The type of the Java field is an enum. Is there a way for JAXB to choose my enum?
Strxml:
<custom prQueryStatus="1" ></custom>
faulty row:
CustAttrPrQuery custom = (CustAttrPrQuery)XmlOperations.deserializeFromXML(CustAttrPrQuery.class, strXmlCustom);
XmlOperations.deserializeFromXML():
public static Object deserializeFromXML(Class obj, String strXml) {
Object result = null;
JAXBContext jaxbContext;
try {
jaxbContext = JAXBContext.newInstance(obj);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(strXml);
result = unmarshaller.unmarshal(reader);
return result;
} catch (JAXBException e) {
return new String("-3 JAXB deSerialize Error");
}
}
CustAttrPrQuery:
#XmlAccessorType(javax.xml.bind.annotation.XmlAccessType.FIELD)
#XmlRootElement(name = CustAttrPrQuery.RootElement)
public class CustAttrPrQuery {
public final static String RootElement = "custom";
#javax.xml.bind.annotation.XmlAttribute
private PrQueryStatus prQueryStatus = PrQueryStatus.NONE;
public PrQueryStatus getPrQueryStatus() {
return prQueryStatus;
}
public void setPrQueryStatus(PrQueryStatus prQueryStatus) {
this.prQueryStatus = prQueryStatus;
}
}
enum:
public enum PrQueryStatus {
NONE,
ACIK,
TUMU
}
You need to annotate your enum type with #XmlEnum
and its constants with #XmlEnumValue,
so that JAXB will know how to map from XML attributes ("0", "1", "2") to the enum constants (NONE, ACIK, TUMU):
#XmlEnum
public enum PrQueryStatus {
#XmlEnumValue("0") NONE,
#XmlEnumValue("1") ACIK,
#XmlEnumValue("2") TUMU
}
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();