Spring MVC handle Exceptions and how to show in same view - spring

If I handle exceptions with #ControllerAdvice and #ExceptionHandler How can I show error message to user in the same View. For ex. suppose user in "customer/new" view. and invoke "save" action, then it will go to controller and I call methods service layer. but if internal exception occurred in service layer, I want to show error message on same "customer/new" View.
I have written a separate class to handle exceptions as follow.
#ControllerAdvice
public class DefaultControllerHandler {
#ExceptionHandler({MyProjectException.class, DataAccessException.class})
public ResponseEntity<String> handleInternalErrorException(Exception e) {
logger.error(e.getMessage(), e);
return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Appreciate your ideas.
Thanks!

You can use flash redirect attributes.
#RequestMapping(value = "/administrator/users", method = RequestMethod.POST)
public String adminAddUser(#ModelAttribute("user") #Valid User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttrs) {
String redirectUrl = "/administrator/users";
try {
userService.save(user);
} catch (YourServiceException e) {
redirectAttrs.addFlashAttribute("errorMessage", "error occured: " + e.getMessage());
redirectAttrs.addFlashAttribute("userObject", user);
redirectUrl = "/administrator/users?form"; // If error - return to same view
}
return "redirect:" + redirectUrl;
}
#RequestMapping(value = "/administrator/users", params = "form", method = RequestMethod.GET, produces = "text/html")
public String adminUsersList(#ModelAttribute("errorMessage") final String errorMessage, #ModelAttribute("userObject") final User user Model model) {
if(user == null) {
user = new User();
}
model.addAttribute("user", user);
if(errorMessage != null) {
model.addAttribure("errorMessage", errorMessage);
}
return "administrator/users/create";
}
In that case you must have section on your users.jsp page to show errorMessaage. Something like this:
<c:if test="${not empty errorMessage}">${errorMessage}</c:if>
If you can fetch url path and redirectAttributes from controller method - you can do this through #ControllerAdvice

The only solution I can think about is make your call to the service layer AJAX and then redirect only if there are no errors, if not display the error message.
It might look something like this in your Javascript file
$("#someButton").click(function(){
//make your ajax call
if (noError){
window.location.href = contextPath+"/somePath";
}
else{
//display your error message
}
});

Related

Throwing custom exceptions in multi-layered spring project

I am trying to handle the use case where there's a violation for an unique field (for example, the username/mail), would it be correct to handle it like so? (I am using jdbcInsert on the dao layer)
#Transactional
#Override
public User register(String name, String surname, String username, String email, String password) {
User user = null;
try {
user = userDao.register(name, surname, username,
email, passwordEncoder.encode(password));
} catch (DuplicateKeyException duplicateKeyException) {
throw new DuplicateUserException(duplicateKeyException.getMessage());
} catch (DataAccessException dataAccessException) {
throw new SystemUnavailableException(dataAccessException.getMessage());
}
return user;
}
And catching my custom exceptions in the controller:
#ControllerAdvice
public class ErrorControllerAdvice {
#ExceptionHandler(DuplicateUserException.class)
public ModelAndView keyViolation(DuplicateUserException ex) {
ModelAndView mav = new ModelAndView("admin/user/new");
mav.addObject("duplicateMessage", ex.getErrorMessage());
return mav;
}
#ExceptionHandler(SystemUnavailableException.class)
#ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView unexpectedDatabaseError(SystemUnavailableException ex) {
LOGGER.error(ex.getErrorMessage());
return new ModelAndView("500");
}
}
Looks fine to me. Your custom exceptions live at a different level of abstraction, which gives them good reason for existing.
You might consider handling the exceptions in your controller, instead of using an Error translator class (ErrorControllerAdvice) though. This makes things more explicit and limit surprises about how exceptions are handled.

unable to return a page from Restcontroller

I am using stripe as a payment gateway. I just need to return a page from Webhook controller which is a rest controller. I know Restcontroller should not return a view but can't see any other option except this. Now I am using ModelandView interface to return a view but unable to do that. So please tell me that how can I return a view from restcontroller and what is wrong in this code.
#RestController
public class StripeWebhookController {
#Autowired
private FoodhubServiceImpl service;
#Autowired
private Payment payment;
private String endpointSecret="some endpointSecret";
#PostMapping("/foodhub/endpoint")
public ModelAndView handleStripeEvents(#RequestBody String payload, #RequestHeader("Stripe-Signature") String sigHeader, HttpServletRequest request) {
if(sigHeader == null) {
System.out.println("sigheader is null");
return null;
}
Event event;
// Only verify the event if you have an endpoint secret defined.
// Otherwise use the basic event deserialized with GSON.
try {
event = Webhook.constructEvent(
payload, sigHeader, endpointSecret
);
} catch (SignatureVerificationException e) {
// Invalid signature
System.out.println("Webhook error while validating signature.");
System.out.println(e);
return null;
}
// Deserialize the nested object inside the event
EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
StripeObject stripeObject = null;
if (dataObjectDeserializer.getObject().isPresent()) {
stripeObject = dataObjectDeserializer.getObject().get();
} else {
// Deserialization failed, probably due to an API version mismatch.
// Refer to the Javadoc documentation on `EventDataObjectDeserializer` for
// instructions on how to handle this case, or return an error here.
}
// Handle the event
switch (event.getType()) {
case "payment_intent.succeeded":
PaymentIntent paymentIntent = (PaymentIntent) stripeObject;
System.out.println("Payment succeeded for "+paymentIntent.getAmount());
handlePaymentIntentSucceeded(paymentIntent);
// Then define and call a method to handle the successful payment intent.
break;
default:
System.out.println("Unhandled event type: " + event.getType());
break;
}
ModelAndView mv = new ModelAndView();
mv.setViewName("payment-success.jsp");
return mv;
}

SPRING MVC: Defined HttpSession in One Method is Not Available to Another Method

I am facing a problem regarding to the Httpsession that I implementing in the Spring MVC project.
First of all, after the user successfully login, I will take the Httpsession object in loginAuthentication controller and set attribute with the name and value I want. (Shown in following figure).
A.java controller file,
#RequestMapping(value="login-authentication", method = RequestMethod.POST)
public String authentication(#Valid #ModelAttribute("systemAccount") SystemAccount systemAccount,
BindingResult bindingResult, Model model, HttpServletRequest request){
if (bindingResult.hasErrors()) {
model.addAttribute(GenericConstant.MessageAttributeName.ERROR_MSG_NAME.toValue(), SystemMessage.SystemException.LOGIN_INCORRECT_USERNAME_PASSWORD.toValue());
model.addAttribute("systemAccount", new SystemAccount());
return "index";
}else {
if (systemAccountService.authenticate(systemAccount.getUsername(), systemAccount.getPassword()) != null &&
!"".equals(systemAccountService.authenticate(systemAccount.getUsername(), systemAccount.getPassword()))) {
SystemAccount dbSystemAccount = systemAccountService.authenticate(systemAccount.getUsername(), systemAccount.getPassword());
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ID.toValue(),dbSystemAccount.getAccountID());
//check account role
if(dbSystemAccount.getCounterStaff()!= null && !"".equals(dbSystemAccount.getCounterStaff())){
CounterStaff counterStaff = dbSystemAccount.getCounterStaff();
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_NAME.toValue(), counterStaff.getStaffName());
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ROLE.toValue(), GenericConstant.SystemRole.COUNTER_STAFF.toValue());
}else if(dbSystemAccount.getCustomer()!= null && !"".equals(dbSystemAccount.getCustomer())){
Customer customer = dbSystemAccount.getCustomer();
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_NAME.toValue(), customer.getCustomerName());
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ROLE.toValue(), GenericConstant.SystemRole.CUSTOMER.toValue());
}else if(dbSystemAccount.getManager()!= null && !"".equals(dbSystemAccount.getManager())){
Manager manager = dbSystemAccount.getManager();
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_NAME.toValue(), manager.getManagerName());
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ROLE.toValue(), GenericConstant.SystemRole.MANAGER.toValue());
}else if(dbSystemAccount.getDoctor()!= null && !"".equals(dbSystemAccount.getCounterStaff())){
Doctor doctor = dbSystemAccount.getDoctor();
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_NAME.toValue(), doctor.getDoctorName());
request.setAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ROLE.toValue(), GenericConstant.SystemRole.DOCTOR.toValue());
}
request.setAttribute(SessionAttribute.AttributeName.LOGIN_DATE.toValue(), DateTimeUtil.getCurrentDate());
return "mainPage";
}else {
model.addAttribute(GenericConstant.MessageAttributeName.ERROR_MSG_NAME.toValue(), SystemMessage.SystemException.LOGIN_INCORRECT_USERNAME_PASSWORD);
model.addAttribute("systemAccount", new SystemAccount());
return "index";
}
}
}
After everything is ready, the controller will navigate user to the main page and the main page able to access all the defined variable without issue. (The following figure shown the controller that mapped with mainPage).
A.java controller file,
#RequestMapping(value = "/mainPage", method = RequestMethod.GET)
public String renderMainPageView(Model model, HttpServletRequest request) {
if(request.getAttribute(SessionAttribute.AttributeName.LOGIN_CHECK.toValue()) != null) {
model.addAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ID.toValue(),
request.getAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ID.toValue()));
model.addAttribute(SessionAttribute.AttributeName.LOGIN_ACC_NAME.toValue(),
request.getAttribute(SessionAttribute.AttributeName.LOGIN_ACC_NAME.toValue()));
model.addAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ROLE.toValue(),
request.getAttribute(SessionAttribute.AttributeName.LOGIN_ACC_ROLE.toValue()));
model.addAttribute(SessionAttribute.AttributeName.LOGIN_DATE.toValue(),
request.getAttribute(SessionAttribute.AttributeName.LOGIN_DATE.toValue()));
return "mainPage";
}else {
model.addAttribute("systemAccount", new SystemAccount());
return "index";
}
}
In the navigation menu of main page, I click on the selection to direct me to add manager web page. (The following shown the link).
<a href="addManager" target="ifrm" >Add New Account</a>
The controller that mapped with the link (GET) able to detect. However, this controller (renderAddManagerView) does not recognised the HTTP session that I defined earlier when I try to access using the getAttribute method in the if condition. It keep showing null value (Shown in the following figure.
B.java controller file,
#RequestMapping(value = "/addManager", method = RequestMethod.GET)
public String renderAddManagerView(Model model, HttpSession httpSession) {
if(httpSession.getAttribute(SessionAttribute.AttributeName.LOGIN_CHECK.toValue()) != null) {
model.addAttribute("manager", new Manager());
model.addAttribute(FormSelectionValue.FormSelectionAttributeName.COUNTRY_SELECTION.toValue(), FormSelectionValue.COUNTRY_SELECTION_LIST);
model.addAttribute(FormSelectionValue.FormSelectionAttributeName.GENDER_SELECTION.toValue(), FormSelectionValue.GENDER_SELECTION_LIST);
return "addManager";
}else {
model.addAttribute("systemAccount", new SystemAccount());
return "index";
}
}
So I am not sure what is the issue for my code and there is no error message is displayed.
I have solved the issue by using the HttpServletRequest instead of HttpSession.
Now my session will not be loss even redirect or navigate to any pages in JSP.
Something like this:
#RequestMapping("/renderview", method = RequestMethod.GET)
#Controller
public class TestController {
#RequestMapping(method = RequestMethod.GET)
public String myMethod(HttpServletRequest request)
{
request.getSession().setAttribute("mySession", "XXX");
return "jspview";
}
}
Reference: Set session variable spring mvc 3

404 Not Found exception handling

I have a controller that, in case there is no user with the given name, will return 404 NOT FOUND.
#GetMapping(value = "/profile/{username}", produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<User> getUsers(#PathVariable("username") String username) {
User user = userService.findOneByUsername(username);
if(user != null) {
return ResponseEntity.ok(user);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Then I created a controller that will be able to handle this exception
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public ModelAndView handleNotFound(NoHandlerFoundException e) {
return new ModelAndView("redirect:/signIn");
}
}
However, it has no effect. The controller returns the normal default 404 error page. It does not respond to my controller.
EDIT: I set spring.mvc.throw-exception-if-no-handler-found = true, but that also did not help. I'm using Spring Boot.
You're not throwing NoHandlerFoundException in your controller. This way the ControllerAdvice will not run.

Spring Boot - redirect to a different controller method

I am creating a very basic application with SpringBoot and Thymeleaf. In the controller I have 2 methods as follows:
Method1 - This method displays all the data from the database:
#RequestMapping("/showData")
public String showData(Model model)
{
model.addAttribute("Data", dataRepo.findAll());
return "show_data";
}
Method2 - This method adds data to the database:
#RequestMapping(value = "/addData", method = RequestMethod.POST)
public String addData(#Valid Data data, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "add_data";
}
model.addAttribute("data", data);
investmentTypeRepo.save(data);
return "add_data.html";
}
HTML files are present corresponding to these methods i.e. show_data.html and add_data.html.
Once the addData method completes, I want to display all the data from the database. However, the above redirects the code to the static add_data.html page and the newly added data is not displayed. I need to somehow invoke the showData method on the controller so I need to redirect the user to the /showData URL. Is this possible? If so, how can this be done?
Try this:
#RequestMapping(value = "/addData", method = RequestMethod.POST)
public String addData(#Valid Data data, BindingResult bindingResult, Model model) {
//your code
return "redirect:/showData";
}
sparrow's solution did not work for me. It just rendered the text "redirect:/"
I was able to get it working by adding HttpServletResponse httpResponse to the controller method header.
Then in the code, adding httpResponse.sendRedirect("/"); into the method.
Example:
#RequestMapping("/test")
public String test(#RequestParam("testValue") String testValue, HttpServletResponse httpResponse) throws Exception {
if(testValue == null) {
httpResponse.sendRedirect("/");
return null;
}
return "<h1>success: " + testValue + "</h1>";
}
Below Solution worked for me.
getAllCategory() method displays the data and createCategory() method add data to the database. Using return "redirect:categories";, will redirect to the getAllCategory() method.
#GetMapping("/categories")
public String getAllCategory(Model model) {
model.addAttribute("categories",categoryRepo.findAll());
return "index";
}
#PostMapping("/categories")
public String createCategory(#Valid Category category) {
categoryRepo.save(category);
return "redirect:categories";
}
OR using ajax jQuery also it is possible.
You should return a http status code 3xx from your addData request and put the redirct url in the response.

Resources