Jaxb Giving null on Unmarshalling nested Soap Message - java

I need to unmarshall nested soap response to java object using jaxb. But i am always getting a null pointer exception.
I have checked almost all links out there like the following:
jaxb unmarshalling returns null
jaxb unmarshalling giving NULL to anytype element in xsd
But nothing worked out for me.
Here is my soap response message
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsap.org/sap/env/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<soapenv:Body>
<ns1:subscribeProductResponse xmlns:ns1='http://www.csapi.org/schema/parlayx/subscribe/manage/v1_0/local'>
<ns1:subscribeProductRsp>
<result>22007233</result>
<resultDescription>Temporary Order saved successfully! DataSendStep finish end.</resultDescription>
</ns1:subscribeProductRsp>
</ns1:subscribeProductResponse>
</soapenv:Body>
</soapenv:Envelope>
Here are my Java Pojo Classes
#XmlRootElement (name="subscribeProductResponse",namespace="http://www.csapi.org/schema/parlayx/subscribe/manage/v1_0/local")
#XmlAccessorType(XmlAccessType.FIELD)
public class SubscribeProductResponse {
// #XmlElementWrapper
#XmlElement(name="subscribeProductRsp")
private SubscribeProductRsp subscribeProductRsp;
public SubscribeProductRsp getSubscribeProductRsp() {
return subscribeProductRsp;
}
public void setSubscribeProductRsp(SubscribeProductRsp subscribeProductRsp) {
this.subscribeProductRsp = subscribeProductRsp;
}
}
#XmlRootElement(name="subscribeProductRsp")
public class SubscribeProductRsp {
private String result;
private String resultDescription;
public String getResult() {
return result;
}
#XmlElement(name = "result", required = true)
public void setResult(String result) {
this.result = result;
}
public String getResultDescription() {
return resultDescription;
}
#XmlElement(name = "resultDescription", required = true)
public void setResultDescription(String resultDescription) {
this.resultDescription = resultDescription;
}
#Override
public String toString() {
return "ClassPojo [result = " + result + ", resultDescription = "
+ resultDescription + "]";
}
}
Below is the code to unmarshal the response message
JAXBContext jc = JAXBContext.newInstance(SubscribeProductResponse.class);
Unmarshaller um = jc.createUnmarshaller();
SubscribeProductResponse output = (SubscribeProductResponse)um.unmarshal(soapResponse.getSOAPBody().extractContentAsDocument());
System.out.println(output.getSubscribeProductRsp().getResult());
JAXBContext jc = JAXBContext.newInstance(SubscribeProductResponse.class);
Unmarshaller um = jc.createUnmarshaller();
SubscribeProductResponse output = (SubscribeProductResponse)um.unmarshal(soapResponse.getSOAPBody().extractContentAsDocument());
System.out.println(output.getSubscribeProductRsp().getResult());
I am getting output.getSubscribeProductRsp() as null
Can anyone please tell me what i am doing wrong.

Related

Add QName as string to #XmlMixed#XmlAnyElement(lax = true) list

