Spring redirect: prefix issue - spring

I have an application which uses Spring 3. I have a view resolver which builds my views based on a String. So in my controllers I have methods like this one.
#RequestMapping(...)
public String method(){
//Some proccessing
return "tiles:tileName"
}
I need to return a RedirectView to solve the duplicate submission due to updating the page in the browser, so I have thought to use Spring redirect: prefix. The problem is that it only redirects when I user a URL alter the prefix (not with a name a resolver can understand). I wanted to do something like this:
#RequestMapping(...)
public String method(){
//Some proccessing
return "redirect:tiles:tileName"
}
Is there any way to use RedirectView with the String (the resolvable view name) I get from the every controller method?
Thanks

the call prefixed by redirect: is a url, which is sent in a standard browser 302 redirect. you can't redirect to a view, because a view isn't a url. instead you'll need a new servelet mapping to a 'success' view and then redirect to that instead
#RequestMapping("processing.htm")
public String method(){
//Some proccessing
return "redirect:success.htm"
}
#RequestMapping("success.htm")
public String method(){
return "tiles:tileName"
}
this case works fine when you just need to show a 'thank you' page, which requires no specific data from the processing stage. however, if your success page needs to show some information from the processing, there are 2 ways to do it.
1) pass the information in the url as a get post ("redirect:success.htm?message=hi"). this is incredibly hackable, and thus highly unrecommended.
2) the better way is to store information in the http session, using #SessionAttributes and #ModelAttribute

Related

Login page with Spring

I'm setting up my security support in Spring and I am exploring what it has to offer. While setting up my login URL and view, I needed to register simple controllers for home and login URL. Concretely, code is:
#Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/").setViewName("home");
registry.addViewController("/login");
}
In line registry.addViewController("/").setViewName("home"); it seems that that line simply adds controller for URL "/" and for GET to that URL simply uses view "home" for rendering.
Did I get it right? Main question is about line registry.addViewController("/login");. Here I add controller for "/login" URL.
But how application knows which view to use? When I tried the app it really used "login" view I created.
Well, you are on the right track but not completely. If you are NOT logged in and open the URL "yourdomain.tld/" you will be redirected to the URL "yourdomain.tld/home" which normally could be a login page. (first line). Of course, you need a controller to handle this request and return a proper JSP/JSF page.
Your second line is not needed. The trick is that with such a configuration you can use the URL "yourdoamin.tld/" with a different page for logged in users.
And to answer your main question, you need a controller for every URL or part of the URL you want to use. And within this controller, you can choose the views according to the requested URL.
Example:
#Controller
#RequestMapping(value="/info")
#Scope(value="session")
public class InfoController extends AbstractWebController {
#RequestMapping
public ModelAndView infoPage() {
ModelAndView mav = this.getModelAndView("info/infopage");
return mav;
}
}
This controller is responsible for all URLs starting with "yourdomain.tld/info". And as you can see I have only the URL "yourdomain.tld/info" to return a proper view. In this example I return "infopage.jsp". You can add code to return different views for other pages.

Changing back the url to original if exception thrown and return back to form

I have a thymeleaf signup form, which if we submit then a controller at "/signup_do" is called which validates and saves the user to database:
<form action="/signup_do" method="post">
...
</form>
The controller at "/signup_do" passes the request to the accountRegistration service method, which does the validation:
#PostMapping("/signup_do")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "Success";
}
The account registration method can throw an exception SignupFormException, which is handled by the #ExceptionHandler defined in that controller class:
#ExceptionHandler(value=SignupFormException.class)
public String handle(HttpSession session, Model response) {
Account returnDataToForm = (Account) session.getAttribute("accountToRegister");
response.addAttribute("name", returnDataToForm.getFirstName());
session.invalidate();
return "signup";
}
Now the problem is that when exception occurs, the inputs entered in the form is passed back to the signup form, and the entered data remains intact, but the url still remains as /signup_do.
I have tried using return "redirect:/signup" instead, which does change the url, but it ends up making a get request to the /signup url like
/signup?name=John...
but my /signup controller is not designed to handle a get request, it just knows to display the form, so the information is lost.
#GetMapping("/signup")
public String signupPage() {return "signup";}
I also tried using forward:/signup, but that just ended up throwing 405 error.
I figured out a clean workaround a few hours after asking this question.
What I did is change the name of the controller that handles the signup process to ("/signup") as well. Since the controller that displays the page is a #GetMapping("/signup") and the one that handles the signup process is a #PostMapping("/signup") there is no clash.
Now even if the controller changes, the url remains the same, since both of them are signup...
#GetMapping("/signup")
public String signupPage() {return "signup";}
#PostMapping("/signup")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "success";
}
And this works just like I wanted!!
Redirecting will make a get request to the controller looking for the view to display, which in your situation means losing your data for the reasons you give. I can think of two workarounds:
Don't do the redirect and change the URL manually with javascript everytime you enter this view. If you dislike having a "wrong" URL in a view, editing it manually looks the most reasonable and direct approach. You can see how to do this here, including it in a script that executes everytime the page loads/the submit button is pressed.
Do the redirect and avoid losing your info by storing it in the session for a while longer, accessing it in thymeleaf in this way, instead of getting it from a model attribute. This would mean you would have to be careful to remove this session attributes later. It's also not very "clean" that your get request for the form view includes the user info, so I wouldn't go with this solution if avoidable.

