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

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)
}

Related

Sending #Value annotated fields to a DTO layer returns null

I have a class which is composed of 2 different objects :
public class MyClass{
private OptionClass optionClass;
private ConstantClass constantClass;
public DocumentToSignRestRequest(OptionClass optionClass, ConstantClass constantClass) {
this.optionClass= optionClass;
this.constantClass= constantClass;
}
}
My first class is a classic POJO. My second class retrieve values from the application.properties file.
public class ConstantClass {
#Value("${api.url}")
private String hostName;
#Value("${sign.path}")
private String pathStart;
public ConstantClass () {
this.hostName= getHostName();
this.path = getPath();
}
I map MyClass with MyClassDto in order to call a service.
#PostMapping(
value="/sign",
consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE },
produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE }
)
public MyClassRest prepareDocument(#RequestBody DocumentToPrepare documentToPrepare) throws Exception {
MyClassRest returnValue = new MyClassRest ();
ModelMapper modelMapper = new ModelMapper();
MyClassDto myClassDto = modelMapper.map(documentToPrepare, MyClassDto .class);
DocumentDto signedDocument = documentService.signDocument(documentDto);
returnValue = modelMapper.map(signedDocument, DocumentRest.class);
return returnValue;
}
My DTO class work fine and retrieve the OptionClass datas, but concerning the second Class, i obtain null as value, while i try to print it out in the service layer.
Your ConstantClass should be a Bean or a Component (as #cassiomolin says in comments)
#Component
public class ConstantClass {
private String hostName;
private String pathStart;
public ConstantClass (#Value("${api.url}") String url, #Value("${sign.path}") String path ) {
this.hostName = url;
this.pathStart = path;
}
// getters...
Then you can easily inject this component in your Controller and use it.
#Controller
public class YourController(){
private ConstantClass constantClass;
public YourController(ConstantClass constantClass){
this.constantClass = constantClass;
}
#PostMapping("...")
public MyClass post(.....){
.....
MyClass myclass = new MyClass(this.constantClass,...)
.....
}
}
note that Spring can autowire #Value and #Component, ... via the constructor; that can be very useful when you do unit-testing

Jackson Inheritance

Can't seem to figure this out. I keep getting various errors so I'll just write this with the current error I'm getting from Jackson.
public class ResponseDetail {
private Response response;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseTypeOne.class, name = "ResponseTypeOne"),
#JsonSubTypes.Type(value = ResponseTypeTwo.class, name = "ResponseTypeTwo"),
#JsonSubTypes.Type(value = ResponseTypeThree.class, name = "ResponseTypeThree")
})
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class Response {
}
In other packages I have these three:
public class ResponseTypeOne extends Response {
private Integer status;
}
public class ResponseTypeTwo extends Response {
private String message;
}
public class ResponseTypeThree extends Response {
private String value;
}
Error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.services.models.Response]: missing type id property '#type' (for POJO property 'response')
I have tried various iterations of this #JsonTypeInfo with various includes and various property's also with Id.CLASS with no luck.
You need to declare how the type should be recognized.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "#ttype")
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseTypeOne.class, name = "ResponseTypeOne"),
#JsonSubTypes.Type(value = ResponseTypeTwo.class, name = "ResponseTypeTwo"),
#JsonSubTypes.Type(value = ResponseTypeThree.class, name = "ResponseTypeThree")
})
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class Response {
#JsonProperty("#ttype")
public abstract String getChildType();
}
And in child types do like below:
#JsonTypeName("ResponseTypeOne")
public class ResponseTypeOne extends Response {
#Override
public String getChildType() {
return "ResponseTypeOne";
}
}
And incoming json should be like below to enable jackson to find the correct child implementation:
{
//some attributes of child Response
"#ttype": "ResponseTypeOne"
}

Marshal list of objects of different types with JAXB

