How to use RestTemplate with different ResponseEntities? - java

What if a XML webservice can respond with different xml structures? Eg an <OkResponse> and an <ErrorResponse>, having completely different fields?
ResponseEntity<Response> rsp = restTemplate
.postForEntity(url, new HttpEntity<>(xml, HEADERS), OkResponse.class);
Before sending the request, I don't know which type of response will come back. If I'm using OkResponse.class, I will get a ClassCastException if an ErrorResponse is returned.
How could I handle this?
The autogenerated beans are as follows:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({
OkResponse.class,
ErrorResponse.class
})
public class AbstractResponse {
}

Use String.class
ResponseEntity<String> rsp = restTemplate
.postForEntity(url, new HttpEntity<>(xml, HEADERS), String.class);
String responseBody = (String)rsp.getBody();
Object response=mapper.readValue(responseBody, Class.forName(responseClass))
Once response body is obtained. make use of service class that you want to map and convert it using jackson mapper .Made use of reflection since the entity passed can be different/dynamic

RestTemplate uses Jackson for JSON serialization, and it supports inherited types though the #JsonTypeInfo annotation. But it requires that all responses have a common 'type' property. If there is no common property that all responses share, then I think you need to use the String approach, and use String.contains() to find a unique property to determine which response type it is.

Related

Custom handler for response body in Jackson / Spring

I am trying to intercept the object that is being returned in my controller so that I can create a flat JSON structure of the response, before Spring invokes Jackson's serialization process.
I am going to support a query parameter that allows the client to flatten the response body. Something like:
/v1/rest/employees/{employeId}/id?flat=true
The controller method looks something like:
public Employee getEmployee(...) {}
I would like to avoid implementing this flattening logic in every one of my service calls and continue to return the Employee object.
Is there some kind of facility in Spring that would allow me to A) read the query string and B) intercept the object that is being returned as the response body?
Here's one idea. There may be a better way, but this will work:
Define an extra request mapping to do the flat mapping:
#RequestMapping(path = "/endpoint", params = {"flat"})
public String getFlatThing() {
return flatMapper.writeValueAsString(getThing());
}
// The Jackson converter will do its ordinary serialization here.
#RequestMapping(path = "/endpoint")
public Thing getFlatThing() {
return new Thing();
}
the "flatMapper" implementation can be whatever you like so long as it works.
One option is to use Jackson's ObjectMapper to write the value as json first and then use https://github.com/wnameless/json-flattener to flatten that to your desired output. There may also be a way to define a custom ObjectMapper that does flat mapping, though that would take some more work on your part.

Having alias param names to accept Url Encoded Form data values

In my Spring web application, I have an API that accepts requests with application/x-www-form-urlencoded content type.
#RequestMapping(value = "/do-it", method = {RequestMethod.POST})
public String test(#ModelAttribute("request")RequestDTO request,HttpServletRequest
httpServletRequest, Map<String, Object> model, RedirectAttributes redirectAttributes){
.....
}
My RequestDTO has following fields in it.
public class RequestDTO {
private String paramOne;
private String paramTwo;
// standard getters and setters
}
This implementation works fine, all the request params get mapped to the request dto as expected. However, now I have this requirement to accept the requests with the fields in following pattern.
param_one, param_two
I understand that, using #JsonProperty annotation on the fields in my request dto is not gonna work in my case since the request is not in the type of application/json.
The only way I have found to solve the issue is creating new setters like following (which looks ugly in my opinion when it comes to naming convention).
public void setParam_one(String param_one) {
this.paramOne = param_one;
}
Can some one help me to find a better way to get this done? I cannot change the param names in original request dto.
Thank you..!
I was able to get this done. Thanks to #neetash for guiding me.
Everything I needed to have was a Custom HandlerMethodArgumentResolver to map the post request body data to the object that I wanted to get.
I followed following linked tutorial to implement it. It contains every single thing someone needs to know to create a HandlerMethodArgumentResolver.
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-creating-a-custom-handlermethodargumentresolver/

Consuming PagedResources in abstract class

