Reading attribute of a node in XML using itemReader Spring Batch - java

I have an xml below
<?xml version="1.0" encoding="UTF-8"?>
<employee siteId="1">
<name>john</name>
<age>36</age>
</employee>
I'd like to get the value of siteId (attribute value) using itemReader. I'm able to successfully read name and age child elements. I don't know how to get the value of siteId. Please help. Thanks!

You can read siteId attribute value by specifying #XmlAttribute(name = "siteId") in class please find example below
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "employee")
public class Employee {
private int siteId;
private String name;
private int age;
#XmlAttribute(name = "siteId")
public int getSiteId() {
return siteId;
}
public void setSiteId(int siteId) {
this.siteId= siteId;
}
#XmlElement(name = "age")
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
#XmlElement
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

Related

I want to 'pase xml and serve objects in rest api

` This is my xml code.Iam very new to restservice.
<?xml version="1.0" encoding="UTF-8"?>
<departments>
<deptname name="Research">
<employee>
<eid>r-001</eid>
<ename>Dinesh R</ename>
<age>35</age>
<deptcode>d1</deptcode>
<deptname>Research</deptname>
<salary>20000</salary>
</employee>
</deptname>
<deptname name="Sales">
<employee>
<eid>s-001</eid>
<ename>Kanmani S</ename>
<age>35</age>
<deptcode>d2</deptcode>
<deptname>Sales</deptname>
<salary>30000</salary>
</employee>
</deptname>
</departments>
By using this xml i want to create Restservice.I have tried , i created java classes for that(i don't know that correct or not ).but i am stuck in controller in that area how i will mapping.
Here is a simplest way, in my opinion
1) create a Departments pojo
2) create Department pojo which will be composed (composition) into departments
3) create a regular springboot controller, ensure the controller method produces and consumes application/xml
4) include below dependency in your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
here is my example,
Departments.java
import org.springframework.stereotype.Component;
#Component
public class Departments {
private List<Department> department;
public List<Department> getDepartment() {
return department;
}
public void setDepartment(List<Department> department) {
this.department = department;
}
}
Department.java
import org.springframework.stereotype.Component;
#Component
public class Department {
private String name;
private String id;
private int employeeCount;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getEmployeeCount() {
return employeeCount;
}
public void setEmployeeCount(int employeeCount) {
this.employeeCount = employeeCount;
}
#Override
public String toString() {
return "Department [name=" + name + ", id=" + id + ", employeeCount=" +
employeeCount + "]";
}
public Department() { }
public Department(String name) {
this.name = name;
}
public Department(String name, String id) {
this.name=name;
this.id=id;
}
public Department(String name, String id, int employeeCount) {
this.name=name;
this.id=id;
this.employeeCount = employeeCount;
}
}
SpringBootApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication(scanBasePackages = {"test.controllers","test.main", "test.model"})
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
SpringController
#RestController
public class GreetingController {
#RequestMapping("/hello/{name}")
String hello(#PathVariable String name) {
return "Hello, " + name + "!";
}
#PostMapping(path = "/departments", produces = MediaType.APPLICATION_XML_VALUE, consumes = MediaType.APPLICATION_XML_VALUE)
#ResponseBody
Departments newEmployee(#RequestBody List<Department> departments) {
Departments departmentsObj = new Departments();
for(Department department : departments) {
System.out.println(department);
}
departmentsObj.setDepartment(departments);
return departmentsObj;
}
}

Selective marshalling object

I need to execute a selective marshalling object. For example:
There is a class Contact:
#XmlRootElement(name = "contact")
public class Contact {
private String name;
private String number;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Marshalling the object:
public static void main(String[] args) throws JAXBException {
Contact contact = new Contact();
contact.setAddress("5 Av.");
contact.setName("John");
contact.setNumber("5555555");
JAXBContext context = JAXBContext.newInstance(Contact.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(contact, System.out);
}
As a result we have:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<contact>
<address>5 street</address>
<name>John</name>
<number>5555555</number>
</contact>
Is there any simple way to perform a selective marshalling? For example, only the address to get at the output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<contact>
<address>5 street</address>
</contact>
Thanks!
When unspecified, the default is as-if the following annotation is on the class:
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
That means that all public member will be used by JAXB, i.e. all your getter methods.
You can either keep that default and suppress the getter methods you don't want, or you can change the default and explicitly mark the getter methods you do want processed.
To suppress a getter method, annotate with #XmlTransient:
Prevents the mapping of a JavaBean property/type to XML representation.
To change default, annotate with #XmlAccessorType(XmlAccessType.NONE):
None of the fields or properties is bound to XML unless they are specifically annotated with some of the JAXB annotations.
Then explicitly annotate desired getter methods with #XmlElement:
Maps a JavaBean property to a XML element derived from property name.
Using #XmlTransient
#XmlRootElement(name = "contact")
class Contact {
private String name;
private String number;
private String address;
#XmlTransient
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlTransient
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Using #XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name = "contact")
#XmlAccessorType(XmlAccessType.NONE)
class Contact {
private String name;
private String number;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
#XmlElement
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Output (from both)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<contact>
<address>5 Av.</address>
</contact>

JAXB - change property name without changing variable name in class

So I have code like this:
#XmlRootElement(name = "person")
#XmlType(propOrder = {"name", "secondName"})
public class Person {
private String name;
private String secondName;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
public String getSecondName() {
return secondName;
}
}
And when I want to create XML file it makes me:
<person>
<name>John</name>
<secondName>Smith</secondName>
</person>
Is it any way to make in xml file <second-name> instead of <secondName> without changing in class on private String second-name?
Problem solved. I should just do this:
#XmlElement(name="second-name")
public String getSecondName() {
return secondName;
}

#XmlRegistry - how does it work?

I have found some examples of JAXB2 #XmlRegistry over the internet but no good in-depth tutorials that talk about the concept of using #XmlRegistry with #XmlElementDecl, wonder if its a concept not much explored in general.
Anyways here is my question, first some sample classes that I am using to unmarshall an xml using JAXB:
The main class I am trying to unmarshal using JAXB - Employee.java
package com.test.jaxb;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;
import com.test.jaxb.dto.Address;
#XmlRootElement
public class Employee {
private int id;
private String name;
private String email;
private List<Address> addresses;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Address> getAddresses() {
return addresses;
}
public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
#SuppressWarnings("unused")
#XmlRegistry
public static class XMLObjectFactory {
#XmlElementDecl(scope = Employee.class, name= "id")
JAXBElement<String> createEmployeeId(String value) {
return new JAXBElement<String>(new QName("id"), String.class, "100");
}
#XmlElementDecl(scope = Employee.class, name= "name")
JAXBElement<String> createName(String value) {
return new JAXBElement<String>(new QName("name"), String.class, "Fake Name");
}
#XmlElementDecl(scope = Employee.class, name= "email")
JAXBElement<String> createEmail(String value) {
return new JAXBElement<String>(new QName("email"), String.class, value);
}
#XmlElementDecl(scope = Employee.class, name= "addresses")
JAXBElement<List> createAddresses(List value) {
return new JAXBElement<List>(new QName("addresses"), List.class, value);
}
}
}
The child class - Address.java
package com.test.jaxb.dto;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;
import com.test.jaxb.Employee;
#XmlRootElement
public class Address {
private String addressLine1;
private String addressLine2;
private String addressLine3;
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getAddressLine3() {
return addressLine3;
}
public void setAddressLine3(String addressLine3) {
this.addressLine3 = addressLine3;
}
#SuppressWarnings("unused")
#XmlRegistry
private static class XMLObjectFactory {
#XmlElementDecl(scope = Employee.class, name= "addressLine1")
JAXBElement<String> createAddressLine1(String value) {
return new JAXBElement<String>(new QName("addressLine1"), String.class, value);
}
#XmlElementDecl(scope = Employee.class, name= "addressLine2")
JAXBElement<String> createAddressLine2(String value) {
return new JAXBElement<String>(new QName("addressLine2"), String.class, value);
}
#XmlElementDecl(scope = Employee.class, name= "addressLine3")
JAXBElement<String> createAddressLine3(String value) {
return new JAXBElement<String>(new QName("addressLine3"), String.class, value);
}
}
}
The xml to be unmarshalled - employee.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
<id>1</id>
<name>Vaishali</name>
<email>Vaishali#example.com</email>
<addresses>
<address>
<addressLine1>300</addressLine1>
<addressLine2>Mumbai</addressLine2>
<addressLine3>India</addressLine3>
</address>
<address>
<addressLine1>301</addressLine1>
<addressLine2>Pune</addressLine2>
<addressLine3>India</addressLine3>
</address>
</addresses>
</employee>
Unmarshalling Code :
package com.test.jaxb;
import java.io.FileReader;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
public class ObjectFactoryTest {
public static void main(String[] args) throws Exception {
FileReader reader = new FileReader("resources/employee.xml");
JAXBContext context = JAXBContext.newInstance(Employee.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Object obj = unmarshaller.unmarshal(reader);
System.out.println(obj);
}
}
When I unmarshal the employee xml using above code, the address list does not get populated. The resulting employee object only has a blank list of adresses. Is there anything wrong with my mappings?
To find out what is going on and see if the employee objects are actually being created using the Object Factory (having the #XMLRegistry annotation), I changed the value of id and name in factory methods, however that had no effect in the output, which tells me JAXB is not actually using the ObjectFactory, why?
Am I going aboout this all wrong? Any help would be appreciated.
#XmlRegistry - how does it work?
#XmlRegistry is used to mark a class that has #XmlElementDecl annotations. To have your JAXB implementation process this class you need to ensure that it is included in the list of classes used to bootstrap the JAXBContext. It is not enough for it to be a static inner class of one of your domain model classes:
JAXBContext context = JAXBContext.newInstance(Employee.class, Employee.XMLObjectFactory.class);
#XmlElementDecl - how does it work?
If the value of the field/property is going to be a JAXBElement then you need to leverage #XmlElementDecl. A JAXBElement captures information that is can be useful:
Element name, this is necessary if you are mapping to a choice structure where multiple elements are of the same type. If the element name does not correspond to a unique type then you would not be able to round-trip the document.
JAXBElement can be used to represent an element with xsi:nil="true".
XmlObjectFactory
#XmlElementDecl also allows you to specify a scope. I have modified the model from you post a bit. I have introduced an XmlObjectFactory class that has two #XmlElementDecl. Both specify a name of address. I have leveraged the scope property so that for properties within the Employee class the #XmlElementDecl corresponding to the Address class with be used.
package forum11078850;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
#XmlRegistry
public class XmlObjectFactory {
#XmlElementDecl(scope = Employee.class, name = "address")
JAXBElement<Address> createAddress(Address value) {
return new JAXBElement<Address>(new QName("address"), Address.class, value);
}
#XmlElementDecl(name = "address")
JAXBElement<String> createStringAddress(String value) {
return new JAXBElement<String>(new QName("address"), String.class, value);
}
}
Employee
The #XmlElementRef annotation will cause the value of the property to be matched on its root element name. Possible matches will include classes mapped with #XmlRootElement or #XmlElementDecl.
package forum11078850;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlType(propOrder = { "id", "name", "email", "addresses" })
public class Employee {
private int id;
private String name;
private String email;
private List<JAXBElement<Address>> addresses;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#XmlElementWrapper
#XmlElementRef(name="address")
public List<JAXBElement<Address>> getAddresses() {
return addresses;
}
public void setAddresses(List<JAXBElement<Address>> addresses) {
this.addresses = addresses;
}
}
ObjectFactoryTest
package forum11078850;
import java.io.FileReader;
import javax.xml.bind.*;
public class ObjectFactoryTest {
public static void main(String[] args) throws Exception {
FileReader reader = new FileReader("src/forum11078850/input.xml");
JAXBContext context = JAXBContext.newInstance(Employee.class, XmlObjectFactory.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Object obj = unmarshaller.unmarshal(reader);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(obj, System.out);
}
}
The Address class and input.xml from my original answer can be used to run this example.
ORIGINAL ANSWER
I'm not sure how you are attempting to use #XmlRegistry, so I will focus on the following part of your post:
When I unmarshal the employee xml using above code, the address list
does not get populated. The resulting employee object only has a blank
list of adresses. Is there anything wrong with my mappings?
Your list of Address objects is wrapped in a grouping element (addresses), so you need to use the #XmlElementWrapper annotation to map this use case. Below is a complete example:
Employee
package forum11078850;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlType(propOrder = { "id", "name", "email", "addresses" })
public class Employee {
private int id;
private String name;
private String email;
private List<Address> addresses;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#XmlElementWrapper
#XmlElement(name = "address")
public List<Address> getAddresses() {
return addresses;
}
public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
}
Address
package forum11078850;
public class Address {
private String addressLine1;
private String addressLine2;
private String addressLine3;
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getAddressLine3() {
return addressLine3;
}
public void setAddressLine3(String addressLine3) {
this.addressLine3 = addressLine3;
}
}
ObjectFactoryTest
package forum11078850;
import java.io.FileReader;
import javax.xml.bind.*;
public class ObjectFactoryTest {
public static void main(String[] args) throws Exception {
FileReader reader = new FileReader("src/forum11078850/input.xml");
JAXBContext context = JAXBContext.newInstance(Employee.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Object obj = unmarshaller.unmarshal(reader);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(obj, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
<id>1</id>
<name>Vaishali</name>
<email>Vaishali#example.com</email>
<addresses>
<address>
<addressLine1>300</addressLine1>
<addressLine2>Mumbai</addressLine2>
<addressLine3>India</addressLine3>
</address>
<address>
<addressLine1>301</addressLine1>
<addressLine2>Pune</addressLine2>
<addressLine3>India</addressLine3>
</address>
</addresses>
</employee>
You have to take a List object of Address. In that object, you will have to add the object which contains data like addressline1. addressline2 and so on.
i.e.
List addrObjList = new List();
addrObjList.add(object); // Bind an object containing data and add one by one

Unmarshalling returned null object from a successfully marshalled XML

i have the following classes which is marshalled as an XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer id="100">
<age>21</age>
<hobbies>
<hobby>
<cost>sd</cost>
<name>na</name>
</hobby>
<hobby>
<cost>sd</cost>
<name>nb</name>
</hobby>
</hobbies>
<name>test</name>
</customer>
However, when i tried to unmarshall, I can only create the customer object but not hobby, which returns null
Am i doing something wrong here? The problem seems to be with the XML hierarchy?
package com.mytest.jxb;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
#XmlRootElement(name="customer")
#XmlSeeAlso({Hobby.class})
public class Customer {
String name;
int age;
int id;
#XmlElementRef
private List<Hobby> hobbyList = new ArrayList<Hobby>();
public Customer(){
//hobbyList = new ArrayList<Hobby>();
}
//#XmlElement
public String getName() {
return name;
}
#XmlElement
public void setName(String name) {
this.name = name;
}
//#XmlElement
public int getAge() {
return age;
}
#XmlElement
public void setAge(int age) {
this.age = age;
}
//#XmlAttribute
public int getId() {
return id;
}
#XmlAttribute
public void setId(int id) {
this.id = id;
}
public void addHobby(Hobby h) {
hobbyList.add(h);
}
#XmlElementWrapper(name="hobbies")
#XmlElement(name = "hobby")
public List<Hobby> getHobbies(){
return hobbyList;
}
}
package com.mytest.jxb;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
class Hobby {
private String name;
private String cost;
public Hobby(){
}
public Hobby(String name, String cost){
this.name = name;
this.cost = cost;
}
//#XmlElement
public void setName(){
this.name = name;
}
#XmlElement
public String getName(){
return name;
}
//#XmlElement
public void setCost(){
this.cost = cost;
}
#XmlElement
public String getCost(){
return cost;
}
}
Having addHobby(Hobby h) is not enough for JAXB. Your class should be real POJO and should have void setHobbies(List<Hobby> hobbyList) { this.hobbyList = hobbyList; }. Also I think you can safely remove #XmlElementRef from hobbyList field.
EDIT: I have checked your classes and created the version which works OK:
package test;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
#XmlRootElement(name = "customer")
public class Customer {
String name;
int age;
int id;
private List<Hobby> hobbyList = new ArrayList<Hobby>();
public Customer() {
}
#XmlElement
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlElement
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
#XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void addHobby(Hobby h) {
hobbyList.add(h);
}
#XmlElementWrapper(name = "hobbies")
#XmlElement(name = "hobby")
public List<Hobby> getHobbies() {
return hobbyList;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
package test;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
#XmlRootElement
class Hobby {
private String name;
private String cost;
public Hobby() {
}
public Hobby(String name, String cost) {
this.name = name;
this.cost = cost;
}
public void setName(String name) {
this.name = name;
}
#XmlElement
public String getName() {
return name;
}
public void setCost(String cost) {
this.cost = cost;
}
#XmlElement
public String getCost() {
return cost;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}

Categories

Resources