Sorry for the foggy title, I know it does not tell much.
Please consider the following xsd type definition:
<xsd:complexType name="TopicExpressionType" mixed="true">
<xsd:sequence>
<xsd:any processContents="lax" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="Dialect" type="xsd:anyURI" use="required"/>
<xsd:anyAttribute/>
</xsd:complexType>
Complete XSD: http://docs.oasis-open.org/wsn/b-2.xsd
Corresponding JAXB generated Java class:
package org.oasis_open.docs.wsn.b_2;
import org.w3c.dom.Element;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "TopicExpressionType", propOrder = {
"content"
})
public class TopicExpressionType {
#XmlMixed
#XmlAnyElement(lax = true)
protected List<Object> content;
#XmlAttribute(name = "Dialect", required = true)
#XmlSchemaType(name = "anyURI")
protected String dialect;
#XmlAnyAttribute
private Map<QName, String> otherAttributes = new HashMap<QName, String>();
public List<Object> getContent() {
if (content == null) {
content = new ArrayList<Object>();
}
return this.content;
}
public String getDialect() {
return dialect;
}
public void setDialect(String value) {
this.dialect = value;
}
public Map<QName, String> getOtherAttributes() {
return otherAttributes;
}
}
The first goal is to produce an XML like this with JAXB:
<wsnt:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</wsnt:TopicExpression>
Please note the followings:
The value of the TopicExpression element is basically a query string that refers to QNames. Example: tns:t1/*/t3
The value of the TopicExpression element contains one or more QName like strings (tns:t1). It must be a string as in the example, it cannot be an Element (e.g.: <my-expresseion>tns:t1/*/t3<my-expresseion/>)
The value of the TopicExpression element is an arbitrary string (at least from the schema's perspective, it follows the rules defined here: https://docs.oasis-open.org/wsn/wsn-ws_topics-1.3-spec-os.pdf page 18)
Even though the value is a string, I need to define the corresponding name space declarations. So if I have an expression like this:
tns:t1 then xmlns:tns has to be declared. If my expresseion is tns:t1/*/tns2:t3 then both xmlns:tns and xmlns:tns2 have to be declared.
The second goal is to get the value of TopicExpression on the other side together with the namespace, using JAXB.
I am completely stuck, I don't know how I could implement this. My only idea is to manually build the value for the TopicExpression and somehow tell the marshaller to include the related namespace declaration despite there is no actual element using it.
Update
Example for a complete SOAP request that includes the before mentioned TopicExpression:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header>
<Action xmlns="http://www.w3.org/2005/08/addressing">http://docs.oasis-open.org/wsn/bw-2/NotificationProducer/SubscribeRequest</Action>
<MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:57182d32-4e07-4f5f-8ab3-24838b3e33ac</MessageID>
</env:Header>
<env:Body>
<ns3:Subscribe xmlns:ns3="http://docs.oasis-open.org/wsn/b-2" xmlns:ns4="http://www.w3.org/2005/08/addressing" >
<ns3:ConsumerReference>
<ns4:Address>http://my-notification-consumer-url</ns4:Address>
</ns3:ConsumerReference>
<ns3:Filter>
<ns3:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple" xmlns:ns5="http://my.org/TopicNamespace" xmlns:ns6="http://extension.org/TopicNamespace">
ns5:t1/*/ns6:t3
<ns3:TopicExpression/>
</ns3:Filter>
</ns3:Subscribe>
</env:Body>
</env:Envelope>
Not sure, If I have understood your requirement correctly. See if this code sample is helpful for you. If not then maybe try to edit your question a bit and make me understand what exactly you are looking for. I will try to modify and update the code accordingly. Trying with a simple example would be better than providing the complete XSD. Also, look into the following methods: beforeMarshal and afterUnmarshal.
Following is the XML I am trying to marshal and unmarshal
<tns:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</tns:TopicExpression>
TopicExpressionType.class:
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class TestPojo {
#XmlValue
private String TopicExpression;
#XmlAnyAttribute
private Map<String, Object> anyAttributes = new HashMap<>();
}
Main.class:
public class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("topic.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(TestPojo.class).createUnmarshaller();
final TestPojo topic = unmarshaller.unmarshal(xmlStreamReader, TestPojo.class).getValue();
System.out.println(topic.toString());
StringWriter stringWriter = new StringWriter();
Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("http://my.org/TopicNamespace", "tns");
Marshaller marshaller = JAXBContext.newInstance(TestPojo.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);
QName qName = new QName("http://my.org/TopicNamespace","TopicExpression","tns");
JAXBElement<TestPojo> root = new JAXBElement<TestPojo>(qName, TestPojo.class, topic);
marshaller.marshal(root, stringWriter);
String result = stringWriter.toString();
System.out.println(result);
}
}
As you can see as of now I have populated the namespaces map directly. If this is dynamic then you can populate the same in a map and accordingly you can add it while marshaling.
This will provide the following output during the unmarshalling:
TestPojo(TopicExpression=
tns:t1/*/t3
, anyAttributes={Dialect=http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete})
During marshaling:
<tns:TopicExpression xmlns:tns="http://my.org/TopicNamespace" Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete">
tns:t1/*/t3
</tns:TopicExpression>
So the solution I've implemented:
Created a new TopicExpressionType class which has fields not only for the expression but for the namespaces too, used in the expression:
public class TopicExpressionType {
String dialect;
String expression;
List<Namespace> namespaces;
public TopicExpressionType(String dialect, String expression, List<Namespace> namespaces) {
this.dialect = dialect;
this.expression = expression;
this.namespaces = namespaces;
}
public static class Namespace {
String prefix;
String namespace;
public Namespace(String prefix, String namespace) {
this.prefix = prefix;
this.namespace = namespace;
}
public String getPrefix() {
return prefix;
}
public String getNamespace() {
return namespace;
}
}
}
Then implemented an XmlAdapter that is aware of the specifics, knows how to extract namespace prefixes from the expression string and it can read/write namespace declarations on the TopicExpression XML element:
public class TopicExpressionTypeAdapter extends XmlAdapter<Element, TopicExpressionType> {
#Override
public Element marshal(TopicExpressionType topicExpression) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://docs.oasis-open.org/wsn/b-2", "mns1:TopicExpression");
element.setAttribute("Dialect", topicExpression.getDialect());
element.setTextContent(topicExpression.getExpression());
for (var ns : topicExpression.namespaces) {
element.setAttribute("xmlns:" + ns.prefix, ns.namespace);
}
return element;
}
#Override
public TopicExpressionType unmarshal(Element arg0) throws Exception {
if (arg0.getFirstChild() instanceof Text text) {
var expression = text.getData();
if (expression == null || expression.isBlank())
throw new TopicExpressionAdapterException("Empty content");
// Extract the prefixes from the expression
var namespacePrefixes = new ArrayList<String>();
getNamespacePrefixes(expression, namespacePrefixes);
//Now get the namespaces for the prefixes
var nsMap = new ArrayList<TopicExpressionType.Namespace>();
for (var prefix : namespacePrefixes) {
var namespace = arg0.getAttribute("xmlns:" + prefix);
if (namespace == null || namespace.isBlank())
throw new TopicExpressionAdapterException("Missing namespace declaration for the following prefix: " + prefix);
nsMap.add(new TopicExpressionType.Namespace(prefix, namespace));
}
var dialect = arg0.getAttribute("Dialect");
if (dialect == null || dialect.isBlank())
throw new TopicExpressionAdapterException("Missing Dialect attribute");
return new TopicExpressionType(dialect, expression, nsMap);
} else {
throw new TopicExpressionAdapterException("Unexpected child element type: " + arg0.getFirstChild().getClass().getName());
}
}
public static class TopicExpressionAdapterException extends Exception {
public TopicExpressionAdapterException(String message) {
super(message);
}
}
}
Note: Implementation of the getNamespacePrefixes() method is left out intentionally from this answer.
The last step is to add the following annotation wherever the TopicExpressionType is used in JAXB generated classes:
#XmlJavaTypeAdapter(TopicExpressionTypeAdapter.class)
TopicExpressionType topicExpression;

