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)
Related
I am having hard time unmarshalling the following XML using JAXB. This is the xml which is having only one field with an attribute. I have referred many tutorials where they only do an example of reading a string in fields which s not helpful in my case.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CircuitImpactConfigs id="1">
<Objects>
<Object type="1" impactAnalysisDataBuilderClassName="tttttttt"/>
<Object type="2" impactAnalysisDataBuilderClassName="yyyyyyyyy"/>
<Object type="3" impactAnalysisDataBuilderClassName="eeeeeee" />
<Object type="4" impactAnalysisDataBuilderClassName="iiiiiiiii"/>
<Object type="5" impactAnalysisDataBuilderClassName="rrrrrrrrrr"/>
<Object type="6" impactAnalysisDataBuilderClassName="zzzzzz"/>
<Object type="7" impactAnalysisDataBuilderClassName="qqqqqqqqqqq"/>
</Objects>
<ForceSwitchMode name="FORCE_SWITCHED" />
</CircuitImpactConfigs>
Based on what i learnt from tutorial
My Classes are CircuitImpactConfigs.java
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "CircuitImpactConfigs")
#XmlAccessorType(XmlAccessType.FIELD)
public class CircuitImpactConfigs {
private ForceSwitchMode ForceSwitchMode;
List<Obj> Object;
#XmlElement
public List<Obj> getObject() {
return Object;
}
public void setObject(List<Obj> object) {
Object = object;
}
#XmlElement
public ForceSwitchMode getForceSwitchMode() {
return ForceSwitchMode;
}
public void setForceSwitchMode(ForceSwitchMode forceSwitchMode) {
ForceSwitchMode = forceSwitchMode;
}
}
and
import javax.xml.bind.annotation.XmlAttribute;
public class ForceSwitchMode {
private String name;
#XmlAttribute
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and
import javax.xml.bind.annotation.XmlAttribute;
public class Obj {
String type;
String impactAnalysisDataBuilderClassName;
#XmlAttribute
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#XmlAttribute
public String getImpactAnalysisDataBuilderClassName() {
return impactAnalysisDataBuilderClassName;
}
public void setImpactAnalysisDataBuilderClassName(String impactAnalysisDataBuilderClassName) {
this.impactAnalysisDataBuilderClassName = impactAnalysisDataBuilderClassName;
}
}
I am getting null list when doing the unmarshalling. This is the class where i create the JAXBcontext object and create unmarshalling object.
import java.io.File;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class CircuitImpactConfUnmarshaller {
public static void main(String[] args) throws JAXBException {
File file = new File("CircuitImpact.xml");
System.out.println(file.exists());
JAXBContext jaxbContext = JAXBContext.newInstance(CircuitImpactConfigs.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
CircuitImpactConfigs que = (CircuitImpactConfigs) jaxbUnmarshaller.unmarshal(file);
System.out.println(que.getForceSwitchMode().getName());
List<Obj> list = que.getObject();
System.out.println(list);
}
}
the last print statement is giving null. I understand i am doing something wrong in the class Obj
JAXB uses implicit naming conventions and explicit annotations to define a mapping between a XML and a Java structure.
Either element and attribute names in the XML match field names in Java (match by naming convention) or you need to use annotations to establish the mapping.
The Java list CircuitImpactConfigs.Object is not getting filled because the mapping failed, since the corresponding element in the XML is named Objects.
You can now either rename CircuitImpactConfigs.Object to CircuitImpactConfigs.Objects or use the name parameter of a JAXB annotation to define the corresponding name:
#XmlElement(name="Objects")
public List<Obj> getObject() {
EDIT: As you indicate in your comments there are still other mapping issues with your code. I would suggest that you adapt another approach:
Create a CircuitImpactConfigs object with all subobjects filled.
Marhsall that object to a XML file.
Check that the XML is in the expected format. If not, tweak the mapping.
Following this approach you can be sure that a XML file in the desired format can be unmarshalled to your Java structure. The code to marshall is
CircuitImpactConfigs que = ...
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(que, System.out);
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
I'm trying to unmarshall some dozer mapping files in order to provide a mapping availability library to a number of applications. But i cant get the JaxB annotations to work correctly. Either the list of mappings us unmarshalled as null or empty
From the mapping file, all i'm interested in is.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<mappings>
<mapping>
<class-a>package.MySourceClass</class-a>
<class-b>other.package.DestinationClass</class-b>
</mapping>
</mappings>
I have a mappings class
#XmlRootElement(name="mappings")
#XmlAccessorType(XmlAccessType.FIELD)
public class Mappings {
#XmlElementWrapper(name="mappings")
private List<Mapping> mappingEntries = null;
//Getters and setters omitted
and A mapping class
#XmlRootElement(name="mapping")
#XmlAccessorType(XmlAccessType.FIELD)
public class Mapping {
#XmlElement(name ="class-a")
private String classA;
#XmlElement(name = "class-b")
private String classB;
I've tried numerous combinations of the annotations and I cant figure out what i'm doing wrong.
Can someone point me in the right direction.
You could do the following:
Mappings
package forum11193953;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="mappings") // Match the root element "mappings"
#XmlAccessorType(XmlAccessType.FIELD)
public class Mappings {
#XmlElement(name="mapping") // There will be a "mapping" element for each item.
private List<Mapping> mappingEntries = null;
}
Mapping
package forum11193953;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Mapping {
#XmlElement(name ="class-a")
private String classA;
#XmlElement(name = "class-b")
private String classB;
}
Demo
package forum11193953;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Mappings.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml= new File("src/forum11193953/input.xml");
Mappings mappings = (Mappings) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(mappings, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappings>
<mapping>
<class-a>package.MySourceClass</class-a>
<class-b>other.package.DestinationClass</class-b>
</mapping>
</mappings>
Try the JMapper Framework: http://code.google.com/p/jmapper-framework/
With JMapper you have all the advantages of dynamic mapping with the performance of static code and much more.
I have some objects being unmarshalled from an XML file by JAXB. Is it possible to have JAXB tell me or somehow find out where in the XML file (line and column) each object comes from?
This information is available at some point, because JAXB gives it to me during schema validation errors. But I would like to have it available for validated objects too.
You could do this in JAXB by leveraging an XMLStreamReader and an Unmarshaller.Listener:
Demo
package forum383861;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.Unmarshaller.Listener;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
FileInputStream xml = new FileInputStream("src/forum383861/input.xml");
XMLStreamReader xsr = xif.createXMLStreamReader(xml);
Unmarshaller unmarshaller = jc.createUnmarshaller();
LocationListener ll = new LocationListener(xsr);
unmarshaller.setListener(ll);
Customer customer = (Customer) unmarshaller.unmarshal(xsr);
System.out.println(ll.getLocation(customer));
System.out.println(ll.getLocation(customer.getAddress()));
}
private static class LocationListener extends Listener {
private XMLStreamReader xsr;
private Map<Object, Location> locations;
public LocationListener(XMLStreamReader xsr) {
this.xsr = xsr;
this.locations = new HashMap<Object, Location>();
}
#Override
public void beforeUnmarshal(Object target, Object parent) {
locations.put(target, xsr.getLocation());
}
public Location getLocation(Object o) {
return locations.get(o);
}
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<customer>
<address/>
</customer>
Output
[row,col {unknown-source}]: [2,1]
[row,col {unknown-source}]: [3,5]
Customer
package forum383861;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Customer {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address
package forum383861;
public class Address {
}
For More Information
http://blog.bdoughan.com/2011/08/using-unmarshallerlistener-to-capture.html
I'm afraid not. JAXB builds on top of a XML parser, this one will have built up a logical representation of your XML document forgetting the original string representation of your document.
The validation step is done while your string is still read in, so your parser is able to give you an error message telling you the position of the error. JAXB will only bypass that error message. But as soon as the XML is validated and parsed, only the logical representation will exist.
I would like to add a processing instruction whenever a collection/array property is serialized to get something like
<alice>
<? array bob ?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Is this possible with JAXB? Or at least with some specific JAXB implementation?
You could leverage an XMLStreamWriter and an XmlAdapter to do this:
BobAdapter
Things to note about the XmlAdapter:
It's stateful and references an XMLStreamWriter. We will later leverage JAXB's ability to set a stateful XmlAdapter on a Marshaller.
It converts from a List<String> to a List<String>, we're just using an XmlAdapter here to get an event.
The marshal method is where we call writeProcessingInstruction on the XMLStreamWriter:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.stream.XMLStreamWriter;
public class BobAdapter extends XmlAdapter<List<String>, List<String>> {
private boolean first = true;
private XMLStreamWriter xmlStreamWriter;
public BobAdapter() {
}
public BobAdapter(XMLStreamWriter xmlStreamWriter) {
this();
this.xmlStreamWriter = xmlStreamWriter;
}
#Override
public List<String> marshal(List<String> stringList) throws Exception {
if(first) {
xmlStreamWriter.writeProcessingInstruction("array", "bob");
first = false;
}
return stringList;
}
#Override
public List<String> unmarshal(List<String> stringList) throws Exception {
return stringList;
}
}
Alice
The #XmlJavaTypeAdapter annotation is used to link the XmlAdapter with the field/property:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Alice {
private List<String> bob;
#XmlJavaTypeAdapter(BobAdapter.class)
public List<String> getBob() {
return bob;
}
public void setBob(List<String> bob) {
this.bob = bob;
}
}
Demo
package forum6931520;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Alice.class);
File xml = new File("src/forum6931520/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Alice alice = (Alice) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
XMLOutputFactory xof = XMLOutputFactory.newFactory();
XMLStreamWriter xmlStreamWriter = xof.createXMLStreamWriter(System.out);
marshaller.setAdapter(new BobAdapter(xmlStreamWriter));
marshaller.marshal(alice, xmlStreamWriter);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<alice>
<?array bob?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Output
<?xml version='1.0' encoding='UTF-8'?><alice><?array bob?><bob>edgar</bob><bob>david</bob></alice>
Note
This example needs to be run with EclipseLink JAXB (MOXy) as the JAXB RI will throw the following exception (I'm the MOXy lead):
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
java.util.List is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
java.util.List does not have a no-arg default constructor.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
For More Information
http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
UPDATE
I have entered an enhancement request (https://bugs.eclipse.org/354286) to add native support for processing instructions. This would eventually allow you to specify the following on your property:
#XmlProcessingInstruction(target="array", value="bob")
public List<String> getBob() {
return bob;
}