Parse XML into DOM tree with custom object implementations in Java - java

I want to parse an XML document into a DOM tree in Java such that certain objects (e.g. instances of org.w3c.dom.Node or org.w3c.dom.Element) in the tree can be downcast to instances of classes that I have created, while minimizing the amount of XML-related code that I need to (re)implement. As a (very simple) example, if I have an XML element like:
<Vector size="5">
1.0 -1.0 3.0 -2.73e2
</Vector>
I would like to customize the parser to instantiate the following for it:
public class Vector extends /* some parser class */ {
private double[] elements;
/* constructors; etc.*/
public double dotProduct(Vector v) {
/* implementation */
}
}
such that I can pass instances of Vector created by the parser to, for example, javax.xml.xpath objects' methods and have them work correctly. What is the quickest way to achieve this? Is it possible with Java SE alone, or are third-party libraries (e.g. Xerces) necessary?

I'm not sure what your requirements are, but assuming you're in control of what the XML looks like, what I would use is XStream. It will allow you to skip all the DOM manipulation completely.
Now from their 2 minute tutorial, it may not seem like it's built for this use case, but it actually is. You create your java classes first, make sure they generate the XML the way you want it to look, and then use it to read your already existing XML back into your program as XStream objects. It's a very pleasant library to use.

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
The Binder mechanism in JAXB may be what you are looking for. It doesn't allow a DOM node to be cast to a domain object, but it does maintain a link between a domain object and its corresponding DOM node.
Note: The following code ran clean when using the MOXy as the JAXB provider, but threw an exception when using the impl of JAXB included in the version of the JDK I happen to be running.
JAVA MODEL
I will use the following domain model for this example.
Customer
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Customer {
private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
#XmlElementWrapper
#XmlElement(name="phoneNumber")
public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
}
PhoneNumber
import javax.xml.bind.annotation.*;
public class PhoneNumber {
private String type;
private String number;
#XmlAttribute
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#XmlValue
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
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
XML (input.xml)
<?xml version="1.0" encoding="UTF-8"?>
<customer>
<phoneNumbers>
<phoneNumber type="work">555-1111</phoneNumber>
<phoneNumber type="home">555-2222</phoneNumber>
</phoneNumbers>
</customer>
DEMO CODE
In the demo code below I will do the following:
Use XPath to find a child element, then use the Binder to find the corresponding domain object.
Update the domain object and use the Binder to apply the change to the DOM.
Update the DOM and use the Binder to apply the change to the domain object.
Demo
import javax.xml.bind.Binder;
import javax.xml.bind.JAXBContext;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
public class Demo {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse("src/forum16599580/input.xml");
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Binder<Node> binder = jc.createBinder();
binder.unmarshal(document);
// Use Node to Get Object
Node phoneNumberElement = (Node) xpath.evaluate("/customer/phoneNumbers/phoneNumber[2]", document, XPathConstants.NODE);
PhoneNumber phoneNumber = (PhoneNumber) binder.getJAXBNode(phoneNumberElement);
// Modify Object to Update DOM
phoneNumber.setNumber("555-2OBJ");
binder.updateXML(phoneNumber);
System.out.println(xpath.evaluate("/customer/phoneNumbers/phoneNumber[2]", document, XPathConstants.STRING));
// Modify DOM to Update Object
phoneNumberElement.setTextContent("555-2DOM");
binder.updateJAXB(phoneNumberElement);
System.out.println(phoneNumber.getNumber());
}
}
Output
555-2OBJ
555-2DOM

