How to handle both reactive and mvc ServerHttpRequest in a same #ControllerAdvice? - java

I have code like this below :
MyExceptionHandler {
#ExceptionHandler(Exception.class)
public Object handleMvc(Exception ex, org.springframework.http.server.ServerHttpRequest request) {
return request.getbody;
}
#ExceptionHandler(Exception.class)
public Object handleReactive(Exception ex, org.springframework.http.server.reactive.ServerHttpRequest request) {
return request.getBody();
}
}
The code above produces IllegalStateException: Ambiguous #ExceptionHandler method mapped for Exception error.
Is there a way to handle both MVC and reactive requests in the ControllerAdvice? I'm working on a common project that works across multiple projects using different httprequest type.

**In spring boot using this**
package com.example.demo.exception;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class UserCVExceptionController {
#ExceptionHandler(value = UserCVNotfoundException.class)
public ResponseEntity<Object> exceptionMsg(UserCVNotfoundException exception) {
Map<String, String> headers=new HashMap<String, String>();
headers.put("Status", "404");
headers.put("Message", "User Not found");
headers.put("User", exception.getEmail());
return new ResponseEntity<>(headers, HttpStatus.NOT_FOUND);
}
}

Related

How can I return validation errors in a RFC-7807 format in Spring Boot Data Rest controllers?

I have a Spring repository
#RepositoryRestResource
public interface MongoIntegrationTokenRepository extends MongoRepository<IntegrationToken, String>, CrudRepository<IntegrationToken, String> {}
I've added the validation configuration to add validation support and my entity has the validation annotations:
#Configuration
class MyRestMvcConfiguration implements RepositoryRestConfigurer {
private final LocalValidatorFactoryBean localValidatorFactoryBean;
#Autowired
public MyRestMvcConfiguration(LocalValidatorFactoryBean localValidatorFactoryBean) {
this.localValidatorFactoryBean = localValidatorFactoryBean;
}
#Override
public void configureValidatingRepositoryEventListener(
ValidatingRepositoryEventListener validatingRepositoryEventListener) {
validatingRepositoryEventListener.addValidator("beforeCreate", localValidatorFactoryBean);
validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
}
}
When running the create operation, if there are any validation errors, the entity creation fails but the JSON response doesn't contain any errors details.
A POST to my endpoint with invalid data simply returns:
{
"message": "Server Error",
"details": [
"Validation failed"
]
}
I'd like to return the validation errors in the RFC7807 format. This should be possible via Spring HATEOAS or by this popular library by Zalando https://github.com/zalando/problem-spring-web but I'm unsure how they need to be wired in or which approach should be used.
I found this lone example on Github. https://github.com/marciovmartins/problem-spring-web-starter-expanded/blob/aed5825c958fad93f4aaad022689958926cf3b4a/src/main/kotlin/com/github/marciovmartins/problem/spring/web/expanded/ProblemExceptionHandler.kt and rewrote it in Java. This seems to do it.
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
import org.zalando.problem.ThrowableProblem;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import org.zalando.problem.violations.Violation;
#ControllerAdvice
public class ProblemControllerAdvice implements ProblemHandling {
#ExceptionHandler
public ResponseEntity<Problem> handleRepositoryConstraintViolationException(RepositoryConstraintViolationException exception, NativeWebRequest request) {
List<Violation> violationList = exception.getErrors().getAllErrors()
.stream()
.map(objectError -> {
if (objectError instanceof FieldError) {
return new Violation(((FieldError) objectError).getField(), objectError.getDefaultMessage());
}
return new Violation(null, objectError.getDefaultMessage());
})
.collect(Collectors.toList());
ThrowableProblem problem = Problem.builder()
.withTitle("Constraint Violation")
.withStatus(defaultConstraintViolationStatus())
.with("violations", violationList)
.build();
return create(problem, request);
}
}

ControllerAdvice not catching exception

