I have been working hard to make below program work, but there seems to be some serious flaw either in my code or with #XmlPath annotation.
XML that I am trying to parse:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<information>
<customer id="customer1">
<billingAddress id="address1">
<street id="street1">1 Billing Street</street>
<street id="street2">2 Billing Street</street>
</billingAddress>
</customer>
</information>
The Pojo that I am creating:
package parser;
import lombok.ToString;
import org.eclipse.persistence.oxm.annotations.XmlPath;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
#ToString
#XmlRootElement(name = "information")
#XmlAccessorType(XmlAccessType.FIELD)
public class Information {
#XmlPath("customer/#id")//-------------------------------------> (1)
private String customerId;
#XmlPath("customer[#id='customer1']/billingAddress/#id") //-----> (2)
private String billingAddressId;
}
How I am unmarshalling the xml:
import parser.Information;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
public class Main {
public static void main(String[] args) throws JAXBException {
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(new Class[]{Information.class}, null);
Unmarshaller jaxbMarshaller = jaxbContext.createUnmarshaller();
Information information = (Information)jaxbMarshaller.unmarshal(new File("information.xml"));
System.out.println(information);
}
}
Output for the above is:
Information(customerId=null, billingAddressId=address1)
Clearly the output is incorrect. customerId is showing null instead of customer1. However, if I comment out the line (2) in pojo class then the customerId is getting correct value. Why is it so ? Why can't I read the correct customerId value in above program ?
Removing the [#id='customer1'] from the 2nd XmlPath does solve the issue for the provided code, even though I assume the real Information entity has much more fields which you address with XmlPath.
Why don't you use some classes to reflect the XML structure ... kinda like object-oriented? It'll simplify the JAXB modelling.
Related
I have 1-2MB xml files and I would like to load them into the list of objects. I wanted to do it through JAXB because I have the appropriate XSD, only the problem appears when loading these files because it throws me errors saying that there is an unclosed tag somewhere, or something like that, when I check the file, there are no errors there. When I loaded files with a size of e.g. 40KB, there was no problem, everything was loading properly. So I understand there's a problem in jaxb when unmarshaling larger files. Is there any way to eat this? Another way of unmarshalling is unlikely to be included in the game because each xml file has a slightly different structure and objects that I have created with XSD.
The structure of xml files:
<Request>
<Header>
<Name> </Name>
<Id> </Id>
</Header>
<RequestItems>
<Request>
<Header>
<Name> </Name>
<Id> </Id>
</Header>
<ObjectName>
<City> </City>
<Street> </Street>
</ObjectName>
</Request>
</RequestItems>
</Request>
Inside the RequestItems tags there is a list of Request objects and in the Request ObjectName objects it is different depending on the file.
I do unmrashal in the simplest way and for smaller files it works fine, and the larger ones have strange syntax errors but I understand that it just cuts lines because it is too long and therefore there are syntactic errors.
JAXBContext jaxbContext = JAXBContext.newInstance (Request.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller ();
Request request = (Request) jaxbUnmarshaller.unmarshal (xmlFile);
If I have understood your question correctly then this should work. I have used Moxy which is an extension of Jaxb and Project Lombok for Getter setter.
Imagine the following is the large XML with different structures request.xml:
<request>
<person>
<name>Batman</name>
<age>29</age>
<job>IT</job>
</person>
<animal>
<name>Tommy</name>
<age>5</age>
<type>Dog</type>
</animal>
<person>
<name>Superman</name>
<age>30</age>
<job>HR</job>
</person>
</request>
Following are the related classes and interface:
import jakarta.xml.bind.annotation.XmlSeeAlso;
#XmlSeeAlso({Person.class, Animal.class})
public interface XmlSupportExtension {
Object xmlSupport();
}
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlTransient;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
#Data
#NoArgsConstructor
#AllArgsConstructor
#XmlAccessorType(XmlAccessType.FIELD)
#XmlTransient
public class Common implements Serializable {
private String name;
private String age;
}
import lombok.*;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
public class Person extends Common implements XmlSupportExtension {
private String job;
#Override
public Person xmlSupport() {
return this;
}
}
import lombok.*;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
public class Animal extends Common implements XmlSupportExtension {
private String type;
#Override
public Animal xmlSupport() {
return this;
}
}
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Arrays;
public class MainStack {
public static void main(String[] args) throws JAXBException, XMLStreamException, FileNotFoundException {
final String[] EVENT_TYPES = new String[]{"person", "animal"};
InputStream inputStream = MainStack.class.getResourceAsStream("/request.xml");
//Create an instance of XMLStreamReader to read the events one-by-one
final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
final XMLStreamReader xmlStreamReader = inputFactory.createXMLStreamReader(inputStream);
//Create an instance of JAXBContext and Unmarshaller for unmarshalling the classes to respective event
final Unmarshaller unmarshaller = JAXBContext.newInstance(Person.class, Animal.class).createUnmarshaller();
//Navigate to next and start of the XML Elements
xmlStreamReader.next();
//Read Until the end of the file and unmarshall event-by-event
while (xmlStreamReader.hasNext()) {
//Check if the initial element is one of the elements from "EVENT_TYPES"
if (xmlStreamReader.isStartElement() && Arrays.asList(EVENT_TYPES).contains(xmlStreamReader.getLocalName())) {
//Get the event type
final String eventType = xmlStreamReader.getLocalName();
Object event = null;
System.out.println(eventType);
// Based on eventType make unmarshaller call to respective event class
switch (eventType) {
case "person":
//Unmarshal the Person
event = unmarshaller.unmarshal(xmlStreamReader, Person.class).getValue();
break;
case "animal":
//Unmarshal the Animal
event = unmarshaller.unmarshal(xmlStreamReader, Animal.class).getValue();
break;
default:
//If NONE of the event type matches then do not convert and make a note
System.out.println("XML event does not match any of the required event : " + event);
break;
}
System.out.println(" After Unmarhsalling : " + event.toString());
}
//Move to the next event/element in InputStream
xmlStreamReader.next();
}
}
}
This will produce the following output:
person
After Unmarhsalling : Person(super=Common(name=Batman, age=29), job=IT)
animal
After Unmarhsalling : Animal(super=Common(name=Tommy, age=5), type=Dog)
Is it possible to directly unmarshal a list of wrapped elements into a List<String> in JAXB?
For example I have a XML like:
<TEST>
<MIME_INFO>
<MIME>
<MIME_SOURCE>foo.png</MIME_SOURCE>
<MIME_PURPOSE>normal</MIME_PURPOSE>
</MIME>
<MIME>
<MIME_SOURCE>bar.png</MIME_SOURCE>
<MIME_PURPOSE>normal</MIME_PURPOSE>
</MIME>
</MIME_INFO>
</TEST>
So would it be possible to directly unmarshal this XML file into a List<String> only containing { "foo.png", "bar.png" }?
I know that you could create a class hierarchy with the correct annotations to perform this unmarshalling but i would like to have a code like:
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "TEST")
#XmlAccessorType (XmlAccessType.FIELD)
public class Info {
// -> what annotation to put here?
private List<String> infos;
public List<String> getInfos() {
return infos;
}
}
And the main file:
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class UnmarshallingJAXB {
public static void main(String[] args) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(Info.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Info info = (Info) jaxbUnmarshaller.unmarshal(new File("test.xml"));
// should output foo.png and bar.png
info.getInfos().forEach(System.out::println);
}
}
Is there any way to do that?
Apologies for answering so late, but this is the first hit on Google if search for related problems so it might help anyway.
Yes, it is possible, using
#XmlElementWrapper
in combination
#XmlElement
See this example for more detailed info.
i'm trying to unmarshall an xml like this:
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:b2bHotelSOAP" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:getAvailableHotelResponse>
<return xsi:type="ns1:getAvailableHotelResponse">
<responseId xsi:type="xsd:integer">1</responseId>
<searchId xsi:type="xsd:string">HR-47754204</searchId>
<totalFound xsi:type="xsd:integer">20</totalFound>
<availableHotels SOAP-ENC:arrayType="ns1:hotel[20]" xsi:type="ns1:hotelArray">
...
</availableHotels>
</return>
</ns1:getAvailableHotelResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
i'm using that package-info.java to specify the namespace used into soap response:
#XmlSchema(
namespace="http://schemas.xmlsoap.org/soap/envelope/",
elementFormDefault=XmlNsForm.QUALIFIED,
xmlns={
#XmlNs(namespaceURI = "http://schemas.xmlsoap.org/soap/envelope/", prefix = "SOAP-ENV"),
#XmlNs(namespaceURI = "urn:b2bHotelSOAP", prefix = "ns1"),
#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema", prefix = "xsd"),
#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi"),
#XmlNs(namespaceURI = "http://schemas.xmlsoap.org/soap/encoding/", prefix = "SOAP-ENC")
}
)
#XmlAccessorType(XmlAccessType.FIELD)
package com.giove.viaggi.hsw.provider.hotelspro;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
what i need is to unmarshall that soap xml into this bean:
package myPackage;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name="Envelope")
public class MyBean {
#XmlPath("return/availableHotels/item")
private List<Hotel> hotels;
public List<Hotel> getHotels(){
return this.hotels==null?new ArrayList<Hotel>():this.hotels;
}
}
When i try to unmarshall it using jaxbUnmarshaller it gives me always null for the hotels attribute even if they are present into soap response.
Do i make any mistake?
Can please someone give me an help?
Thanks!
MyBean
Below is what the #XmlPath annotation on your MyBean class should look like. Some things to note:
Your domain class must be in the same package as the package-info class you want applied to it.
When you use #XmlPath you need to include each step in the path, you had left some out.
Namespace qualfifcation in the #XmlPath corresponds to the prefixes you have defined in the #XmlSchema annotation (see: http://blog.bdoughan.com/2010/09/xpath-based-mapping-geocode-example.html).
package com.giove.viaggi.hsw.provider.hotelspro;
import java.util.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name="Envelope")
public class MyBean {
#XmlPath("SOAP-ENV:Body/ns1:getAvailableHotelResponse/return/availableHotels/item")
private List<Hotel> hotels;
public List<Hotel> getHotels(){
return this.hotels==null?new ArrayList<Hotel>():this.hotels;
}
}
Note
Instead of mapping the MyBean class to the Envelope element I would map it to the local root return and pass it that element to unmarshal instead.
http://blog.bdoughan.com/2012/08/handle-middle-of-xml-document-with-jaxb.html
I have a class which contains an ArrayList(SuperClass) property. Now I wish to unmarshall the following XML file which contains different element names in that collection because these are subclasses of the Superclass. Is there a way of doing this with Moxy?
<?xml version="1.0" encoding="UTF-8"?>
<SmMessageSet xmlns:nav="urn:ccsds:recommendation:navigation:schema:ndmxml:R1.5"
xmlns="urn:ccsds:recommendation:service_management:schema:sccs:R1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:ccsds:recommendation:service_management:schema:sccs:R1.0 file:/C:/CCSDS-910.11-B-1_XML_schemas/CCSDS-910.11-B-1_XML_schemas/SmSchema-v1.0.0.xsd">
<sccsSmVersionRef>sccsSmVersionRef0</sccsSmVersionRef>
<smSource>smSource0</smSource>
<smDestination>smDestination0</smDestination>
<serviceAgreementRef>serviceAgreementRef0</serviceAgreementRef>
<smMessages>
<querySpaceCommunicationServiceProfileFailedReturn>
<messageSequenceNumber>50</messageSequenceNumber>
<messageTimestamp>2006-05-04T18:13:51.0</messageTimestamp>
<invocationMessageSequenceNumber>50</invocationMessageSequenceNumber>
<spaceCommunicationServiceProfileRef>spaceCommunicationServiceProfileRef0
</spaceCommunicationServiceProfileRef>
<qscspError>
<erroredItem>erroredItem0</erroredItem>
<diagnostic>operation timeout</diagnostic>
</qscspError>
<qscspError>
<erroredItem>erroredItem1</erroredItem>
<diagnostic>operation timeout</diagnostic>
</qscspError>
</querySpaceCommunicationServiceProfileFailedReturn>
<createUserAccountInvocation1>
<messageSequenceNumber>50</messageSequenceNumber>
<messageTimestamp>2006-05-04T18:13:51.0</messageTimestamp>
<username>createdUser</username>
<password>createdPassword</password>
<firstname>Test</firstname>
<lastname>User</lastname>
<email>test.user#host.de</email>
<role>SCHEDULING_OFFICER</role>
<superuser>0</superuser>
</createUserAccountInvocation1>
</smMessages>
</SmMessageSet>
The querySpaceCommunicationServiceProfileFailedReturn and createUserAccountInvocation are in my java object model subclasses of SmMessage base class, which is held by the SmMessageSet class in an, as above described, ArrayList of SmMessage classes.
I would also like to not change the current XML structure (i.e. create a wrapper element around the SmMessages in the XML file).
Any help would be appreciated :)
You could do the following leveraging #XmlElementWrapper and #XmlElementRef:
Java Model
SmMessageSet
You can use the #XmlElementWrapper annotation to add a grouping element around the collection (see: http://blog.bdoughan.com/2010/09/jaxb-collection-properties.html). You can also use the #XmlElementRef annotation to model the element name as the inheritance indicator (substitution groups in XML Schema, see: http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html).
package forum20745762;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="SmMessageSet")
#XmlAccessorType(XmlAccessType.FIELD)
public class SmMessageSet {
#XmlElementWrapper
#XmlElementRef
private List<SmMessage> smMessages;
}
SmMessage
JAXB/MOXy won't automatically pull in all subclasses of a class, so you can use the #XmlSeeAlso annotation to have them pulled in.
package forum20745762;
import javax.xml.bind.annotation.XmlSeeAlso;
#XmlSeeAlso({CreateUserAccountInvocation.class, QuerySpaceCommunicationServiceProfileFailedReturn1.class})
public class SmMessage {
}
CreateUserAccountInvocation
One each of the subclasses you need to annotate with #XmlRootElement. This is the element name that the #XmlElementRef annotation will match on.
package forum20745762;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class CreateUserAccountInvocation extends SmMessage {
}
QuerySpaceCommunicationServiceProfileFailedReturn1
package forum20745762;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class QuerySpaceCommunicationServiceProfileFailedReturn1 extends SmMessage {
}
package-info
We will use the package level #XmlSchema annotation to map the namespaces (see: http://blog.bdoughan.com/2010/08/jaxb-namespaces.html).
#XmlSchema(
namespace="urn:ccsds:recommendation:service_management:schema:sccs:R1.0",
elementFormDefault=XmlNsForm.QUALIFIED
)
package forum20745762;
import javax.xml.bind.annotation.*;
Demo Code
Demo
The following demo code will read the XML from your question, and then write it back out.
package forum20745762;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(SmMessageSet.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum20745762/input.xml");
SmMessageSet smMessageSet = (SmMessageSet) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(smMessageSet, System.out);
}
}
Output
The output below corresponds to just the subset of your XML document that I had mapped to:
<?xml version="1.0" encoding="UTF-8"?>
<SmMessageSet xmlns="urn:ccsds:recommendation:service_management:schema:sccs:R1.0">
<smMessages>
<querySpaceCommunicationServiceProfileFailedReturn1/>
<createUserAccountInvocation/>
</smMessages>
</SmMessageSet>
I am stuck with (I guess) a pretty trivial problem considering MOXy. Converting a class like this example (pastebin) to XML is no problem, converting it back goes without any errors too. Though, fields that are referencing another (or more) Person, will result in a null value.
Is there any way to make this work without losing relationships? My guess is this is due to a reference by ID only, since JAXB has no way of knowing other existing objects. I have tried using #XmlInverseReference, though this resulted in an infinite loop on every try.
EclipseLink MOXy's #XmlInverseReference is used to solve the infinite loop problem. I will demonstrate below with an example based on your model:
Person
package forum15821738;
import java.io.Serializable;
import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlInverseReference;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Person implements Serializable {
private String name;
#XmlInverseReference(mappedBy="children")
private Person parent;
#XmlElementWrapper
#XmlElement(name="child")
private List<Person> children;
public Person getParent() {
return parent;
}
public List<Person> getChildren() {
return children;
}
// OTHER GETTERS AND SETTERS
}
jaxb.properties
To specify 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
package forum15821738;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum15821738/input.xml");
Person person = (Person) unmarshaller.unmarshal(xml);
for(Person child : person.getChildren()) {
System.out.println(child.getParent());
}
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(person, System.out);
}
}
Input/Output
forum15821738.Person#5893a012
forum15821738.Person#5893a012
<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>Jane</name>
<children>
<child>
<name>Bobbie</name>
</child>
<child>
<name>Sue</name>
</child>
</children>
</person>
For More Information
http://blog.bdoughan.com/2010/07/jpa-entities-to-xml-bidirectional.html
http://blog.bdoughan.com/2013/03/moxys-xmlinversereference-is-now-truly.html