Spring 3 -- how to do a GET request from a forward - spring-mvc

I'm trying to forward a request to another Spring controller that takes a GET request, but it's telling me POST is not supported. Here is the relevant portion from my first controller method, which does take a POST request, since I'm using it for a login function.
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#ModelAttribute("administrator") Administrator administrator,
Model model) {
// code that's not germane to this problem
return "forward:waitingBulletins";
}
Here is the method I'm trying to forward to.
#RequestMapping(value = "/waitingBulletins", method = RequestMethod.GET)
public String getWaitingBulletins(Model model) {
// the actual code follows
}
Here is the error message in my browser.
HTTP Status 405 - Request method 'POST' not supported
--------------------------------------------------------------------------------
type Status report
message Request method 'POST' not supported
description The specified HTTP method is not allowed for the requested resource (Request method 'POST' not supported).

forward maintains the original request intact, so you are forwarding a POST request and missing a handler for it.
By the looks of it, what you're really trying to implement is the POST-redirect-GET pattern, which uses redirect instead of forward.
You only need to change your POST handler to:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#ModelAttribute("administrator") Administrator administrator,
Model model) {
// code that's not germane to this problem
return "redirect:waitingBulletins";
}
to make it work.

Related

Dynamically changing the #ResponseStatus in annotation driven Spring MVC

I am really not sure if this is feasible using Spring 3.2 MVC.
My Controller has a method declared as below:
#RequestMapping(method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<Foo> getAll(){
return service.getAll();
}
Questions:
What is the meaning of #ResponseStatus(HttpStatus.OK) ?
Does it signifies that the method will always return a HttpStatus.OK status code.
What if an exception is thrown from the service layer?
Can I change the Response Status on occurrence of any exception?
How can I handle multiple response statuses depending on conditions in the same method?
#ResponseStatus(HttpStatus.OK) means that the request will return OK if the handling method returns normally (this annotation is redundant for this case, as the default response status is HttpStatus.OK). If the method throws an exception, the annotation does not apply; instead, the status will be determined by Spring using an exception handler.
How can I handle multiple response statuses depending on conditions in the same method?
That question has already been asked.
Can I change the Response Status on occurrence of any exception
You have two choices. If the exception class is one of your own, you could annotate the exception class with #ResponseStatus. The other choice is to provide the controller class with an exception handler, annotated with #ExceptionHandler, and have the exception handler set the response status.
If you return a ResponseEntity directly, you can set the HttpStatus in that:
// return with no body or headers
return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
If you want to return an error other than 404, HttpStatus has lots of other values to choose from.
You cannot set multiple status value for #ResponseStatus. One approach I can think of is to use #ExceptionHandler for response status which is not HttpStatus.OK
#RequestMapping(value = "login.htm", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public ModelAndView login(#ModelAttribute Login login) {
if(loginIsValidCondition) {
//process login
//.....
return new ModelAndView(...);
}
else{
throw new InvalidLoginException();
}
}
#ExceptionHandler(InvalidLoginException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ModelAndView invalidLogin() {
//handle invalid login
//.....
return new ModelAndView(...);
}

prevent json of my command object from comming down to client during post

My controller has following calls
#ModelAttribute("commandObject")
public UsersCommand getCommand(HttpServletRequest req) throws Exception {
...
return command;
}
#RequestMapping(value = {"addusers.json"}, method = RequestMethod.GET)
public void handleGet() {
//empty method
}
#RequestMapping(value = {"addusers.json"}, method = RequestMethod.POST)
public void handlePost(#ModelAttribute("commandObject") UsersCommand command, HttpServletRequest req) throws Exception {
//do stuff
}
during the get i get the json of my UsersCommand object, however after I do a post, I am getting the json of my command object which i do not need as i want to do a fire and forget post.
How can I avoid the json object from coming down to browser during post?
It seems like you only want JSON returned with the GET method. To do this, remove the #ModelAttribute method all-together. This tells Spring to add the return object to your model on every handler in the controller, which you don't want. Then modify your GET handler to be like the following:
#RequestMapping(value = {"addusers.json"}, method = RequestMethod.GET)
#ResponseBody
public UserCommand handleGet() {
UserCommand cmd = getUserCommand();
return cmd;
}
The #ResponseBody annotation tells Spring to serialize the return type to JSON (or XML if you annotated your class with JAXB, and depending on the request accept headers). For this to work, you also need to add Jackson to your classpath and make sure you're using <mvc:annotation-driven /> or #EnableWebMvc in your XML or Java config, respectively.
This will sound like self-promotion, but I wrote a post about this if you want more detail: http://codetutr.com/2013/04/09/spring-mvc-easy-rest-based-json-services-with-responsebody/.
Hope that's helpful! Let me know if I can add any more clarity for you.

spring redirect not working

Spring redirect in controller not working like return "redirect:/reservation/reservationSuccess" but return "/reservation/reservationSuccess"; is working. Why it is not working. where it went wrong. Please help.
#RequestMapping(method = RequestMethod.POST)
public String submitForm(#ModelAttribute("reservation") Reservation reservation,
BindingResult result,
SessionStatus status,
Model model) {
validator.validate(reservation, result);
if(result.hasErrors()) {
model.addAttribute("reservation",reservation);
return "reservation/reservationForm";
}
reservationService.make(reservation);
status.setComplete();
return "redirect:reservation/reservationSuccess";
}
When you're doing a redirect to reservation/reservationSuccess, by definition, the browser will send a new request to the URL reservation/reservationSuccess of your web app. You will see the complete URL in the address bar of your browser.
If this URL is not mapped to any servlet in your web app, you will obviously get a 404 error.
You need to understand that the point of a redirect is not to dispatch to a view (a JSP). The point is to make the browser go to another URL in your web app. The path you put after the redirect: prefix is thus supposed to be the path of an action of your Spring MVC app. Not the path of a view.
You have to have another method in your Controller to intercept the reservation/reservationSuccess GET request.
For example:
#RequestMapping(value="reservation/reservationSuccess", method = RequestMethod.GET)
public String getSuccess() {
return "reservation/reservationForm";
}
"redirect:xxx" is looking for a RequestMapping to match the redirect string xxx, however, the return "xxx" is going to look for View Resolver to map that string to a JSP page. That is the main difference.

Redirecting to a new URL in spring 3.1 MVC?

I am very confused using spring web MVC to "redirect".
I want to redirect to a URL, but the address bar in brwser is not updated.
First, I post a form to a controller and in this controller I use the "redirect:" prefix to a new controller (it works), but the address bar is still referencing the previous URL.
Here is my code for teh controller to receive the post request:
#RequestMapping(method=RequestMethod.POST)
public ModelAndView processSubmit(LoginFormBean formBean, BindingResult result,
Model model) {
System.out.println(formBean.getUsername());
System.out.println(formBean.getPassword());
return new ModelAndView("redirect:/index");
}
For the controller to redirect:
#Controller
#RequestMapping("/index")
public class IndexController {
#RequestMapping(method=RequestMethod.GET)
public String show() {
return "index";
}
}
Before the form is posted, the form, URL is "http://localhost:7001/mobi"
after post and redirect, the URL is not "http://localhost:7001/mobi/index", but still "http://localhost:7001/mobi"
Are there any visible error in my code that need to be corrected?
Can the community assist me in implementing this correctly?
The only visible answer I can surmise based on your code is that its doubtful you have a modelandview that's a redirect to index. You can pretty much ditch the modelandview syntax with Spring 3, and just return the pages you want directly. Here's an example from my own code, where I also receive a posted form and redirect to another controller:
#RequestMapping(value = REQUEST_MAP, method = RequestMethod.POST)
public String processForm(HttpServletRequest req, HttpServletResponse res, #ModelAttribute("myForm") myForm form, BindingResult result)
throws Exception {
//super cool form handling logic here!
return "redirect:/anotherControllerMappedURI";
}
I could have also just returned a string to return a view page directly. Like, return "index" to send the user to an index.jsp page. Then you could use a meta tag to do an html redirect if necessary.

Spring 3 MVC Handle multiple form submit with a single Controller

Spring 3 MVC Handle multiple form submit with a Controller.
I am developing JSP page with multiple forms. 1) Search Customer, 2) Search Product, 3) Print Something etc. I've a different form bind object tied to each form and my controller code looks similar to below
#Controller
#RequestMapping(value="/search.do")
public class SearchController {
#RequestMapping(method = RequestMethod.GET)
public String pageLoad(ModelMap modelMap) {
modelMap.addAttribute("productSearch", new ProductSearchCriteria());
modelMap.addAttribute("customerSearch", new CustomerSearchCriteria());
modelMap.addAttribute("print", new PrintForm());
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView searchProducts(#ModelAttribute("productSearch") ProductSearchCriteria productSearchCriteria,
BindingResult result, SessionStatus status) {
//Do Product search
return modelAndView;
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView searchCustomers(#ModelAttribute("customerSearch") CustomerSearchCriteria customerSearchCriteria,
BindingResult result, SessionStatus status) {
//Do Customer search
return modelAndView;
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView printSomething(#ModelAttribute("print") PrintForm printForm,
BindingResult result, SessionStatus status) {
//Print something
return modelAndView;
}
}
Above certainly doesn't work as I assumed it would. I get exception saying 'Request method 'POST' not supported'. If I have only one POST method inside above controller say searchProducts it works well. But it won't with more than one methods with POST. I also tried adding hidden parameter in JSP and changing method signatures similar to below only to get the same exception again.
#RequestMapping(method = RequestMethod.POST, params="pageAction=searchProduct")
public ModelAndView searchProducts(#ModelAttribute("productSearch") ProductSearchCriteria productSearchCriteria,
BindingResult result, SessionStatus status) {
//Do Product search
return modelAndView;
}
Can anyone please suggest correct way to achieve above? Also any reference to source material or further reading will be greatly appreciated. Thanks.
EDIT #1: The above approach with params="pageAction=searchProduct" works perfectly as far as you get your hidden parameter right in JSP (see comment below). In addition to that, answers by #Bozho and #Biju Kunjummen is also very helpful and a good (possibly better?) alternative to tackle multiple form submit.
Your mappings are not completely correct #TMan:
The mappings in web.xml are to get Spring Dispatcher servlet to handle your request -
eg. like in your case:
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/META-INF/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
appServlet
*.do
So now any request to URI's ending with .do will be handled by Spring's DispatcherServlet
The controller can have a mapping, like in your case:
#Controller
#RequestMapping(value="/search.do")
But where you have gone wrong is in the RequestMapping for the controller :
#Controller
#RequestMapping(value="/search")
The .do at the end of RequestMapping should not be there, as it is purely for the Dispatcher servlet to be invoked, once that is invoked it will handle dispatching to the correct Controller method, so your call to /search.do will end up with the controller.
Now, each method can be annotated with RequestMapping like in your case, with a RequestMethod atribute of RequestMapping, specifying whether to dispatch to the method in case of POST or GET:
#RequestMapping(method = RequestMethod.GET)
#RequestMapping(method = RequestMethod.POST)
So when there is a POST to the /search.do URI the appropriate method will be called.
In your case, there are multiple methods annotated with the RequestMethod.POST attribute, so the AnnotationMethodHandlerAdapter component of Spring simply doesn't know which method to dispatch to.
There are a couple of things that you can do:
put a request mapping for each method, the way suggested by #Bozho:
#RequestMapping(value="/customers" method=Request.POST)
#RequestMapping(value="/products" method=Request.POST)
so now your request URI's would be
/search/customers.do
/search/products.do
and you should be doing POST to get the correct dispatch method.
Get rid of method=Request.POST all together and depend on the #RequestMapping like above to find the correct method.
You can pass an optional params attribute to RequestMapping, which again is a way to help Spring find your correct method to dispatch to:
#RequestMapping(method=Request.POST, params="customers")
with customers parameters in the request or say products parameters in the request.
The simplest will be option 1 though.
EDIT 1: Adding a reference to a good Spring document - http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html
When you have multiple forms, why don't you map the three forms to different URLs?
#RequestMapping("/products")
public ModelAndView searchProducts(..)
#RequestMapping("/customers")
public ModelAndView searchCustomers(..)
And have your form actions be pointed at /search/products and /search/customers (no need of the .do)
I've had the same problem: two forms (register and contact) on the same page with two submit buttons.
Each form has its own validation by Spring.
I've managed to do this in one Controller:
#RequestMapping(value = "/contactOrRegister.do", method = RequestMethod.GET)
public void contactOrRegisterFormGet(#ModelAttribute("contactForm") ContactForm contactForm, #ModelAttribute("registerForm") RegisterForm registerForm) {
//-----prepare forms for displaying here----
}
#RequestMapping(value = "/contact.do", method = RequestMethod.POST)
public String contactFormPost(#ModelAttribute("contactForm") ContactForm contactForm, BindingResult result, final Model model) {
contactValidator.validate(contactForm, result);
if (result.hasErrors()) {
model.addAttribute("registerForm", new RegisterForm());
return "contactOrRegister";
}
//--- validation is passed, do submit for contact form here ---
}
#RequestMapping(value = "/register.do", method = RequestMethod.POST)
public String registerFormPost(#ModelAttribute("registerForm") RegisterForm registerForm, BindingResult result, final Model model) {
registerValidator.validate(registerForm, result);
if (result.hasErrors()) {
model.addAttribute("contactForm", new ContactForm());
return "contactOrRegister";
}
//--- validation is passed, do submit for register form here ---
}
I need to create a new form (contact or register) and put it in model when validation is failed because "contactOrRegister" view needs both forms to display. So when "contact" form was submitted and has errors the "register" form content will be erased. This way fits for me.
The contactOrRegister.jsp contains both forms with different action:
<form:form action="/register.do" modelAttribute="registerForm" method="POST">
<!-- register form here -->
</form:form>
<form:form action="/contact.do" modelAttribute="contactForm" method="POST">
<!-- contact form here -->
</form:form>
You can write
#RequestMapping(method = {RequestMethod.GET,RequestMethod.POST})
when using multiple method on the same controller. This is useful when sometimes the user wants to submit the data using GET or when you use return "forward:url"; from another controller.

Resources