Spring MVC 3 - How come #ResponseBody method renders a JSTLView? - java

I have mapped one of my method in one Controller to return JSON object by #ResponseBody.
#RequestMapping("/{module}/get/{docId}")
public #ResponseBody Map<String, ? extends Object> get(#PathVariable String module,
#PathVariable String docId) {
Criteria criteria = new Criteria("_id", docId);
return genericDAO.getUniqueEntity(module, true, criteria);
}
However, it redirects me to the JSTLView instead. Say, if the {module} is product and {docId} is 2, then in the console I found:
DispatcherServlet with name 'xxx' processing POST request for [/xxx/product/get/2]
Rendering view [org.springframework.web.servlet.view.JstlView: name 'product/get/2'; URL [/WEB-INF/views/jsp/product/get/2.jsp]] in DispatcherServlet with name 'xxx'
How can that be happened? In the same Controller, I have another method similar to this but it's running fine:
#RequestMapping("/{module}/list")
public #ResponseBody Map<String, ? extends Object> list(#PathVariable String module,
#RequestParam MultiValueMap<String, String> params,
#RequestParam(value = "page", required = false) Integer pageNumber,
#RequestParam(value = "rows", required = false) Integer recordPerPage) {
...
return genericDAO.list(module, criterias, orders, pageNumber, recordPerPage);
}
Above do returns correctly providing me a list of objects I required.
Anyone to help me solve the mystery?

If a controller method returns null, Spring interprets that as saying that you want the framework to render the "default view".
It would be better, I think, that when the method is #RequestBody-annotated, this logic should not apply, but perhaps that's hard to implement - how would it handle a null return from a method that normally returns XML, for example?
Anyway, to stop this from happening, you need to make sure you return something, like an empty Map.

Related

Zero length part of URL in Spring controller RequestMapping PathVariable breaks resolution

I'm trying to make an app's REST API more RESTful and it feels like I'm not using the Spring RequestMappings in the way intended.
I have a single GET end point for doing reads:
#RequestMapping(value = "thing/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName", required = false)
String thingName,
#RequestParam(value = "findByComponent", required = false)
String findByComponentQuery,
#AuthenticationPrincipal User user) {
...
To be more restful, I want this endpoint to do both:
GET /thing/{thingName} returns a single thing having that name
GET /thing or /thing/ with query params returns lists of things
So in my controller, I can test whether {thingName} is null or zero-length and if so, check the query params for known query names.
However calling this with http://localhost:8080/thing/?findByComponent=component123 returns a 404 from Spring with this logging:
12:45:18.485 PageNotFound : No mapping found for HTTP request with URI [/thing/] in DispatcherServlet with name 'dispatcher' : WARN : XNIO-1 task-3 : org.springframework.web.servlet.DispatcherServlet
Spring does not allow path variables ({thingName}) to be mapped to an empty String. In practice, this means that the URL /thing/?findByComponent=component123 does not map to thing/{thingName} with an empty {thingName}, but rather, it expects there to be some mapping for thing. Since there is no endpoint that maps to the path thing (without the path variable), a 404 error is returned.
To solve this issue, you can break this single endpoint into two separate endpoints:
#RequestMapping(value = "thing/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName") String thingName,
#AuthenticationPrincipal User user) {
// ...
}
#RequestMapping(value = "thing",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThings(,
#RequestParam(value = "findByComponent", required = false) String findByComponentQuery,
#AuthenticationPrincipal User user) {
// ...
}
For more information, see With Spring 3.0, can I make an optional path variable?.
The required=false flag allows for two types of requests:
/thing
/thing/<some_value>
Strictly speaking, including a trailing slash at the end of the URL (i.e. /thing/) means that a decision was made to include a value for the path variable, but none was provided. In the context of REST APIs, /thing and /thing/ are two different endpoints, where the latter means that a value after the trailing slash was expected.
A workaround for not having to create three separate request mappings (one for each case above) is to set the #RequestMapping value for the controller to the base path and then have a "" and "/{thingName} request mapping for the two endpoints:
#RestController
#RequestMapping("thing")
public class ThingController {
#RequestMapping(value = "/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName") String thingName) {
return "foo";
}
#RequestMapping(value = "",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThings(
#RequestParam(value = "findByComponent", required = false) String findByComponentQuery) {
return "bar";
}
}
In this case, the following mappings will occur:
/thing: getThings
/thing/: getThings
/thing/foo: getThing
An example of this workaround, including test cases can be found here.

