Sample interceptor code
package com.my.interceptor;
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle( ... ) throws Exception {
throw new Exception("test exception"); // throw test exception
}
}
Sample controller code
package com.my.controller;
#RestController
#RequiredArgsConstructor
public MyController {
#GetMapping( "/my" )
public ResponseEntity getMy() {
...
}
}
Sample exception handler code
package com.my.handler;
#RestControllerAdvice( basePackages = { "com.my.interceptor" } ) // Doesn't handle interceptor
// #RestControllerAdvice( basePackages = { "com.my.controller" } ) // Handle both interceptor and controller
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler( value = { Exception.class } )
public void handleException( HttpServletRequest request, Exception e) {
...
}
}
I want to handle exception from interceptor without creating custom exceptions.
But basePackages = { "com.my.interceptor" } in MyExceptionHandler doesn't handle exception from interceptor.
When I change basePackages to basePackages = { "com.my.controller" }, it handles both interceptor and controller.
Why this happen and how can I handle interceptor's exception only?
Related
I am writing a test case for the Rest Controller for an error condition and when I set expected exception to Exception.class the test runs with no errors. When I change the expected exception to CustomException.class, the test fails with assertion error
Here is my Controller class:
#RestController
public class CustomController<CustomFieldRequest customFieldRequest, CustomField customField> {
#PutMapping(path = "/{customFieldId}",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_ATOM_XML_VALUE})
public CustomField updateCustomField(#PathVariable String customFieldId, #RequestBody CustomFieldRequest customFieldRequest) throws CustomException {
customFieldRequest.setAuthorId(getUser());
customFieldRequest.setId(customFieldId);
return CustomService.updateCustomeField(customFieldRequest);
}
}
Here is my service class:
#Service
public class CustomServiceImpl implements CustomService {
#Override
public CustomField updateCustomeField(CustomFieldRequest customFieldRequest) throws CustomException {
}
}
Here is my custom exception class:
public class CustomException extends Exception {
public CustomException(final String message) {
super(message);
}
public CustomException(final String message, final Throwable cause) {
super(message, cause);
}
}
Here is my test class:
public class CustomControllerTest {
private CustomService customService = Mockito.mock(CustomService.class);
#Test(expected=Exception.class)
public void updateCustomFieldThrowsException() throws CustomException {
//exception.expect(Exception.class);
CustomFieldRequest request = getRequest();
when(customService.updateCustomeField(request)).thenThrow(Exception.class);
//customController.updateCustomField(request.getId(), request);
}
}
As mentioned above if I change my expected exception to CustomException.class in the test like below:
#Test(expected=CustomException.class)
public void updateCustomFieldThrowsException() throws CustomException {
//exception.expect(Exception.class);
CustomFieldRequest request = getRequest();
when(customService.updateCustomeField(request)).thenThrow(CustomException.class);
//customController.updateCustomField(request.getId(), request);
}
I see the test failing with java.lang.AssertionError. I tried the #Rule annotation(shown commented above).
You need a default constructor for CustomException or create the exeception in your test like this: new CustomException("")
I'm developing a spring web application. I have no XML configuration at all.
The main class of the spring boot app is annotatd with a component scan which includes all the beans here listed.
Having this controller class:
#CrossOrigin
#RestController
#RequestMapping(value = "/documento/detail/tabs")
public class InfoController {
#Autowired
private DocDetailService detailService;
/**
* SEC 26-28: Pricing e posizioni
*/
#LogMethod
#GetMapping(value = "/pricing/{numOperazione}", produces = MediaType.APPLICATION_JSON_VALUE)
private ResponseEntity<DetailPricingDTO> getDettagliPricingEPosizioni(
#PathVariable(value = "numOperazione") final String numOperazione) throws DocumentNotFoundException {
return ResponseEntity.ok(detailService.getDettagliPricing(numOperazione));
}
And #LogMethod defined like this:
#Documented
#Retention(RUNTIME)
#Target({ METHOD })
public #interface LogMethod {
}
With an aspect defined as follows, to log all method annotated with that request
#Aspect
#Component
#Scope("singleton")
public class LogEventAspect {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#PostConstruct
public void postConstruct() {
log.info("# LogMethod annotation ASPECT is enabled #");
}
#Pointcut("#annotation(LogMethod)")
public void logEventAnnotationPointCut() {
// Method is empty as this is just a point-cut, the implementations are in the
// advises.
}
#AfterThrowing(pointcut = "logEventAnnotationPointCut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'",
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
e.getCause() != null ? e.getCause() : "NULL", e.getMessage(), e);
}
#Around("logEventAnnotationPointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// Log before execution
if (log.isDebugEnabled()) {
log.debug("Enter>>>: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
}
Object result = joinPoint.proceed();
// log after execution
if (log.isDebugEnabled()) {
log.debug("Exit<<<: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), result);
}
return result;
}
}
The detailService in the controller is NULL. If I remove the #LogMethod the service is correctly initialized.
Also, if I use the #LogMethod in a #Service class instead of the #RestController, the autowiring of other beans does work.
Why does this happen?
I'd like to return a custom HTTP status 422 instead of a default 400 on a spring validation.
My validator:
#Component
#RequiredArgsConstructor
public class EmailUpdateDtoValidator implements Validator {
private Errors errors;
private EmailUpdateDto emailUpdateDto;
#Override
public boolean supports(Class<?> clazz) {
return EmailUpdateDto.class.equals(clazz);
}
#Override
public void validate(Object object, Errors errors) {
this.errors = errors;
this.emailUpdateDto = (EmailUpdateDto) object;
validateEmail();
}
private void validateEmail() {
if (!Email.isValid(emailUpdateDto.getEmail())) {
errors.rejectValue("email", UserValidationErrorCodes.EMAIL_NOT_VALID.name());
}
}
}
How I setup the validation in the Controller:
#Slf4j
#RestController
#RequiredArgsConstructor
public class UserController {
private final EmailUpdateDtoValidator emailUpdateDtoValidator;
#InitBinder("emailUpdateDto")
protected void initEmailValidationBinder(final WebDataBinder binder) {
binder.addValidators(emailUpdateDtoValidator);
}
#RequestMapping(value = "/users/{hashedId}/email", method = RequestMethod.PUT)
public void updateEmail(#RequestBody #Valid EmailUpdateDto emailUpdateDto) {
...
}
}
Using this setup I always get a 400. How could I customize the HTTP status on the return?
Thanks
The validation process would throw an org.springframework.web.bind.MethodArgumentNotValidException, therefore you can add an exception handler to your controller:
import org.springframework.web.bind.MethodArgumentNotValidException;
#ExceptionHandler
public ResponseEntity<String> handleException(MethodArgumentNotValidException ex) {
return new ResponseEntity<String>(HttpStatus.UNPROCESSABLE_ENTITY);
}
As workaround you can define a ExceptionHandler and override the default behavior.
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<Object> customHttpStatus() {
return ResponseEntity.status(422).build();
}
}
I have a test case where am throwing exception incase of some basic validation. but ExceptionMapper is not being invoked. But if i run from postman to hit the service it is working fine.
Do Junit test have to run differently for ExceptionMapper ?
Test case :
#Test
public void itShouldHavePersonNumber() {
RestAuthController controller = new RestAuthController();
Response response = controller.insertGuid(null, "m012");
assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> {controller.insertGuid(null, "m012");});
assertThat(response.getStatus()).isEqualTo(Status.BAD_REQUEST.getStatusCode());
}
Controller:
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response insertGuid(#QueryParam("personNumber") Integer personNumber, #QueryParam("guId") String guId ) throws ValidationException {
if(guId == null || guId.isEmpty()) {
throw new ValidationException("guId is Required");
}
}
Exception Mapper :
#Provider
public class ValidationMapper implements ExceptionMapper<ValidationException> {
#Override
public Response toResponse(ValidationException ex) {
return Response.status(Response.Status.BAD_REQUEST).entity(ex.getMessage()).type(MediaType.TEXT_PLAIN).build();
}
}
Exception:
public class ValidationException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
public ValidationException() {
super();
}
public ValidationException(String message, Throwable cause) {
super(message, cause);
}
public ValidationException(String message) {
super(message);
}
}
Why do you think the exception mapper should be called? It is not an integration test. All you are doing is instantiating the class and then calling a method. There is nothing magical in Java that will make the exception mapper be called. You need to run an integration test with the Jersey application running (and the mapper registered) if you want the mapper to be called.
One way to run an integration test with Jersey is to use it's Test Framework. Below is an example.
public class ValidationExceptionTest extends JerseyTest {
public static class ValidationException extends RuntimeException {}
public static class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
#Override
public Response toResponse(ValidationException e) {
return Response.status(400).entity("boo boo").build();
}
}
#Path("echo-name")
public static class EchoNameResource {
#GET
public String echoName(#QueryParam("name") String name) {
if (name == null || name.isEmpty()) {
throw new ValidationException();
}
return name;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(EchoNameResource.class)
.register(ValidationExceptionMapper.class);
}
#Test
public void testResponseOkWithQueryParam() {
final Response response = target("echo-name")
.queryParam("name", "peeskillet")
.request()
.get();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.readEntity(String.class)).isEqualTo("peeskillet");
}
#Test
public void testResponseBadRequestWithNoQueryParam() {
final Response response = target("echo-name")
.request()
.get();
assertThat(response.getStatus()).isEqualTo(400);
}
}
In my main controller, when the exception is thrown, I want it to be catched by the ExceptionHandler in my error handling controller, but that never happens. Instead, I am getting Error 500. I am suspecting the problem is in #ResponseBody annotation of my main controller. Any idea how to achieve wanted behavior?
Main controller
#RequestMapping(value = "/person/{person}", method = RequestMethod.GET)
public #ResponseBody Person execute(#PathVariable(value = "person") String person) {
if(person.isValid(person)) {
return person;
} else {
throw new ResourceNotFoundException("Invalid person format.");
}
}
Exception
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException() {
}
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String message, Throwable throwable) {
super(message, throwable);
}
public ResourceNotFoundException(Throwable throwable) {
super(throwable);
}
}
Error controller
private static final String ERROR_PAGE = "errors/error.jsp";
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ExceptionHandler(ResourceNotFoundException.class)
public ModelAndView invalidApiCall(){
return generateView(ERROR_404);
}
private ModelAndView generateView(String errorCode) {
return new ModelAndView(ERROR_PAGE);
}
My error view never gets generated (#ExceptionHandler never catches the exception). Instead I am getting error 500. Is there a way for ExceptionHandler to catch my exception?
Try to add #ControllerAdvice annotation for the Error Controller. If it is already added, check whether the class' package is included in package scan.