I work with Spring and JAXB and want to marshal a list of different DTO Objects so
the required XML response should be like :
<root>
<dto_list>
<dto1>
<name>xxx</name>
</dto1>
<dto2>
<location>xxx</location>
</dto2>
</dto_list>
</root>
Assuming the Class Objects are:
#XmlRootElement(name = "Dto1")
#XmlAccessorType(XmlAccessType.NONE)
public class Dto1 {
#XmlElement
private String name;
// setter/getters
}
and
#XmlRootElement(name = "Dto2")
#XmlAccessorType(XmlAccessType.NONE)
public class Dto2 {
#XmlElement
private String location;
// setter/getters
}
and the wrapper class:
#XmlRootElement(name = "root")
#XmlAccessorType(XmlAccessType.NONE)
public class DTOsWrapper {
private List<Object> dto;
public void setDto(List<Object> dto) {
this.dto = dto;
}
#XmlElementWrapper(name = "dto_list")
#XmlElements({
#XmlElement(name = "dto1", type = Dto1.class),
#XmlElement(name = "dto2", type = Dto2.class)
})
public List<Object> getDto() {
return dto;
}
}
and the endpoint:
#RestController
public class DTOEndpoint {
#Autowired
private IDTOService service;
#RequestMapping(value = "/restxml", produces = "application/xml")
public Object retrieveAllDTOs() {
DTOsWrapper o = service.findDtos(); //returns a DTOsWrapper obj of a list containing Dto objs, i.e Dto1, Dto2 etc
return o;
}
I get {"error": "org.springframework.http.converter.HttpMessageConversionException: Could not instantiate JAXBContext for class [class <>to.DTOsWrapper]: 1 counts of IllegalAnnotationExceptions; anyone have a clue?

Unable to show XML data

Hi my need is to show both xml and json data .
I am able to see this in local by JaxB but unable to see same code in server.
When ever I deploy that to server I got this error.
I don't know how to solve this error.
Unable to solve this, Tried a lot but Nothing happened , in local everything is fine, but when it comes to server it shows different exception.
Error 500: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Could not instantiate JAXBContext for class [class com.rest.model.ExerciseInstructionsList]: null; nested exception is javax.xml.bind.JAXBException - with linked exception: [com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions Class has two properties of the same name "exerciseList" this problem is related to the following location: at public java.util.List com.rest.model.ExerciseInstructionsList.getExerciseList()
at com.rest.model.ExerciseInstructionsList this problem is related to the following location: at public java.util.List com.rest.model.ExerciseInstructionsList.exerciseList at com.rest.model.ExerciseInstructionsList ]
My Controller IS
#Controller
#RequestMapping("/")
public class ExerciseController {
#Autowired
private ExerciseService exerciseService;
private static final Logger logger = LoggerFactory.getLogger(ExerciseController.class);
#Consumes
#Produces
#RequestMapping(value=OaesRestURIConstants.GET_EXERCISE_ALL,method=RequestMethod.GET,produces={"application/json"})
public #ResponseBody List<ExerciseInstructions> getAllExercise()throws Exception{
logger.info("Start getAllExercises.");
System.out.println("<<<<<<<<<<<<<<<<<--------------Coming Inside List Exercise Controller----------->>>>>>>>>>>");
List<ExerciseInstructions> listExercise = new ArrayList<ExerciseInstructions>();
//ExerciseInstructionsList exe = new ExerciseInstructionsList();
/*This list contains Exercise Instructions Data*/
listExercise = exerciseService.getAllExercise();
/*here i kept the list in ExerciseInstructionsList list so that i can fetch xml data also and can show the list.*/
//exe.setExerciseList(listExercise);
return listExercise;
}
#RequestMapping(value=OaesRestURIConstants.GET_EXERCISE_XML_ALL,method=RequestMethod.GET,produces={"application/xml"})
public #ResponseBody ExerciseInstructionsList getAllXmlExercise()throws Exception{
logger.info("Start getAllExercises.");
System.out.println("<<<<<<<<<<<<<<<<<--------------Coming Inside List Exercise Controller----------->>>>>>>>>>>");
List<ExerciseInstructions> listExercise = new ArrayList<ExerciseInstructions>();
ExerciseInstructionsList exeList = new ExerciseInstructionsList();
/*This list contains Exercise Instructions Data*/
listExercise = exerciseService.getAllExercise();
/*here i kept the list in ExerciseInstructionsList list so that i can fetch xml data also and can show the list.*/
exeList.setExerciseList(listExercise);
return exeList;
}
#RequestMapping(value=OaesRestURIConstants.EXERCISE_SAVE,method=RequestMethod.POST)
public #ResponseBody ExerciseInstructions saveExercise(#RequestBody ExerciseInstructions exerciseInstructions)throws Exception{
logger.info("Start saveExercise.");
exerciseService.saveExercise(exerciseInstructions);
return exerciseInstructions;
}
//#Consumes({"application/xml","application/json"})
// #Produces({"application/xml","application/json"})
#RequestMapping(value=OaesRestURIConstants.GET_EXERCISE_ID,method=RequestMethod.GET,produces={"application/xml","application/json"})
public #ResponseBody ExerciseInstructions getExerciseById(#PathVariable("id") String exerciseId ) throws Exception{
logger.info("Start getExerciseById. ID="+exerciseId);
ExerciseInstructions exercise = null;
try {
exercise = exerciseService.getExerciseById(exerciseId);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Coming Here>>>>>>>>>>>"+exercise);
return exercise;
//return exerciseService.getExerciseById(exerciseId);
}
#RequestMapping(value=OaesRestURIConstants.EXERCISE_DELETE,method=RequestMethod.PUT)
public #ResponseBody ExerciseInstructions deleteById(#PathVariable("id") String exerciseId) throws Exception{
logger.info("Start deleteExercise.");
exerciseService.deleteExercise(exerciseId);
return null;
}
}
My Model class is :
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ExerciseInstructions {}
My Model List Class Is :
#XmlRootElement(name="exerciseInstructions")
//#XmlSeeAlso({ExerciseInstructions.class})
#XmlAccessorType(XmlAccessType.FIELD)
public class ExerciseInstructionsList {
public List<ExerciseInstructions> exerciseList;
public List<ExerciseInstructions> getExerciseList() {
return exerciseList;
}
public void setExerciseList(List<ExerciseInstructions> exerciseList) {
this.exerciseList = exerciseList;
}
}
So can anyone help me in this.
I want to fetch and see both xml and json.
When you carefully read the error-message you see the reason: (I formatted and highlighted the message for better readability)
IllegalAnnotationExceptions Class has two properties of the same name "exerciseList"
this problem is related to the following location: at public java.util.List com.rest.model.ExerciseInstructionsList.getExerciseList() at com.rest.model.ExerciseInstructionsList
this problem is related to the following location: at public java.util.List com.rest.model.ExerciseInstructionsList.exerciseList at com.rest.model.ExerciseInstructionsList
So the program complains that your class ExerciseInstructionsList has two properties which can be mapped to exerciseList, these are getExerciseList() and exerciseList.
To fix this you can declare exerciseList as private.
#XmlRootElement(name="exerciseInstructions")
#XmlAccessorType(XmlAccessType.FIELD)
public class ExerciseInstructionsList {
private List<ExerciseInstructions> exerciseList;
public List<ExerciseInstructions> getExerciseList() {
return exerciseList;
}
public void setExerciseList(List<ExerciseInstructions> exerciseList) {
this.exerciseList = exerciseList;
}
}