I am learning java and using spring boot to develop web app. I am using #controllerAdvice to catch all the exceptions but none of my exceptions is being intercepted by it. I am using spring boot 2.6.2. git repo https://github.com/jitenderchand1/java-microservices
Controller file
package com.learning.rest.webservices.restfulwebservices.user;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#RequestMapping("/user-service")
public class UserController {
#Autowired
private UserDaoService service;
#GetMapping(path = "/users")
public List<User> retrieveAllUser() {
return service.getAllUser();
}
#GetMapping(path = "/users/{id}")
public User retrieveOneUser(#PathVariable Integer id) {
User user = service.fineOne(id);
if(user == null){
throw new UserNotFoundException("id -" + id);
//throw new Exception();
}
return user;
}
#PostMapping(path = "/user/save")
public ResponseEntity<User> saveUser(#RequestBody User user) {
User savedUser = service.save(user);
return new ResponseEntity(savedUser, HttpStatus.CREATED);
}
}
custom Exception handler
package com.learning.rest.webservices.restfulwebservices.user;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;
#ControllerAdvice
#RestController
public class CustomException extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "I messed up", request.getDescription(true));
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "You are not from this world",
request.getDescription(false));
return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
}
}

Which exception handler is handling MissingRequestHeaderException?

I am working on a large Spring Boot codebase in which I am trying to introduce request header validation.
This is my sample controller:
import javax.validation.constraints.Size;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#Validated
#RequestMapping("/abc/test/v1")
public class TestController {
#GetMapping("/testMethod")
public String testMethod(#RequestHeader
#Size(min = 10, message = "Not valid header") String foo) {
return foo;
}
}
And this is the #ControllerAdvice error handler:
import java.util.LinkedList;
import java.util.List;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.abc.test.exception.RequestParamValidationException;
#SuppressWarnings({ "unchecked", "rawtypes" })
#ControllerAdvice
#RequestMapping( produces = MediaType.APPLICATION_JSON_VALUE)
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<RequestParamValidationException> handleConstraintVoilationexception(ConstraintViolationException ex, WebRequest req) {
List<String> details = new LinkedList<>();
for(ConstraintViolation<?> violation : ex.getConstraintViolations()) {
details.add(violation.getMessage());
}
RequestParamValidationException exception = new RequestParamValidationException
("Request Param Validation Failed", details);
return new ResponseEntity(exception, HttpStatus.BAD_REQUEST);
}
}
This is a minimal reproducible example of my project, but I have no other error handlers present.
Even though I should be getting a ConstraintVoilationexception, but the MissingRequestHeaderException keeps being handled by a mysterious error handler which I have not declared, as I can see from my logs:
2020-06-08 10:49:10.655 WARN 3180 --- [0.0-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Missing request header 'foo' for method parameter of type String]
It never reaches my exception handler.
How do I find out what is this mysterious exception handler is ?
Thanks.
First you need to define order of ValidationExceptionHandler
It's done with annotation #Order(Ordered.HIGHEST_PRECEDENCE)
Second, you need to override method because MissingRequestHeaderException is subclass of ServletRequestBindingException
#Override
protected ResponseEntity<Object> handleServletRequestBindingException(
final ServletRequestBindingException ex,
final HttpHeaders headers,
final HttpStatus status,
final WebRequest request
) {
return super.handleServletRequestBindingException(ex, headers, status, request);
}
MissingRequestHeaderException is a subclass of ServletRequestBindingException.Extending ResponseEntityExceptionHandler will automatically give you various methods to handle different exceptions by default.One of the method is handleServletRequestBindingException.
So your MissingRequestHeaderException is handled by the method of ResponseEntityExceptionHandler.
If you have to verify, go to the code of ResponseEntityExceptionHandler in your IDE.
Hope this helps.

Redirection inside reactive Spring Webflux REST controller

