Read XML body element having multiple values based on different requests - java

I am parsing a XML request using Java. The XML structure is like this:
<?xml version="1.0" encoding="UTF-8"?>
<TestServices>
<header>
//Header Details
</header>
<body>
<ele1>
<ele2>
<ele3>534159XXXXXX0176</ele3> //Or ele_3, ele03, ele_03
</ele2>
</ele1>
</body>
</TestServices>
I have created classes for the same to read the Header and Body elements. Each node is a class and I am reading the ele3 value like this.
String ele3 = testServicesRequest.getBody().getEle1().getEle2().getEle3();
The element name for ele3 can be different based on different request. I have used Generate Java class from xsd feature in eclipse and it has generated classes like this.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"ele3"
})
public class ReqEle2 {
#XmlElement(name = "ele3", required = true)
protected String ele3;
public String getEle3() {
return ele3;
}
public void setEle3(String value) {
this.ele3 = value;
}
}
My requirement is simple. I just want to put multiple element names for single getEle3() method. eg. ele_3, ele03, ele_03 using less code changes. Or Please suggest me any other efficient way to do that.
For now I am trying to do this like this which I think is not good.
public class ReqEle3 {
#XmlElement(name = "ele03", required = true)
protected String ele3_1="";
#XmlElement(name = "ele_3", required = true)
protected String ele3_2="";
#XmlElement(name = "ele3", required = true)
protected String ele3_3="";
#XmlElement(name = "ele3_old", required = true)
protected String ele3_4="";
public String getEle3() {
if(ele3_1 != null && !ele3_1.isEmpty()){
return ele3_1;
}
else if(ele3_2 != null && !ele3_2.isEmpty()){
return ele3_2;
}
else if(ele3_3 != null && !ele3_3.isEmpty()){
return ele3_3;
}
else if(ele3_4 != null && !ele3_4.isEmpty()){
return ele3_4;
}
return "";
}
}

You can write custom deserialiser for ele3 node. To be precise, custom deserialiser for ele2 node because this is the last constant node. Below example contains only required part to understand the solution:
import org.w3c.dom.Element;
import javax.xml.bind.JAXBContext;
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 javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.StringReader;
public class JaxbApp {
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(ReqEle1.class);
String xml0 = "<ele1><ele2><ele3>534159XXXXXX0176</ele3></ele2></ele1>";
String xml1 = "<ele1><ele2><ele_3>534159XXXXXX0176</ele_3></ele2></ele1>";
String xml2 = "<ele1><ele2><ele03>534159XXXXXX0176</ele03></ele2></ele1>";
for (String xml : new String[]{xml0, xml1, xml2}) {
StringReader reader = new StringReader(xml);
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(reader);
System.out.println(unmarshal);
}
}
}
#XmlRootElement(name = "ele1")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {"ele2"})
class ReqEle1 {
#XmlJavaTypeAdapter(ReqEle2XmlAdapter.class)
#XmlElement(name = "ele2", required = true)
protected ReqEle2 ele2;
// getters, setters
}
class ReqEle2XmlAdapter extends XmlAdapter<Object, ReqEle2> {
#Override
public ReqEle2 unmarshal(Object v) {
Element element = (Element) v;
ReqEle2 reqEle2 = new ReqEle2();
reqEle2.setEle3(element.getFirstChild().getTextContent());
return reqEle2;
}
#Override
public Object marshal(ReqEle2 v) throws Exception {
return null; // Implement if needed
}
}
class ReqEle2 {
protected String ele3;
// getters, setters
}
Above code prints:
ReqEle1{ele2=ReqEle2{ele3='534159XXXXXX0176'}}
ReqEle1{ele2=ReqEle2{ele3='534159XXXXXX0176'}}
ReqEle1{ele2=ReqEle2{ele3='534159XXXXXX0176'}}
See also:
JAXB #XmlAdapter for arbitrary XML

Related

How to create XmlElement with no value with JAXB

Would like to create the following XML element using JAXB, no value(content), no closing element name, just closing '/' :
<ElementName attribute1="A" attribute2="B"" xsi:type="type" xmlns="some_namespace"/>
Trying the following
#XmlAccessorType(XmlAccessType.FIELD)
public class ElementName {
#XmlElement(name = "ElementName", nillable = true)
protected String value;
#XmlAttribute(name = "attribute1")
protected String attribute1;
#XmlAttribute(name = "attribute2")
protected String attribute2;
}
When constructing an object of this type as below, there is an exception
ElementName element = new ElementName();
What is the correct way of doing it ?
In case you want to achieve it for ElementName with value set to null remove nillable attribute. Simple example how to generate XML payload:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
public class JaxbApp {
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(ElementName.class);
ElementName en = new ElementName();
en.attribute1 = "A";
en.attribute2 = "B";
en.value = null;
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(en, System.out);
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "ElementName")
class ElementName {
#XmlElement(name = "ElementName")
protected String value;
#XmlAttribute(name = "attribute1")
protected String attribute1;
#XmlAttribute(name = "attribute2")
protected String attribute2;
}
prints:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ElementName attribute1="A" attribute2="B"/>

Two different schemas and JAXB Marshaller

Lets suppose that we have XML consnensual with Schema and Java class with some common fields:
<objectFromSchema1>
<element1/>
<commonElement1/>
<commonElement2/>
<element2/>
</objectFromSchema1>
public class X {
private String element1;
private String commonElement1;
private String commonElement2;
private String element2;
}
Is a nice way to unmarschall such kind of XML to Java object ? It means: convert all consensual fields and set null on rest.
The answer is "yes". This is the way JAXB works. Take a look on basic JAXB tutorial, e.g. https://jaxb.java.net/tutorial/
http://docs.oracle.com/javase/tutorial/jaxb/intro/
http://www.vogella.com/tutorials/JAXB/article.html
"YES"
If you have an xsd you also could generate automatically these classes by <artifactId>maven-jaxb2-plugin</artifactId>maven plugin.
An example of your Class
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "objectFromSchema1", propOrder = {
})
#XmlRootElement(name = "objectFromSchema1")
public class ObjectFromSchema1
implements Serializable
{
private final static long serialVersionUID = 12343L;
protected String element1;
protected String element2;
protected String commonElement1;
protected String commonElement2;
public String getElement1() {
return element1;
}
public void setElement1(String element1) {
this.element1 = element1;
}
public String getElement2() {
return element2;
}
public void setElement2(String element2) {
this.element2 = element2;
}
public String getCommonElement1() {
return commonElement1;
}
public void setCommonElement1(String commonElement1) {
this.commonElement1 = commonElement1;
}
public String getCommonElement2() {
return commonElement2;
}
public void setCommonElement2(String commonElement2) {
this.commonElement2 = commonElement2;
}
}
Main method to use it
public static void main(String[] args) throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(ObjectFromSchema1.class);
final Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
final ObjectFromSchema1 objectFromSchema1 = new ObjectFromSchema1();
objectFromSchema1.setCommonElement1("commonElement1");
objectFromSchema1.setCommonElement2("commonElement2");
objectFromSchema1.setElement1("element1");
objectFromSchema1.setElement2("element2");
m.marshal(objectFromSchema1, System.out);
}
output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectFromSchema1>
<element1>element1</element1>
<element2>element2</element2>
<commonElement1>commonElement1</commonElement1>
<commonElement2>commonElement2</commonElement2>
</objectFromSchema1>

Change Java's output format for nillable XML elements

I'm using java's jaxb to create XML files from java objects.
The problem I'm facing is the exact opposite as stated here: LinqToXml does not handle nillable elements as expected
In short: I want to properly depict members that are null in the resulting xml file.
I have following member in my class
#XmlElement (name = "order-detail", nillable = true)
private String orderDetail;
if I marshal an instance of this class, the resulting xml element is
<order-detail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
Since also non-technicians are reading, maybe also manipulating, the file, I'd rather have it that way
<order-detail />
since I don't want to confuse them. So how can I achieve this?
UPDATE
Using an empty string instead of null
#XmlElement (name = "order-detail", nillable = true)
private String orderDetail = "";
yields
<order-detail></order-detail>
SSCCE
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
public class Example
{
public static void main(String[] args) throws JAXBException
{
Data data = new Data();
JAXBContext context = JAXBContext.newInstance(Data.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(data, System.out);
}
#XmlRootElement(name = "data")
static class Data
{
private String orderDetail;
#XmlElement (name = "order-detail", nillable = true)
public String getOrderDetail() { return orderDetail; }
public void setOrderDetail(String orderDetail) { this.orderDetail = orderDetail; }
}
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<data>
<order-detail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</data>
JAXB will marshal an empty string ("") as an empty element. You could change your code so that when the field has a value of ("") the property reports a null value.
#XmlRootElement(name = "data")
static class Data
{
private String orderDetail = "";
#XmlElement (name = "order-detail", nillable = true)
public String getOrderDetail() {
if(orderDetail.length() == 0) {
return null;
}
return orderDetail;
}
public void setOrderDetail(String orderDetail) {
if(null == orderDetail) {
this.orderDetail = "";
} else {
this.orderDetail = orderDetail;
}
}
}
Normally an XmlAdapter is used to "fix up" values, but the JAXB reference implementation does not apply an XmlAdapter to null values.
Note
In short: I want to properly depict members that are null in the
resulting xml file.
Any empty element is not a valid representation of null in XML.

JAXB unmarshal XML elements to HashMap

I found a lot of articles that describe how to unmarshal a sequence of XML elements to a HashMap as long as they are within a "parent" element. However, I do not get this to work with the children directly under the root element!
Option 1 - Works:
<?xml version="1.0" encoding="UTF-8"?>
<checks>
<checks>
<check key="check1"/>
<check key="check2"/>
...
</checks>
</checks>
Option 2 - Does not work:
<?xml version="1.0" encoding="UTF-8"?>
<checks>
<check key="check1"/>
<check key="check2"/>
...
</checks>
Checks:
package com.foo.conf;
import java.util.Map;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="checks")
public class Checks {
#XmlJavaTypeAdapter(ChecksAdapter.class)
#XmlElement(name="checks")
public Map<String, Check> checkMap;
}
Check:
package com.foo.conf;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
public class Check {
#XmlAttribute public String key;
#XmlValue public String description;
public Check() { }
public Check(String key) {
this.key = key;
}
public String getCheckKey() {
return this.key;
}
}
CheckMapType:
package com.foo.conf;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
class CheckMapType {
#XmlElement(name="check")
public List<Check> checkList; // = new ArrayList<Check>();
}
ChecksAdapter:
package com.foo.conf;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlAdapter;
final class ChecksAdapter extends XmlAdapter<CheckMapType, Map<String, Check>> {
#Override
public CheckMapType marshal(Map<String, Check> arg0) throws Exception {
return null;
}
#Override
public Map<String, Check> unmarshal(CheckMapType arg0) throws Exception {
System.out.println("u: " + arg0.checkList.size());
Map<String, Check> map = new HashMap<String, Check>();
for (Check check : arg0.checkList) {
System.out.println(check);
map.put(check.key, check);
}
return map;
}
}
This is (some dummy test lineS) how I generate the classes/invoke the unmarshalling:
JAXBContext jc = JAXBContext.newInstance(Checks.class);
Unmarshaller u = jc.createUnmarshaller();
Checks c = (Checks) u.unmarshal(new File("checks.xml"));
System.out.println(c.checkMap.size());
Any idea on how to get option #2 to work? It works when using a List instead of the Map but I need the HashMap as I have to access the objects by the given keys...
Any hints much appreciated!
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
JAXB will treat each object relationship with a nesting relationship. Map is treated like an Object instead of a Collection so this is why you are getting the behaviour that you are seeing.
MOXy has an XPath based mapping extension called #XmlPath that could be used for this use case.
package com.foo.conf;
import java.util.Map;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name="checks")
public class Checks {
#XmlJavaTypeAdapter(ChecksAdapter.class)
#XmlPath(".")
public Map<String, Check> checkMap;
}
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
How are you generaing the JAXB classes? I am not sure what exactly are you trying to do but the below very simple code works for me ..
JAXBContext jc = JAXBContext.newInstance(ChecksType.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
ChecksType chksType = (ChecksType) unmarshaller.unmarshal(new File("/path/to/xml"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(chksType, System.out);
System.err.println(chksType.getCheck().get(0).getKey());
for (CheckType checkType : chksType.getCheck()) {
System.out.println("key = " + checkType.getKey() + ", " + checkType);
}
and here is my JAXB generated classes ..
ChecksType (Root element)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "checksType", propOrder = { "check" })
#XmlRootElement(name = "checks")
public class ChecksType {
#XmlElement(required = true)
protected List<CheckType> check;
public List<CheckType> getCheck() {
if (check == null) {
check = new ArrayList<CheckType>();
}
return this.check;
}
}
And checkType (the child)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "checkType")
public class CheckType {
#XmlAttribute(name = "key")
protected String key;
public String getKey() {
return key;
}
public void setKey(String value) {
this.key = value;
}
}

JAXB : How to add attributes to an inner element

I have the below XML that I want to read. Using JAXB on java 1.6, how do I annotate for the attribute regex ? Can I have the field to be of type boolean ?
<?xml version="1.0" encoding="utf-8"?>
<authStore>
<authList>
<auth>
<resource>res1</resource>
<privilege regex = "true">PRIV_FILE_.+?_READ</privilege>
</auth>
<auth>
<resource>res2</resource>
<privilege>PRIV_FILE_READ</privilege>
</auth>
</authStore>
UPDATE : Is it possible to make the attribute optional ? If yes, when I unmarshal, will I get regex field to be false when a privilege element does not have the optional attribute regex ?
UDPATE2 : I don't want to define separate classes for resource and privilege. Also, I don't want to use MOXy. Pls. suggest solution for sun/oracle JDK 1.6 JAXB only.
UPDATE3 : My current object model is something like this
// AuthStore.java
#XmlRootElement
public class AuthStore {
#XmlElementWrapper(name = "authList")
#XmlElement(name = "auth")
private ArrayList<Auth> authList;
public void setAuthList(ArrayList<Auth> authList) {
this.authList = authList;
}
public ArrayList<Auth> getAuthsList() {
return authList;
}
}
// Auth.java
#XmlRootElement(name = "auth")
#XmlType(propOrder = { "resource", "privilege" })
public class Auth
{
private String resource;
private String privilege;
#XmlElement(name = "resource")
public String getResource()
{
return resource;
}
public void setResource(String resource)
{
this.resource = resource;
}
#XmlElement(name = "privilege")
public String getPrivilege()
{
return privilege;
}
public void setPrivilege(String author)
{
this.privilege = author;
}
}
Because privilege contains an attribute (It's actually complex type), you must create a class to hold both the value and the attribute:
import java.io.InputStream;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
#XmlRootElement(name = "authStore")
#XmlAccessorType(XmlAccesssType.FIELD)
public class AuthStore {
public static void main(String []args) throws Exception {
InputStream inputStream = AuthStore.class.getResourceAsStream("test.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(AuthStore.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
AuthStore authStore = (AuthStore)unmarshaller.unmarshal(inputStream);
System.out.println(authStore.getAuthList().get(0).getResource());
System.out.println(authStore.getAuthList().get(0).getPrivilege().getRegex());
System.out.println(authStore.getAuthList().get(0).getPrivilege().getValue());
}
#XmlElementWrapper(name = "authList")
#XmlElement(name = "auth")
private List<Auth> authList;
public List<Auth> getAuthList() {
return authList;
}
#XmlAccessorType(XmlAccesssType.FIELD)
public static class Auth {
#XmlElement(name = "resource")
private String resource;
#XmlElement(name = "privilege")
private Privilege privilege;
public String getResource() {
return resource;
}
public Privilege getPrivilege() {
return privilege;
}
#XmlAccessorType(XmlAccesssType.FIELD)
public static class Privilege {
#XmlAttribute(name = "regex")
private Boolean regex;
#XmlValue
private String value;
public Boolean getRegex() {
return regex;
}
public String getValue() {
return value;
}
}
}
}

Categories