I am attempting to set-up a scheme for uniformly handling exceptions in Spring. As such, I need a way to pass context information into an #ExceptionHandler-annotated method, for example consider the following:
#ExceptionHandler(Exception.class)
public void handleException(Exception ex, HttpServletRequest request) {
// Need access to myContext from login()
}
#RequestMapping(value = "{version}/login", method = RequestMethod.POST)
public void login(HttpServletRequest request, #PathVariable String version, #RequestParam("userName") String userName, #RequestParam("password") String password, ModelMap model) throws Exception {
...
myContext = "Some contextual information"
...
i_will_always_throw_an_exception();
}
Since Spring is responsible for translating a thrown exception into an invocation of handleException(), I am having difficulty trying to find a way to pass myContext to the handler. One thought I have is creating a subclass of HttpServletRequest. If that approach works I would have code like this:
#ExceptionHandler(Exception.class)
public void handleException(Exception ex, MyCustomHttpServletRequest request) {
// I now have access to the context via the following
String myContext = request.getContext();
}
#RequestMapping(value = "{version}/login", method = RequestMethod.POST)
public void login(MyCustomHttpServletRequest request, #PathVariable String version, #RequestParam("userName") String userName, #RequestParam("password") String password, ModelMap model) throws Exception {
...
myContext = "Some contextual information"
request.setContext(myContext);
...
i_will_always_throw_an_exception();
}
But, if I follow this approach, how do I properly use my own arbitrary sub-class of HttpServletRequest to make this work?
Can't you just put it into exception (if necessary - wrapping the original exception with the new one)?
#ExceptionHandler(MyContextualException.class)
public void handleException(MyContextualException ex) {
// Need access to myContext from login()
}
#RequestMapping(value = "{version}/login", method = RequestMethod.POST)
public void login(HttpServletRequest request, #PathVariable String version, #RequestParam("userName") String userName, #RequestParam("password") String password, ModelMap model) throws Exception {
...
myContext = "Some contextual information"
...
try {
i_will_always_throw_an_exception();
} catch (Exception ex) {
throw new MyContextualException(myContext, ex);
}
}
An alternative approach is to pass the context as a request attribute:
request.setAttribute("myContext", myContext);
Related
I've written a few extensions of ExceptionHandlerExceptionResolver, it intercepts all exceptions that it should, but instead of returning only error message and HTTP status code it makes really weird redirect by its own URL built upon users requested URL. For instance:
user's url -> .../myModule/api/myEntity/123 (it's an id)
resolver's redirect url -> .../myModule/api/myEntity/myEntity/123
Server doesn't have such resource and obviously it will respond with 404.
The question is: why it makes redirect and how to configure it to return only a message and status code?
My resolver:
public class BusinessLayerExceptionHandler extends ExceptionHandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView wrappedResponse = new ModelAndView();
wrappedResponse.addObject("errorMessage", ex.getMessage());
wrappedResponse.setStatus(HttpStatus.BAD_REQUEST);
return wrappedResponse;
}
}
I guess the usage of ModelAndView assumes redirection. At least that's a method description that I found in DispatcherServlet.
...
* #return a corresponding ModelAndView to forward to
* #throws Exception if no error ModelAndView found
*/
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
...
If so, how to make it return just error message and HTTP status code?
You can return just error message and HTTP status code by creating a custom View.
public class YourCustomView implements View {
private final String errorMessage;
public YourCustomView(String errorMessage) {
this.errorMessage = errorMessage;
}
#Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter pw = response.getWriter()) {
pw.write(errorMessage);
}
}
}
You need to put the custom View object into ModelAndView object in HandlerExceptionResolver#resolveException.
public class BusinessLayerExceptionHandler implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView wrappedResponse = new ModelAndView();
wrappedResponse.setStatus(HttpStatus.BAD_REQUEST);
wrappedResponse.setView(new YourCustomView(ex.getMessage()));
return wrappedResponse;
}
}
why it makes redirect
It seems that Spring recognize the view name as a defaultViewName and forwards to it (by calling RequestDispatcher#forward).
In DispatcherServlet#processHandlerException, a defaultViewName is set to the view name of a ModelAndView returned by resolveException when it doesn't have View object. A defaultViewName is got from DispatcherServlet#getDefaultViewName that translates a HTTP request into a view name.
Another Solution
I think you may be able to use #ControllerAdvice and #ExceptionHandler instead. It also can handle an exception thrown from a controller.
#ControllerAdvice
public class YourControllerAdvice {
#ExceptionHandler
public ResponseEntity<Map<String, String>> handleBusinessLayerException(
Exception exception) {
Map<String, String> body = Map.of("errorMessage", exception.getMessage());
return ResponseEntity.badRequest().body(body);
}
}
See Also
Spring Web MVC document about HandlerExceptionResolver
Spring Web MVC document about ControllerAdvice
I want to validate date as request parameter.
My endpoint url is like
http://localhost:8080/api/get/getCurrencyRate?date=02-20-2017
Controller:
#RequestMapping(value = "/getCurrencyRate", produces={"application/json"},
method = RequestMethod.GET)
public CurrenctRate getCurrencyrate(#RequestHeader ("Authorization") String
authorization, #RequestParam(value="date") #DateTimeFormat(pattern="MM-dd-
yyyy") #Valid Date date) throws Exception {
For the above input (02-20-2017) service is working fine. I want to validate the request param send appropiate response to the user. How can I do that.
e.g.
if the request is like
http://localhost:8080/api/get/getCurrencyRate?date=02/20/2017
response should be "Please enter date in "MM-DD-YYYY" format"
whereas now I am getting
Error code **400**
<b>JBWEB000069: description</b>
<u>JBWEB000120:
- The request sent by the client was syntactically incorrect.
</u>
Please advice.
The best solution I can think of is to have methods for all types of date format BUT formation the path, or use path parameters, like so:
//Using Path
#RequestMapping(value = "/getCurrencyRate/{date}", produces={"application/json"}, method = RequestMethod.GET)
public CurrenctRate getCurrencyRateOfDate(#RequestHeader ("Authorization") String authorization, #PathVariable("date") #DateTimeFormat(pattern="MM/dd/yyyy") #Valid Date date) throws Exception {
OR, with request parameter
//Using Request Parameter
#RequestMapping(value = "/getCurrencyRate", produces={"application/json"}, method = RequestMethod.GET)
public CurrenctRate getCurrencyrate(#RequestHeader ("Authorization") String authorization, #RequestParam(value="date") #DateTimeFormat(pattern="MM/dd/yyyy") #Valid Date date) throws Exception {
That way, Spring REST can match your request to your API call.
You have to use #ControllerAdvice, create exception handler for MethodArgumentTypeMismatchException exception type and also create class for your proper exception class which you need to send as a response to the client. For instance,
I have #ControllerAdvice class RestErrorHandler with below exceptionhandler for HttpMessageNotReadableException exception.
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
Throwable throwable = ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
if (throwable instanceof EnumValidationException) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
}
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}
ValidationErrorDTO is the class having few setter/getters and when HttpMessageNotReadableException exception occurs then send ValidationErrorDTO in the response with the message which I want the client to see.
I created custom exception handler extending ResponseEntityExceptionHandler with #ControllerAdvice. where I override
handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request). This way I created handled the exception and created my own response.
Please refer below:
#Override
protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
char quotes='"';
String error ="Invalid date "+ quotes+ ex.getValue()+quotes +".. Please enter date in MM/dd/YYYY.";
err (error);
CustomException customExcepton = new CustomException (HttpStatus.BAD_REQUEST, "101", ex.getLocalizedMessage(), error);
return new ResponseEntity <Object> (customExcepton, new HttpHeaders(), customExcepton.getStatus());
}
My CustomException class is:
#JsonInclude(Include.NON_NULL)
public class CustomException implements Serializable {
private static final long serialVersionUID = -6839345326601547899L;
private HttpStatus status;
private String exceptionCode;
private String exceptionMessage;
private List <String> errors = null;
public CustomException() {
// Default
}
public CustomException (HttpStatus status, String exceptionCode, String exceptionMessage, String error) {
super();
this.status = status;
this.exceptionCode = exceptionCode;
this.exceptionMessage = exceptionMessage;
this.errors = Arrays.asList (error);
}
//getters and setters
I am wondering how to bind exception handling method to url mapping method:
#Controller
public class UserController {
#Autowired
UserDao userDao;
#RequestMapping(value = "/user", method = RequestMethod.GET)
public String users(#ModelAttribute("model") ModelMap model) {
model.addAttribute("userList", userDao.getAll());
String[] b = new String[0];
String a = b[1];
return "user";
}
#ExceptionHandler(Exception.class)
public String handleAllException(Exception ex, #ModelAttribute("model") ModelMap model) {
model.addAttribute("error", "Exception happened");
return "error_screen";
}
}
I intentionally provoke java.lang.ArrayIndexOutOfBoundsException in users method. But I see that handleAllException method wasn't executed.
Question:
What have I forgotten to get done to make Exception Handling work appropriately?
Try to do somedthing like this:
#ExceptionHandler(Exception.class)
public ModelAndView handleAllException(Exception ex) {
ModelAndView model = new ModelAndView("error_screen");
model.addAttribute("error", "Exception happened");
return model;
}
Try the following
#ExceptionHandler(Exception.class) -> #ExceptionHandler({Exception.class})
The reason is it has failed with the below exception while trying to invoke the handleAllException() method:
DEBUG [http-nio-8080-exec-6] --- ExceptionHandlerExceptionResolver: Failed to invoke #ExceptionHandler method: public java.lang.String controllers.handleAllException(java.lang.Exception,org.springframework.ui.ModelMap)
java.lang.IllegalStateException: No suitable resolver for argument [1] [type=org.springframework.ui.ModelMap]
HandlerMethod details:
Change the Method as below:
#ExceptionHandler(Exception.class)
public String handleAllException(Exception ex) {
// model.addAttribute("error", String.format("Exception happened"));
return "error_screen";
}
I'm new to spring mvc , I'm working on a web project admin panel.
Here is some example of my admin pages controllers :
#Controller
#RequestMapping("/admin/article/**")
public class ArticleController {
private ArticleDao articleDao;
private String fileName;
private String baseUrl;
public ArticleController() {
articleDao = ArticleDaoFactory.create();
}
#RequestMapping(value = "/admin/article",method = RequestMethod.GET)
public String doGet(ModelMap model,HttpServletRequest request,ArticleForm articleForm) {
//some codes
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.GET)
public String doGetAdd(ModelMap model,ArticleForm articleForm) {
model.addAttribute("article", articleForm);
return "admin/articleAdd";
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.POST)
public String doPost(#ModelAttribute ArticleForm article, BindingResult result ,ModelMap model){
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.GET)
public String getEdit(ModelMap model, #PathVariable("id") int id) {
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.POST)
public String postEdit(ModelMap model, #PathVariable("id") int id, ArticleForm article, BindingResult result) {
//some codes
}
#RequestMapping(value = "/admin/article/delete/{id}",method = RequestMethod.GET)
public void getDelete(ModelMap model, #PathVariable("id") int id, HttpServletResponse response) {
//some codes
}
}
now I need another mapping in another contoller named AdminController (for example) to Authenticate admin and bring him to login page if he is not logged in. for sure Authenthication is one example, I might want to use more classes on every admin page.
Note that my authenthication class needs request and session references (and for sure my other classes will need other references created by spring)
I got to know that I can not get HttpServletRequest and ... using a constructor method so I wrote another request mapping to call a method.
Eventhough I can set my properties this way ,but I can not use this method on every admin url.
#Controller
#RequestMapping(value = "/admin/**",method = RequestMethod.GET)
public class AdminController {
Authentication authentication;
HttpServletRequest request;
HttpSession session;
HttpServletResponse response;
public void checkAndSet(HttpSession session,HttpServletRequest request,HttpServletResponse response) {
authentication = new Authentication(session,request);
this.request = request;
this.session = session;
this.response = response;
if(!authentication.isLoggedIn()){
System.out.println(" I'm not logged in");
response.setHeader("Location","/admin/login");
}
}
So I need some suggestion on how to write a request mapping in a controller to call a method on every other controllers that are 'admin' page child ?
FYI : I'm not thinking for spring security for this.
thanks;
I think you can do it by implementing a servlet filter.
For example :
public class AuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getServletPath();
HttpSession session = request.getSession(false);
Authentication authentication = new Authentication(session,request);
if (isAdminUrl(url) && !authentication.isLoggedIn()) {
res.sendRedirect/admin/login");
}
chain.doFilter(req, res);
}
}
And then, you have to implement the method isAdminUrl(String url) to determine if you want to apply your filter.
Otherwise, I strongly recommend you to take a look at Spring Security
My main problem here is returning a string with a pathvariable value from one controller to another.
See here:
#RequestMapping(value = "/profile/{location}")
public ModelAndView profile(#PathVariable("location") String location) throws Exception {
return new ModelAndView("profile", "*", *);
}
#RequestMapping(value = "/records", method = "RequestMethod.POST")
public String inRecords(#Valid User user, BindingResult result) {
if(result.hasErrors()) {
return "profile/system";
}
else {
.....
return "somewhere";
}
}
My problem here is the return "profile/system" going to WEB-INF/views/profile/system.jsp. Am I doing anything wrong with #PathVariable or with the return statement itself?
Any suggestions?
Why you dont try something like this.
#RequestMapping(value = "/records", method = "RequestMethod.POST")
public void inRecords(#Valid User user, HttpServletResponse response) {
if(result.hasErrors()) {
response.sendRedirect("/YourApp/profile/system")
}
I think ModelAndView is taking the returned String and try to run ViewResolver that try to get the jso, avoid that calling or redirecting the request directly to the needed endpoint.
Or If you want to keep modelAndView use this
return new ModelAndView("redirect:/profile/system");