From 0d16c9100afc492a6f2258dfc2dfc9c9515438c2 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 4 Feb 2021 15:25:43 +0000 Subject: [PATCH] MIME types by Class for Encoder, Decoder, HttpMessageReader|Writer Closes gh-26212 --- .../springframework/core/codec/Decoder.java | 30 +++++++-- .../springframework/core/codec/Encoder.java | 24 ++++++- .../http/codec/DecoderHttpMessageReader.java | 6 +- .../http/codec/EncoderHttpMessageWriter.java | 6 +- .../http/codec/HttpMessageReader.java | 28 +++++++-- .../http/codec/HttpMessageWriter.java | 24 ++++++- .../codec/json/AbstractJackson2Decoder.java | 4 ++ .../codec/json/AbstractJackson2Encoder.java | 7 ++- .../http/codec/json/Jackson2CodecSupport.java | 16 ++++- .../web/reactive/function/BodyExtractors.java | 4 +- .../web/reactive/function/BodyInserters.java | 4 +- ...AbstractMessageReaderArgumentResolver.java | 23 ++++--- .../AbstractMessageWriterResultHandler.java | 4 +- .../ResponseEntityResultHandlerTests.java | 62 ++++++++++++++++++- 14 files changed, 208 insertions(+), 34 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/codec/Decoder.java b/spring-core/src/main/java/org/springframework/core/codec/Decoder.java index 7370a143a67..cc5271d5c01 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/Decoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/Decoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,7 @@ package org.springframework.core.codec; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -59,7 +60,7 @@ public interface Decoder { * this type must have been previously passed to the {@link #canDecode} * method and it must have returned {@code true}. * @param mimeType the MIME type associated with the input stream (optional) - * @param hints additional information about how to do encode + * @param hints additional information about how to do decode * @return the output stream with decoded elements */ Flux decode(Publisher inputStream, ResolvableType elementType, @@ -72,7 +73,7 @@ public interface Decoder { * this type must have been previously passed to the {@link #canDecode} * method and it must have returned {@code true}. * @param mimeType the MIME type associated with the input stream (optional) - * @param hints additional information about how to do encode + * @param hints additional information about how to do decode * @return the output stream with the decoded element */ Mono decodeToMono(Publisher inputStream, ResolvableType elementType, @@ -85,7 +86,7 @@ public interface Decoder { * @param buffer the {@code DataBuffer} to decode * @param targetType the expected output type * @param mimeType the MIME type associated with the data - * @param hints additional information about how to do encode + * @param hints additional information about how to do decode * @return the decoded value, possibly {@code null} * @since 5.2 */ @@ -111,8 +112,27 @@ public interface Decoder { } /** - * Return the list of MIME types this decoder supports. + * Return the list of MIME types supported by this Decoder. The list may not + * apply to every possible target element type and calls to this method + * should typically be guarded via {@link #canDecode(ResolvableType, MimeType) + * canDecode(elementType, null)}. The list may also exclude MIME types + * supported only for a specific element type. Alternatively, use + * {@link #getDecodableMimeTypes(ResolvableType)} for a more precise list. + * @return the list of supported MIME types */ List getDecodableMimeTypes(); + /** + * Return the list of MIME types supported by this Decoder for the given type + * of element. This list may differ from {@link #getDecodableMimeTypes()} + * if the Decoder doesn't support the given element type or if it supports + * it only for a subset of MIME types. + * @param targetType the type of element to check for decoding + * @return the list of MIME types supported for the given target type + * @since 5.3.4 + */ + default List getDecodableMimeTypes(ResolvableType targetType) { + return (canDecode(targetType, null) ? getDecodableMimeTypes() : Collections.emptyList()); + } + } diff --git a/spring-core/src/main/java/org/springframework/core/codec/Encoder.java b/spring-core/src/main/java/org/springframework/core/codec/Encoder.java index aa4f5d8aff3..e7bc3c7d078 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/Encoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/Encoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,7 @@ package org.springframework.core.codec; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -90,8 +91,27 @@ public interface Encoder { } /** - * Return the list of mime types this encoder supports. + * Return the list of MIME types supported by this Encoder. The list may not + * apply to every possible target element type and calls to this method should + * typically be guarded via {@link #canEncode(ResolvableType, MimeType) + * canEncode(elementType, null)}. The list may also exclude MIME types + * supported only for a specific element type. Alternatively, use + * {@link #getEncodableMimeTypes(ResolvableType)} for a more precise list. + * @return the list of supported MIME types */ List getEncodableMimeTypes(); + /** + * Return the list of MIME types supported by this Encoder for the given type + * of element. This list may differ from the {@link #getEncodableMimeTypes()} + * if the Encoder doesn't support the element type or if it supports it only + * for a subset of MIME types. + * @param elementType the type of element to check for encoding + * @return the list of MIME types supported for the given element type + * @since 5.3.4 + */ + default List getEncodableMimeTypes(ResolvableType elementType) { + return (canEncode(elementType, null) ? getEncodableMimeTypes() : Collections.emptyList()); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java index 27c2672baad..d3500ad6cc5 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -87,6 +87,10 @@ public class DecoderHttpMessageReader implements HttpMessageReader { return this.mediaTypes; } + @Override + public List getReadableMediaTypes(ResolvableType elementType) { + return MediaType.asMediaTypes(this.decoder.getDecodableMimeTypes(elementType)); + } @Override public boolean canRead(ResolvableType elementType, @Nullable MediaType mediaType) { diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 6c1413ab4f8..3fe19207738 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -105,6 +105,10 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { return this.mediaTypes; } + @Override + public List getWritableMediaTypes(ResolvableType elementType) { + return MediaType.asMediaTypes(getEncoder().getEncodableMimeTypes(elementType)); + } @Override public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java index ed99da6c9ab..16d578b4f4a 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,7 @@ package org.springframework.http.codec; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -43,10 +44,29 @@ import org.springframework.lang.Nullable; public interface HttpMessageReader { /** - * Return the {@link MediaType}'s that this reader supports. + * Return the list of media types supported by this reader. The list may not + * apply to every possible target element type and calls to this method + * should typically be guarded via {@link #canRead(ResolvableType, MediaType) + * canWrite(elementType, null)}. The list may also exclude media types + * supported only for a specific element type. Alternatively, use + * {@link #getReadableMediaTypes(ResolvableType)} for a more precise list. + * @return the general list of supported media types */ List getReadableMediaTypes(); + /** + * Return the list of media types supported by this Reader for the given type + * of element. This list may differ from {@link #getReadableMediaTypes()} + * if the Reader doesn't support the element type, or if it supports it + * only for a subset of media types. + * @param elementType the type of element to read + * @return the list of media types supported for the given class + * @since 5.3.4 + */ + default List getReadableMediaTypes(ResolvableType elementType) { + return (canRead(elementType, null) ? getReadableMediaTypes() : Collections.emptyList()); + } + /** * Whether the given object type is supported by this reader. * @param elementType the type of object to check @@ -56,7 +76,7 @@ public interface HttpMessageReader { boolean canRead(ResolvableType elementType, @Nullable MediaType mediaType); /** - * Read from the input message and encode to a stream of objects. + * Read from the input message and decode to a stream of objects. * @param elementType the type of objects in the stream which must have been * previously checked via {@link #canRead(ResolvableType, MediaType)} * @param message the message to read from @@ -66,7 +86,7 @@ public interface HttpMessageReader { Flux read(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints); /** - * Read from the input message and encode to a single object. + * Read from the input message and decode to a single object. * @param elementType the type of objects in the stream which must have been * previously checked via {@link #canRead(ResolvableType, MediaType)} * @param message the message to read from diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java index 0b1b94b714d..33f70ae0fdb 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,7 @@ package org.springframework.http.codec; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -43,10 +44,29 @@ import org.springframework.lang.Nullable; public interface HttpMessageWriter { /** - * Return the {@link MediaType}'s that this writer supports. + * Return the list of media types supported by this Writer. The list may not + * apply to every possible target element type and calls to this method should + * typically be guarded via {@link #canWrite(ResolvableType, MediaType) + * canWrite(elementType, null)}. The list may also exclude media types + * supported only for a specific element type. Alternatively, use + * {@link #getWritableMediaTypes(ResolvableType)} for a more precise list. + * @return the general list of supported media types */ List getWritableMediaTypes(); + /** + * Return the list of media types supported by this Writer for the given type + * of element. This list may differ from {@link #getWritableMediaTypes()} + * if the Writer doesn't support the element type, or if it supports it + * only for a subset of media types. + * @param elementType the type of element to encode + * @return the list of media types supported for the given class + * @since 5.3.4 + */ + default List getWritableMediaTypes(ResolvableType elementType) { + return (canWrite(elementType, null) ? getWritableMediaTypes() : Collections.emptyList()); + } + /** * Whether the given object type is supported by this writer. * @param elementType the type of object to check diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java index 38335957fa6..14195ad22f7 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java @@ -259,6 +259,10 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple return getMimeTypes(); } + @Override + public List getDecodableMimeTypes(ResolvableType targetType) { + return getMimeTypes(targetType); + } // Jackson2CodecSupport diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java index 70ff83924c5..89644fecff9 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -357,6 +357,11 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple return getMimeTypes(); } + @Override + public List getEncodableMimeTypes(ResolvableType elementType) { + return getMimeTypes(elementType); + } + @Override public List getStreamingMediaTypes() { return Collections.unmodifiableList(this.streamingMediaTypes); diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java index a34411c99f7..92b3990e89b 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -18,6 +18,7 @@ package org.springframework.http.codec.json; import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -148,7 +149,7 @@ public abstract class Jackson2CodecSupport { return Collections.emptyMap(); } - private Map, Map> getObjectMapperRegistrations() { + protected Map, Map> getObjectMapperRegistrations() { return (this.objectMapperRegistrations != null ? this.objectMapperRegistrations : Collections.emptyMap()); } @@ -159,6 +160,17 @@ public abstract class Jackson2CodecSupport { return this.mimeTypes; } + protected List getMimeTypes(ResolvableType elementType) { + Class elementClass = elementType.toClass(); + List result = null; + for (Map.Entry, Map> entry : getObjectMapperRegistrations().entrySet()) { + if (entry.getKey().isAssignableFrom(elementClass)) { + result = (result != null ? result : new ArrayList<>(entry.getValue().size())); + result.addAll(entry.getValue().keySet()); + } + } + return (CollectionUtils.isEmpty(result) ? getMimeTypes() : result); + } protected boolean supportsMimeType(@Nullable MimeType mimeType) { if (mimeType == null) { 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 5dd63b72efb..f433ccdfb40 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -196,7 +196,7 @@ public abstract class BodyExtractors { .map(readerFunction) .orElseGet(() -> { List mediaTypes = context.messageReaders().stream() - .flatMap(reader -> reader.getReadableMediaTypes().stream()) + .flatMap(reader -> reader.getReadableMediaTypes(elementType).stream()) .collect(Collectors.toList()); return errorFunction.apply( new UnsupportedMediaTypeException(contentType, mediaTypes, elementType)); 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 15f6ad99fa0..fe951e0f59b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -385,7 +385,7 @@ public abstract class BodyInserters { BodyInserter.Context context, @Nullable MediaType mediaType) { List supportedMediaTypes = context.messageWriters().stream() - .flatMap(reader -> reader.getWritableMediaTypes().stream()) + .flatMap(reader -> reader.getWritableMediaTypes(bodyType).stream()) .collect(Collectors.toList()); return new UnsupportedMediaTypeException(mediaType, supportedMediaTypes, bodyType); 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 5d405af73d6..c278ca05971 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 @@ -17,11 +17,11 @@ package org.springframework.web.reactive.result.method.annotation; import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -76,8 +76,6 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho private final List> messageReaders; - private final List supportedMediaTypes; - /** * Constructor with {@link HttpMessageReader}'s and a {@link Validator}. @@ -99,9 +97,6 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho Assert.notEmpty(messageReaders, "At least one HttpMessageReader is required"); Assert.notNull(adapterRegistry, "ReactiveAdapterRegistry is required"); this.messageReaders = messageReaders; - this.supportedMediaTypes = messageReaders.stream() - .flatMap(converter -> converter.getReadableMediaTypes().stream()) - .collect(Collectors.toList()); } @@ -212,8 +207,9 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho if (contentType == null && method != null && SUPPORTED_METHODS.contains(method)) { Flux body = request.getBody().doOnNext(buffer -> { DataBufferUtils.release(buffer); - // Body not empty, back to 415.. - throw new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes, elementType); + // Body not empty, back toy 415.. + throw new UnsupportedMediaTypeStatusException( + mediaType, getSupportedMediaTypes(elementType), elementType); }); if (isBodyRequired) { body = body.switchIfEmpty(Mono.error(() -> handleMissingBody(bodyParam))); @@ -221,7 +217,8 @@ 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, elementType)); + return Mono.error(new UnsupportedMediaTypeStatusException( + mediaType, getSupportedMediaTypes(elementType), elementType)); } private Throwable handleReadError(MethodParameter parameter, Throwable ex) { @@ -263,4 +260,12 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho } } + private List getSupportedMediaTypes(ResolvableType elementType) { + List mediaTypes = new ArrayList<>(); + for (HttpMessageReader reader : this.messageReaders) { + mediaTypes.addAll(reader.getReadableMediaTypes(elementType)); + } + return mediaTypes; + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java index 40652318fb6..08837c3fa22 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -192,7 +192,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa List writableMediaTypes = new ArrayList<>(); for (HttpMessageWriter converter : getMessageWriters()) { if (converter.canWrite(elementType, null)) { - writableMediaTypes.addAll(converter.getWritableMediaTypes()); + writableMediaTypes.addAll(converter.getWritableMediaTypes(elementType)); } } return writableMediaTypes; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index c215ee7a77a..3374b17ed8b 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -28,6 +28,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Single; import org.junit.jupiter.api.BeforeEach; @@ -78,6 +80,9 @@ import static org.springframework.web.testfixture.method.ResolvableMethod.on; */ public class ResponseEntityResultHandlerTests { + private static final String NEWLINE_SYSTEM_PROPERTY = System.getProperty("line.separator"); + + private ResponseEntityResultHandler resultHandler; @@ -393,6 +398,37 @@ public class ResponseEntityResultHandlerTests { .verify(); } + @Test // gh-26212 + public void handleWithObjectMapperByTypeRegistration() throws Exception { + MediaType halFormsMediaType = MediaType.parseMediaType("application/prs.hal-forms+json"); + MediaType halMediaType = MediaType.parseMediaType("application/hal+json"); + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); + + Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(); + encoder.registerObjectMappersForType(Person.class, map -> map.put(halMediaType, objectMapper)); + EncoderHttpMessageWriter writer = new EncoderHttpMessageWriter<>(encoder); + + ResponseEntityResultHandler handler = new ResponseEntityResultHandler( + Collections.singletonList(writer), new RequestedContentTypeResolverBuilder().build()); + + MockServerWebExchange exchange = MockServerWebExchange.from( + get("/path").header("Accept", halFormsMediaType + "," + halMediaType)); + + ResponseEntity value = ResponseEntity.ok().body(new Person("Jason")); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Person.class)); + HandlerResult result = handlerResult(value, returnType); + + handler.handleResult(exchange, result).block(); + + assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(halMediaType); + assertThat(exchange.getResponse().getBodyAsString().block()).isEqualTo( + "{" + NEWLINE_SYSTEM_PROPERTY + + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + + "}"); + } + private void testHandle(Object returnValue, MethodParameter returnType) { MockServerWebExchange exchange = MockServerWebExchange.from(get("/path")); @@ -451,6 +487,8 @@ public class ResponseEntityResultHandlerTests { ResponseEntity responseEntityVoid() { return null; } + ResponseEntity responseEntityPerson() { return null; } + HttpHeaders httpHeaders() { return null; } Mono> mono() { return null; } @@ -470,4 +508,26 @@ public class ResponseEntityResultHandlerTests { Object object() { return null; } } + + @SuppressWarnings("unused") + private static class Person { + + private String name; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + }