Jersey ExceptionMapper not working with spring boot? - java

I am using jersey with spring boot, and I have a exception mapper:
package org.smarter.providers;
import com.google.common.collect.ImmutableMap;
import org.smarter.exception.ApiException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.util.Optional;
import static org.smarter.exception.UserException.UNAUTHORIZED;
#Provider
public class ExceptionMapper implements javax.ws.rs.ext.ExceptionMapper<ApiException> {
#Override
public Response toResponse(ApiException exception) {
return Response.status(status(exception)).build();
}
private Integer status(ApiException exception) {
return Optional.ofNullable(ImmutableMap.<String, Integer>builder()
.put(UNAUTHORIZED, 401)
.build()
.get(exception.code()))
.orElse(404);
}
}
Also registered, and using debug, i can see this mapper get invoked correctly, but how ever the final response still returning 404, no matter using junit test or manual test.
Here is the test:
when()
.get("/users/current/memos/daily")
.then()
.statusCode(401);
And then registered in Jersey Configuration:
register(ExceptionMapper.class);
I am using jersey with spring boot, and don't know how to trouble shooting on this. any advice will be appreciated.

I can see this mapper gets invoked correctly, but how ever the final response still returning 404.
It may be an issue with the implementation of your status() method. Are you sure exception.code() returns 401?
By the way, do you need such level of complexity using an Optional and an ImmutableMap? You could use:
private Integer status(ApiException exception) {
if (exception.code() == 401) {
return exception.code();
} else {
return 404;
}
}

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 being invoked but I can still see the exception in the response. Need to handle exceptions centrally

Since I have just started off with Spring boot, I'm creating a very basic project with very basic rest controllers. The intent is to understand the basics before trying out any complicated code so the examples below are very basic. There is no complex business logic, just minimal lines of code.
I have created a basic project with a simple rest controller, it has the following annotations
#RestController
#RequestMapping("users")
public class UserController
{
In the get method I have intentionally created a null pointer exception by assigning null to a variable and then checking the length.
#GetMapping(path="/{UserId}",produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<UserViewDTO> getUser(#PathVariable String UserId)
{
String firstName = null;
int len_fname = firstName.length();
UserViewDTO responseBody= new UserViewDTO();
responseBody.setFirstname("1stname");
responseBody.setLastname("lastname");
responseBody.setEmail("email#gmail.com");
responseBody.setUserid(UserId);
return new ResponseEntity<UserViewDTO>(responseBody,HttpStatus.OK);
}
As a result of this I get the bellow message in response in Postman.
{
"timestamp": "2021-03-07T19:56:48.790+00:00",
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.NullPointerException",
"message": "No message available",
"path": "/users/1212121"
}
I wanted to do handle this with #Controller advice from a central class.
I created the following
import org.apache.catalina.WebResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class AppExceptionHandler extends ResponseEntityExceptionHandler
{
#ExceptionHandler(value= {Exception.class})
public ResponseEntity<Object> handleAnyException(Exception ex, WebRequest request)
{
return new ResponseEntity<>(
ex, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
When I put a debugger the code under the #ControllerAdvice does not even get activated.
Also here is my application.properties code
server.error.include-exception=true
server.error.include-message=always
server.error.include-binding-errors=always
What am I doing wrong over here ?
I use it without extending. You should be remove extends ResponseEntityExceptionHandler
or you must be override parent class handler method
This code is run, You can review the usage here https://github.com/cebrailinanc/spring-cloud/tree/master/product-service/src/main/java/com/cbrl/cloud/product/api
#ControllerAdvice
public class AppExceptionHandler {
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object> handleAnyException(Exception ex) {
return new ResponseEntity(ex, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Truth be told i dont see any problem in your #ControllerAdvice .
If possible please remove the value inside the annotation as If there is just one element named value, then the name may be omitted, as in the case of #ExceptionHandler it takes an array of Throwable Class.
And also if possible and if not needed please remove WebRequest and use #ResponseBody and #ResponseStatus
#ResponseBody
#ResposneStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return e.getMessage();
}

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 .....
}

Return a custom error message for 400 requests that don't hit the controller

I need to be able to return a custom error message for requests that are Bad Requests, but don't hit a controller (ex. Having bad JSON). Does anyone have any idea how to go about this? I tried the #ExceptionHandler annotation to no avail.
Any help would be appreciated.
Since Spring 3.2 you can add a ControllerAdvice like this
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
#ControllerAdvice
public class BadRequestHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseBody
public ErrorBean handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
ErrorBean errorBean = new ErrorBean();
errorBean.setMessage(e.getMessage());
return errorBean;
}
class ErrorBean {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
}
In handleHttpMessageNotReadableException, which is annotated with #ExceptionHandler(HttpMessageNotReadableException.class), you can handle the exception and render a custom response. In this case a ErrorBean becomes populated and return to the client. If Jackson is available on classpath and Content-Type was set to application/json by the client, this ErrorBean gets returned as json.

Categories

Resources