Convert Oracle Pivot XML to Java Object with JAXB - java

Considered the following XML (generated by Oracle 11g PIVOT XML function):
<PivotSet>
<item>
<column name="title">Post A</column>
<column name="published_date">07-Aug-2013</column>
</item>
<item>
<column name="title">Post B</column>
<column name="published_date">08-Aug-2013</column>
</item>
</PivotSet>
How to convert the whole PivotSet into Java object/list of items? I want to try JAXB to produce the object. But got stuck with those column elements. I expect the following result:
// List of items ...
public class Item {
private String title;
private String published_date;
// getters and setters
}

You have to use a wrapper class for PivotSet and another wrapper for Item class. Inside the Item there is a list of Columns.
Collections can either be represented as arrays, as List or as a Set. In the following exmaple I use List.
Usage example follows after.
class PivotSet {
private List<Item> item;
public List<Item> getItem() {
return item;
}
public void setItem(List<Item> item) {
this.item = item;
}
}
class Item {
private List<Column> column;
public List<Column> getColumn() {
return column;
}
public void setColumn(List<Column> column) {
this.column = column;
}
}
class Column {
private String name;
private String value;
#XmlAttribute
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Usage example:
// Read an XML
PivotSet p = JAXB.unmarshal(new File("pivotset-in.xml"), PivotSet.class);
// Write an XML
JAXB.marshal(p, new File("pivotset-out.xml"));
This solution works also if other columns appear later in the XML.
If you want a representation in the format you showed, you can write a simple converter method which would convert the list of Column instances to your preferred form (finding columns with name "title" and "published_date" and store their values to your custom Item class.

I have to do the same, but using JACKSON. This is my solution:
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
#JacksonXmlRootElement(localName = "PivotSet")
public class PivotSet {
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName="item")
private List<Item> items = new ArrayList<Item>();
}
#Getter
#Setter
public class Item {
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName="column")
List<Column> columns = new ArrayList<Column>();
}
#Getter
#Setter
public class Column {
#JacksonXmlProperty(isAttribute = true, localName = "name")
private String name;
#JacksonXmlText
private String value;
}
I recover the pivot XML into a oracle.sql.Clob property, so i have to convert it to String:
Reader charStream = ((java.sql.Clob)pivotXml).getCharacterStream();
String xmlString = IOUtils.toString(charStream);
and finally, the deserialization:
XmlMapper xmlMapper = new XmlMapper();
PivotSet value = xmlMapper.readValue(xmlString, PivotSet.class);

Related

JAXB XMLAdapter: Is there a way to convert this method into JAXB XmlAdapter

