How to do a redirect in Spring MVC controller? - spring

I am trying use a PRG (Post-Redirect-Get) pattern in one of my Spring MVC controller. The controller collects user data from an HTML form, does some processing, stores data in DB and then shows a JSP page. After saving data a redirect should happen and then the JSP page should be displayed.
I also tried to prepend "redirect:" in front of the VIEW_NAME but I get 404 then.
Please guide.
CartPageController.java
#Controller
#RequestMapping("/cartPageController.do")
public class CartPageController {
private static final Logger LOG = Logger.getLogger(CartPageController.class);
private static final String VIEW_NAME = "cart";
#Autowired
protected MasterDao masterDao;
#RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView processRequest(HttpServletRequest request, HttpServletResponse response) {
LOG.debug("Into the CartPageController...");
HttpSession session = request.getSession();
ModelAndView mav = new ModelAndView();
//create Cart object and store it in session
Cart cart = null;
if (session.getAttribute("cart") != null) {
cart = (Cart) session.getAttribute("cart");
} else {
cart = createCart();
session.setAttribute("cart", cart);
}
LOG.debug("cart = " + cart);
//determine the cart operation
String btnAddToCart = GenericUtils.nullToEmptyString(request.getParameter("btnAddToCart"));
String removeProduct = GenericUtils.nullToEmptyString(request.getParameter("removeProduct"));
String updateProduct = GenericUtils.nullToEmptyString(request.getParameter("updateProduct"));
LOG.debug("btnAddToCart = " + btnAddToCart);
LOG.debug("removeProduct = " + removeProduct);
LOG.debug("updateProduct = " + updateProduct);
if (btnAddToCart.length() > 0) {
addToCart(request, cart);
} else if (removeProduct.length() > 0) {
removeProduct(request, cart);
} else if (updateProduct.length() > 0) {
updateCart(request, cart);
}
//TODO: Should use PRG pattern here
//TRIED TO APPEND "redirect:" here but does not work, gives me 404
mav.setViewName(VIEW_NAME);
return mav;
}
//some more code below here
}

You should redirect to url not a view name. as Spring doc says:
While
the use of RedirectView works fine, if the controller itself creates
the RedirectView, there is no avoiding the fact that the controller is
aware that a redirection is happening. This is really suboptimal and
couples things too tightly. The controller should not really care
about how the response gets handled. In general it should operate only
in terms of view names that have been injected into it.
The special redirect: prefix allows you to accomplish this. If a view
name is returned that has the prefix redirect:, the
UrlBasedViewResolver (and all subclasses) will recognize this as a
special indication that a redirect is needed. The rest of the view
name will be treated as the redirect URL.

you can try
return new ModelAndView(new RedirectView("/page"));

You can't just prepend "redirect:" to your view name which results in redirect:cart and then obviously 404, what you have to do for redirect is to specify the redirect path for e.g.:
redirect:/cart.htm
This should explain it more in details.

You can do this way
redirect:cart.do and have requestmapping in controller which will return cart view.
Hope this will work.

Related

When returning a json, how can you tell using Spring that you want to go to a certain url?

In my code, I am returning a path in String form(/successPassword or /systemError). Basically this tells Spring to go to the path /successPassword or /systemError, /successPassword goes to successPassword.html and /systemError goes to systemError.html. These html files are located in src/main/resources/templates.
#RequestMapping(value = "/user/new_password", method = RequestMethod.POST)
public String createNewPassword(#RequestParam(value = "newpassword", required = false) String password,
#RequestParam(value = "hash") String hash, Model model) {
LOG.info("set new password using hash " + hash);
LOG.info("new password " + password);
boolean hashValid = passwordService.isHashValid(hash);
if (hashValid) {
ValidateMessage message = passwordService.validateNewPassword(password);
if (!message.isError()) {
Account account = passwordService.getAccount(hash);
passwordResetService.saveNewPassword(password, account);
model.addAttribute("account", account);
return "/successPassword";
} else {
LOG.info("Password not complex enough.");
return "/systemError";
}
} else {
LOG.info("Invalid hash.");
return "/systemError";
}
}
Now it looks like I need to return a json object instead. I know that I have to annotate the method with #ResponseBody instead, but my question is how would i package now the object that I will return so that whoever receives it will know to which path it needs to go to? My html codes are using Thymeleaf (no javascript whatsoever).