MessageBodyReader not found for media type=text/xml

Unable to read the response, getting MessageBodyProviderNotfoundException at the line response.readEntity(VendorResponse.class). Not sure what is missing here..
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/xml, type=class com.test.services.payment.connector.domain.xml.Response, genericType=class com.test.services.payment.connector.domain.xml.Response.
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:208)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1071)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:850)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:784)
at org.glassfish.jersey.client.ClientResponse.readEntity(ClientResponse.java:297)
at org.glassfish.jersey.client.InboundJaxrsResponse$1.call(InboundJaxrsResponse.java:91)
Code snippet:
try {
WebTarget queryWebTarget = client.target("SomeURL");
jax.ws.rs.core.Response response = queryWebTarget.property("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8").request().post(
Entity.entity("some xmlRequest" , MediaType.APPLICATION_FORM_URLENCODED));
VendorResponse vendorResponse = response.readEntity(VendorResponse.class);
} catch (Exception e) {
System.out.println("errors is "+ e);
}
#XmlType(name = "", propOrder = {
"result"
})
#XmlRootElement(name = "Response")
public class VendorResponse {
#XmlElement(name = "Result", required = true)
protected ResponseResult result;
public ResponseResult getResult() {
return result;
}
public void setResult(ResponseResult value) {
this.result = value;
}
}
Response
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Result response="SYNC" count="1">
<Status>Success</Status>
</Result>
</Response>

Null Pointer Exception while unmarshalling XML with #XmlAnyElement annotation

