MessageSource support for Spring MVC and WebFlux exceptions
See gh-28814
This commit is contained in:
@@ -16,10 +16,14 @@
|
||||
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
@@ -55,13 +59,22 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public abstract class ResponseEntityExceptionHandler {
|
||||
public abstract class ResponseEntityExceptionHandler implements MessageSourceAware {
|
||||
|
||||
/**
|
||||
* Common logger for use in subclasses.
|
||||
*/
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@Nullable
|
||||
private MessageSource messageSource;
|
||||
|
||||
|
||||
@Override
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle all exceptions raised within Spring MVC handling of the request .
|
||||
@@ -306,12 +319,25 @@ public abstract class ResponseEntityExceptionHandler {
|
||||
}
|
||||
|
||||
if (body == null && ex instanceof ErrorResponse errorResponse) {
|
||||
body = errorResponse.getBody();
|
||||
body = resolveDetailViaMessageSource(errorResponse, exchange.getLocaleContext().getLocale());
|
||||
}
|
||||
|
||||
return createResponseEntity(body, headers, status, exchange);
|
||||
}
|
||||
|
||||
private ProblemDetail resolveDetailViaMessageSource(ErrorResponse response, @Nullable Locale locale) {
|
||||
ProblemDetail body = response.getBody();
|
||||
if (this.messageSource != null) {
|
||||
locale = (locale != null ? locale : Locale.getDefault());
|
||||
Object[] arguments = response.getDetailMessageArguments(this.messageSource, locale);
|
||||
String detail = this.messageSource.getMessage(response.getDetailMessageCode(), arguments, null, locale);
|
||||
if (detail != null) {
|
||||
body.setDetail(detail);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link ResponseEntity} to use from the given body, headers,
|
||||
* and statusCode. Subclasses can override this method to inspect and possibly
|
||||
|
||||
@@ -18,13 +18,15 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.context.support.StaticMessageSource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -33,6 +35,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
@@ -97,7 +100,7 @@ public class ResponseEntityExceptionHandlerTests {
|
||||
|
||||
@Test
|
||||
void handleWebExchangeBindException() {
|
||||
testException(new WebExchangeBindException(null, null));
|
||||
testException(new WebExchangeBindException(null, new BeanPropertyBindingResult(new Object(), "foo")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,20 +123,40 @@ public class ResponseEntityExceptionHandlerTests {
|
||||
testException(new ErrorResponseException(HttpStatus.CONFLICT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorResponseProblemDetailViaMessageSource() {
|
||||
|
||||
Locale locale = Locale.UK;
|
||||
LocaleContextHolder.setLocale(locale);
|
||||
|
||||
StaticMessageSource messageSource = new StaticMessageSource();
|
||||
messageSource.addMessage(
|
||||
"problemDetail." + UnsupportedMediaTypeStatusException.class.getName(), locale,
|
||||
"Content-Type {0} not supported. Supported: {1}");
|
||||
|
||||
this.exceptionHandler.setMessageSource(messageSource);
|
||||
|
||||
Exception ex = new UnsupportedMediaTypeStatusException(MediaType.APPLICATION_JSON,
|
||||
List.of(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML));
|
||||
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")
|
||||
.acceptLanguageAsLocales(locale).build());
|
||||
|
||||
ResponseEntity<?> responseEntity = this.exceptionHandler.handleException(ex, exchange).block();
|
||||
|
||||
ProblemDetail body = (ProblemDetail) responseEntity.getBody();
|
||||
assertThat(body.getDetail()).isEqualTo(
|
||||
"Content-Type application/json not supported. Supported: [application/atom+xml, application/xml]");
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ResponseEntity<ProblemDetail> testException(ErrorResponseException exception) {
|
||||
ResponseEntity<?> responseEntity =
|
||||
this.exceptionHandler.handleException(exception, this.exchange).block();
|
||||
|
||||
assertThat(responseEntity).isNotNull();
|
||||
assertThat(responseEntity.getStatusCode()).isEqualTo(exception.getStatusCode());
|
||||
|
||||
assertThat(responseEntity.getBody()).isNotNull().isInstanceOf(ProblemDetail.class);
|
||||
ProblemDetail body = (ProblemDetail) responseEntity.getBody();
|
||||
assertThat(body.getType()).isEqualTo(URI.create(exception.getClass().getName()));
|
||||
|
||||
return (ResponseEntity<ProblemDetail>) responseEntity;
|
||||
ResponseEntity<?> entity = this.exceptionHandler.handleException(exception, this.exchange).block();
|
||||
assertThat(entity).isNotNull();
|
||||
assertThat(entity.getStatusCode()).isEqualTo(exception.getStatusCode());
|
||||
assertThat(entity.getBody()).isNotNull().isInstanceOf(ProblemDetail.class);
|
||||
return (ResponseEntity<ProblemDetail>) entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,9 +165,7 @@ public class ResponseEntityExceptionHandlerTests {
|
||||
private Mono<ResponseEntity<Object>> handleAndSetTypeToExceptionName(
|
||||
ErrorResponseException ex, HttpHeaders headers, HttpStatusCode status, ServerWebExchange exchange) {
|
||||
|
||||
ProblemDetail body = ex.getBody();
|
||||
body.setType(URI.create(ex.getClass().getName()));
|
||||
return handleExceptionInternal(ex, body, headers, status, exchange);
|
||||
return handleExceptionInternal(ex, null, headers, status, exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user