How to unmarshal xml with repetitive entries using JAXB and Spring - java

I have to convert xml to Map<String,String>. I have following XML structure:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Environments>
<Environment Name="A" URIPath="http://a.com" />
<Environment Name="B" URIPath="http://b.com" />
<Environment Name="C" URIPath="http://c.com" />
</Environments>
I tried multiple ways but eneded with Class has two properties of the same name "URIPath". What is the right design for unmarshalling this XML?
UPDATE:
Using provided solution #1 I am getting :
Class has two properties of the same name "environments"
this problem is related to the following location:
at public java.util.List app.model.Environments.getEnvironments()
at app.model.Environments
this problem is related to the following location:
at public java.util.List app.model.Environments.environments
at app.model.Environments
Class has two properties of the same name "URIPath"
this problem is related to the following location:
at public java.lang.String app.model.Environment.getURIPath()
at app.model.Environment
at public java.util.List app.model.Environments.environments
at app.model.Environments
this problem is related to the following location:
at java.lang.String app.model.Environment.URIPath
at app.model.Environment
at public java.util.List app.model.Environments.environments
at app.model.Environments
] with root cause

You have 2 options:
1) Unmarshal a collection of Environment instances with 2 fields: Name and URIPath. You can build the map later if you want to from the collection.
2) Use a custom XmlAdapter which properly creates the map from the collection.
Elaborating Solution #1
This solution needs the following classes:
class Environments {
#XmlElement(name = "Environment")
public List<Environment> environments;
}
class Environment {
#XmlAttribute(name = "Name")
public String Name;
#XmlAttribute(name = "URIPath")
public String URIPath;
}
And using these, unmarhaling:
Environments environments = JAXB.unmarshal(new File("env.xml"),
Environments.class);
Elaborating Solution #2
If you want to use a custom XmlAdapter to directly get a Map, the XML input in its current form cannot be used. It has to be slightly modified to put a wrapper XML element around it. This is required because in Java the Map is a property of a class but the <Environments> tag is just the wrapper for the Map. Example modified XML:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<wrapper>
<Environments>
<Environment Name="A" URIPath="http://a.com" />
<Environment Name="B" URIPath="http://b.com" />
<Environment Name="C" URIPath="http://c.com" />
</Environments>
</wrapper>
Taking this as the input XML, here is the solution:
class EnvironmentMap {
#XmlJavaTypeAdapter(value = EnvMapAdapter.class)
#XmlElement(name = "Environments")
public Map<String, String> envMap;
}
class Environments {
#XmlElement(name = "Environment")
public List<Environment> environments;
}
class Environment {
#XmlAttribute(name = "Name")
public String name;
#XmlAttribute(name = "URIPath")
public String uriPath;
}
class EnvMapAdapter extends XmlAdapter<Environments, Map<String, String>> {
#Override
public Map<String, String> unmarshal(Environments envs) throws Exception {
Map<String, String> map = new HashMap<>();
for (Environment env : envs.environments)
map.put(env.name, env.uriPath);
return map;
}
#Override
public Environments marshal(Map<String, String> map) throws Exception {
Environments environments = new Environments();
// This method is only called if you marshal (Java -> XML)
environments.environments = new ArrayList<>(map.size());
for (Entry<String, String> entry : map.entrySet()) {
Environment e = new Environment();
e.name = entry.getKey();
e.uriPath = entry.getValue();
environments.environments.add(e);
}
return environments;
}
}
And using it:
EnvironmentMap envMap = JAXB.unmarshal(new File("env2.xml"),
EnvironmentMap.class);
System.out.println(envMap.envMap);
Which prints:
{A=http://a.com, B=http://b.com, C=http://c.com}

Related

How to write multiple XML files for child beans in Spring Batch ItemWriter?

I have a batch job that retrieves records from a database, processes them, and passes the result into the writer. The bean passed to the writer has four fields that need to be written to separate xml files. One of the fields is a bean representing the original record, and the other three fields are collections of child elements associated with the record.
I initially tried to use jackson to parse the beans and generate the files, but I found that approach had trouble when applied to the batch model.
Next, I've shifted to using StaxEventItemWriters for each child field, which individually seem perfectly adequate, but I'm having trouble implementing a writer that can handle all the various sub-types. I've looked into the CompositeItemWriter and ClassifierCompositeItemWriter, but they seem more suited to having multiple writers for the same type of bean, whereas I need multiple writers appropriate for differing types. Any advice would be greatly appreciated!
Domain example:
public class MyBean {
private RecordBean recordBean;
private List<ChildTypeA> aBeans;
private List<ChildTypeB> bBeans;
private List<ChildTypeC> cBeans;
}
#XmlRootElement(name = "RECORD")
public class RecordBean extends MyAbstractBean {
#XmlElement(name = "ID")
private String recordId;
#XmlElementWrapper(name = "A_CHILDREN")
#XmlElement(name="CHILD_TYPE_A")
List<Long> aChildIds}
#XmlElementWrapper(name = "B_CHILDREN")
#XmlElement(name="CHILD_TYPE_B")
List<Long> bChildIds}
#XmlElementWrapper(name = "C_CHILDREN")
#XmlElement(name="CHILD_TYPE_C")
List<Long> cChildIds}
}
#XmlRootElement(name = "CHILD_TYPE_A")
public class ChildTypeA extends MyAbstractBean {
#XmlElement(name = "ID") private String aId;
}
#XmlRootElement(name = "CHILD_TYPE_B")
public class ChildTypeB extends MyAbstractBean {
#XmlElement(name = "ID") private String bId;
}
#XmlRootElement(name = "CHILD_TYPE_C")
public class ChildTypeC extends MyAbstractBean {
#XmlElement(name = "ID") private String cId;
}
For each container bean passed to the writer, I need to create a unique XML file for each RecordBean e.g. record_1.xml, and I need to write each collection into an aggregate file that will serve as a library of all children for that child type, across all the records.
Output example:
record_1.xml
<?xml version="1.0" encoding="UTF-8"?>
<RECORD>
<ID>1</ID>
<A_CHILDREN>
<CHILD_TYPE_A>1</CHILD_TYPE_A>
<CHILD_TYPE_A>2</CHILD_TYPE_A>
</A_CHILDREN>
<B_CHILDREN>
<CHILD_TYPE_B>1</CHILD_TYPE_B>
<CHILD_TYPE_B>2</CHILD_TYPE_B>
</B_CHILDREN>
<A_CHILDREN>
<CHILD_TYPE_C>1</CHILD_TYPE_C>
<CHILD_TYPE_C>2</CHILD_TYPE_C>
</A_CHILDREN>
</RECORD>
</xml>
record_2.xml
<?xml version="1.0" encoding="UTF-8"?>
<RECORD>
<ID>2</ID>
<A_CHILDREN>
<CHILD_TYPE_A>3</CHILD_TYPE_A>
<CHILD_TYPE_A>4</CHILD_TYPE_A>
</A_CHILDREN>
<B_CHILDREN>
<CHILD_TYPE_B>3</CHILD_TYPE_B>
<CHILD_TYPE_B>4</CHILD_TYPE_B>
</B_CHILDREN>
<A_CHILDREN>
<CHILD_TYPE_C>3</CHILD_TYPE_C>
<CHILD_TYPE_C>4</CHILD_TYPE_C>
</A_CHILDREN>
</RECORD>
</xml>
a_children.xml
<?xml version="1.0" encoding="UTF-8"?>
<A_CHILDREN>
<CHILD_TYPE_A>
<ID>1</ID>
</CHILD_TYPE_A>
<CHILD_TYPE_A>
<ID>2</ID>
</CHILD_TYPE_A>
<CHILD_TYPE_A>
<ID>3</ID>
</CHILD_TYPE_A>
<CHILD_TYPE_A>
<ID>4</ID>
</CHILD_TYPE_A>
</A_CHILDREN>
</xml>
<!-- Decide which format to use -->
b_children.xml & c_children.xml are the same as a_children.xml.
So after the better part of two days, I decided to take a different approach. I think it would be possible to create a set of writers to handle the task, but I think it would require a fair amount of customization, and a complex hierarchy of delegation.
For example:
CompositeItemWriter delegates to:
ClassifierCompositeItemWriter classifies, and delegates to:
StaxEventItemWriter for ChildTypeA
StaxEventItemWriter for ChildTypeB
StaxEventItemWriter for ChildTypeC
MultiResourceItemWriter Dynamically assigns FilesystemResource for each RecordBean.
Despite the fact that it probably could be done, I realized that it's just simpler to write the XML as strings wherever I want. Something along the lines of:
#Component
#Scope("step")
public class MyXmlWriter implements ItemWriter<MyBean> {
#Override
public void write(List<? extends MyBean> beans) throws Exception {
for(MyBean bean : beans) {
this.writeRecordBean(bean.getRecordBean());
bean.getAChildIds().forEach(a -> this.writeElement(a, pathA));
bean.getBChildIds().forEach(b -> this.writeElement(b, pathB));
bean.getCChildIds().forEach(c -> this.writeElement(c, pathC));
}
}
private void writeElement(Long Id, Path path) {
// Handle serialization and FileIO here
}
.
.
.
}
Anyway, I hope this helps anyone digging into the Spring Batch weeds like I was. Cheers!