Why I can't redirect from a Spring MVC controller method to another controller method?

I am pretty new in Spring MVC and I have some problem trying to redirect to a controller method after that another controller method terminate its execution.
So I have the following situation. Into a controller class I have this method that correctly handle POST request toward the validaProgetti resource:
#RequestMapping(value = "validaProgetti", method=RequestMethod.POST)
public #ResponseBody String validaProgetti(#RequestBody List<Integer> checkedRowList) {
System.out.println("ID progetti da aggiornare: " + checkedRowList);
List<Twp1007Progetto> progettiDaValidare = new ArrayList<Twp1007Progetto>();
for (int i=0; i<checkedRowList.size(); i++) {
System.out.println("ID PROGETTO: " + checkedRowList.get(i));
progettiDaValidare.add(progettoService.getProgetto(checkedRowList.get(i)));
}
progettoService.validaProgetti(progettiDaValidare);
return "redirect:ricercaValidazione";
}
So this method is correctly mapped and when the validaProgetti resource is called it is executed.
At the end of this method I don't return a view name that render a JSP page but I have to redirect to another method (that do something and render a JSP page). So, instead to return a view name, I redirect toward another resource:
return "redirect:ricercaValidazione";
Then in the same controller class I have declared this method that handle request toward this ricercaValidazione resource:
#RequestMapping(value = "ricercaValidazione", method=RequestMethod.POST)
public String ricercaValidazione(#ModelAttribute ConsultazioneFilter consultazioneFilter, Model model, HttpServletRequest request) {
RicercaConsultazioneViewObject filtro = null;
try {
filtro = new ObjectMapper().readValue(request.getParameter("filtro"), RicercaConsultazioneViewObject.class);
filtro.setSelStatoProgetto(3); // Progetti da validare
} catch (IOException e) {
logger.error(e);
}
consultazioneFilter = new ConsultazioneFilter(filtro);
model.addAttribute("consultazioneFilter", consultazioneFilter);
model.addAttribute("listaProgetti", new ListViewObject<Twp1007Progetto>(progettoService.getListaProgettiConsultazione(consultazioneFilter)) );
return "validazione/tabellaRisultati";
}
The problem is that it can't work and after the redirection can't enter into the ricercaValidazione() method.
I think that maybe the problem is that this ricercaValidazione() method handle POST request toward the ricercaValidazione resource and the return "redirect:ricercaValidazione"; maybe generate a GET request.
But I am not sure about it.
Why? What am I missing? How can I solve this issue?
Tnx
the redirect and fordward prefix are for resolving views; you are tring to redirect from one controller to another one. This can be done but redirect works in the following way
A response is sent to the browser with the redirect http status code and and url
The browser loads via GET the request URL
Your Spring controller (and the corresponding ammping method) is invocated if it matches the annotation params
From what you write I'm not sure this is what you really want; as you already noted there is a mismatch between HTTP methods (GET vs POST).
Your second method ricercaValidazione expects a filtro param in order to filter some data, but in the validaProgetti there is nothing similar, so it seems that the two controllers are not directly chainable. If what you want is to display a page after validaProgetti that shows a form and the the user can submit it you must add a method annotated with a method GET and url ricercaValidazione; the new method must return the view containing the form; which points via POST to url of validaProgetti. In this way you can redirect from ricercaValidazione to validaProgetti
Give mapping name of your controller with redirect like
redirect:/controll-mapping_name/ricercaValidazione
have a look on this question
Unable to redirect from one controller to another controller-Spring MVC

