JAXB #XmlValue Not Working - spring-boot

SampleMessage class:
#XmlRootElement(name = "SampleMessage")
#XmlAccessorType(XmlAccessType.FIELD)
public class SampleMessage {
#XmlAttribute
private String type;
#XmlElement(name = "Content", required = true)
private Content content;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Content getContent() {
return content;
}
public void setContent(Content content) {
this.content = content;
}
#XmlAccessorType(XmlAccessType.FIELD)
private static class Content {
#XmlValue
private String value;
#XmlAttribute(name = "name")
private String name;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
The Spring Boot - Rest Controller I have the follow method which basically just output the same XML form with what we passed, well supposed to be. This is just to check if it is parsing the XML properly.
#PostMapping(consumes = {MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE})
public String handler(#RequestBody SampleMessage message) {
StringWriter writer = new StringWriter();
try {
JAXBContext jaxbContext = JAXBContext.newInstance(SampleMessage.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(message, writer);
} catch (JAXBException ex) {
System.out.println(ex.toString());
}
return writer.toString();
}
XML Sample 1: This is the initial XML sample I have:
<?xml version="1.0" encoding="UTF-8"?>
<SampleMessage type="TestType">
<Content name="TestContent">This is a sample content.</Content>
</SampleMessage>
Output (XML Sample 1): The output I get from the marshaller is this:
<?xml version="1.0" encoding="UTF-8"?>
<SampleMessage type="TestType">
<Content name="TestContent"/>
</SampleMessage>
Notice that there is no content text in the "Content" element that reads "This is a sample content". However, if I pass additional element Value inside "Content" element then it is able to output correctly.
XML Sample 2
<?xml version="1.0" encoding="UTF-8"?>
<SampleMessage type="TestType">
<Content name="TestContent"><Value>This is a sample content.</Value></Content>
</SampleMessage>
Correct Output
<?xml version="1.0" encoding="UTF-8"?>
<SampleMessage type="TestType">
<Content name="TestContent">This is a sample content.</Content>
</SampleMessage>
Any thoughts why is this?

This has resolved. Just noticed that this is a duplicate from my other post, sorry.
JAXB #XmlValue not able to get the text, Not generating empty XML element, and not able to read attribute

Related

spring Jaxb2XmlDecoder fails to decode when using inheritance with #XmlSeeAlso (polymorphism)

While introducing WebClient into my current project I ran into the issue that the call
response.bodyToMono(RealEstate::class.java)
causes the error
org.springframework.core.codec.DecodingException: Could not unmarshal XML to class test.RealEstate; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[com.sun.istack.SAXParseException2; lineNumber: 2; columnNumber: 1; Unable to create an instance of test.RealEstate]
at org.springframework.http.codec.xml.Jaxb2XmlDecoder.unmarshal(Jaxb2XmlDecoder.java:242)
Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below:
We have the following simplified class hierarchy:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "RealEstate", namespace = "http://anything.you.want", propOrder = {"address"})
#XmlSeeAlso({House.class, Apartment.class})
public abstract class RealEstate {
protected String address;
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Apartment", propOrder = {"livingArea"}, namespace = "http://anything.you.want")
#XmlRootElement(name = "apartment", namespace = "http://anything.you.want")
public class Apartment extends RealEstate {
private String livingArea;
public String getLivingArea() {
return livingArea;
}
public void setLivingArea(String livingArea) {
this.livingArea = livingArea;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "House", propOrder = {"plotArea"}, namespace = "http://anything.you.want")
#XmlRootElement(name = "house", namespace = "http://anything.you.want")
public class House extends RealEstate {
private String plotArea;
public String getPlotArea() {
return plotArea;
}
public void setPlotArea(String plotArea) {
this.plotArea = plotArea;
}
}
One possible response body to be decoded is
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objects:house xmlns:objects="http://anything.you.want">
<address>Somewhere around the corner</address>
<plotArea>small</plotArea>
</objects:house>
First question would be if there is something wrong with how JAXB is used?
In case there is nothing really wrong with that I go on to my other question.
Looking into the Decoder there is the logic to call the Unmarshaller
Unmarshaller unmarshaller = initUnmarshaller(outputClass);
XMLEventReader eventReader = StaxUtils.createXMLEventReader(events);
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
return unmarshaller.unmarshal(eventReader);
}
else {
JAXBElement<?> jaxbElement = unmarshaller.unmarshal(eventReader, outputClass);
return jaxbElement.getValue();
}
Since RealEstate is not annotated as XML root element the else block is executed which causes the error. Using the other method without handing over the target class would work.
Now the question would be why is that not working? I assume using XMLSeeAlso should cause the unmarshaller to create the right type of real estate, shouldn't it?
As a side note I also looked into how the RestTemplate solved the task and there it boils down to the MarshallingHttpMessageConverter doing
Object result = this.unmarshaller.unmarshal(source);
if (!clazz.isInstance(result)) {
throw new TypeMismatchException(result, clazz);
}
return result;
It seems that the decoder differentiates for some reason while the message converter is doing the type check after unmarshalling.
And since it might be relevant how the Jaxb2Marshaller is setup
Jaxb2Marshaller().apply {
setPackagesToScan(
RealEstate::class.java.getPackage().name,
)
setCheckForXmlRootElement(false)
setSupportJaxbElementClass(true)
}

Spring Boot marshall Xml from RestTemplate without RootElement

I am using a RestTemplate like this:
return this.getForEntity(baseUrl, BasicResponse.class, parameters);
This is the BasicResponse class:
public class BasicResponse {
private String status;
private String statusMsg;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatusMsg() {
return statusMsg;
}
public void setStatusMsg(String statusMsg) {
this.statusMsg = statusMsg;
}
}
No exceptions are thrown but the fields in the returned ResponseEntity body are 'null'. I think it's because the element does not have a valid XML structure (as in no root element). I do not have control over the parsed XML. How can I map my object?
Since the XML is not valid,
I believe that you will not be able to use RestTemplate.getForEntity
to get a BasicResponse object.
Try this:
private static final String VALUE_END_TAG = "</blammy>";
private static final String VALUE_START_TAG = "<blammy>";
private XmlMapper xmlMapper; // initialize this correctly, somewhere off page.
method stuff
{
final String actualResponse;
final StringBuilder correctedResponse = new StringBuilder();
final BasicResponse returnValue;
actualResponse = restTemplate.getForEntity(baseUrl, BasicResponse.class, parameters);
correctedResponse.append(VALUE_START_TAG);
correctedResponse.append(actualResponse);
correctedResponse.append(VALUE_END_TAG);
returnValue = xmlMapper.readValue(correctedResponse.toString(), BasicResponse.class);
return returnValue;
}
Use some reasonable value as the element name in the start and end tags,
perhaps "" and "".
Consider using some Jackson annotations,
for example #JacksonXmlRootElement(localName = "blammy")
(this local name matches my example).

JAXB #XmlValue not able to get the text, Not generating empty XML element, and not able to read attribute

I have a Spring Rest Controller, that accepts request as Xml. This is the sample request format coming in.
<Message>
<Header id="101" desc="Header content description">
<title text="The quick brown fox" />
</Header>
<Content />
<Footer name="test">Footer content sample.</Footer>
</Message>
this is my controller:
#RestController
#RequestMapping("myservice")
public class MessageController {
#PostMapping(consumes = MediaType.APPLICATION_XML_VALUE)
public String handler(#RequestBody Message message) {
System.out.println(message);
System.out.println("\n\n\n");
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Message.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(message, System.out);
} catch(JAXBException ex) {
System.out.println(ex.toString());
}
return "Done!";
}
}
and I have the following classes, for the Message class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Message")
public class Message {
#XmlElement(name = "Header", required = true)
private Header header;
#XmlElement(name = "Content", required = true)
private Content content;
#XmlElement(name = "Footer", required = true)
private Footer footer;
// Getters and setters here...
#Override
public String toString() {
// In here, I outputted the values of the header and footer.
}
}
Header class:
#XmlRootElement(name = "Header")
#XmlAccessorType(XmlAccessType.FIELD)
public class Header {
#XmlAttribute(name = "id", required = true)
private String id;
#XmlAttribute(name = "desc", required = true)
private String description;
// Getters and setters here...
}
Content class:
#XmlRootElement(name = "Content")
#XmlAccessorType(XmlAccessType.FIELD)
public class Content {
}
and for the Footer class:
#XmlRootElement(name = "Footer")
#XmlAccessorType(XmlAccessType.FIELD)
public class Footer {
#XmlValue
private String value;
#XmlAttribute(name = "name")
private String name;
//Getter and setters here...
}
So there are three issues that I see here from the output:
The description attribute value from the Header is always null. Basically I wanted to have a different field name in the class but reading an attribute ("desc") from the XML. The attribute "id" is fine though, I can retrieve the value from it.
It can't generate an empty Content XML e.g. . If I put nillable=true, it will generate Content with extra attributes e.g. xmnls="..." />. Not sure how to remove those extra attributes so that it generates only empty content element.
Footer attribute "name" value can be read but not the text that says "Footer content sample".
Any thoughts?
This has been resolved. My bad that I imported the following from my gradle file.
compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml')
So by removing this from build.gradle, everything works as expected!

#XmlPath not working

#XmlPath is not working.
Customer.java
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name= "Customer")
public class Customer {
private String CustomerId;
private String organizationCode;
private Extn extn;
private String organizationName;
private int reset;
private CustomerSchedulingPreferences customerSchedulingPreferences;
private ArrayList<RestrictedState> restrictedStateList;
#XmlAttribute
public String getCustomerId() {
return CustomerId;
}
public void setCustomerId(String customerId) {
CustomerId = customerId;
}
#XmlAttribute
public String getOrganizationCode() {
return organizationCode;
}
public void setOrganizationCode(String organizationCode) {
this.organizationCode = organizationCode;
}
#XmlElement(name="Extn")
public Extn getExtn() {
return extn;
}
public void setExtn(Extn extn) {
this.extn = extn;
}
#XmlPath("BuyerOrganization/#OrganizationName")
public String getOrganizationName() {
return organizationName;
}
public void setOrganizationName(String organizationName) {
this.organizationName = organizationName;
}
#XmlPath("BuyerOrganization/Extn/USSCORestrictedStateList")
#XmlElement(name = "USSCORestrictedState")
public ArrayList<RestrictedState> getRestrictedStateList() {
return restrictedStateList;
}
public void setRestrictedStateList(ArrayList<RestrictedState> restrictedStateList) {
this.restrictedStateList = restrictedStateList;
}
#XmlPath("BuyerOrganization/Extn/USSCORestrictedStateList/#Reset")
public int getReset() {
return reset;
}
public void setReset(int reset) {
this.reset = reset;
}
#XmlElement(name="CustomerSchedulingPreferences")
public CustomerSchedulingPreferences getCustomerSchedulingPreferences() {
return customerSchedulingPreferences;
}
public void setCustomerSchedulingPreferences(
CustomerSchedulingPreferences customerSchedulingPreferences) {
this.customerSchedulingPreferences = customerSchedulingPreferences;
}
}
Client.java
import javax.xml.transform.stream.StreamResult;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
public class Client
{
public static void main(String[] args)throws IOException
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Marshaller marshaller = (Marshaller)context.getBean("jaxbMarshallerBean");
Customer customer=new Customer();
customer.setCustomerId("12345");
customer.setOrganizationCode("SUPPLY");
Extn extn = new Extn();
extn.setExtnBillCreditCode("000");
extn.setExtnBillSubscriptionId("132131");
customer.setExtn(extn);
RestrictedState resState1= new RestrictedState();
resState1.setOrgCode("952121");
resState1.setRestrictedStateCode("IN");
RestrictedState resState2= new RestrictedState();
resState2.setOrgCode("60325");
resState2.setRestrictedStateCode("IL");
ArrayList<RestrictedState> restrictedStateList = new ArrayList<RestrictedState>();
restrictedStateList.add(resState1);
restrictedStateList.add(resState2);
CustomerSchedulingPreferences custSchedPref = new CustomerSchedulingPreferences();
custSchedPref.setIsLineShipComplete("Y");
custSchedPref.setIsLineShipSingleNode("N");
custSchedPref.setOptimizationType("03");
customer.setCustomerSchedulingPreferences(custSchedPref);
customer.setRestrictedStateList(restrictedStateList);
marshaller.marshal(customer, new StreamResult(new FileWriter("customer.xml")));
System.out.println("XML Created Sucessfully");
}
}
applicationContext.Xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/oxm
http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
<oxm:jaxb2-marshaller id="jaxbMarshallerBean">
<oxm:class-to-be-bound name="com.javatpoint.Customer"/>
</oxm:jaxb2-marshaller>
</beans>
Structure of Output needed :
<Customer CustomerID="952121" OrganizationCode="SUPPLY" >
<Extn ExtnBillCreditCode="000" ExtnBillSubscriptionID="952121" />
<BuyerOrganization OrganizationName="Buy.com1" >
<Extn>
<USSCORestrictedStateList Reset="Y">
<USSCORestrictedState OrganizationCode="952121" RestrictedStateCode="IN"/>
</USSCORestrictedStateList>
</Extn>
</BuyerOrganization>
<CustomerSchedulingPreferences IsLineShipComplete="" IsLineShipSingleNode="" />
</Customer>
================================================================================
Please help me in resolving this:
Currently i am getting output like below :
<Customer organizationCode="SUPPLY" customerId="12345">
<CustomerSchedulingPreferences IsLineShipSingleNode="N" IsLineShipComplete="Y"/>
<Extn ExtnBillSubscriptionID="132131" ExtnBillCreditCode="000"/>
<reset>0</reset>
<USSCORestrictedState restrictedStateCode="IN" OrganizationCode="952121"/>
<USSCORestrictedState restrictedStateCode="IL" OrganizationCode="60325"/>
</Customer>
To leverage the #XmlPath extension you need to be using EclipseLink MOXy as your JAXB provider.
eclipselink.jar on your classpath
a jaxb.properties file in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html)
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Below is a link that will help set this up:
http://wiki.eclipse.org/EclipseLink/Examples/MOXy/Spring

