Imagine this simple controller method :
public ResponseEntity<?> findById(#Parameter(description = "id") #PathVariable String id) {
Optional<Model> model = repository.findById(id);
if(model.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("Model not found");
}
return ResponseEntity.status(HttpStatus.OK)
.body(model.get());
}
It returns the model if it is found or an error string message if not. The case could be more complexe then this.
So I'm returning a String or a Model types, which could not be bound to the generic type of ResponseEntity.
My question is why spring team designed this class as a generic type ? or am I using wrong this object ?
Among other reasons, ResponseEntity<T> is also returned by Spring's RestTemplate HTTP client. The generic type allows the client code to specify the type that should be used to interpret the HTTP response and get an appropriate Java object as the response body.
When you define your API you define what is the type of the object you will return in case of the operation goes OK, this is the object that you should use in the generic type of the response entity.
If you want to return different types you can remove the generic and return only a ResponseEntity and add to it whatever object you want
Related
I have some legacy controller which puts some amount of data into the Model object (needed for Thymeleaf template).
Now I have to return the same data as JSON in REST service.
For these purposes I have wrapped the block of data preparation into separate method to use in two places: in the old method which is used for thymeleaf template and in the new one:
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model) {
prepareIndexModel(model);
return "index";
}
#RequestMapping(value = "/index/model", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public Map<String, Object> indexModel(Model model) {
prepareIndexModel(model);
return model.asMap();
}
private void prepareIndexModel(Model model) {
model.addAttribute("prop1", ...);
...
}
However, when I try access via GET /index/model I receive the following error:
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "/WEB-INF/templates/index/model.html")
so it simply considers my method not as REST method. I guess, that it is because method is actually returns instance of ExtendedModelMap class which implements both interfaces: Model and Map.
Therefore, after chaning /index/model method to:
#RequestMapping(value = "/index/model", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public Map<String, Object> indexModel(Model model) {
prepareIndexModel(model);
return new LinkedHashMap<>(model.asMap());
}
everything started working expected: GET /index/model returns me desired JSON. So basically I just wrapped the model into LinkedHashMap.
My question is: is this a specified behaviour or simply a bug? I expected that by annotating a method with #ResponseBody annotation, Spring should ignore the fact that returning object is instance of Model. Shouldn't it?
UPDATE: #Sotirios Delimanolis provided the link to the very similar question, which was about version 3.2. But my question is not about why, but is it a bug of Spring or is it specified some where in the documentation?
UPDATE-2: Please, also note, that in the linked question a method has Model returning type, and its behaviour is described. In my case I have Map<...> returning type, what in my opinion makes this behaviour strange and inconsistent!
is this a specified behaviour or simply a bug?
I would say it's undefined behavior.
The Spring Framework Reference defines that a return type of Model means a View with a Model. It also defines that a return type of Map means a View with the Map as the Model. These are both well-defined.
It also specifies that for a method annotated with #ResponseBody, the returned value is written to the response HTTP body. Again, this is well-defined.
What it doesn't specify, is what happens when a #ResponseBody returns a Model or a Map.
It is very common to return a Map to be encoded as JSON, and that works correctly, i.e. #ResponseBody takes precedence over return type Map. This makes sense.
However, a Model is specifically for the purpose of model attributes for a View, so the fact that return type Model takes precedence over #ResponseBody makes some sense. But as I said, it's not specified behavior, but neither is it a bug. It is undefined.
If you asked me for what it should do, I'd leave it undefined, since Model as #ResponseBody doesn't make any sense to me.
Also, the documentation doesn't make the distinction between declared return type and actual return type, so it is undefined which is used.
The implementation uses the actual return type, which has the advantage that your handler method can return Object and then return e.g. a ModelAndView or an HttpEntity, depending on conditions. This flexibility makes sense, but it isn't explicitly defined, as far as I can see.
So, result of the combination of #ResponseBody, declared return type of Map, but actual return type of Model, is undefined.
If you ask me (and you kind of did), I'd say that your code is bugged. If you want to return a Map to be sent as the response body, why not just create the Map yourself. Asking the Spring framework for a view Model makes no sense at all to me.
Even reading the code, I'd be unsure what you actually intended, given the mixed signals of using a Model and specifying #ResponseBody.
Conclusion: Don't be lazy, and create the Map yourself.
I have a generic response wrapper class:
public class Response <T> {
T response;
}
and unrelated classes to be wrapped:
public class ServiceResponse {
String someField;
}
When I make a service request, I get a JSON response that looks something like:
{ "code":200, "response":{"someField":"some text"} }
Now, all my service responses have the same outer wrapper, i.e., they all have:
{ "code":200, "timestamp":"....", "response":... }
But the actual format/type of the response field is different for each service request. When I deserialize the response, I need to know the type of the response field so I can create the appropriate instance, if the deserialization was done within Response, I could use:
response = new T(jsonParser);
However, I'm doing all of this from within a library that is driven by reflection, so I normally deserialize the whole tree with code like:
wrapper = deserializer.parseObject(Response<ServiceResponse>.class)
but, at this point my parseObject method can't correctly determine the type of T.
I can use something like:
Response<ServiceResponse> response = new Response<>();
Field field = response.getClass().getDeclaredField("response");
Type type = field.getGenericType();
which then tells me that response is of type T but what I actually need is ServiceResponse
Per this SO question I tried casting as ParameterizedType but that would actually seem to apply to a field of type Response<ServiceResponse> and not the actual field within (and it fails because type can't be cast as ParameterizedType)
Is there any way to determine (at run time) the raw type of response?
Eventually, I may wind up having to create an annotation providing more details about how to deserialize the field, probably by providing a function to do it, but would prefer a more transparent approach.
Another possibility might be to actually assign a void instance of T to response at initialization time and then I could grab the actual type from that...
Check out this post:
http://mydailyjava.blogspot.com/2013/06/advanced-java-generics-retreiving.html
It's actually exactly what you're looking for.
According to this, you'll just need to extend your Response class and then query the generic type of its super.
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();
I am using Spring MVC for a Rest API and am trying to call the following method on the controller:
#RequestMapping(method=RequestMethod.POST, value=IApiVersion.VERSION_ALL_PREFIX + ORG_RESPONSE_PAUSE_WRITES_URL)
public #ResponseBody Boolean setResponsePauseWrites(#RequestParam List<Long> orgIds, #RequestParam boolean pauseWrites){
validateDatabusClient();
organizationService.setPauseResponseWrites(orgIds, pauseWrites);
return Boolean.TRUE;
}
I am using Spring's RestTemplate to submit the request like this:
#Override
public void setPauseWrites(List<Long> orgIds, boolean pauseWrites){
String orgIdString = StringUtils.collectionToCommaDelimitedString(orgIds);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("orgIds", orgIdString);
parameters.add("pauseWrites", Boolean.toString(pauseWrites));
restTemplate.postForObject(orgResponsePauseWritesURL, parameters, Boolean.class);
}
This works fine and all, but I would prefer to not need to convert the list of orgIds to a comma delimited string. I am frustrated because the spring mvc controller has no problem converting the strings back to the parameters it is expecting, List and boolean. I would expect the RestTemplate to have a built in message converter to handle basic java classes like List and Boolean.
When I try this code:
#Override
public void setPauseWrites(List<Long> orgIds, boolean pauseWrites){
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
parameters.add("orgIds", orgIds);
parameters.add("pauseWrites", Boolean.valueOf(pauseWrites));
restTemplate.postForObject(orgResponsePauseWritesURL, parameters, Boolean.class);
}
I get the following exception message:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [java.util.ArrayList]
What is my best option? I am planning on continuing to convert the parameters to Strings for the rest template call for now, but I would like to know if there is a MessageConverter I can add to my RestTemplate to make this work. Currently I am just using the default MessageConverters. I have tried adding the MappingJacksonMessageConverter and changing my content type header to support json, but I get the same results.
Thank you.
A message converter does not just deal with the Java type (such as Boolean) that is of interest to a Java programmer. Its job is to covert Java objects to and from a byte stream representation that has a format that is identified by a media type name. Spring is only ever going to provide media converters for widely used media types. There is no widely used media type for a list of arbitrary Java objects; how could there be? As for a list of integers; I doubt there will ever be such a type, ironically, because it is just a bit too simple. The TSV (tab separated values) media type is a simple type that can represent a simple list of integers.
So, yes, you will have to write your own converter.
I am new to Jersey, and I discovered that we can define our own parameter types to handle other types than string, as dates or boolean for instance.
I will work with an ORM to store the data in a database, so that I will be able to map an identifier to an instance of a class, let's say to a User.
Is it a good practise to define a param class which would handle the user id given in parameter (path or query for instance), and return the instance of User corresponding to the id?
If your param is directly mappable to a primitive type then there's no need to define your own. It sounds like you want to accept a user ID as a param, which is likely to be a long, int or String. All of these are automatically mapped.
For example;
#Path("/")
public class UserService {
#GET
#Produces(MediaType.APPLICATION_XML)
#Path("/{id}")
public User getUser(#PathParam("id") String id) {
//Your implementation here
}
}
Well, lets take some example:
stackoverflow.com/users/1235336/
Here we have the path with usedId in it. And if we proceed the link (execute a GET request), we will get the some user enity in response.
So defining a user id as a path parameter and returning some user instance is kind of normal practice.