Spring path params with multiple slash

I have a Spring boot app where I have an API that takes other urls as path params. For example:
host:port/{eid} is my base path and after this I can have URLs like
host:port/{eid}/abc
host:port/{eid}/abc/pqr/
host:port/{eid}/abc/pqr/b=2
host:port/{eid}/abc/pqr/xyz
host:port/{eid}/abc/pqr/xyz?a=1
...and so on...
I would like to define a controller that I can map to all the above URLs and that should work something like
#RequestMapping(value = "/{eid}/{urlParts}", method = RequestMethod.GET)
public ResponseEntity<Object> share(
#PathVariable String eid,
#PathVariable String urlParts) {
......
}
I tried using #PathVariable Map<String, String> path and also #RequestMapping(value = "/{eid}/{urlParts:.+}"
but couldn't get the expected result.
Is there any solution to receive path slash(/) in path param.
Note: I can not URL encode the slash(/) in the URL. That's not an option for me.
I know the query is too old but still it's useful and this answer can help others.
You can get the full url parts using request attribute as below.
#RequestMapping(value = "/{eid}/**", method = RequestMethod.GET)
public ResponseEntity<Object> share(#PathVariable String eid, HttpServletRequest request) {
Object uriObject = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (null != uriObject) {
String urlParts = uriObject.toString().replaceFirst("^/" eid + "/", "");
}
....
}
why don't you try #RequestParam to take url if you working with jsp or other stuff..
#PathVariable means that the annotated method argument should be extracted from the path of the invoked URL. #RequestParam means that the annotated method argument must be extracted from the request parameters. None of these annotations cause the annotated arguments to be put in the request, session or application scope.
so you use your map also...
${username} means "write the value of the username attribute (found in page, or request, or session, or application scope) in the response". Since you didn't include any username attribute in any of those scopes, it doesn't write anything.
The code would work if the method returned a ModelAndView object, and the model contained a username attribute and a studentid attribute.
you can refer below code and link :
First URL : localhost:8080/projectName/test?firstname=john
Second URL :localhost:8080/projectName/test?firstname=john&secondname=roy
#Controller
#RequestMapping("/test")
public class TestController {
#RequestMapping(value = { "/test/{firstname}/test" }, method = { RequestMethod.GET })
public String someMethod(#PathVariable("firstname") String firstname){
return someMethod(firstValue )
}
#RequestMapping(value = { "/test/{firstname}/{otherString}/test" }, method = { RequestMethod.GET })
public String someOtherMethod(#PathVariable("firstname") String firstname, #PathVariable("secondname") String secondValue) {
return someMethod(firstValue + "/" + secondValue)
}
}
so I am not sure if there is a direct spring implementation to doing this however, you could us a mixture of things.
#RequestParam - returns a map of the URL params (succeeding the ?)
#PathVariable - return the eid
HttpServletRequest - use the request to return the URI and strip host:port/{eid} and anything after ? , then use Arrays.asList(str.split("/")); (remember this is a wrapper of an array use new ArrayList<Sting>(Arrays.asList(str.split("/"))) )
#RequestMapping(value = "/{eid}", method = RequestMethod.GET)
public ResponseEntity<Object> share(
#PathVariable String eid,
#RequestParam Map<String,String> allRequestParams,
HttpServletRequest request) {
......
}

Receive Collection/Iterable as arguments in spring boot controller

