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 7292c4ca7f..ac19c4feff 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-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -75,11 +75,14 @@ public abstract class Jackson2CodecSupport { private static final String JSON_VIEW_HINT_ERROR = "@JsonView only supported for write hints with exactly 1 class argument: "; - private static final List DEFAULT_MIME_TYPES = List.of( + private static final List defaultMimeTypes = List.of( MediaType.APPLICATION_JSON, new MediaType("application", "*+json"), MediaType.APPLICATION_NDJSON); + private static final List problemDetailMimeTypes = + Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON); + protected final Log logger = HttpLogging.forLogName(getClass()); @@ -90,8 +93,6 @@ public abstract class Jackson2CodecSupport { private final List mimeTypes; - private final List problemDetailMimeTypes; - /** * Constructor with a Jackson {@link ObjectMapper} to use. @@ -99,15 +100,7 @@ public abstract class Jackson2CodecSupport { protected Jackson2CodecSupport(ObjectMapper objectMapper, MimeType... mimeTypes) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); this.defaultObjectMapper = objectMapper; - this.mimeTypes = (!ObjectUtils.isEmpty(mimeTypes) ? List.of(mimeTypes) : DEFAULT_MIME_TYPES); - this.problemDetailMimeTypes = initProblemDetailMediaTypes(this.mimeTypes); - } - - private static List initProblemDetailMediaTypes(List supportedMimeTypes) { - List mimeTypes = new ArrayList<>(); - mimeTypes.add(MediaType.APPLICATION_PROBLEM_JSON); - mimeTypes.addAll(supportedMimeTypes); - return Collections.unmodifiableList(mimeTypes); + this.mimeTypes = (!ObjectUtils.isEmpty(mimeTypes) ? List.of(mimeTypes) : defaultMimeTypes); } @@ -193,7 +186,7 @@ public abstract class Jackson2CodecSupport { if (!CollectionUtils.isEmpty(result)) { return result; } - return (ProblemDetail.class.isAssignableFrom(elementClass) ? this.problemDetailMimeTypes : getMimeTypes()); + return (ProblemDetail.class.isAssignableFrom(elementClass) ? problemDetailMimeTypes : getMimeTypes()); } protected boolean supportsMimeType(@Nullable MimeType mimeType) { diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index 595e70a323..a380f0171a 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -91,8 +91,9 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener ENCODINGS.put("US-ASCII", JsonEncoding.UTF8); } + private static final List problemDetailMediaTypes = + Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON); - private List problemDetailMediaTypes = Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON); protected ObjectMapper defaultObjectMapper; @@ -126,17 +127,9 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener @Override public void setSupportedMediaTypes(List supportedMediaTypes) { - this.problemDetailMediaTypes = initProblemDetailMediaTypes(supportedMediaTypes); super.setSupportedMediaTypes(supportedMediaTypes); } - private List initProblemDetailMediaTypes(List supportedMediaTypes) { - List mediaTypes = new ArrayList<>(); - mediaTypes.add(MediaType.APPLICATION_PROBLEM_JSON); - mediaTypes.addAll(supportedMediaTypes); - return Collections.unmodifiableList(mediaTypes); - } - /** * Configure the main {@code ObjectMapper} to use for Object conversion. * If not set, a default {@link ObjectMapper} instance is created. @@ -216,8 +209,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener if (!CollectionUtils.isEmpty(result)) { return result; } - return (ProblemDetail.class.isAssignableFrom(clazz) ? - this.problemDetailMediaTypes : getSupportedMediaTypes()); + return (ProblemDetail.class.isAssignableFrom(clazz) ? problemDetailMediaTypes : getSupportedMediaTypes()); } private Map, Map> getObjectMapperRegistrations() { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java index 79ecdb84dc..6cd9596a04 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -127,7 +127,11 @@ public class ResponseBodyResultHandlerTests { // JSON requested exchange = MockServerWebExchange.from(get("/path").accept(MediaType.APPLICATION_JSON)); - testProblemDetailMediaType(exchange, MediaType.APPLICATION_JSON); + testProblemDetailMediaType(exchange, MediaType.APPLICATION_PROBLEM_JSON); + + // JSON & Problem Detail requested (gh-29588) + exchange = MockServerWebExchange.from(get("/path").accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_PROBLEM_JSON)); + testProblemDetailMediaType(exchange, MediaType.APPLICATION_PROBLEM_JSON); // No match fallback exchange = MockServerWebExchange.from(get("/path").accept(MediaType.APPLICATION_PDF)); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java index e7018c11e1..8b151228f8 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -403,7 +403,13 @@ public class RequestResponseBodyMethodProcessorTests { @Test void problemDetailWhenJsonRequested() throws Exception { this.servletRequest.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE); - testProblemDetailMediaType(MediaType.APPLICATION_JSON_VALUE); + testProblemDetailMediaType(MediaType.APPLICATION_PROBLEM_JSON_VALUE); + } + + @Test // gh-29588 + void problemDetailWhenJsonAndProblemJsonRequested() throws Exception { + this.servletRequest.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE + "," + MediaType.APPLICATION_PROBLEM_JSON_VALUE); + testProblemDetailMediaType(MediaType.APPLICATION_PROBLEM_JSON_VALUE); } @Test