Downloading a pdf file from JSP file in Java

I want to download one of my JSP pages as PDF. For example, this is my function which returns to JSP page:
#RequestMapping(value = "/cart", method = RequestMethod.GET)
public String cart(HttpSession httpSession ,final Model model, RedirectAttributes redirectAttributes){
StudentData studentData =(StudentData)httpSession.getAttribute("student");
if(studentData ==null){
redirectAttributes.addFlashAttribute("isFromCartFlag", Boolean.TRUE);
return REDIRECT_PREFIX + "/login" ;
}
final List<ProductData> cartDataFromDb = listofProducts.retrieveCartEntriesFromDb(studentData.getName());
List<ProductData> sessionCart = (List<ProductData>) httpSession.getAttribute("sessionCart");
if (sessionCart != null) {
for (ProductData sessionCartEntryData : sessionCart) {
for (ProductData cartEntryData : cartDataFromDb) {
if(cartEntryData.getProductName().equalsIgnoreCase(sessionCartEntryData.getProductName())){
int quantityOfItem = cartEntryData.getQuantity()+sessionCartEntryData.getQuantity();
sessionCartEntryData.setQuantity(quantityOfItem);
}
}
listofProducts.insertCartEntriesIntoDb(studentData.getName(),sessionCartEntryData.getProductName(),sessionCartEntryData.getQuantity());
}
}
final List<ProductData> cartData = listofProducts.retrieveCartEntriesFromDb(studentData.getName());
List<ProductData> productDataListForCart = new ArrayList<ProductData>();
for (ProductData cartDataEntry : cartData) {
ProductData productData = listofProducts.displayProductDetailsFromProductId(cartDataEntry.getProductName());
if(productData.getProductName().equalsIgnoreCase(cartDataEntry.getProductName())){
cartDataEntry.setBasePrice(productData.getBasePrice());
cartDataEntry.setDiscout(productData.getDiscout());
cartDataEntry.setProductImagePath(productData.getProductImagePath());
productDataListForCart.add(cartDataEntry);
}
}
model.addAttribute("listOfProducts", productDataListForCart);
model.addAttribute("studentData", httpSession.getAttribute("student"));
if(productDataListForCart.isEmpty()){
model.addAttribute("isCartEmptyFlag", Boolean.TRUE);
}
return "CartPage";
}
This function displays cartPage and I want to display a link on this cart page so that if I click on that link the get page will be downloaded as a PDF file with the same format as it displayed in JSP page. This is how my page looks like, in case you're interested:
Spring Web MVC with PDF View Example outlines an approach. In summary, you implement a bean that extends Spring's AbstractView. This view accepts the model from Spring MVC which you can then use to render the PDF using the API of your choice. The article uses a ResourceBundleViewResolver to map the view name to a bean but I personally prefer to use a BeanNameViewResolver.
Spring does provide an AbstractPdfView class but use ann older version of iText for its implementation.

SpringBoot/MVC & Thymleaf form validation on POST with URL parameters