Spring controller, why is the returned view ignored?

So, say I have an existing, working page Display Cashier, which displays information about a cashier in a shop. Now, I add a button to this page that looks like:
Manager
The request-mapping for this URL maps it (successfully) to a controller: HandleGetManager
the HandleGetManager controller looks like this:
#Controller
public class HandleGetManager{
private employeeBO employeeBO; //BO handles all business logic
//spring hooks
public HandleGetManager(){}
public void setemployeeBo(employeeBO employeeBO){
this.employeeBO = employeeBO;
}
//get controller
#RequestMapping(method=RequestMethod.GET)
public String getManager(#RequestParam String cashierId){
Long managerId = employeeBO.getManagerByCashierId(cashierId);
String redirectUrl = "/displayManager.ctl?managerId=" + managerId.toString();
return redirectUrl;
}
}
Here's what happens when I try it:
I hit the new button on the Display Cashier page, I expect the following to happen:
The browser sends a get request to the indicated URL
The spring request-mapping ensures that the flow of control is passed to this class.
the #RequestMapping(method=RequestMethod.GET) piece ensures that this method is evoked
The #RequestParam String cashierId instructs Spring to parse the URL and pass the cashierId value into this method as a parameter.
The EmployeeBo has been injected into the controller via spring.
The Business logic takes place, envoking the BO and the managerId var is populated with the correct value.
The method returns the name of a different view, with a new managerId URL arg appended
Now, up until this point, everything goes to plan. What I expect to happen next is:
the browsers is directed to that URL
whereupon it will send a get request to that url,
the whole process will start again in another controller, with a different URL and a different URL arg.
instead what happens is:
this controller returns the name of a different view
The browser is redirected to a half-right, half wrong URL: handleGetManager.ctl?managerId=12345
The URL argument changes, but the name of the controller does not, despite my explicitly returning it
I get an error
What am I doing wrong? Have I missed something?
Assuming you have a UrlBasedViewResolver in your MVC configuration, the String value you return is a View name. The ViewResolver will take that name and try to resolve a View for it.
What you seem to want to do is to have a 301 response with a redirect. With view names, you do that by specifying a redirect: prefix in your view name. It's described in the documentation, here.
Here's a question/answer explaining all the (default) ways you can perform a redirect:
How can I prevent Spring MVC from doing a redirect?

Handle url pattern that has user name as part of the url

Suppose I have 3 url patterns that needs to be handled by Spring MVC as follows:
1) www.example.com/login (to login page)
2) www.example.com/home (to my home page)
3) www.example.com/john (to user's home page)
I would like to know what is the best practice way of handling the url pattern that has username as part of the url (real world example is facebook fanpage www.faceboo.com/{fanpage-name})
I have come up with my own solution but not sure if this is the clean way or possible to do it.
In my approach, I need to intercept the request before it being passed to Spring MVC's dispatchservlet, then query the database to convert username to userid and change the request URI to the pattern that Spring MVC can recognize like www.example/user/userId=45.
But I am not sure if this is doable since the ServletAPI does not have the setter method
for requestURI(it does have the getter method for requestURI)
Or if you have a better solution please share with me. Thank in advance :-)
Spring MVC should be able to handle this just fine with PathVariables.
One handler for /login, one handler for /home, and one handler for /{userName}. Within the username handler you can do the lookup to get the user. Something like this:
#RequestMapping(value="/login", method=RequestMethod.GET)
public String getLoginPage() {
// Assuming your view resolver will resolve this to your jsp or whatever view
return "login";
}
#RequestMapping(value="/home", method=RequestMethod.GET)
public String getHomePage() {
return "home";
}
#RequestMapping(value="/{userName}", method=RequestMethod.GET)
public ModelAndView getUserPage( #PathVariable() String userName ) {
// do stuff here to look up the user and populate the model
// return the Model and View with the view pointing to your user page
}

Resources