jackson XML serialization: list of inherited classes

I want to serialize/deserialize such XML:
<Multi>
<child-001>
<name/>
<value/>
</child-001>
<child-002>
<name/>
<value/>
</child-002>
</Multi>
where the child-001 and child-002 are classes inherited from the same parent.
public abstract class Parent {
private String name;
}
#JacksonXmlRootElement(localName = "child-001")
public class Child001 extends Parent {
private String value;
}
#JacksonXmlRootElement(localName = "child-002")
public class Child002 extends Parent {
private String value;
}
The encapsulating class looks like this:
class Multi {
#JacksonXmlElementWrapper(useWrapping = false)
private List<Parent> nodes = new ArrayList<>();
}
Without #JacksonXmlElementWrapper(useWrapping = false) I've got:
<Multi>
<nodes>
<nodes>
<name>name001</name>
<value>value001</value>
</nodes>
<nodes>
<name>name002</name>
<value>value002</value>
</nodes>
</nodes>
</Multi>
With the annotation I've got:
<Multi>
<nodes>
<name>name001</name>
<value>value001</value>
</nodes>
<nodes>
<name>name002</name>
<value>value002</value>
</nodes>
</Multi>
This is quite close to what I need, but still need to replace "nodes" with "child-001" and "child-002".
Can someone point me where to find a solution? Or should I use JAXB instead of Jackson?
Thanks
Normally to represent exaclty the example XML file, Multi class should look like this and this works well:
class Multi {
#JacksonXmlProperty(localName = "child-001")
private Child001 child001;
#JacksonXmlProperty(localName = "child-002")
private Child002 child002;
}
In your code you are using a List of objects of parent class instead, presumably because you may have any number of elements deserialized into subclasses of Parent ? That's a case of polymorphic deserialization that requires defining type info e.g.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(name = "child-001", value = Child001.class),
#JsonSubTypes.Type(name = "child-002", value = Child002.class)
})
abstract class Parent {
private String name;
}
Unfortunately, due to an as yet (jackson 2.9.2) unresolved issue Jackson expects a wrapper around each element e.g.
<Multi>
<nodes>
<child-001>
<name>n1</name>
<value>v1</value>
</child-001>
</nodes>
<nodes>
<child-002>
<name>n2</name>
<value>v2</value>
</child-002>
</nodes>
</Multi>
Until this issue is resolved consider if the first option. If there are fields you don't want serialized leave them null.