JAXB Error while using in SpringREST to return a ArrayList of a domain object

I am trying to use JAXB in Spring RESTful webservice.
My code is as follows:
#RequestMapping(value = "/countries",
method = RequestMethod.GET,
headers="Accept=application/xml, application/json")
public #ResponseBody CountryList getCountry() {
logger.debug("Provider has received request to get all persons");
// Call service here
CountryList result = new CountryList();
result.setData(countryService.getAll());
return result;
}
The CountryList.java class looks like:
#XmlRootElement(name="countries")
public class CountryList {
#XmlElement(required = true)
public List<Country> data;
#XmlElement(required = false)
public List<Country> getData() {
return data;
}
public void setData(List<Country> data) {
this.data = data;
}
}
The Country.java looks like:
#XmlRootElement(name="country")
public class Country {
private Calendar createdDt;
private String updatedBy;
private String createdBy;
private Long id;
private String countryName;
private Calendar updatedDt;
// getters and setters for all attributes goes here
}
Now, when I access the method getCountry(), I am getting the following exception
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Class has two properties of the same name "data"
this problem is related to the following location:
at public java.util.List com.cisco.bic.services.model.CountryList.getData()
at com.cisco.bic.services.model.CountryList
this problem is related to the following location:
at public java.util.List com.cisco.bic.services.model.CountryList.data
at com.cisco.bic.services.model.CountryList
Would anyone has any idea why is this error coming. Am I doing anything wrong in the annotaion part ??
Please help.
Regards
Saroj
You can't annotate both the getter/setter and the field, you need to decide on one of them.

Resources