I have a server that exposes data with the spring-data-rest project and now I am writing services to consume those data and I started with a generic service that will suit all the common needs, one of which is getting the Page object.
I configured my RestTemplate to use the Jackson2HalModule as suggested here.
I've tried a lot of combinations and I was only able to use consume it properly in a non-generic way like this:
PagedResources<Resource<Company>> response2 = restTemplate.exchange(getUrl(), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference<PagedResources<Resource<Company>>>(){}).getBody();
But trying the same code with T didn't work (Resource links were deserialized but content of Resource object was null)
PagedResources<Resource<T>> response3 = restTemplate.exchange(getUrl(), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference<PagedResources<Resource<T>>>(){}).getBody();
Generically I am only able to deserialize the Company data using the following code:
PagedResources<T> response1 = restTemplate.exchange(getUrl(), HttpMethod.GET, HttpEntity.EMPTY, PagedResources.class).getBody();
But this one doesn't deserialize the Resource object so the Company&Links data of what should be the Resource object are stored in a LinkedHashMap instead.
I also tried using the object mapper on the LinkedHashMap with data but I was unsuccessful. It's been a long day so I might be too close to see the correct way of doing this. I'll appreciate any help with this. Thank you.
The question: Is there a way of getting proper generics working in this case?

RestTemplate getForEntity map to list of objects

I have a response from URL which looks like:
{"seq":1,"id":"Test1","changes":[{"rev":"1-52f5cdf008ecfbadf621c2939af7bd80"}]}
{"seq":2,"id":"Test2","changes":[{"rev":"1-8ce403a89dc5e7cb4187a16941b3fb7d"}]}
{"seq":3,"id":"Test3","changes":[{"rev":"1-52as7ddfd8ecfbadf621c2939af7bd80"}]}
{"seq":4,"id":"Test4","changes":[{"rev":"1-6yy03a89dc5e7cb45677a16941b3fb7d"}]}
If the mapped object is String, then getting all the changes feed.
ResponseEntity<String> responseEntity = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, String.class);
Whereas, if I happen to use a custom Value object, somethings like:
public class KnChanges {
private long seq;
private String id;
private List changes;
with getter and setter methods, then I'm getting only the first doc change details. Even if the KnChanges[] (array) is used, only the first change is obtained.
Can you please help as to how the JSON list structure mentioned above can be mapped to an object?
Thanks
Harsha
Some people asked for a better answer with some explaination. So here it is:
As sujim mentioned: You need to
ParameterizedTypeReference<List<KnChanges>> responseType = new ParameterizedTypeReference<List<KnChanges>>() {};
ResponseEntity<List<KnChanges>> resp = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, responseType);
List<KnChanges> list = resp.getBody();
Explaination:
The last parameter of the exchange method call defines the class that gets instantiated when the response is received. The response data will then be mapped to the resulting object. So you need a List.class in fist place. Because you expect a JSON array. Now you need to define the type of the content of that List. Here Java's type erasure throws some stones in your way. As Java removes generic type information at compile-time, you can't just define the expected List to be a List<KnChanges>.class. "Luckily" there is a hack ;) And that hack is new ParameterizedTypeReference<List<KnChanges>>() {}. Provided that object the application is able to read the generic type information at runtime. And therefore is able to map the received data to your Java objects.
As a side-note: There a several implementations of that hack. It's commonly used for dependency injection or mapper systems, where type erasure can sometimes be an issue. Also Googles Guava offers an implementation. See the code for more information. There you can also learn how it's done, if you like.
ParameterizedTypeReference<List<KnChanges>> responseType = new ParameterizedTypeReference<List<KnChanges>>() {};
ResponseEntity<List<KnChanges>> resp = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, responseType);
List<KnChanges> list = resp.getBody();

How to serialize POJO fields differently based on Accepts header

We have a POJO that contains a collection and have it annotated thus:
#XmlElement(name = "<MyId")
#XmlElementWrapper(name = "MyIds")
private final Set<Long> myIds;
We are using JacksonJaxbJsonProvider in CXF to do the marshalling in our REST service.
The problem we are seeing is that if someone requests application/xml the response is correct, in that the user gets:
<MyIds>
<MyId>123</MyId>
<MyId>456</MyId>
...
</MyIds>
But when application/json is requested, the user gets (note the singular field name):
{
"MyId" : [123, 456, ...]
}
What I want to know is if there's a way to make that plural in the JSON response, and if so, how.
This feels like it may be bug in Jackson but there may be a perfectly good reason this is happening. Also, I realize that if everyone used the same POJO, we wouldn't have to care about what the marshalled text looked like, but in this case, one of the consumers cannot use our POJO.
If you're using version 2.1 or above of Jackson there's a feature which can be enabled to get the behaviour you want
ObjectMapper m = new ObjectMapper()
m.configure(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME, true);

Categories