I'm currently trying to unmarshal some XML into a java object using JAXB and I'm getting a strange Null Pointer Exception. This is only an issue while unmarshalling. I can marshal with these classes just fine. Here are the relevant pieces of code (irrelevant portions are denoted by "..."):
The JAXB Root element:
...
#XmlRootElement(name="assets")
public class Assets {
String product;
Images images = new Images();
...
Public Assets() {}
...
public String getProduct() { return this.product; }
#XmlAttribute(name="product")
public void setProduct(String newProduct) { this.product = newProduct; }
public Images getImages() { return this.images; }
#XmlElement(name="images")
public void setImages(Images newImages) { this.images = newImages; }
...
}
The Images sub-element of the root element:
...
#XmlRootElement(name="images")
#XmlSeeAlso(Image.class)
#XmlType(propOrder = {"mainAsset", "image"})
public class Images {
List<Image> images;
Image mainAsset;
private static char counter = 'a';
private static final String prefix = "product-image-";
// Here's the part that causes the NPE
#XmlAnyElement(lax=true)
public List<JAXBElement<Image>> getImage() {
final List<JAXBElement<Image>> imageList = new ArrayList<JAXBElement<Image>>();
for (final Image g : this.images) {
imageList.add(new JAXBElement(new QName(prefix + counter++), Image.class, g));
}
counter = 'a';
return imageList;
}
#XmlElement(name="renditions")
public void setImage(List<Image> newImages) { this.images = newImages; }
public Image getMainAsset() { return this.mainAsset; }
#XmlElement(name="main-asset-name")
public void setMainAsset(Image newMainAsset) { this.mainAsset = newMainAsset; }
}
The logic for unmarshalling the XML:
...
public void modifyXML() {
try {
JAXBContext context = JAXBContext.newInstance(Assets.class, Images.class, Image.class);
Unmarshaller um = context.createUnmarshaller();
File f = new File("/path/to/my.xml");
Assets assets = (Assets) um.unmarshal(f);
...
} catch (JAXBException e) {
e.printStackTrace();
}
}
...
Finally, the XML I'm trying to unmarshal (it might help to know that this xml file was actually generated using the JAXB marshaller, which runs without any problems):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assets product="TEST PRODUCT">
<images>
<main-asset-name>
<path>stuff.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</main-asset-name>
<product-image-a>
<path>48x48.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-a>
<product-image-b>
<path>140x140.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-b>
<product-image-c>
<path>1280x1280.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-c>
<product-image-d>
<path>319x319.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-d>
</images>
</assets>
Okay, so that's all the relevant code (I think). When I run my program, I get the following error right after invoking the unmarshaller:
java.lang.NullPointerException
at com.my.companys.name.Images.getImage(Images.java:25)
And the line number referenced is the line where the for loop starts in my Images.java class file.
Does anyone have any ideas why this.images might be null here? Any help is greatly appreciated. Thanks ahead of time.
Here's the solution I've been using for now. It's a bit of a hack, but hell... It does the job.
First, I get the raw xml as a string instead of reading it as a file:
public void modifyXML() {
try {
JAXBContext context = JAXBContext.newInstance(Assets.class, Image.class);
Unmarshaller um = context.createUnmarshaller();
// This xml should be formatted in a way that JAXB can work with
String rawXML = getRawXML("/path/to/my.xml");
// Hand the modified xml to the unmarshaller
Assets umAssets = (Assets) um.unmarshal(new StreamSource(new StringReader(rawXML)));
// Do stuff with umAssets
...
} catch (Exception e) {
e.printStackTrace();
}
}
private String getRawXML(String path) throws IOException {
String xml = readFile(path, StandardCharsets.UTF_8);
// This should make it so JAXB can handle the xml properly
xml = xml.replaceAll("<main-asset-name>", "<image>");
xml = xml.replaceAll("</main-asset-name>", "</image>");
xml = xml.replaceAll("<product-image.*>", "<image>");
return xml.replaceAll("</product-image.*>", "</image>");
}
private String readFile(String path, Charset encoding) throws IOException {
// A simple file reader
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}
This seems to do the trick, so I'm going to use this for now. Of course, I'm still open to better solutions if anyone has one.

ClassCastException while extracting data from List