How to avoid or rename key and entry tag in jaxb?

Hi I am currently using jaxb to get my model saved to xml . My model I have one string and one hashmap. So the problem here is while exporting the hashmap to xml i am getting something like this .
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<addressMap>
<entry>
<key>col2</key>
<value>data2</value>
</entry>
<entry>
<key>col1</key>
<value>data1</value>
</entry>
</addressMap>
</customer>
SO here i donot want this entry tag and key instead of that something like below xml I expect..
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<addressMap>
<col2>data2</col2>
<col1>data1</col1>
</addressMap>
</customer>
Is it possible to achieve this
Almost. I would like to suggest a change to the xml-format. Using element names like col1, col2, etc is a "bad" idea. It's not well structured. If you can accept the following format of the xml data I can give you an example on how:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<addressMap>
<col key="col2">data2</col>
<col key="col1">data1</col>
</addressMap>
</customer>
I guess you had a class defined something like this:
#XmlRootElement
public class Customer {
#XmlElement
public Map<String,String> addressMap;
}
When marshaled with JAXB it should produce your first output. Change it to the following and add the necessary classes:
#XmlRootElement
public class Customer {
#XmlElement
public AddressMap addressMap;
}
public class AddressMap {
#XmlElement
public List<Column> col;
}
public class Column {
#XmlAttribute
public String key;
#XmlValue
public String value;
}
Fill it with your data and marshal it and the output should look like my xml example.
EDIT:
Keeping addressMap as a HashMap:
Make Customer class look like this:
#XmlRootElement
public class Customer {
#XmlElement
#XmlJavaTypeAdapter(MapAdapter.class)
public Map<String,String> addressMap;
}
and create the class MapAdapter:
public class MapAdapter extends XmlAdapter<AddressMap, Map<String,String>> {
#Override
public AddressMap marshal(Map<String,String> map) throws Exception {
AddressMap myMap = new AddressMap();
myMap.col = new ArrayList<Column>();
for (Entry<String,String> entry : map.entrySet()) {
Column col = new Column();
col.key = entry.getKey();
col.value = entry.getValue();
myMap.col.add(col);
}
return myMap;
}
#Override
public Map<String,String> unmarshal(AddressMap myMap) throws Exception {
Map<String,String> map = new HashMap<String, String>();
for (Column col : myMap.col) {
map.put(col.key, col.value);
}
return map;
}
}
Keep classes AddressMap and Column as is.

