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

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!

Related

How to annotate DTO so that it shows up in SwaggerUI Schema?

I have a controller with a #RequestBody DTO. I need to show the DTO's schema instead of the default string in the RequestBody Schema in Swagger.
By using #Operation above the API and #Parameter within, I've been able to describe the DTO in two places
and fill in the example (see code). I've tried #Schema in the #Operation (under requestBody) and #Parameter annotations. The former throws an NPE and the latter changes nothing, with a variety of tries regarding counterpart annotations in the DTO itself.
Sample Controller
#RequestMapping(value = "/{myPathVar}", method = RequestMethod.POST)
#Operation(summary = "Create something.",
parameters = { #Parameter(in = ParameterIn.PATH, name = "myPathVar", description = "Some path variable. Swagger uses this description.") },
requestBody = #io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "My description here.",
content = #Content(examples = #ExampleObject("{\"A\" : \"a\",\"B\" : \"{\"b\" : \"foo\", \"bb\" : \"bar\"}"))))
#ApiResponse(content = #Content(schema = #Schema(implementation = MyReturningType.class)))
public MyReturningType doSomethingCool(
#Parameter(description = "Some description Swagger ignores.", example = "123") #PathVariable(value = "myPathVar") int myPathVar,
#Parameter(description = "My other description here.", schema = #Schema(implementation = MyDto.class)) #RequestBody MyDto dto) {
// do something cool
}
Sample DTO
// Do I need certain annotations here?
public class MyDto {
// What annotation goes here? #Parameter, #JsonProperty, #Schema, something else?
private int someInt;
private String someString;
private Object someObject;
}
What combination of annotations do I need to correctly label the DTO Schema within the DTO and then reference this Schema from the controller such that the Schema field is populated in SwaggerUI?
The issue might have been caused by the fact that the fields in your DTO are of private visibility and from the code you shared, doesn't look like they have getters and setters available.
Refer to the below example for a working example of how it can be done
Controller
// Using the specific mapping annotation will keep the code clean
#PostMapping("/{myPathVar}")
// The below annotation describes the operation
#Operation(
summary = "Brief description of the operation",
description = "Detailed description of the operation")
// Describe the possible responses next. Prefer using #ApiResponses for multiple response
#ApiResponse(
// specify the http response code
responseCode = "201",
// some description. Maybe use the corresponding http response code's description
description = "Created",
// describe the content that will be returned for this response code
content = #Content(
// optionally, specify the media type for the response here as shown in the below code
mediaType = MediaType.APPLICATION_JSON_VALUE,
// specify the implementation of the response DTO here
schema = #Schema(implementation = Void.class)))
public Void doSomethingCool(
// Use #Parameter for #PathVariable and #RequestVariable
#Parameter(description = "Description for path/request-parameter here")
#PathVariable(value = "myPathVar")
int myPathVar,
// Both these #RequestBody annotations are mandatory.
#io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Controller-level model description here")
#org.springframework.web.bind.annotation.RequestBody
TestDTO dto) {
// ... do some cool stuff here
return null;
}
DTO
#Schema(description = "Model-level description here")
public class TestDTO {
#Schema(description = "Field-level description here")
private int someInt;
#Schema(description = "Another Field-level description here")
private String someString;
#Schema(description = "Yet another Field-level description here")
private Object someObject;
// all getters and setters here
}
This gives you the output as below
Annotated your dto like the following . It works for me
#Schema(description = "User Dto")
#Data
public class UserDto {
private int id;
#JsonProperty
private String email;
#JsonProperty
private String password;
#JsonProperty
private String firstName;
#JsonProperty
#Schema(description = "User Id")
private String lastName;
}

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?

JAXB #XmlValue Not Working

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

Update index dynamically doesn't work (Spring Data Elasticsearch)

I have a model which is:
#Document(indexName = "index", type = "logs")
public class Log {
#Id
private String id;
private String test_no;
private String entire_log;
private String method;
private String timestamp;
private String thread_name;
private String level;
private String logger_name;
private String formatted_message;
// CONSTRUCTORS, GETTERS AND SETTERS...
}
I have an applicationContext.xml file which contains a bean, used to save the value of the dynamic index. The location of this file is inside of src/main/resources directory, the same location than application.properties file. The bean is:
<bean id="index" class="elastest.loganalyzer.es.client.model.Index">
<property name="v" value="defaut"></property>
</bean>
where the Index class is a simple class which contains a param "v":
public class Index {
String v;
public Index() {
this.v = "default";
}
public String getV() {
return v;
}
public void setV(String v) {
this.v = v;
}
}
What I want to do is to make the index of the above model (Log) dynamic. I have tried it by different ways but I can't find the correct solution:
#Document(indexName = "#{index}", type = "logs")
#Document(indexName = "#{index.v}", type = "logs")
#Document(indexName = "#{index.getV()}", type = "logs")
So... How can I make it works?

Spring Boot request body semi-required fields

In our application user can write a message based on user id or screen name.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = ???) Long userId,
#JsonProperty(value = "screen_name", required = ???) String screenName,
#JsonProperty(value = "text", required = true) String text) {
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}
Fields userId and screenName can't be optional at same time, one should be provided.
How in Spring Boot to mark that they are semi-required?
This seems like more of a validation concern rather than deserialization.
Create a Validator then put #Valid within the #RequestMapping on the controller.
See more here:
Spring REST Validation Example
From jenkov tutorials:
#JsonValue
The Jackson annotation #JsonValue tells Jackson that Jackson should
not attempt to serialize the object itself, but rather call a method
on the object which serializes the object to a JSON string. Note that
Jackson will escape any quotation marks inside the String returned by
the custom serialization, so you cannot return e.g. a full JSON
object. For that you should use #JsonRawValue instead (see previous
section).
The #JsonValue annotation is added to the method that Jackson is to
call to serialize the object into a JSON string. Here is an example
showing how to use the #JsonValue annotation:
public class PersonValue {
public long personId = 0;
public String name = null;
#JsonValue
public String toJson(){
return this.personId + "," + this.name;
}
}
The output you would get from asking Jackson to serialize a
PersonValue object is this:
"0,null"
So you can use #JsonValue and put your code either to ignore or not from some fields when you try to convert into JSON
#JsonValue
public String toJson(){
//ignore fields or include them here
}
Just throw an IllegalArgumentException. The best case would be to deserialize, then run through a validator though so you separate the concerns of serialization, and domain validation.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = false) Long userId,
#JsonProperty(value = "screen_name", required = false) String screenName,
#JsonProperty(value = "text", required = true) String text) {
if(userId == null && screenName == null) {
throw new IllegalArgumentException("userId or screenName must be provided.");
}
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}

Resources