I have been through exactly this over the last 10 years, building XML DOMs for chemistry, graphics, maths, etc. My own solution has been to use a DOM where the elements can be subclassed (I use xom.nu but there are others). The w3c dom does not allow subclassing (IIRC) and so you would have to build a delegate model. (I tried this many years ago and rejected it, but software tools and libraries make all this much easier (e.g. the IDE will generate delegat methods).
If you are doing a lot, and especially if you are creating a lot of custom methods then I would recommend rolling your own system. The effort will be in your methods (dotProduct), not the XML.
Here, for example, is my class for a 3D point.
public class CMLPoint3 extends AbstractPoint3
(which extends the base class CMLElement, which extends nu.xom.Element
The creation of elements is a factory. Here's a chunk of my SVGDOM:
public static SVGElement readAndCreateSVG(Element element) {
SVGElement newElement = null;
String tag = element.getLocalName();
if (tag == null || tag.equals(S_EMPTY)) {
throw new RuntimeException("no tag");
} else if (tag.equals(SVGCircle.TAG)) {
newElement = new SVGCircle();
} else if (tag.equals(SVGClipPath.TAG)) {
newElement = new SVGClipPath();
} else if (tag.equals(SVGDefs.TAG)) {
newElement = new SVGDefs();
} else if (tag.equals(SVGDesc.TAG)) {
newElement = new SVGDesc();
} else if (tag.equals(SVGEllipse.TAG)) {
newElement = new SVGEllipse();
} else if (tag.equals(SVGG.TAG)) {
...
} else {
newElement = new SVGG();
newElement.setClassName(tag);
System.err.println("unsupported svg element: "+tag);
}
if (newElement != null) {
newElement.copyAttributesFrom(element);
createSubclassedChildren(element, newElement);
}
return newElement;
You can see tools for copying and recursing.
The questions you need to think about are:
how closely is this bound to an XSD
do I use XSD data Types
do I validate on input
am I using the DOM as the primary data structure (I do)
how frequently will things change.
FWIW I have been through 6 revisions of this and am contemplating another (using Scala as the main engine).

Related

Spring MVC - receiving XML Objects and deserialzing into Collection

I'm attempting to deserialize an XML payload (body of a SOAP message, but nothing else), with a specific hierarchy of tags / objects. When attempting to aggregate unwrapped objects into a List, a MismatchedInputException is thrown.
Example Payload
<libraryRequest>
<libraryProfile>
<libraryId>
<libraryName>
<newBookInfo>
<bookId>...</bookId>
<bookTitle>...</bookTitle>
<datePublished>...</datePublished>
</newBookInfo>
<currentBooks>
<bookId>...</bookId>
<bookTitle>...<bookTitle>
<datePublished>...</datePublished>
</currentBooks>
<currentBooks>
<bookId>...</bookId>
<bookTitle>...<bookTitle>
<datePublished>...</datePublished>
</currentBooks>
<currentBooks>...</currentBooks>
</libraryProfile>
</libraryRequest>
Java objects are
public class LibraryRequest {
private LibraryProfile libraryProfile;
#XmlElement(name = "libraryProfile")
public LibraryProfile getLibraryProfile(){
...
}
// setters
public class LibraryProfile {
// constructors, getters & setters for primitive types
private List<BookInfo> bookInfos;
public List<BookInfo> getBookInfo(){
return this.BookInfos;
}
// rest of the functions
My issue is that I don't know how many currentBooks tags will come in the XML payload, and they don't come in a wrapper element. I need to keep track of each currentBook element, which is why I was using a Collection, but I am not able to properly fill the collection with the information contained within the currentBooks tags.
Would I be able to use JAXB to group the XML sequence into a Java Collection/List, and if not would I be able to use Jackson's XML functionality to group the unwrapped XML tags into a Java Collection?
The main goal is to use have an XML request come into a Spring Controller and have the XML sequence properly deserialized into a Java List / Collection. Any advice would help.
I'm using Spring Boot 1.5.8 (later version was giving me trouble in a different way), and Jackson version 2.9.5
This is based on the XmlElement explanation from actimem.com.
The mechanics explained:
- #XmlElement is only needed if the field name is not equal to the xml tag name.
- If you would like to rename your field newBookInfo to newestBook but without changing the xml you'd simply rename your field and annotate it with #XmlElement(name="newBookInfo")
- #XmlElementWrapper is explicitly not used to advice JAXB it should search for the list tags directly in the parent node
The XML represenation classes Book
#XmlAccessorType(XmlAccessType.FIELD)
public static class Book {
private String bookId;
private String bookTitle;
// ... accessors and toString
}
and LibraryProfile
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class LibraryProfile {
private String libraryId;
private String libraryName;
private Book newBookInfo;
// the trick is NOT to use #XmlElementWrapper here
private List<Book> currentBooks;
private String foobar; // just to show a tag after the list
// ... accessors
}
The input based on your question (I skipped the <libraryRequest> to keep the example short)
<libraryProfile>
<libraryId>1</libraryId>
<libraryName>library of alexandria</libraryName>
<newBookInfo>
<bookId>42</bookId>
<bookTitle>the answer</bookTitle>
</newBookInfo>
<currentBooks>
<bookId>2</bookId>
<bookTitle>the second</bookTitle>
</currentBooks>
<currentBooks>
<bookId>1</bookId>
<bookTitle>the first</bookTitle>
</currentBooks>
<foobar>test-foo</foobar>
</libraryProfile>
And here the testing class:
package com.stackoverflow.answer;
import javax.xml.bind.JAXB;
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 java.io.StringReader;
public class Tester {
public static final String INPUT = "..."; // here goes your xml
public static void main(String[] args) throws Exception {
LibraryProfile lib = JAXB.unmarshal(new StringReader(INPUT), LibraryProfile.class);
System.out.println(lib.getLibraryName() + " currently contains");
System.out.println(lib.getCurrentBooks());
System.out.println("the newest book is: " + lib.getNewBookInfo());
}
}
The output is now
library of alexandria currently contains
[Book{bookId='2', bookTitle='the second'}, Book{bookId='1', bookTitle='the first'}]
the newest book is: Book{bookId='42', bookTitle='the answer'}

How do I programmatically create XML from Java?

I am trying to programmatically create XML elements using JAXB in Java. Is this possible? I am reading this page here for something I can use, but have so far found nothing.
Usually you start by defining a bean
#XmlRootElement public class MyXML {
private String name;
public String getName() { return name; }
#XmlElement public void setName(String s) { this.name = s; }
}
and serialize it with code like
public class Serializer {
static public void main(String[] args) {
MyXML m = new MyXML();
m.setName("Yo");
JAXBContext jaxbContext = JAXBContext.newInstance(MyXML.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(m, new File("MyXML_"+ ".xml"));
}
}
that whould produce the following XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myXML>
<name>Yo</name>
</myXML>
How would I program my Java class to create the element tag name depending on what is entered in the program? For instance in my example the tag element is called 'name'. How could I set this at runtime though? Is this possible with generics or some other way?
The B in JAXB stands for Bean so no, there's no way to use JAXB without defining beans.
You just want to dinamically create an XML so take a look at jOOX for example (link to full Gist)
Document document = JOOX.builder().newDocument();
Element root = document.createElement("contacts");
document.appendChild(root);
for (String name : new String[]{"John", "Jessica", "Peter"}) {
$(root).append(
$("contact"
, $("name", name)
, $("active", "true")
)
);
}
Here, you use annotation before compile-time while you have no knowledge yet of the format you will need.. Marshalling this way is not that different from serializing, and it basically map directly the fields of a java object to an XML representation --> (if something is not defined in the object, it won't appear in the representation).
What you thrive to do looks like simple xml crafting (a XML parser would be enough S(t)AX/DOM whatever -- I like Jackson).
For the sake of curiosity, if you really want to fiddle with annotation you can use a bit of reflection in conjonction with the answer you will find here

Deserialize Document type to a Java Class

Is it possible to deserialize a "org.w3c.dom.Document" type into a Java class?
We have this class (Employee), and we have an existing method in our application that calls a web service and returns a "Document" type of the Employee class. Can we serialize the Document to Employee class?
Can you please show some example? Most of the sample I found from the internet reads an XML file in a directory then deserialize it to the class. But in our scenario, the object is a Document type.
What I'm trying to achieve is something like as below (pseudocode):
String employeeID = "12344444";
org.w3c.dom.Document xmlEmployee = helperClass.callWebServiceEmployee(employeeID);
EmployeeClass employee = new EmployeeClass();
employee = (EmployeeClass)xmlEmployee.
Part of the reason people aren't leaping to help you is that you've got the terminology wrong. Going from DOM objects to domain-specific Java objects is NOT serialization. It is translation one object form to another object form. Serialization is when you go from an object form (e.g. DOM) to a serial form; e.g. XML.
(And it isn't deserialisation either :-) )
Anyway ...
Given where you currently are, I think this approach should work:
Serialise the DOM objects to XML.
Deserialise the XML to POJOs using XStream or JAXB or some other "XML binding" technology.
It is not the most efficient approach (in terms of runtime costs), but I suspect that the efficient approach would entail implementing a mapping / binding technology that doesn't currently exist and (frankly) wouldn't be much use to other people.
But then I don't know why you are starting from DOM objects in the first place. It doen't make a lot of sense to me:
If you got the DOM objects by parsing XML, then it would better to start with the XML and go directly to step 2.
If you constructed the DOM some other way ..... why?
You can't directly type cast Document type Object to your own class. What you can do is having a Constructor in Your class that takes Document as parameter and then populate your class attributes with the values from Document Object
String employeeID = "12344444";
org.w3c.dom.Document xmlEmployee = helperClass.callWebServiceEmployee(employeeID);
EmployeeClass employee = new EmployeeClass(xmlEmployee);
where this constructor might have implementation like:
public EmployeeClass(Document xmlEmployee) {
// this is just an example assuming your xml had a Name Node
this.employeeName = xmlEmployee.getElementByTagName("Name")[0].getNodeValue();
}
Similarly you can populate other attributes of your EmployeeClass
EDIT
If you are looking for some other way of doing things you can make use of JSON APIs. In that case your web service might return a JSON Object String and you can get your object from it. here is a Stack overflow post that might help you to check different options for JSON.
The following static methods will achieve the required conversion:
import java.io.StringWriter;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Node;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
public class Utils {
public static String convertNodeToString(Node node) {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer;
try {
transformer = tf.newTransformer();
// below code to remove XML declaration
// transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(node), new StreamResult(writer));
String output = writer.getBuffer().toString();
return output;
} catch (TransformerException e) {
e.printStackTrace();
}
return null;
}
public static <T> T convertNodeToObject(Node node, Class<T> type) throws JsonMappingException, JsonProcessingException {
return convertNodeToObject(node, new XmlMapper(), type);
}
public static <T> T convertNodeToObject(Node node, XmlMapper xmlMapper, Class<T> type) throws JsonMappingException, JsonProcessingException {
T obj=null;
String xmlStr = convertNodeToString(node);
obj = xmlMapper.readValue(xmlStr, type);
return obj;
}
}
Following is the dependency I used for Jackson:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.12.7</version>
</dependency>

JAXB Unmarshalling with dynamic elements [duplicate]

This question already has answers here:
Dealing with poorly designed XML with JAXB
(4 answers)
Closed 9 years ago.
I have an XML as bellow.
<hotelRoomDetails>
<NightRates>
<Night1>67</Night1>
<Night2>67.5</Night2>
........
........
<Night25>65</Night25>
</NightRates>
.......
</hotelRoomDetails>
The element Night1,Night2,Night3 are always dynamic, and this count may vary from 1 to 35 times. Parent tag <NightRates> is always consistent.
The element <Night1>, <Night2> .. are not having any other attribute. It gives information about the hotel rate per night.
I want to create a 'LinkedList'(to preserve order) which contains the rate information of individual night. How can I handle this situation without knowing the occurrence count of element? How to create java class for this xml?
You can make NightRates field of type any. And work with its content through org.w3c.dom.Element:
#XmlAnyElement
protected Element NightRates;
Unmarshal Only
If you are just doing unmarshalling then you can use a StreamReaderDelegate to strip off the numeric suffix.
Demo
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.stream.util.StreamReaderDelegate;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(HotelRoomDetails.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource("src/forum19834756/input.xml"));
xsr = new StreamReaderDelegate(xsr) {
#Override
public String getLocalName() {
String localName = super.getLocalName();
if(localName.startsWith("Night") && !localName.equals("NightRates")) {
return "Night";
}
return localName;
}
};
Unmarshaller unmarshaller = jc.createUnmarshaller();
HotelRoomDetails details = (HotelRoomDetails) unmarshaller.unmarshal(xsr);
System.out.println(details.getNightRates().size());
}
}
HotelRoomDetails
Then you have a collection property that maps to the element called Night.
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class HotelRoomDetails {
private List<String> nightRates = new ArrayList<String>();
#XmlElementWrapper(name = "NightRates")
#XmlElement(name = "Night")
public List<String> getNightRates() {
return nightRates;
}
}
Unmarshal & Marshal
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
If you need to support reading and writing to this format you could use MOXy's #XmlVariableNode extension.
http://blog.bdoughan.com/2013/06/mapping-bad-xml-enumerated-collection.html

Using Java-to-schema mapped object as Freemarker data model

Background:
I have a webservice that previously only received requests for xml, but now needs to return html for browser access.
I have a Java class that is mapped to XML with the XmlRootElement annotation.
I am using Freemarker to generate HTML based on this Java class, but cannot find a way to do so directly.
At the moment I use NodeModel.parse to parse the xml to a freemarker datamodel, but since the NodeModel.parse takes a File, I first write the Java object to a file. That is obviously an inefficient way to do it, but it does the job.
Does anyone know a way to go get a freemarker datamodel out of a this Java class without first writing it to an XML file?
The following is my code:
The Java-to-Schema mapped class:
#XmlRootElement(name = "report")
public class Report {
private String id;
private String time;
public Report() {}
public String getTime() {return time;}
public void setTime(String time) {this.time = time;}
public String getId() {return this.id;}
public void setId(String id) {this.id = id;}
}
Merging the data with the template:
public String getReportsAsHtml(#QueryParam("lastUpdate") String lastUpdate){
MySQLAccess dao = new MySQLAccess();
List<Report> reports = dao.readReports(lastUpdate);
Template temp = TemplateConfiguration.getInstance().getTemplateConfiguration().getTemplate("list_template.ftl");
**HashMap<String, NodeModel> root = new HashMap<String, NodeModel>();**
**root.put("doc", NodeModel.parse(Java2XML.getXMLFromJava(reports)));**
StringWriter output = new StringWriter();
temp.process(root, output);
output.flush();
return output.toString();
}
NodeModel has a wrap(org.w3c.dom.Node) method, so you surely don't have to create an XML file. All you need is a tree of org.w3c.dom.Node objects, and FreeMarker doesn't care where it comes from. Actually, if you are using the default object-wrapper of FreeMarker, you don't even need to deal with NodeModel, just drop the org.w3c.dom.Node into the data model as any other POJO, and FreeMarker will recognize it as XML.
Also note that FreeMarker has this ObjectWrapper abstraction. It separates the actual objects from how they are seen from the templates. So you possibly doesn't even need to make a tree of Node-s from those objects, just make an ObjectWrapper implementation that directly understands those annotated object. See how DefaultObjectWrapper extends BeansWrapper, automatically wrapping Node-s, Jython object, etc. You can follow the same pattern. But of course writing your own ObjectWrapper is extra work, especially if you need XPath support too (hint: Jaxen doesn't need Node-s).
I have used the following code to generate a Node tree from a Java-to-Schema annotated class:
public static Node getNodeFromReport(Object report){
JAXBContext context = JAXBContext.newInstance(report.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
DocumentBuilderFactory docFac = DocumentBuilderFactory.newInstance();
Document result = docFac.newDocumentBuilder().newDocument();
marshaller.marshal(report, result);
return result;
}

Categories

Resources