This is my XML file :-
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<logExtract>
<configuration>
<splunk>
<splunkHost>localhost</splunkHost>
<userName>abcd</userName>
<password>1234</password>
<port>8214</port>
</splunk>
<tsdb>
<tsdbHost>localhsot</tsdbHost>
<port>4242</port>
</tsdb>
</configuration>
<dataPart>
<ingestion id="abc">
<tsdbElements>
<metricname>linecount0</metricname>
<tags>splunk_server0</tags>
</tsdbElements>
<splunkQuery>
<Query>index=_internal source=*/splunkd_access.log |head 0000</Query>
</splunkQuery>
</ingestion>
<ingestion id="xyz">
<tsdbElements>
<metricname>linecount</metricname>
<tags>splunk_server</tags>
</tsdbElements>
<splunkQuery>
<query>index=_internal source=*/splunkd_access.log |head 1000</query>
</splunkQuery>
</ingestion>
<ingestion id="def">
<tsdbElements>
<metricname>linecount2</metricname>
<tags>splunk_server2</tags>
</tsdbElements>
<splunkQuery>
<query>index=_internal source=*/splunkd_access.log |head 2000</query>
</splunkQuery>
</ingestion>
</dataPart>
</logExtract>
I have used JAXB and created POJO class structure for it.
For Ingestion element this is my POJO class structure.
private String id;
private List<TsdbElements> TsdbElements;
private List<SplunkQuery> SplunkQuery;
#XmlAttribute
public String getId ()
{
return id;
}
public void setId (String id)
{
this.id = id;
}
#XmlElement
public List<TsdbElements> getTsdbElements ()
{
return TsdbElements;
}
public void setTsdbElements (List<TsdbElements> TsdbElements)
{
this.TsdbElements = TsdbElements;
}
#XmlElement
public List<SplunkQuery> getSplunkQuery ()
{
return SplunkQuery;
}
public void setSplunkQuery (List<SplunkQuery> SplunkQuery)
{
this.SplunkQuery = SplunkQuery;
}
#Override
public String toString()
{
return "ClassPojo [id = "+id+", TsdbElements = "+TsdbElements+", SplunkQuery = "+SplunkQuery+"]";
}
Here is the Problem :-
When I try to extract Objects of ingestion I get error
(java.util.ArrayList cannot be cast to com.jaxb.xmlfile.Ingestio) java.lang.ClassCastException
at line below comment.
String fileName = "Query.xml";
File file = new File(fileName);
//JAXB Parsing - Unmarshling XML File
JAXBContext jaxbContext = JAXBContext.newInstance(XMLData.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
LogExtract logExtract = (LogExtract) jaxbUnmarshaller.unmarshal(file);
Configuration config = logExtract.getConfiguration();
Splunk spluknData = config.getSplunk();
Tsdb tsdbData = config.getTsdb();
DataPart dataPart = logExtract.getDataPart();
List<Ingestion> ingestionData = dataPart.getIngestion();
//Here I get Error
List<TsdbElements> tsdbElementsData = ((Ingestion) ingestionData).getTsdbElements();
//Here I get Error
List<SplunkQuery> splunkQueryData = ((Ingestion) ingestionData).getSplunkQuery();
System.out.println(spluknData.getSplunkHost() + " " + spluknData.getUserName() + " " + spluknData.getPassword() + " " +spluknData.getPort());
System.out.println(tsdbData.getTsdbHost() + " " + tsdbData.getPort());
for (SplunkQuery splunkQuery : splunkQueryData) {
System.out.println(splunkQuery.getQuery());
}
for (TsdbElements tsdbElements : tsdbElementsData) {
System.out.println(tsdbElements.getMetricname() + " " + tsdbElements.getTags());
}
So what am I missing?
EDIT:- (After answer given by #Sanj)
How to save tsdbElement data using for loop and then access them again out of for loop? Any Idea? Because its only saving last XML data, not all of them
List<Ingestion> ingestionData = dataPart.getIngestion();
//Here I get Error
List<TsdbElements> tsdbElementsData = ((Ingestion) ingestionData).getTsdbElements();
The error is stating that ingestionData is a type List, and you are trying to cast it to the Ingestion class.
Looking at your XML, you have a list of of these elements
<ingestion id="abc">
<tsdbElements>
<metricname>linecount0</metricname>
<tags>splunk_server0</tags>
</tsdbElements>
<splunkQuery>
<Query>index=_internal source=*/splunkd_access.log |head 0000</Query>
</splunkQuery>
</ingestion>
So you just need to iterate the list ingestionData to get the tsdbElements. Something like
// instantiate the tsdbElementsData list
List<TsdbElements> tsdbElementsData = new ArrayList<>(TsdbElements)
for (Ingestion ingestion: ingestionData)
{
// get the elements
tsdbElements = ingestion.getTsdbElements();
// do some with the elements, e,g add to a another list
tsdbElementsData.add(tsdbElements);
}
To iterate through the tsdbElementsData list, it is just another loop
for (TsdbElements tsdbElements: tsdbElementsData)
{
// ... do something with tsdbElements
}
Note that the foreach loop above, is the same a writing
for (int i = 0; i < tsdbElementsData.size(); i++)
{
TsdbElements tsdbElements = tsdbElementsData.get(i);
// ... do something with tsdbElements
}

JAXB MOXy marshalling and unmarshalling generic Collecton of Objects (including Strings and Primitive Wrappers)

I am facing issues marshalling and unmarshalling collections using MOXy. Collection may hold elements of any type. In the test case below I am trying to marshal and then unmarshall colection of Strings.
Test generates following output:
Going with type: APPLICATION_XML
Original XmlCollection: [one, two, three]
Marshaled as application/xml: <?xml version="1.0" encoding="UTF-8"?><collection>onetwothree</collection>
Unmarshaled XmlCollection: []
Going with type: APPLICATION_JSON
Original XmlCollection: [one, two, three]
Marshaled as application/json: {"value":["one","two","three"]}
Unmarshaled XmlCollection: []
In the XML case I would expect collection elements to be serialized as nested list xml elements holding values, but it is not so.
In the case of JSON genereated code looks good but I would expect object to get unmarshalled properly, which didn't happen.
When I run test with Collection of SimpleObject (uncomment lines marked as Simple object) everything works like a sharm and output produced is following:
Going with type: APPLICATION_XML
Original XmlCollection: [stringField='one', stringField='two', stringField='three']
Marshaled as application/xml: <?xml version="1.0" encoding="UTF-8"?><collection><simpleObject><stringField>one</stringField></simpleObject><simpleObject><stringField>two</stringField></simpleObject><simpleObject><stringField>three</stringField></simpleObject></collection>
Unmarshaled XmlCollection: [stringField='one', stringField='two', stringField='three']
Going with type: APPLICATION_JSON
Original XmlCollection: [stringField='one', stringField='two', stringField='three']
Marshaled as application/json: {"simpleObject":[{"stringField":"one"},{"stringField":"two"},{"stringField":"three"}]}
Unmarshaled XmlCollection: [stringField='one', stringField='two', stringField='three']
Appreciate any clue why it happens and how this mapping must be done properly.
Test case:
public class TestCase {
// Element name holding value of primitive types
public static final String VALUE_ELEMENT = "value";
// Attribute prefix in JSON
public static final String ATTRIBUTE_PREFIX = "#";
public static void main(String... args) {
try {
for (MediaType type : new MediaType[]{MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) {
System.out.println("Going with type: " + type);
JAXBContext context = (JAXBContext) JAXBContextFactory.createContext(
new Class[]{
XmlCollection.class,
// Simple Object SimpleObject.class,
},
Collections.emptyMap());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, type);
marshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
marshaller.setProperty(MarshallerProperties.JSON_ATTRIBUTE_PREFIX, ATTRIBUTE_PREFIX);
marshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, VALUE_ELEMENT);
marshaller.setProperty(UnmarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);
XmlCollection original = new XmlCollection(Arrays.asList("one", "two", "three"));
// Simple object XmlCollection original = new XmlCollection(Arrays.asList(new SimpleObject("one"), new SimpleObject("two"), new SimpleObject("three")));
System.out.println("Original " + original.toString());
StreamResult result = new StreamResult(new StringWriter());
marshaller.marshal(original, result);
String generated = result.getWriter().toString();
System.out.println("Marshaled as " + type.getMediaType() + ": " + generated);
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, type);
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
unmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, ATTRIBUTE_PREFIX);
unmarshaller.setProperty(UnmarshallerProperties.JSON_VALUE_WRAPPER, VALUE_ELEMENT);
unmarshaller.setProperty(UnmarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);
XmlCollection unmarshaled = unmarshaller.unmarshal(new StreamSource(new StringReader(generated)), XmlCollection.class).getValue();
System.out.println("Unmarshaled " + unmarshaled.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
#XmlRootElement(name = "collection")
#XmlAccessorType(XmlAccessType.FIELD)
public static class XmlCollection<T> {
#XmlAnyElement(lax = true)
private Collection collection = new ArrayList();
public XmlCollection() {
}
public XmlCollection(Collection<T> collection) {
this.collection.addAll(collection);
}
public Collection<T> getCollection() {
return collection;
}
#Override
public String toString() {
return "XmlCollection: " + collection;
}
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static final class SimpleObject{
#XmlElement
public String stringField;
public SimpleObject(){}
public SimpleObject(String stringField){
this.stringField = stringField;
}
public String getStringField() {
return stringField;
}
public void setStringField(String stringField) {
this.stringField = stringField;
}
#Override
public String toString() {
return "stringField='" + stringField + '\'';
}
}
}

Categories