I have a form and validation works. The problem comes in when a url parameter was added. The url parameter is a token and is required. So this is what my controller looks like:
#RequestMapping(value = "/resetpassword", method = RequestMethod.GET)
public String showResetForm(ResetPassword resetPassword, Model model,
#RequestParam(value = "token", required = true) String token,
#RequestParam(value = "msg", required = false) String msg){
model.addAttribute("token", token);
return "resetpassword";
}
#RequestMapping(value = "/resetpassword", method = RequestMethod.POST)
public String setPwd(#ModelAttribute("resetPassword") #Valid ResetPassword resetPassword,// RedirectAttributes reDirectAttr,
BindingResult bindingResult, Model model,
#RequestParam(value = "token", required = true) String token,
#RequestParam(value = "msg", required = false) String msg){
if (bindingResult.hasErrors()) {
//reDirectAttr.addFlashAttribute("org.springframework.validation.BindingResult.resetPassword",bindingResult);
//reDirectAttr.addFlashAttribute("resetPassword",resetPassword);
return "resetpassword?token="+token;
}
else {
if (token == null) {
// TODO: no token, what to do here??
return "redirect:/resetpassword?token=\"\"&msg=notoken";
}
ResetPasswordResponseDto response = super.resetUserPassword(
resetPassword.getUname(), resetPassword.getPassword(),
token);
if (response.getPasswordResetResult() == PasswordResetResult.SUCCESSFUL) {
// TODO: it worked, what now?
return "redirect:/login";
} else if (response.getPasswordResetResult() == PasswordResetResult.INVALID_TOKEN) {
// TODO: bad token
return "redirect:/resetpassword?token="+token+"&msg=badtoken";
} else if (response.getPasswordResetResult() == PasswordResetResult.OUT_OF_POLICY_PW) {
// TODO: out of policy pw
return "redirect:/resetpassword?token="+token+"&msg=outofpolicy";
} else if (response.getPasswordResetResult() == PasswordResetResult.LDAP_FAILURE) {
// TODO: other failure
return "redirect:/resetpassword?token="+token+"&msg=error";
}
}
return "redirect:/resetpassword?token="+token+"&msg=error";
//return new RedirectView("resetpassword?token=\"\"&msg=notoken");
}
So I tried a bunch of things but nothing seems to work. Here is what I would like to happen when the view is requested /resetpassword?token=1232453 the view is displayed. Then if the form has errors the url parameter persists in the url and the form displays the errors. Right now I get an error saying that the template cannot be resolved. Ok fair enough, so I tried doing a redirect instead
return "redirect:/resetpassword?token="+token;
and that seems to work, however the URL parameter is lost and the view loses the bindingResult errors. In the code, I posted I also tried FlashAttributes but I just get an error "Validation failed for object='resetPassword'. Error count: 4" which is correct but I need it to show the form and the errors I coded with Thymeleaf. Any help or suggestions would be great!
Resources I have looked at:
Spring - Redirect after POST (even with validation errors)
&
SpringMVC controller: how to stay on page if form validation error occurs
Have you tried returning a ModelAndView instead of just the redirect string? Attributes on the model will be available as URL query parameters.
ModelAndView redirect = new ModelAndView("redirect:/resetpassword");
redirect.addObject("token", token);
redirect.addObject("msg", "error");
return redirect;

POSTing to my ASP.NET MVC app from 3rd party site

