Is it possible for a Spring controller to handle both kind of requests?
1) http://localhost:8080/submit/id/ID123432?logout=true
2) http://localhost:8080/submit/id/ID123432?name=sam&password=543432
If I define a single controller of the kind:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout", required = false) String logout,
#RequestParam("name") String username,
#RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
the HTTP request with "logout" is not accepted.
If I define two controllers to handle each request separately, Spring complains with the exception "There is already 'Controller' bean method ... mapped".
Before Java 8 and Spring 5 (but works with Java 8+ and Spring 5+ too)
You need to give required = false for name and password request parameters as well. That's because, when you provide just the logout parameter, it actually expects for name and password because they are still "implicitly" mandatory.
It worked when you just gave name and password because logout wasn't a mandatory parameter thanks to required = false already given for logout.
Update for Java 8 and Spring 5 (and above)
You can now use the Optional class from Java 8 onwards to make the parameters optional.
#RequestMapping (value = "/path", method = RequestMethod.GET)
public String handleRequest(#RequestParam("paramName") Optional<String> variableName) {
String paramValue = variableName.orElse("");
// use the paramValue
}
As part of Spring 4.1.1 onwards you now have full support of Java 8 Optional (original ticket) therefore in your example both requests will go via your single mapping endpoint as long as you replace required=false with Optional for your 3 params logout, name, password:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout") Optional<String> logout,
#RequestParam("name") Optional<String> username,
#RequestParam("password") Optional<String> password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
Create 2 methods which handle the cases. You can instruct the #RequestMapping annotation to take into account certain parameters whilst mapping the request. That way you can nicely split this into 2 methods.
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"logout"})
public String handleLogout(#PathVariable("id") String id,
#RequestParam("logout") String logout) { ... }
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"name", "password"})
public String handleLogin(#PathVariable("id") String id, #RequestParam("name")
String username, #RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model, BindingResult errors)
throws LoginException {...}
In case someone is looking for mapping Optional parameters with Pojo, same can be done like below.
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
LoginRequest loginRequest,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
#Data
#NoArgsConstructor
//#AllArgsConstructor - Don't use this
public class LoginRequest {
private Optional<String> logout = Optional.empty();
private Optional<String> username = Optional.empty();
private Optional<String> password = Optional.empty();
}
Note: Do not use #AllArgsConstructor on POJO else it will initialize the fields as null.
Related
The spring boot do not recognize my controllers only if i send more parameters on request. For example:
If i send normal GET request the spring boot recognize my controller:
http://localhost/idp/oauth/123/authorize
If i send GET request with extras parameters the spring boot do not recognize my controller:
http://localhost/idp/oauth/123/authorize?scope=public_profile
I need receive the request exactly for second example (with parameter scope), but the spring boot do not recognize the controller and redirect to /error.
code:
#Controller
#RequestMapping("/idp/oauth")
public class OAuthController {
#RequestMapping(value = "/{clientId}/authorize", method = RequestMethod.GET)
public String authorizeGet(
HttpServletRequest request,
HttpServletResponse response,
#PathVariable String clientId,
Model model) {
// ...
}
#RequestMapping(value = "/{clientId}/authorize", method = RequestMethod.POST)
public String authorizePost(
HttpServletRequest request,
HttpServletResponse response,
#PathVariable String clientId,
Model model) {
// ...
}
}
Since you are passing extra param with name "scope" Spring will search for #RequestParam in methods
It can't find any, thus the error
You need to modify your method to add all #RequestParam
You can also add optional fields if they are not mandatory with required = false
#RequestMapping(value = "/{clientId}/authorize", method = RequestMethod.GET)
public String authorizeGet(
HttpServletRequest request,
HttpServletResponse response,
#PathVariable String clientId,
#RequestParam(value = "scope") String scope,
#RequestParam(required = false, value = "optionalParam") String optionalParam,
Model model) {
// ...
}
You missed #RequestParam in the controller method definition.
More on #RequestParam
What is the difference between #RequestParam and #PathVariable while handling special characters?
+ was accepted by #RequestParam as space.
In the case of #PathVariable, + was accepted as +.
#PathVariable is to obtain some placeholder from the URI (Spring call it an URI Template)
— see Spring Reference Chapter 16.3.2.2 URI Template Patterns
#RequestParam is to obtain a parameter from the URI as well — see Spring Reference Chapter 16.3.3.3 Binding request parameters to method parameters with #RequestParam
If the URL http://localhost:8080/MyApp/user/1234/invoices?date=12-05-2013 gets the invoices for user 1234 on December 5th, 2013, the controller method would look like:
#RequestMapping(value="/user/{userId}/invoices", method = RequestMethod.GET)
public List<Invoice> listUsersInvoices(
#PathVariable("userId") int user,
#RequestParam(value = "date", required = false) Date dateOrNull) {
...
}
Also, request parameters can be optional, and as of Spring 4.3.3 path variables can be optional as well. Beware though, this might change the URL path hierarchy and introduce request mapping conflicts. For example, would /user/invoices provide the invoices for user null or details about a user with ID "invoices"?
#RequestParam annotation used for accessing the query parameter values from the request. Look at the following request URL:
http://localhost:8080/springmvc/hello/101?param1=10¶m2=20
In the above URL request, the values for param1 and param2 can be accessed as below:
public String getDetails(
#RequestParam(value="param1", required=true) String param1,
#RequestParam(value="param2", required=false) String param2){
...
}
The following are the list of parameters supported by the #RequestParam annotation:
defaultValue – This is the default value as a fallback mechanism if request is not having the value or it is empty.
name – Name of the parameter to bind
required – Whether the parameter is mandatory or not. If it is true, failing to send that parameter will fail.
value – This is an alias for the name attribute
#PathVariable
#PathVariable identifies the pattern that is used in the URI for the incoming request. Let’s look at the below request URL:
http://localhost:8080/springmvc/hello/101?param1=10¶m2=20
The above URL request can be written in your Spring MVC as below:
#RequestMapping("/hello/{id}") public String getDetails(#PathVariable(value="id") String id,
#RequestParam(value="param1", required=true) String param1,
#RequestParam(value="param2", required=false) String param2){
.......
}
The #PathVariable annotation has only one attribute value for binding the request URI template. It is allowed to use the multiple #PathVariable annotation in the single method. But, ensure that no more than one method has the same pattern.
Also there is one more interesting annotation:
#MatrixVariable
http://localhost:8080/spring_3_2/matrixvars/stocks;BT.A=276.70,+10.40,+3.91;AZN=236.00,+103.00,+3.29;SBRY=375.50,+7.60,+2.07
And the Controller method for it
#RequestMapping(value = "/{stocks}", method = RequestMethod.GET)
public String showPortfolioValues(#MatrixVariable Map<String, List<String>> matrixVars, Model model) {
logger.info("Storing {} Values which are: {}", new Object[] { matrixVars.size(), matrixVars });
List<List<String>> outlist = map2List(matrixVars);
model.addAttribute("stocks", outlist);
return "stocks";
}
But you must enable:
<mvc:annotation-driven enableMatrixVariables="true" >
#RequestParam is use for query parameter(static values) like: http://localhost:8080/calculation/pow?base=2&ext=4
#PathVariable is use for dynamic values like : http://localhost:8080/calculation/sqrt/8
#RequestMapping(value="/pow", method=RequestMethod.GET)
public int pow(#RequestParam(value="base") int base1, #RequestParam(value="ext") int ext1){
int pow = (int) Math.pow(base1, ext1);
return pow;
}
#RequestMapping("/sqrt/{num}")
public double sqrt(#PathVariable(value="num") int num1){
double sqrtnum=Math.sqrt(num1);
return sqrtnum;
}
1) #RequestParam is used to extract query parameters
http://localhost:3000/api/group/test?id=4
#GetMapping("/group/test")
public ResponseEntity<?> test(#RequestParam Long id) {
System.out.println("This is test");
return ResponseEntity.ok().body(id);
}
while #PathVariable is used to extract data right from the URI:
http://localhost:3000/api/group/test/4
#GetMapping("/group/test/{id}")
public ResponseEntity<?> test(#PathVariable Long id) {
System.out.println("This is test");
return ResponseEntity.ok().body(id);
}
2) #RequestParam is more useful on a traditional web application where data is mostly passed in the query parameters while #PathVariable is more suitable for RESTful web services where URL contains values.
3) #RequestParam annotation can specify default values if a query parameter is not present or empty by using a defaultValue attribute, provided the required attribute is false:
#RestController
#RequestMapping("/home")
public class IndexController {
#RequestMapping(value = "/name")
String getName(#RequestParam(value = "person", defaultValue = "John") String personName) {
return "Required element of request param";
}
}
it may be that the application/x-www-form-urlencoded midia type convert space to +, and the reciever will decode the data by converting the + to space.check the url for more info.http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
#PathVariable - must be placed in the endpoint uri and access the query parameter value from the request
#RequestParam - must be passed as method parameter (optional based on the required property)
http://localhost:8080/employee/call/7865467
#RequestMapping(value=“/call/{callId}", method = RequestMethod.GET)
public List<Calls> getAgentCallById(
#PathVariable(“callId") int callId,
#RequestParam(value = “status", required = false) String callStatus) {
}
http://localhost:8080/app/call/7865467?status=Cancelled
#RequestMapping(value=“/call/{callId}", method = RequestMethod.GET)
public List<Calls> getAgentCallById(
#PathVariable(“callId") int callId,
#RequestParam(value = “status", required = true) String callStatus) {
}
Both the annotations behave exactly in same manner.
Only 2 special characters '!' and '#' are accepted by the annotations #PathVariable and #RequestParam.
To check and confirm the behavior I have created a spring boot application that contains only 1 controller.
#RestController
public class Controller
{
#GetMapping("/pvar/{pdata}")
public #ResponseBody String testPathVariable(#PathVariable(name="pdata") String pathdata)
{
return pathdata;
}
#GetMapping("/rpvar")
public #ResponseBody String testRequestParam(#RequestParam("param") String paramdata)
{
return paramdata;
}
}
Hitting following Requests I got the same response:
localhost:7000/pvar/!##$%^&*()_+-=[]{}|;':",./<>?
localhost:7000/rpvar?param=!##$%^&*()_+-=[]{}|;':",./<>?
!# was received as response in both the requests
#RequestParam:We can say it is query param like a key value pair
#PathVariable:-It is came from URI
I have a request mapping for a controller let's say it's A, it receives post action and uses its post values as parameter, sometimes the parameters will be very long so that's the reason why it's POST not GET apart from the best practices and security;
RequestMapping(value = "/reports/performance/indicator/{indicatorType}", method = RequestMethod.POST)
public String generatePerformanceReportsIndicator(ModelMap map,HttpServletResponse response, #PathVariable("indicatorType") Long indicatorType,
#RequestParam(value = "siteIds", required = false) List<Long> siteIds,
#RequestParam(value = "timeframeIds", required = false) List<String> timeframeIds,
#RequestParam(value = "showTarget", required = false) String showTarget,Locale locale) {
And then it turned out that in another spring controller I need to forward the request to the first one.
The problem is how I can add post parameters to the request before forwarding it to the first request mapping? is that healthy to say for example?
new FirstController().generatePerformanceReportsIndicator(....);
Given that:
I don't want the first request mapping to use get instead of post of the mentioned reasons.
I don't want to write redundant code by creating another method that extract the parameters as attribute from the model map.
You should not manually call other controllers! What you can do is redirect to them with RedirectAttributes like:
#RequestMapping(value = "/doctor/doEditPatientDetails", method = RequestMethod.POST)
public String editPatientDetails(Model model, #ModelAttribute(value = "user") #Valid User user,
BindingResult result, RedirectAttributes attr, Principal principal) {
if (null != principal) {
if (result.hasErrors()) {
attr.addFlashAttribute("org.springframework.validation.BindingResult.user", result);
attr.addFlashAttribute("user", user);
attr.addAttribute("id", user.getId());
return "redirect:/doctor/editPatient/{id}";
}
}
....
return "redirect:/doctor/patients";
}
#RequestMapping(value = "/doctor/editPatient/{id}", method = RequestMethod.GET)
public String showEditPatient(Model model, #ModelAttribute("id") String id, Principal principal) {
if (null != principal) {
//here you can access the model and do what everything you want with the params.
if (!model.containsAttribute("user")) {
model.addAttribute("user", user);
}
....
}
return "/doctor/editPatient";
}
Note that, to redirect to a link like "redirect:/doctor/editPatient/{id}" you have to add the id in RedirectAttributes. Also not that there are many ways you can achive the same functionality like HttpServletRequest
I'm trying to write a simple PUT request method in Spring MVC. I got the following:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
String name,
String email) {
User user = repository.findOne(id);
user.setName(name);
user.setEmail(email);
System.out.println(user.toString());
repository.save(user);
return user;
}
Which is obviously wrong, because it returns the following:
User{id=1, name='null', email='null'}
I also tried with #RequestBody annotation, but that also did not help. Any ideas what I'm doing wrong here would be greatly appreciated.
You can receive name and email whith the #RequestBody annotation:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestBody User user) {}
This is a better practice when it comes to REST applications, as your URL becomes more clean and rest-style.
You can even put a #Valid annotation on the User and validate its properties.
On your postman client, you send the User as a JSON, on the body of your request, not on the URL. Don't forget that your User class should have the same fields of your sent JSON object.
See here:
You did not tell spring how to bind the name and email parameters from the request. For example, by adding a #RequestParam:
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestParam String name,
#RequestParam String email) { ... }
name and email parameters will be populated from the query strings in the request. For instance, if you fire a request to /users/1?name=Josh&email=jb#ex.com, you will get this response:
User{id=1, name='Josh', email='jb#ex.com'}
In order to gain more insight about defining handler methods, check out the spring documentation.
At the moment I just want to test redirecting from the update method back to the sox method. But instead I get an error complaining about a missing "update.jsp".
#RequestMapping(value = "/sox/update", method = RequestMethod.POST)
#ModelAttribute("formWrapper")
public final String update(HttpServletRequest request,
#ModelAttribute("formWrapper") FormWrapper formWrapper,
BindingResult bindResult,
ModelMap model)
{
return "redirect:/sox";
}
#ModelAttribute("formWrapper")
FormWrapper setupForm()
{
FormWrapper formWrapper = new FormWrapper();
return formWrapper;
}
#RequestMapping(value = "/sox", method = RequestMethod.GET)
public final String sox(HttpServletRequest request, ModelMap model)
{
return "sox";
}
I think your problem is the #ModelAttribute on the update method. Try it this way :
#RequestMapping(value = "/sox/update", method = RequestMethod.POST)
public final String update(HttpServletRequest request,
#ModelAttribute("formWrapper") FormWrapper formWrapper,
BindingResult bindResult,
ModelMap model)
{
return "redirect:/sox";
}
When you add the #ModelAttribute to a method spring treats the return as a model attribute :
Any other return type is considered to
be a single model attribute to be
exposed to the view, using the
attribute name specified through
#ModelAttribute at the method level
(or the default attribute name based
on the return type class name). The
model is implicitly enriched with
command objects and the results of
#ModelAttribute annotated reference
data accessor methods.
Look at 15.3.2.8 of the Spring Docs for more info: (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib)