From 3af5f00ee71a22be2a8e246c214749f3d6a6318f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 14 May 2018 15:50:16 -0400 Subject: [PATCH] UnsupportedMediaType[Status]Exception reports body type Issue: SPR-16805 --- .../UnsupportedMediaTypeStatusException.java | 42 ++++++++++++++++--- .../web/reactive/function/BodyExtractors.java | 2 +- .../web/reactive/function/BodyInserters.java | 2 +- .../UnsupportedMediaTypeException.java | 41 ++++++++++++++++-- .../function/server/DefaultServerRequest.java | 3 +- ...AbstractMessageReaderArgumentResolver.java | 4 +- 6 files changed, 80 insertions(+), 14 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java index 65067a62327..7eb399e4585 100644 --- a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.springframework.web.server; import java.util.Collections; import java.util.List; +import org.springframework.core.ResolvableType; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; @@ -37,6 +38,9 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException private final List supportedMediaTypes; + @Nullable + private final ResolvableType bodyType; + /** * Constructor for when the specified Content-Type is invalid. @@ -45,16 +49,32 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException super(HttpStatus.UNSUPPORTED_MEDIA_TYPE, reason); this.contentType = null; this.supportedMediaTypes = Collections.emptyList(); + this.bodyType = null; } /** * Constructor for when the Content-Type can be parsed but is not supported. */ - public UnsupportedMediaTypeStatusException(@Nullable MediaType contentType, List supportedMediaTypes) { - super(HttpStatus.UNSUPPORTED_MEDIA_TYPE, - "Content type '" + (contentType != null ? contentType : "") + "' not supported"); + public UnsupportedMediaTypeStatusException(@Nullable MediaType contentType, List supportedTypes) { + this(contentType, supportedTypes, null); + } + + /** + * Constructor for when trying to encode from or decode to a specific Java type. + * @since 5.1 + */ + public UnsupportedMediaTypeStatusException(@Nullable MediaType contentType, List supportedTypes, + @Nullable ResolvableType bodyType) { + + super(HttpStatus.UNSUPPORTED_MEDIA_TYPE, initReason(contentType, bodyType)); this.contentType = contentType; - this.supportedMediaTypes = Collections.unmodifiableList(supportedMediaTypes); + this.supportedMediaTypes = Collections.unmodifiableList(supportedTypes); + this.bodyType = bodyType; + } + + private static String initReason(@Nullable MediaType contentType, @Nullable ResolvableType bodyType) { + return "Content type '" + (contentType != null ? contentType : "") + "' not supported" + + (bodyType != null ? " for bodyType=" + bodyType.toString() : ""); } @@ -75,4 +95,16 @@ public class UnsupportedMediaTypeStatusException extends ResponseStatusException return this.supportedMediaTypes; } + /** + * Return the body type in the context of which this exception was generated. + * This is applicable when the exception was raised as a result trying to + * encode from or decode to a specific Java type. + * @return the body type, or {@code null} + * @since 5.1 + */ + @Nullable + public ResolvableType getBodyType() { + return this.bodyType; + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java index 2fad402232b..7d40b99ab6c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java @@ -252,7 +252,7 @@ public abstract class BodyExtractors { .flatMap(reader -> reader.getReadableMediaTypes().stream()) .collect(Collectors.toList()); UnsupportedMediaTypeException error = - new UnsupportedMediaTypeException(contentType, supportedMediaTypes); + new UnsupportedMediaTypeException(contentType, supportedMediaTypes, elementType); return unsupportedError.apply(error); }); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java index 05a636c99b1..2ded80b24af 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java @@ -327,7 +327,7 @@ public abstract class BodyInserters { .flatMap(reader -> reader.getWritableMediaTypes().stream()) .collect(Collectors.toList()); UnsupportedMediaTypeException error = - new UnsupportedMediaTypeException(contentType, supportedMediaTypes); + new UnsupportedMediaTypeException(contentType, supportedMediaTypes, bodyType); return Mono.error(error); }); }; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/UnsupportedMediaTypeException.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/UnsupportedMediaTypeException.java index ecbe5c222d0..e53eb984d66 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/UnsupportedMediaTypeException.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/UnsupportedMediaTypeException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; import org.springframework.core.NestedRuntimeException; +import org.springframework.core.ResolvableType; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; @@ -37,6 +38,9 @@ public class UnsupportedMediaTypeException extends NestedRuntimeException { private final List supportedMediaTypes; + @Nullable + private final ResolvableType bodyType; + /** * Constructor for when the specified Content-Type is invalid. @@ -45,15 +49,32 @@ public class UnsupportedMediaTypeException extends NestedRuntimeException { super(reason); this.contentType = null; this.supportedMediaTypes = Collections.emptyList(); + this.bodyType = null; } /** * Constructor for when the Content-Type can be parsed but is not supported. */ - public UnsupportedMediaTypeException(@Nullable MediaType contentType, List supportedMediaTypes) { - super("Content type '" + (contentType != null ? contentType : "") + "' not supported"); + public UnsupportedMediaTypeException(@Nullable MediaType contentType, List supportedTypes) { + this(contentType, supportedTypes, null); + } + + /** + * Constructor for when trying to encode from or decode to a specific Java type. + * @since 5.1 + */ + public UnsupportedMediaTypeException(@Nullable MediaType contentType, List supportedTypes, + @Nullable ResolvableType bodyType) { + + super(initReason(contentType, bodyType)); this.contentType = contentType; - this.supportedMediaTypes = Collections.unmodifiableList(supportedMediaTypes); + this.supportedMediaTypes = Collections.unmodifiableList(supportedTypes); + this.bodyType = bodyType; + } + + private static String initReason(@Nullable MediaType contentType, @Nullable ResolvableType bodyType) { + return "Content type '" + (contentType != null ? contentType : "") + "' not supported" + + (bodyType != null ? " for bodyType=" + bodyType.toString() : ""); } @@ -74,4 +95,16 @@ public class UnsupportedMediaTypeException extends NestedRuntimeException { return this.supportedMediaTypes; } + /** + * Return the body type in the context of which this exception was generated. + * This is applicable when the exception was raised as a result trying to + * encode from or decode to a specific Java type. + * @return the body type, or {@code null} + * @since 5.1 + */ + @Nullable + public ResolvableType getBodyType() { + return this.bodyType; + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java index c6c05cb5a59..aaea8fce8c0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java @@ -62,7 +62,8 @@ class DefaultServerRequest implements ServerRequest { private static final Function ERROR_MAPPER = ex -> (ex.getContentType() != null ? - new UnsupportedMediaTypeStatusException(ex.getContentType(), ex.getSupportedMediaTypes()) : + new UnsupportedMediaTypeStatusException( + ex.getContentType(), ex.getSupportedMediaTypes(), ex.getBodyType()) : new UnsupportedMediaTypeStatusException(ex.getMessage())); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index 8a9ce14dc6e..637a1b31d4d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -191,7 +191,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho if (contentType == null && method != null && SUPPORTED_METHODS.contains(method)) { Flux body = request.getBody().doOnNext(o -> { // Body not empty, back to 415.. - throw new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes); + throw new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes, elementType); }); if (isBodyRequired) { body = body.switchIfEmpty(Mono.error(() -> handleMissingBody(bodyParam))); @@ -199,7 +199,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho return (adapter != null ? Mono.just(adapter.fromPublisher(body)) : Mono.from(body)); } - return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes)); + return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes, elementType)); } private Throwable handleReadError(MethodParameter parameter, Throwable ex) {