I have a simple MVC controller that I annotate with my custom annotation:
#PreAuthorize("hasRole('GESTION_BENEFICIAIRE')")
#AuthentificationForte(otp = "#{args[0]}",transactionId="#{args[1]}")
#RequestMapping(value = "/ajouter", method = { RequestMethod.POST, RequestMethod.GET })
public String addBeneficiaire(#ModelAttribute("beneficiaireForm") BeneficiaireForm beneficiaireForm,
BindingResult result, Model model, Principal principal) {
[...]
}
My custom annotation is linked with an aspect that throws a RuntimeException when the validation doesn't succeed.
#Around(value = "#annotation(annotation)")
public Object verifyOtp(final ProceedingJoinPoint jointPoint,
final AuthentificationForte annotation) throws Throwable {
try {
if (authentificationForteEnabled) {
[...]
} else {
throw new AuthentificationForteException();
}
} else {
return jointPoint.proceed();
}
} finally {
}
}
So now the behavior is that when the validation fails, I am redirected to a 500 Error page. My goal is to stay in the same page and add a rejected message to the BindingResult:
result.rejectValue("suiteRib", "BeneficiaireForm.InvalidRib");
I haven't found a way to do that, the only way that I've found is to change all my logic and not use the annotation, while using a validation service with a try/catch in the controller code.
Is there any way to handle this and to access the binding result and add the error message when the aspect throws this exception?
Most definitely.
There is an example here of manipulating args:
Aspectj overwrite an argument of a method
You can also autowire services into the aspect class (remember to mark it #Configurable).
If you know the arguments before hand, then I think they can be included into the point cut definition, in which case they can be referred to directly in the around method. This is much nicer was as the arguments come strongly type.
You can read more here: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
May be its too late to answer your question but there are two ways you can handle it :
Have a try catch around proceed() in your aspect and when you get runtime exception you can either return the response from the aspect ( like a generic JSP showing the cause of error or generic JSON with error message.)
Second option could be to use Spring Exception Handler Controller advice. Spring MVC provides nice exception handler controller mechanism to call specific handler method for given type of exception class. ( Here is the blog link : https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc )
We currently have an application where we use mix of this approach to handle exception.
Related
Is it possible to add some custom validation message to path variable?
I have some GET
#GetMapping("/v2/tw/{id}")
public TwDto getTw(Authentication auth, #PathVariable Long id) {
In case of /v2/tw/someString I'd like to catch error and add some custom error message like "invalid tw ID"... How to do that? In ControllerAdvice add some ExceptionHandler?
For your particular use case, you can use #ExceptionHandler in the Controller or in a #ControllerAdvice class as shown here. For example, I am returning NOT_FOUND error for the sake of it.
#ExceptionHandler({MethodArgumentTypeMismatchException.class})
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "this is the reason")
public void handle() {
}
You may not see the reason in the actual error response, until you enable
server:
error:
include-message: always
If you think your #ExceptionHandler is only needed in a Controller class you can keep the method inside the controller. Alternatively you can create a #ControllerAdvice class and put the method there, so that you can reuse across multiple controllers in your application.
However, if you want a more complex validation, I will suggest to keep the id type to String and then cast manually into Long and perform the validation. Doing so you can throw your own RuntimeException and handle different cases.
I'm developing an API for an application that is able to find so-called ProductSolutions based on their solutionID (which is an integer).
The code looks like this:
#Repository
public interface ProductSolutionRepository extends CrudRepository<ProductSolution, String> {
public List<ProductSolution> findBySolutionId(#Param("solutionId") int solutionId);
}
When sending a request with a string instead of an integer (localhost:8080/api/v1/productSolutions/search/findBySolutionId?solutionId=dangerousscript)the API returns an error with the following message:
Failed to convert from type [java.lang.String] to type
[#org.springframework.data.repository.query.Param int] for value
'dangerousscript'; nested exception is
java.lang.NumberFormatException: For input string:
\"dangerousscript\""`
Although it appears that Chrome and Firefox are neatly escaping the input (and don't execute any scripts) people are still afraid our API might be used for cross site scripting attacks.
A simple way to fix this would be removing the input the user gave when we throw an exception, or make our own error page. In what way can I configure spring boot to throw a custom exception?
You can define a method with #ExceptionHandler in class annotated with #ControllerAdvice.
You can either annotate this method with #ResponseBody (if you're not using #RestController) and directly return desired response or rethrow exception with correct message.
#ControllerAdvice
public class RestExceptionControllerAdvice {
#ExceptionHandler(NumberFormatException.class)
public ErrorResponse handleSearchParseException(NumberFormatException exception) {
// do whathever you want
}
}
I defined the annotation #ExceptionHandler in my rest controller ,but it doesn't work as i wished when i mark the result type as text/plain,the method was defined to handle an attach upload request . SpringMvc just throw my business exception to the servlet container.Is it a bug of SpringMVC?How can i fix it?
#RestController
#RequestMapping("/api/test")
public class TestController extends BasicController{
#RequestMapping(value="/uploadAttach", headers = ("content-type=multipart/*"),method = RequestMethod.POST,produces="text/plain")
public String test(){
throw new ServiceException("biz exception");
}
#ExceptionHandler(value = {ServiceException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
public #ResponseBody StatusMessage serviceError(ServiceException ex) {
return new StatusMessage(ex.getMessage());
}
}
Since nobody has interest to answer this problem,i have to solve it myself.
I found that the exception handler will look for the right converter to handle the exception result which is defined as return value of the #ExceptionHandler. In my controller ,the exception is a pojo,when i debuged the program,i found there was no converter can write the pojo to type "text/plain" ,so SpringMvc throw the original exception to the servlet container,and returned an error page by Jetty.
you should follow these tips below:
1.you should define a customized exception,
but it must extend Exception.class;
2.setting a global exception hadler,and the dao throws the exception to service,the service throws to controller, then the controller uses the try...catch.. codes block to solve it;
3.a bean should be configured in SpringMVC.xml
like this:
4.and that's all, you can test it by using some simple codes like 1/0;
Using spring-web-4.2.6, I have the following Controller and ExceptionHandler:
#ControllerAdvice
public class ExceptionsHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDTO> HandleDefaultException(Exception ex) {
...
}
#ExceptionHandler(InternalError.class)
public ResponseEntity<ErrorDTO> HandleInternalError(InternalError ex) {
...
}
}
#RestController
#RequestMapping("/myController")
public class MyController {
#RequestMapping(value = "/myAction", method = RequestMethod.POST)
public boolean myAction() {
throw new InternalError("");
}
}
For some reason, the ExceptionsHandler's HandleDefaultException (for Exception.class) method is invoked, with an exception of type NestedServletException, instead of the HandleInternalError call.
When removing the default call, the IntenalError call is called with the proper InternalError exception.
I do not want to remove the default call as it is important to me to have a default handler to allow for a better experience for my users.
What am I missing here?
EDIT:
Apparently I'm using spring-web-4.3.3, without asking for it. I don't understand why exactly, here's my Gradle dependencies tree: http://pastebin.com/h6KXSyp2
Spring MVC should only exhibit the behavior you describe with version 4.3 and above. See this JIRA issue. Previously, Spring MVC would not expose any Throwable values to #ExceptionHandler methods. See
ExceptionHandler doesn't work with Throwable
Since 4.3, Spring MVC will catch any Throwable thrown from your handler methods and wrap it in a NestedServletException, which it will then expose to the normal ExceptionHandlerExceptionResolver process.
Here's a short description of how it works:
Checks if the handler method's #Controller class contains any #ExceptionHandler methods.
If it does, tries to resolve one that can handle the Exception type (including NestedServletException). If it can, it uses that (there's some sorting if multiple matches are found). If it can't, and the Exception has a cause, it unwraps and tries again to find a handler for that. That cause might now be a Throwable (or any of its subtypes).
If it doesn't. It gets all the #ControllerAdvice classes and tries to find a handler for the Exception type (including NestedServletException) in those. If it can, it uses that. If it can't, and the Exception has a cause, it unwraps it and tries again with that Throwable type.
In your example, your MyController throws an InternalError. Since this is not a subclass of Exception, Spring MVC wraps it in an NestedServletException.
MyController doesn't have any #ExceptionHandler methods, so Spring MVC skips it. You have a #ControllerAdvice annotated class, ExceptionsHandler, so Spring MVC checks that. The #ExceptionHandler annotated HandleDefaultException method can handle Exception, so Spring MVC chooses it to handle the NestedServletException.
If you remove that HandleDefaultException, Spring MVC won't find something that can handle Exception. It will then attempt to unwrap the NestedServletException and check for its cause. It'll then find the HandleInternalError which can handle that InternalError.
This is not an easy issue to deal with. Here are some options:
Create an #ExceptionHandler that handles NestedServletException and do the check for InternalError yourself.
#ExceptionHandler(NestedServletException.class)
public ResponseEntity<String> HandleNested(NestedServletException ex) {
Throwable cause = ex.getCause();
if (cause instanceof InternalError) {
// deal with it
} else if (cause instanceof OtherError) {
// deal in some other way
}
}
This is fine unless there's a bunch of different Error or Throwable types you want to handle. (Note that you can rethrow these if you can't or don't know how to handle them. Spring MVC will default to some other behavior, likely returning a 500 error code.)
Alternatively, you can take advantage of the fact that Spring MVC first checks the #Controller (or #RestController) class for #ExceptionHandler methods first. Just move the #ExceptionHandler method for InternalError into the controller.
#RestController
#RequestMapping("/myController")
public class MyController {
#RequestMapping(value = "/myAction", method = RequestMethod.POST)
public boolean myAction() {
throw new InternalError("");
}
#ExceptionHandler(value = InternalError.class)
public ResponseEntity<String> HandleInternalError(InternalError ex) {
...
}
}
Now Spring will first attempt to find a handler for NestedServletException in MyController. It won't find any so it will unwrap NestedServletException and get an InternalError. It will try to find a handler for InternalError and find HandleInternalError.
This has the disadvantage that if multiple controllers' handler methods throw InternalError, you have to add an #ExceptionHandler to each. This might also be an advantage. Your handling logic will be closer to the thing that throws the error.
I am developing a web service, no matter what, the response should always be valid, and I mean valid as in a valid format.
That means that if the web service is expected to return an XML matching a particular XSD schema, it should ALWAYS return a valid XML document, no matter what.
The only approach that I have so far is to do something like this (at controller level)
String xmlResponse = this.loadDefaultXML();
try {
xmlResponse = this.myCoolService.myCoolMethod();
} catch (Throwable t) {
xmlResponse = this.loadDefaultXML(String errorMessage)
} finally {
return xmlResponse
}
Where of course lpoadDefaultXML() will load an xml document like:
<?xml>
<result>Ouch, there was a problem</result>
And loadDefaultXML(String errorMessage) will do
<?xml>
<result>WHATEVER errorMessage contains</result>
Of course the service level takes cares of the normal exceptions, still, I feel that catching Throwable and using the try-catch-finally is the only way to ensure that no matter what, I will be in control so I can return always an XML.
Any better ideas or suggestions?
UPDATE:
I am using Spring MVC 3.2 and JAXB for the marshalling/unmarshalling of xml.
This does use SOAP, also I am not using wsdl for this.
In Spring MVC, when an exception is thrown during the handling of the request, the DispatcherServlet will consult the configured org.springframework.web.servlet.HandlerExceptionResolvers to handle the thrown exception. The resolver can then translate the exception to a view to show the user.
To use it, in short, you can either:
Implement the HandlerExceptionResolver interface, which is only a matter of implementing the resolveException(Exception, Handler) method and returning a ModelAndView.
Or, what I prefer:
You use the #ExceptionHandler method annotation within a controller to specify which method is invoked when an exception of a specific type is thrown during the execution of controller methods. For example:
#Controller
public class PersonController {
#RequestMapping("person/{id}")
#ResponseBody
public Person getById(#PathVariable String id) {
if ("007".equals(id)) {
throw new RuntimeException("007 is a secret agent.");
}
return personService.getById(id);
}
#ExceptionHandler(RuntimeException.class) // this can be an array
#ResponseBody
public String handleRuntimeException(RuntimeException ex,
HttpServletRequest request) {
return "Oops! Something bad happened: "+ex.getMessage();
}
}
Find more info at Web MVC framework - Handling Exceptions.