I'm creating simple controller server for spring reactive project. While setting redirection to another location, I have found an error when calling http://localhost:8080/:
There was an unexpected error (type=Internal Server Error, status=500).
ModelAttributeMethodArgumentResolver does not support multi-value reactive type wrapper: interface reactor.netty.http.server.HttpServerResponse
java.lang.IllegalStateException: ModelAttributeMethodArgumentResolver does not support multi-value reactive type wrapper: interface reactor.netty.http.server.HttpServerResponse
at org.springframework.util.Assert.state(Assert.java:94)
at org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver.resolveArgument(ModelAttributeMethodArgumentResolver.java:112)
at org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:123)
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:190)
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:133)
at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.lambda$handle$1(RequestMappingHandlerAdapter.java:200)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
...
This is the controller code:
import reactor.core.publisher.Mono;
import reactor.netty.http.server.HttpServerResponse;
#RestController
public class BaseController {
#GetMapping("/")
public Mono<Void> indexController(HttpServerResponse response) {
return response.sendRedirect("/api/v1");
}
// ...
}
I expected it to be redirected from localhost:8080/ to localhost:8080/api/v1. But I've got the above exception.
Redirecting with Controller MVC good-old approach:
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.net.URI;
#RestController
public class Controller {
#GetMapping("/")
public Mono<Void> indexController(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("/api/v1"));
return response.setComplete();
}
}
If someone would prefer functional Router/Handler approach might investigate: How to redirect a request in spring webflux?.
#Bean
RouterFunction<ServerResponse> routerFunction() {
return route(GET("/"), req ->
ServerResponse.temporaryRedirect(URI.create("/api/v1"))
.build());
}
When using #RestController a ResponseEntity can work as well (here implemented using kotlin coroutines) :
#RestController
class SomeController() {
suspend fun someMethod() : ResponseEntity<Unit> {
return ResponseEntity
.status(HttpStatus.TEMPORARY_REDIRECT)
.location(URI.create("/api/v1"))
.build()
}
}

Spring boot and Jersey, can't make ExceptionMapper work

I am using Spring boot + Spring Security + Jersey.
I am trying to do something always that an Unathorized error happens with an ExceptionMapper, but it doesn't seem to work. However, other Mappers that I've done work perfectly.
This is my code:
Unauthorized Excepcion:
package com.ulises.usersserver.rest.exceptionsmappers;
import com.ulises.usersserver.rest.dto.ErrorDTO;
import com.ulises.usersserver.rest.dto.ErrorDTOBuilder;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import static com.ulises.usersserver.constants.Constants.REQUEST_ERROR_UNATHORIZED;
public class NotAuthorizedMapper implements ExceptionMapper<NotAuthorizedException> {
#Override
public Response toResponse(NotAuthorizedException e) {
final ErrorDTO errorDTO = ErrorDTOBuilder.builder()
.message(REQUEST_ERROR_UNATHORIZED)
.build();
return Response.status(Response.Status.UNAUTHORIZED)
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(errorDTO)
.build();
}
}
Other custom mapper that works perfectly:
package com.ulises.usersserver.rest.exceptionsmappers;
import com.ulises.usersserver.rest.dto.ErrorDTO;
import com.ulises.usersserver.rest.dto.ErrorDTOBuilder;
import com.ulises.usersserver.services.exceptions.UserNotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import static com.ulises.usersserver.constants.Constants.REQUEST_ERROR_USER_DOESNT_EXIST;
public class UserNotFoundExceptionMapper implements ExceptionMapper<UserNotFoundException> {
#Override
public Response toResponse(UserNotFoundException e) {
final ErrorDTO errorDTO = ErrorDTOBuilder.builder()
.message(REQUEST_ERROR_USER_DOESNT_EXIST)
.build();
return Response.status(Response.Status.NOT_FOUND).entity(errorDTO).build();
}
}
They are of course registered in Jersey's config:
#Bean
public ResourceConfig jerseyConfig() {
final ResourceConfig resourceConfig = new ResourceConfig();
...
resourceConfig.register(NotFoundMapper.class);
resourceConfig.register(NotAuthorizedMapper.class);
...
return resourceConfig;
}
I don't seem to be able to map other exceptions such as InternalServerErrorException (I managed to map this one by doing
ExceptionMapper<Exception>, which doesn't look very correct to me.
Anyone knows why is this happening? I've checked all possible questions here and none of them solved this.
Thanks in advance.
Okay, it seems Jersey has no control over Spring Security's exceptions.
The way to solve this (had to dig alot) is to override the method from AuthenticationEntryPoint that S-Security uses to return the 401 response if an user isn't authorized.
So I created the following class implementing that interface:
public class CustomEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException) throws IOException {
JSONObject json = new JSONObject();
json.put("Message", "You don't have access to view this section. Please, log in with an authorized account.");
response.addHeader("Content-Type", "application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().println(json);
}
}
And then just add this configuration to S-Security to use this class as entry point:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(new CustomEntryPoint());
..... any other config you had .....
}

Categories

Resources