I have a JSON file that I am trying to convert into XML using the JAXB annotation approach. Everything is working fine now and I able to convert the JSON to XML. Now I am trying to refactor the code a little bit so that my class would look clean. Hence, I am trying to remove the method which is present in my class and make it JAXB XMLAdapter so that it can be reused by other classes.
Basically I would like to move the XMLSupport method from CarInfo class to XMLAdapter. I am not sure how to populate the CarInfo objects when I move them to the XMLAdapter.
Following is my JSON file (it has been modified for simplicity purpose):
{
"brand": "Ferari",
"build": "Italy",
"engine": "Mercedes",
"year": "2021"
}
Following is the XML that I expect JAXB to provide: (Observe the carInfo tag which is not present in JSON but I need in XML to match the standard XSD)
<?xml version="1.0"?>
<Car>
<brand>Ferari</brand>
<build>Italy</build>
<carinfo>
<engine>Mercedes</engine>
<year>2021</year>
</carinfo>
</Car>
Following are the classes that I have: (Tha Car class that matches the JSON elements)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlTransient
#XmlSeeAlso({MyCar.class});
public class Car{
private String brand;
private String build;
#XmlTransient
private String engine;
#XmlTransient
private String year;
//Getter, Setters and other consturctiores ommited
}
Following is MYCar class that builds the XML by adding the carInfo tag:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Car")
#XmlType(name = "Car", propOrder = {"brand","build", "carInfo"})
public class MyCar extends Car{
#XmlElement(name="carInfo")
private CarInfo carInfo;
public MyCar xmlSupport() {
if(carInfo == null){
carInfo = new Carinfo();
}
carInfo.setEngine(getEngine);
carInfo.setYear(getYear());
return this;
}
}
Following is my CarInfo class which acts as a helper to build the additional tag around MyCar class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder = {"engine","year"})
public class Carinfo{
private String engine;
private String year;
//Getter, Setters and other consturctiores ommited
}
Following is my Main class which actually builds the XML by using the JAXBCOntext
public class Main{
public static void main(String[] args){
JAXBContext context = JAXBContext.newInstance(MyCar.class);
Marshaller mar = context.createMarshaller();
mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
mar.marshal((MyCar).xmlSupport(), System.out);
System.out.println("-----------------");
}
}
Now coming back to my main question:
As we can see from MyCar class I have the XMLSupport method which is actually populating the CarInfo objects and then using that method I am creating the XML. Is there a way I can move this to XMLAdapter?
I tried creating the XMLAdapter but I am not sure how can I populate the CarInfo objects from the adapter:
public class MyCar extends Car{
#XmlElement(name="carInfo")
#XmlJavaTypeAdapter(ExtensionAdapter.class)
#XmlElement(name = "carInfo")
private CarInfo carInfo;
}
Following is my Adapter class I've tried:
public class ExtensionAdapter extends XmlAdapter<CarInfo, CarInfo> {
#Override
public CarInfo unmarshal(CarInfo valueType) throws Exception {
System.out.println("UN-MARSHALLING");
return null;
}
#Override
public CarInfo marshal(CarInfo boundType) throws Exception {
System.out.println("MARSHALLING");
System.out.println(boundType);
//I get boundType as NULL so I am not sure how to convert the xmlSupport Method to Adapter so I can use this adapter with multiple class
return null;
}
}
You don't need any adapters, you just need a well-defined POJO.
The trick is using getters and setters, not field access, so we can do delegation, and then use #JsonIgnore and #XmlTransient to control which getter/setter methods are used for JSON vs XML.
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#XmlRootElement(name = "Car")
#XmlType(propOrder = { "brand", "build", "carinfo" })
#JsonPropertyOrder({ "brand", "build", "engine", "year" })
public final class Car {
#XmlType(propOrder = { "engine", "year" })
public static final class Info {
private String engine;
private String year;
public String getEngine() {
return this.engine;
}
public void setEngine(String engine) {
this.engine = engine;
}
public String getYear() {
return this.year;
}
public void setYear(String year) {
this.year = year;
}
#Override
public String toString() {
return "Info[engine=" + this.engine + ", year=" + this.year + "]";
}
}
private String brand;
private String build;
private Info carinfo;
public Car() {
// Nothing to do
}
public Car(String brand, String build, String engine, String year) {
this.brand = brand;
this.build = build;
this.carinfo = new Info();
this.carinfo.setEngine(engine);
this.carinfo.setYear(year);
}
public String getBrand() {
return this.brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getBuild() {
return this.build;
}
public void setBuild(String build) {
this.build = build;
}
#JsonIgnore // For XML, not JSON
public Info getCarinfo() {
if (this.carinfo == null)
this.carinfo = new Info();
return this.carinfo;
}
public void setCarinfo(Info info) {
this.carinfo = info;
}
#XmlTransient // For JSON, not XML
public String getEngine() {
return getCarinfo().getEngine();
}
public void setEngine(String engine) {
getCarinfo().setEngine(engine);
}
#XmlTransient // For JSON, not XML
public String getYear() {
return getCarinfo().getYear();
}
public void setYear(String year) {
getCarinfo().setYear(year);
}
#Override
public String toString() {
return "Car[brand=" + this.brand + ", build=" + this.build + ", carinfo=" + this.carinfo + "]";
}
}
Test
Car car = new Car("Ferari", "Italy", "Mercedes", "2021");
// Generate JSON
ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.enable(SerializationFeature.INDENT_OUTPUT);
String json = jsonMapper.writeValueAsString(car);
// Generate XML
JAXBContext jaxbContext = JAXBContext.newInstance(Car.class);
Marshaller xmlMarshaller = jaxbContext.createMarshaller();
xmlMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
String xml;
try (StringWriter writer = new StringWriter()) {
xmlMarshaller.marshal(car, writer);
xml = writer.toString();
}
// Print generated results
System.out.println(car);
System.out.println(json);
System.out.println(xml);
// Parse JSON
Car carFromJson = jsonMapper.readValue(json, Car.class);
System.out.println(carFromJson);
// Parse XML
Unmarshaller xmlUnmarshaller = jaxbContext.createUnmarshaller();
Car carFromXml = xmlUnmarshaller.unmarshal(new StreamSource(new StringReader(xml)), Car.class).getValue();
System.out.println(carFromXml);
Outputs
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
{
"brand" : "Ferari",
"build" : "Italy",
"engine" : "Mercedes",
"year" : "2021"
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Car>
<brand>Ferari</brand>
<build>Italy</build>
<carinfo>
<engine>Mercedes</engine>
<year>2021</year>
</carinfo>
</Car>
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
As you can see, the generated JSON and XML is exactly what you wanted, and the last two lines of output shows that parsing works as well.

How to parse a complex element into a Map, using a attribute as key and the whole element as value, in JAXB Adapter

I would like to parse a list of complex XML elements into a Map, where the key would be an attribute and the value the whole object/element.
Here is an example of my XML:
<product>
<documents>
<document code="100" clazz="DocumentA">
<properties>
<property name="PropA" value="123" />
<property name="PropB" value="qwerty" />
<property name="PropC" value="ABC" />
</properties>
</document>
</documents>
</product>
The example of my class Document:
public class Document {
private Integer code;
private String clazz;
private List<Propertiy> properties;
//getters and setters...
}
I don't know if it's possible, but i would like to parse document elements into a Map, where the key is the attriute code.
Can someone help me?
You could try using an adapter. Let's start building up the POJOs according to your xml. First you have the product:
#XmlRootElement
public class Product {
#XmlElementWrapper(name = "documents")
#XmlElement(name = "document")
private List<Document> documents;
}
Then documents in that product:
#XmlRootElement
public class Document {
#XmlAttribute
private Integer code;
#XmlAttribute
private String clazz;
#XmlElement(name = "properties")
private Properties properties;
}
Inside the properties we use the adapter in order to get the desired Map:
#XmlAccessorType(XmlAccessType.FIELD)
public class Properties {
#XmlElement(name = "property")
#XmlJavaTypeAdapter(MyMapAdapter.class)
private Map<String, String> properties;
}
Our adapter would take the incoming xml in a way that would understand it and turn adapt it to something else (i.e. map). So let's create first a POJO for the property as in xml:
#XmlAccessorType(XmlAccessType.FIELD)
public class Property {
#XmlAttribute
private String name;
#XmlAttribute
private String value;
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
Then we use it in the adapter:
public class MyMapAdapter extends XmlAdapter<Property, Map<String, String>> {
private HashMap<String, String> hashMap = new HashMap<String, String>();
#Override
public Map<String, String> unmarshal(Property v) throws Exception {
hashMap.put(v.getName(), v.getValue());
return hashMap;
}
#Override
public Property marshal(Map<String, String> v) throws Exception {
// do here actions for marshalling if u also marshal
return null;
}
}
Running this, would unmarshal the payload and it would have the values in the map as desired. Hope it helps

JaxB: How Do I Retrieve Text Attribute from Nested Element?

I want the Country class to store the "ISO_3166-1_Alpha-2_Code" code. The code currently gets back the "ISO_3166-1_Numeric-3_Code" code. Can't figure out how to tweak the Country class to get the specific attribute I want.
XML:
<?xml version='1.0' encoding='UTF-8'?>
<wd:Message_Event_Configuration">
<wd:Message_Event_Configuration_Data>
<wd:Country_Reference wd:Descriptor="Saint Martin">
<wd:ID wd:type="WID">66b7082a21e510000961bb6d82b5002a</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-2_Code">MF</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-3_Code">MAF</wd:ID>
<wd:ID wd:type="ISO_3166-1_Numeric-3_Code">663</wd:ID>
</wd:Country_Reference>
<wd:Country_Reference wd:Descriptor="Saint Barthelemy">
<wd:ID wd:type="WID">881527f6cec910000ba81e8dccf61127</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-2_Code">BL</wd:ID>
<wd:ID wd:type="ISO_3166-1_Alpha-3_Code">BLM</wd:ID>
<wd:ID wd:type="ISO_3166-1_Numeric-3_Code">652</wd:ID>
</wd:Country_Reference>
</wd:Message_Event_Configuration_Data>
</wd:Message_Event_Configuration>
Country List:
#XmlRootElement(name = "Message_Event_Configuration")
#XmlAccessorType(XmlAccessType.FIELD)
public class Countries {
#XmlElementWrapper(name = "Message_Event_Configuration_Data")
#XmlElement(name = "Country_Reference")
private List<Country> countries = new ArrayList<Country>();
public List<Country> getCountries() {
return countries;
}
public void setCountries(List<Country> countries) {
this.countries = countries;
}
}
Country:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Country_Reference")
public class Country {
#XmlElement(name = "ID")
private String isoCode;
public Country() {
}
public Country(String isoCode) {
this.isoCode = isoCode;
}
#XmlAttribute(name = "ISO_3166-1_Alpha-2_Code")
public String getISOCode() {
return isoCode;
}
public void setISOCode(String isoCode) {
this.isoCode = isoCode;
}
}
The <Country_Reference> XML element contains the ISO codes in a rather
sophisticated way within several <wd:ID> XML elements.
It is therefore much too simple to model them as a Java String property.
Instead, you need to model the Java-structure with more similarity to the XML-structure.
The sequence of XML elements <wd:ID> can be modeled by a property List<ID> idList
which needs to be annotated by#XmlElement(name="ID") .
The XML attribute wd:Descriptor="...." can be modeled by a property String descriptor
which needs to be annotated by #XmlAttribute(name="Descriptor").
For your convenience you can add an all-arguments-constructor and some methods for getting
the WID and ISO codes from the List<ID>.
#XmlAccessorType(XmlAccessType.FIELD)
public class Country {
#XmlAttribute(name = "Descriptor")
private String descriptor;
#XmlElement(name = "ID")
private List<ID> idList;
public Country() {
}
public Country(String descriptor, String wid, String isoAlpha2Code, String isoAlpha3Code, String isoNumeric3Code) {
this.descriptor = descriptor;
idList = new ArrayList<>();
idList.add(new ID("WID", wid));
idList.add(new ID("ISO_3166-1_Alpha-2_Code", isoAlpha2Code));
idList.add(new ID("ISO_3166-1_Alpha-3_Code", isoAlpha3Code));
idList.add(new ID("ISO_3166-1_Numeric-3_Code", isoNumeric3Code));
}
public String getWid() {
return getIdByType("WID");
}
public String getIsoAlpha2Code() {
return getIdByType("ISO_3166-1_Alpha-2_Code");
}
public String getIsoAlpha3Code() {
return getIdByType("ISO_3166-1_Alpha-3_Code");
}
public String getIsoNumeric3Code() {
return getIdByType("ISO_3166-1_Numeric-3_Code");
}
private String getIdByType(String idType) {
for (ID id : idList) {
if (id.getType().equals(idType))
return id.getValue();
}
return null;
}
}
The XML elements <wd:ID> are quite complex. Therefore we need a separate POJO class for modeling them.
Let's call the class ID.
The XML text between <wd:ID ..> and </wd:ID> is modeled by the property String value
which needs to be annotated by #XmlValue.
The XML attribute wd:type="..." is modeled by the property String type
which needs to be annotated by #XmlAttribute.
For convenient use by the class Country above, an all-arguments-constructor is added.
#XmlAccessorType(XmlAccessType.FIELD)
public class ID {
#XmlAttribute
private String type;
#XmlValue
private String value;
public ID() {
}
public ID(String type, String value) {
this.type = type;
this.value = value;
}
// public getters and setters (omitted here fro brevity)
}
The screenshot below (taken from within the debugger) visualizes the Java structure
and confirms that the unmarshalling of your XML example works correctly:

JAXB collection mapping

I'm new in dealing with XML in Java, but one of services I use returns it as result. Until now, I've dealt with mapping XML into POJO, using #XmlRootElement-like annotations. But now I have absolutely no idea to do with this document:
<?xml version="1.0" encoding="windows-1251"?>
<response>
<status>
<code>0</code>
</status>
<result>
<limit>2</limit>
...
<data>
<row0>
<ID>85427</ID>
<name>Default</name>
<siteID>40628</siteID>
... some elements
</row0>
</data>
</result>
</response>
Until now, I used these classes to bind XML (except 'data' node) into POJO:
#XmlRootElement(name="response")
public class Response {
private Status status;
private String result;
public Status getStatus() {
return status;
}
#XmlElement(name = "status")
public void setStatus(Status status) {
this.status = status;
}
public String getResult() {
return result;
}
#XmlElement(name ="result")
public void setResult(String result) {
this.result = result;
}
}
#XmlRootElement(name = "status")
public class Status {
private String ID;
private String code;
private String error;
public String getID() {
return ID;
}
#XmlElement(name = "ID")
public void setID(String ID) {
this.ID = ID;
}
public String getCode() {
return code;
}
#XmlElement(name = "code")
public void setCode(String code) {
this.code = code;
}
public String getError() {
return error;
}
#XmlElement(name = "error")
public void setError(String error) {
this.error = error;
}
}
But now I need to bind content as collection of elements. I've looked for examples, and everywhere people use specific tag to define root element for collection's item, but in this document, root tags will be as <row0>, <row1> etc.
I use Jackson, which, if I understand correctly, uses JAXB annotations to define XML to POJO bind rules. So could this deal be solved this way, or I have to manipulate this document in DOM-style?
You can solve your problem by using something like this :
Create your Row class that represents your <row0>,<row1> etc... and map it with JAXB like you would do it normally.
Then create a class that extends XmlAdapter<List<Row>,List<Element>> and define the abstracts methods marshall and unmarshall.
Here is some Javadoc to help you :
XmlAdapter : http://docs.oracle.com/javaee/5/api/javax/xml/bind/annotation/adapters/XmlAdapter.html
Element : http://docs.oracle.com/javase/1.5.0/docs/api/org/w3c/dom/Element.html
Then create a Data class :
public class Data{
private List<Row> rows;
public List<Row> getRows() {
return rows;
}
#XmlAnyElement
#XmlJavaTypeAdapter(MyRowsAdapter.class)
public void setRows(List<Row> result) {
this.rows = rows;
}
}
Then you can add this mapping to your Response class :
private Data data;
public Data getData() {
return data;
}
#XmlElement(name="data")
public void setData(Data data) {
this.data = data;
}
Note that for this solution to work, your <data> element must only contains elements like your Row.
Also, you cannot use #XmlElementWrapper instead of using a Data class because of a bug in JAXB which make incompatible #XmlElementWrapper and #XmlJavaTypeAdapter : https://java.net/jira/browse/JAXB-787

Object properties lowercased when reading as JSONObject

I have the following problem. I'm reading a list of records from my MySQL database with Hibernate template, and then I need to modify the structure so I'm JSONObject and JSONArray (using I guess the official library : http://www.json.org/java/). If I'm using the List as a server response, records fields are properly named (thanks to #JsonProperty annotation used). But if I'm trying to create a JSONObject out of this List element, I'm getting all my fields starting with small letter, which breaks my UI.
This is my 'Task' model used :
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
#JsonAutoDetect
#JsonIgnoreProperties(ignoreUnknown = true)
#Entity
#Table(name="tasks")
public class Task {
#Id
#GeneratedValue
#Column(name="Id")
private int Id;
#Column(name="Name", nullable=false)
private String Name;
#JsonProperty("Id")
public int getId() {
return Id;
}
#JsonProperty("Id")
public void setId(int id) {
this.Id = id;
}
#JsonProperty("Name")
public String getName() {
return Name;
}
#JsonProperty("Name")
public void setName(String name) {
this.Name = name;
}
}
and here's the code used for getting records from the DB (stripped of all the unnecessary parts):
public List<Task> getEvents() {
DetachedCriteria criteria = DetachedCriteria.forClass(Task.class);
return hibernateTemplate.findByCriteria(criteria);
}
private static JSONArray read() throws JSONException{
List<Task> list = getEvents();
Iterator<Task> listIterator = list.iterator();
JSONArray ret = new JSONArray();
String parentId;
while(listIterator.hasNext()){
Task task = listIterator.next();
JSONObject taskJSON = new JSONObject(task);
ret.put(taskJSON);
}
}
As you can see in my server response, all fields names start with small letter :
{"id":18,"name":"Release"}
Any ideas how to override this ?
Your class is overannotated, and breaks Java code conventions.
The minimum required is as follows. Everything else you've added is done by default.
#Entity
#Table(name="tasks")
public class Task {
#Id
#GeneratedValue
#Column(name="Id")
#JsonProperty("Id")
private int id;
#Column(name="Name", nullable=false)
#JsonProperty("Name")
private String name;
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;
}
}
To serialise your class all you should need is the ObjectMapper class
String json = new ObjectMapper().writeValueAsString(getEvents());
The output of which should look like:
[{"Id":18,"Name":"Build"}, {"Id":19,"Name":"Release"}]
I would discourage using capitalised property names if possible as it goes against general code conventions.
The JSON.org API is intended for very simple serialization/deserialization, it can't do what your looking for. Having said that, the majority of your annotations are actually from Jackson, which can do what your trying to accomplish.
You already have the POJOs properly annotated for Jackson, so return a JSON string conforming to them, serialize using an ObjectMapper:
final List<Task> list = getEvents();
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(list);

Categories

Resources