Problems with JAXB marshalling lists of non-annotated objects

I've got a bit of an issue with unmarshalling a list of closed objects using JAXB (closed in the sense that I cannot add JAXB annotation) . Basically, my XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<!-- SNIP! -->
</rdf:RDF>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<!-- SNIP! -->
</rdf:RDF>
</document>
And my Document class is:
#XmlRootElement(name = "document", namespace = Namespace.DEFAULT_NAMESPACE)
#XmlAccessorType(XmlAccessType.FIELD)
public class Document {
#XmlJavaTypeAdapter(ModelAdapter.class)
#XmlElement(name = "RDF", namespace = Namespace.RDF_NAMESPACE)
private List<Model> models;
....
Where Model is a class from a framework that I cannot add JAXB annotations to, hence the adaptor.
The implementation of ModelAdapter is as follows:
public class ModelUnmarshalAdapter extends ModelAdapter<Object, Model> {
#Override
public Model unmarshal(final Object v) throws Exception {
// Turn incoming Node into a Model object
Model model = convert(v);
return model;
}
....
}
When I unmarshal the XML I'm finding ModelUnmarshalAdapter.unmarshal() is being called twice as expected (due to the 2 RDF elements in the XML), but the Document instance models property is always null. It's like it doesn't instantiate the necessary list instance.
Any ideas would be greatly appreceiated.
Thanks
Nick
After much trial and error it turns out the solution was to subclass the closed object concrete class (in my case ModelCom, which implements the Model interface) and add the #XmlJavaTypeAdapter to that
#XmlJavaTypeAdapter(ModelAdapter.class)
public class MyModel extends ModelCom {
public Model(Graph base) {
super(base);
}
public Model(Graph base, Personality<RDFNode> personality) {
super(base, personality);
}
}
The Document class is now simply
public class Document {
#XmlElement(name = "RDF", namespace = Namespace.RDF_NAMESPACE)
private List<MyModel> models;

Add nested elements to #XmlAnyElement

I'm using JAXB and I have a problem.
I have an Element with a dynamic number of properties. As a result, the structure of its equivalent XML structure will not be static. This is an example of the desired XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<element>
<property1>value 1</property1>
<property2>value 2</property2>
<property3>value 3</property3>
<propertyn>value n</propertyn>
</element>
To generate this, I used #XmlAnyElement like this:
#XmlRootElement(name = "element")
public class Element {
private Map<String, String> properties = new HashMap<String, String>();
#XmlTransient
public Map<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
#XmlAnyElement
public List<JAXBElement<String>> getElements() {
List<JAXBElement<String>> elements = new ArrayList<JAXBElement<String>>();
for (String property: properties.keySet()) {
JAXBElement<String> jaxbElement = new JAXBElement<String>(new QName(property), String.class, properties.get(property));
elements.add(jaxbElement);
}
return elements;
}
}
This works fine! However, I'm trying to add nested elements to support multi-value properties, so that the XML will be like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<element>
<property1>value 1</property1>
<property2>value 2</property2>
<property3>value 3</property3>
<property4>
<value>value 4a</value>
<value>value 4b</value>
<value>value 4c</value>
</property4>
<propertyn>value n</propertyn>
</element>
Please note that:
The number of properties is not static.
The number of multi-value properties is not static.
The number of values of each multi-value property is not static.
My question is, how can I do that in a simple way? Is #XmlElementWrapper useful in my case?
Thanks!

Categories