XML to Objects mapper

I am trying to make one application file format parser & generator. Application uses xml files with custom DTD. Currently I am thinking about writing object mapper with nokogiri to parse xml to objects and use these object to generate xml back. I have tried HappyMapper and xml-mapping but they didn't utilize full xml format. So Currently now I have made this but I think it is bit bad design.
http://pastie.org/5393012
You can use C# with XMLSerilaizer, it is the best. Here is the example:
---------------just a lot of entity ----------------------
using System;
namespace BuilderSerialization {
public class Address {
public Address() {}
public string Address1;
public string Address2;
public string City;
public string State;
public string Zip;
public string Country;
} }
using System;
namespace BuilderSerialization {
public class Author {
public Author() { }
public string FirstName;
public string MiddleName;
public string LastName;
public string Title;
public string Gender;
public Address AddressObject;
} }
namespace BuilderSerialization {
public class Book {
public Book() { }
public string Title;
public Author AuthorObject;
public string ISBN;
public double RetailPrice;
public string Publisher;
}}
-------------------------------------------------------
using System;
using System.Xml.Serialization;
using System.IO;
namespace BuilderSerialization {
class TestClass {
static void Main(string[] args) {
Book BookObject = new Book();
XmlSerializer ser = new XmlSerializer(typeof(Book));
TextWriter writer = new StreamWriter("booktest.xml");
BookObject.Title = "Practical LotusScript";
BookObject.ISBN = "1884777767 ";
BookObject.Publisher = "Manning Publications";
BookObject.RetailPrice = 43.95;
BookObject.AuthorObject = new Author();
BookObject.AuthorObject.FirstName = "Tony";
BookObject.AuthorObject.LastName = "Patton";
BookObject.AuthorObject.Gender = "Male";
BookObject.AuthorObject.AddressObject = new Address();
BookObject.AuthorObject.AddressObject.Address1 = "1 Main Street";
BookObject.AuthorObject.AddressObject.City = "Anywhere";
BookObject.AuthorObject.AddressObject.State = "KY";
BookObject.AuthorObject.AddressObject.Zip = "40000";
BookObject.AuthorObject.AddressObject.Country = "USA";
ser.Serialize(writer, BookObject);
writer.Close();
} } }
After that you get the XML:
<?xml version="1.0" encoding="utf-8"?>
<Book xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Title>Practical LotusScript</Title>
<AuthorObject>
<FirstName>Tony</FirstName>
<LastName>Patton</LastName>
<Gender>Male</Gender>
<AddressObject>
<Address1>1 Main Street</Address1>
<City>Anywhere</City>
<State>KY</State>
<Zip>40000</Zip>
<Country>USA</Country>
</AddressObject>
</AuthorObject>
<ISBN>1884777767 </ISBN>
<RetailPrice>43.95</RetailPrice>
<Publisher>Manning Publications</Publisher>
</Book>

Resources