I'm testing a payment provider (SagePay) and as part of a process, their server POSTs to my site and expects a response. I can't get this to work using MVC.
I set up a classic asp test reponse page and added it to my MVC app:
<%
Response.Buffer = True
response.Clear()
response.contenttype="text/plain"
response.write "Status=OK" & vbCRLF
response.write "RedirectURL=http://www.redirectsomewhere.co.uk" & vbCRLF
response.End()
%>
This work fine.
However, when I try to do the same with MVC, it doesn't work:
Controller:
[HttpPost]
public ActionResult TestCallback()
{
return View();
}
View:
#{
Response.Buffer = true;
Response.Clear();
Response.ContentType = "text/plain";
Response.Write("Status=OK" + System.Environment.NewLine);
Response.Write("RedirectURL=http://www.redirectsomewhere.co.uk" + System.Environment.NewLine);
Response.End();
}
The error message is a generic error from the payment provider so is no real help, but I have narrowed the error down to the point at which the page renders.
I can browse to both pages fine (i need remove the HttpPost attribute from the MVC controller method for this), and both pages display identical data.
This is the MVC url that the payment provider is POSTing to:
http://myipaddress/CA_UAT/Token/TestCallback
This is the classic asp URL that works fine:
http://myipaddress/CA_UAT/Token/TestCallback.asp
I created a 'Token' directory for the asp page so the urls would match for testing purposes.
What am I doing wrong?
UPDATE
In response to Hari's comment, I installed a Firefox plugin called 'Header Spy' which gives me this information:
Response HTTP/1.1 200 OK
Source: Response
HttpHeader:Server
Request:User-Agent Cookie
Response:Response Date Set-Cookie
Both pages show the same info.
You don't need to return an action result in order to send just plain text back to the screen. The simplest way of accomplishing this is to return a string value. Replace the code in your controller with what is below.
[HttpPost]
public string TestCallback()
{
string result = "Status=OK";
result += System.Environment.NewLine;
result += "RedirectURL=http://www.redirectsomewhere.co.uk";
result += System.Environment.NewLine;
return result;
}
This will return no other response that what you have in the string. By using an ActionResult and View you are likely returning markup from the master view.
Instead of writing the response in the view, I would write it in the action method like this:
[HttpPost]
public ActionResult TestCallback()
{
Response.Buffer = true;
Response.Clear();
Response.ContentType = "text/plain";
Response.Write("Status=OK" + System.Environment.NewLine);
Response.Write("RedirectURL=http://www.redirectsomewhere.co.uk" + System.Environment.NewLine);
Response.Flush();
return new EmptyResult();
}
When returning EmptyResult you will ensure that MVC doesn't append anything to the response.
Try like this:
[HttpPost]
public ActionResult TestCallback()
{
var sb = new StringBuilder();
sb.AppendLine("Status=OK");
sb.AppendLine("RedirectURL=http://www.redirectsomewhere.co.uk");
return Content(sb.ToString(), "text/plain");
}
or in a more MVCish way:
View model:
public class ResponseViewModel
{
public string Status { get; set; }
public string RedirectUrl { get; set; }
}
and then a custom action result:
public class StatusActionResult : ContentResult
{
private readonly ResponseModel _model;
public StatusActionResult(ResponseModel model)
{
_model = model;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
response.ContentType = "text/plain";
response.Write(string.Format("Status={0}{1}", _model.Status, Environment.NewLine));
response.Write(string.Format("RedirectURL={0}", _model.RedirectUrl));
}
}
and finally your controller action:
[HttpPost]
public ActionResult TestCallback()
{
var model = new ResponseModel
{
Status = "OK",
RedirectUrl = "http://www.redirectsomewhere.co.uk"
};
return new StatusActionResult(model);
}
I wonder if sagepay is expecting a file extension..ie doing some kind of URL validation on heir side. Do you know if your Action is being invoked?
Also try adding a route that makes your mvc URL look like "TestCallback.asp".

ID in Spring-MVC 2.5 edit form using #Controller

I have a problem with the my Controller code. GET works fine (both empty form + form populated from db), POST works fine only for creating new object, but doesn't work for editing. Part of my #Controller class:
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.GET)
public String setUpForm(#RequestParam(value="id", required = false) Long id, ModelMap model) {
Vehicle v;
if (id == null) {
v = new Vehicle();
} else {
v = vehicleManager.findVehicle(id);
}
model.addAttribute("vehicle", v);
return "vehicle_save";
}
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.POST)
public String save(#ModelAttribute("vehicle") Vehicle vehicle, BindingResult result, SessionStatus status) {
vehicleValidator.validate(vehicle, result);
if (result.hasErrors()) {
return "vehicle_save";
}
if(vehicle.getId() == null) {
vehicleManager.createVehicle(vehicle);
} else {
vehicleManager.updateVehicle(vehicle);
}
status.setComplete();
return "redirect:vehicle_list.html";
}
The first method creates a vehicle object (including its ID). But the second method gets the same object without the ID field (set to null).
What could I do: manually set vehicle.setID(id from parameters) and then save it to database. This causes JPAOptimisticLockException + I don't like that solution.
Is there a way to pass my Vehicle object with ID to the second method? BTW, I would like to avoid adding hidden ID field to the JSP.
the example you suggested is using session to store the value. the #SessionAttribute is to bind an existing model object to the session. Look at the source code the class is annotated with #SessionAttributes("pet").Which means your model attribute named "pet" is getting stored in session.Also look at the code in processSubmit method of EditPetForm class
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete(); //look at its documentation
return "redirect:/owners/" + pet.getOwner().getId();
}
}
I havnt used something like this before.But i guess putting ur id in session is the way
BTW, I would like to avoid adding hidden ID field to the JSP.
This is common solution. What's wrong with it ? You should create hidden input with id.
May be you can try using session, cause you cant store info between two request. But that will be uglier i guess.
Btw, Can you please explain a little why you want to avoid adding hidden fields? I'm little curious

Resources