I am new to Spring and I want to write a controller which will take Collection/Iterable as arguments. Like this:
#RequestMapping(value = "friends", method = RequestMethod.POST)
public #ResponseBody Callable<Iterable<User>>
getFriendsOfUser(#RequestParam(required = true, name = "mobiles") Iterable<String> mobs) {
// return callable
}
There is no compilation error, but I cannot make it work. Can you say how will this work? And how shall be the request to this api be constructed?
public String getFriendsOfUser(#RequestParam(required = true, value = "mobiles") String[] mobiless){
....
}
and your mobile should be
mobiles=myValue1&mobiles=myValue2&mobiles=myValue3
or
mobiles=myvalue1,myValue2,myValue3
still if you have any doubt post your front-end code and Ajax call.
You've mapped a POST method so you might need #RequestBody instead of #RequestParam
#RequestParam is, as the name implies, for request parameters: [host]/endpoint?param=foo&secondParam=bar
whereas
#RequestBody is for JSON/XML or any other type content sent as the request's body.

How to map REST parameters to complex object?

I want to create a REST service with spring that takes a bunch of parameters. I'd like these parameters to be mapped automatically into a complex transfer object, like:
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public String content(#RequestParam RestDTO restDTO) {
Sysout(restDTO); //always null
}
public class RestDTO {
private boolean param;
//getter+setter
}
But: when I execute a query like localhost:8080/myapp?param=true the restDTO param remains null.
What am I missing?
Try with localhost:8080/myapp?param=true.
Probably a case where another pair of eyes sees the obvious :)
EDIT
Remove #RequestParam from method signature, works for me.
It turned out I have to omit the #RequestParam for complex objects:
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public String content(RestDTO restDTO) {
Sysout(restDTO);
}
So, I see few problems (if it's not mistyping of course):
localhost:8080/myapp&param=true "&" isn't correct, you have to use "?" to split params from URL like localhost:8080/myapp?param=true.
I don't see mapping value in #RequestMapping(method = RequestMethod.GET) (But if you caught the request you've made correct configuration).

Spring MVC and thymeleaf ModelAttribute either null or not evaluated

I'm developing a web application with Spring MVC and Thymeleaf as my ViewResolver. I have the following controller handler method:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(#ModelAttribute("error") String error /*, other attributes */) {
// find out if there is an error
error = getErrorMessage();
return "someHTMLfile";
}
My view contains this line:
<p><span th:text="${error}">Error Message goes here</span></p>
When executed, the tag does not render to anything. This is probably due to ${error} evaluating to an empty string but I can't understand why. Doesn't Spring's #ModelAttribute annotation add the object to the model map automatically, where Thymeleaf can find it?
If I instead have:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(ModelMap map /*, other attributes */) {
// find out if there is an error
String error;
error = getErrorMessage();
map.addAttribute("error", error);
return "someHTMLfile";
}
The view is rendered perfectly fine with the error message. Does #ModelAttribute not add the object to the request model?
Edit: I've tried doing both:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(#ModelAttribute("error") String error, ModelMap map /*, other attributes */) {
// find out if there is an error
error = getErrorMessage();
map.addAttribute("error", error);
return "someHTMLfile";
}
This also doesn't work.
Actually I don't think your issue is related to Thymeleaf, just SpringMVC :-)
In your first snippet, you don't add anything to the request model but try to get an object called "error" back from the form.
In your second snippet, you do add an object to the model, that's why your view is well rendered.
Take a look at the SpringMVC doc here (16.3.3.8) to have a better understanding of the #ModelAttribute annotation on a method argument.
I feel stupid but whatever, we all make mistakes.
Spring was creating a new String instance for me and injecting it into my method and into the model under the key error. String is an immutable object, so when I do error = getErrorMessage(), I assign another instance to my error object. Now there is my error and the error String in Spring's model with a value of "". That's why Thymeleaf rendering only finds the empty string.

Categories