ProblemDetail XML support via Jackson
Closes gh-29927
This commit is contained in:
@@ -80,9 +80,6 @@ public abstract class Jackson2CodecSupport {
|
||||
new MediaType("application", "*+json"),
|
||||
MediaType.APPLICATION_NDJSON);
|
||||
|
||||
private static final List<MimeType> problemDetailMimeTypes =
|
||||
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
|
||||
|
||||
|
||||
protected final Log logger = HttpLogging.forLogName(getClass());
|
||||
|
||||
@@ -186,7 +183,16 @@ public abstract class Jackson2CodecSupport {
|
||||
if (!CollectionUtils.isEmpty(result)) {
|
||||
return result;
|
||||
}
|
||||
return (ProblemDetail.class.isAssignableFrom(elementClass) ? problemDetailMimeTypes : getMimeTypes());
|
||||
return (ProblemDetail.class.isAssignableFrom(elementClass) ? getMediaTypesForProblemDetail() : getMimeTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the supported media type(s) for {@link ProblemDetail}.
|
||||
* By default, an empty list, unless overridden in subclasses.
|
||||
* @since 6.0.5
|
||||
*/
|
||||
protected List<MimeType> getMediaTypesForProblemDetail() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
protected boolean supportsMimeType(@Nullable MimeType mimeType) {
|
||||
|
||||
@@ -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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.http.codec.json;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -46,6 +47,10 @@ import org.springframework.util.MimeType;
|
||||
*/
|
||||
public class Jackson2JsonEncoder extends AbstractJackson2Encoder {
|
||||
|
||||
private static final List<MimeType> problemDetailMimeTypes =
|
||||
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
|
||||
|
||||
|
||||
@Nullable
|
||||
private final PrettyPrinter ssePrettyPrinter;
|
||||
|
||||
@@ -68,6 +73,11 @@ public class Jackson2JsonEncoder extends AbstractJackson2Encoder {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected List<MimeType> getMediaTypesForProblemDetail() {
|
||||
return problemDetailMimeTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectWriter customizeWriter(ObjectWriter writer, @Nullable MimeType mimeType,
|
||||
ResolvableType elementType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
@@ -91,9 +91,6 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
ENCODINGS.put("US-ASCII", JsonEncoding.UTF8);
|
||||
}
|
||||
|
||||
private static final List<MediaType> problemDetailMediaTypes =
|
||||
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
|
||||
|
||||
|
||||
protected ObjectMapper defaultObjectMapper;
|
||||
|
||||
@@ -209,13 +206,23 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
if (!CollectionUtils.isEmpty(result)) {
|
||||
return result;
|
||||
}
|
||||
return (ProblemDetail.class.isAssignableFrom(clazz) ? problemDetailMediaTypes : getSupportedMediaTypes());
|
||||
return (ProblemDetail.class.isAssignableFrom(clazz) ?
|
||||
getMediaTypesForProblemDetail() : getSupportedMediaTypes());
|
||||
}
|
||||
|
||||
private Map<Class<?>, Map<MediaType, ObjectMapper>> getObjectMapperRegistrations() {
|
||||
return (this.objectMapperRegistrations != null ? this.objectMapperRegistrations : Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the supported media type(s) for {@link ProblemDetail}.
|
||||
* By default, an empty list, unless overridden in subclasses.
|
||||
* @since 6.0.5
|
||||
*/
|
||||
protected List<MediaType> getMediaTypesForProblemDetail() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
|
||||
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
|
||||
|
||||
@@ -99,6 +99,10 @@ import org.springframework.util.xml.StaxUtils;
|
||||
*/
|
||||
public class Jackson2ObjectMapperBuilder {
|
||||
|
||||
private static boolean jackson2XmlPresent = ClassUtils.isPresent(
|
||||
"com.fasterxml.jackson.dataformat.xml.XmlMapper", Jackson2ObjectMapperBuilder.class.getClassLoader());
|
||||
|
||||
|
||||
private final Map<Class<?>, Class<?>> mixIns = new LinkedHashMap<>();
|
||||
|
||||
private final Map<Class<?>, JsonSerializer<?>> serializers = new LinkedHashMap<>();
|
||||
@@ -755,7 +759,12 @@ public class Jackson2ObjectMapperBuilder {
|
||||
objectMapper.setFilterProvider(this.filters);
|
||||
}
|
||||
|
||||
objectMapper.addMixIn(ProblemDetail.class, ProblemDetailJacksonMixin.class);
|
||||
if (jackson2XmlPresent) {
|
||||
objectMapper.addMixIn(ProblemDetail.class, ProblemDetailJacksonXmlMixin.class);
|
||||
}
|
||||
else {
|
||||
objectMapper.addMixIn(ProblemDetail.class, ProblemDetailJacksonMixin.class);
|
||||
}
|
||||
this.mixIns.forEach(objectMapper::addMixIn);
|
||||
|
||||
if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) {
|
||||
|
||||
@@ -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.
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -45,6 +47,10 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
|
||||
|
||||
private static final List<MediaType> problemDetailMediaTypes =
|
||||
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
|
||||
|
||||
|
||||
@Nullable
|
||||
private String jsonPrefix;
|
||||
|
||||
@@ -88,6 +94,11 @@ public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMes
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected List<MediaType> getMediaTypesForProblemDetail() {
|
||||
return problemDetailMediaTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
|
||||
if (this.jsonPrefix != null) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.converter.json;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
|
||||
|
||||
/**
|
||||
* Intended to be identical to {@link ProblemDetailJacksonMixin} but for used
|
||||
* instead of it when jackson-dataformat-xml is on the classpath. Customizes the
|
||||
* XML root element name and adds namespace information.
|
||||
*
|
||||
* <p>Note: Unfortunately, we cannot just use {@code JsonRootName} to specify
|
||||
* the namespace since that is not inherited by fields of the class. This is
|
||||
* why we need a dedicated mixin for use when jackson-dataformat-xml is on the
|
||||
* classpath. For more details, see
|
||||
* <a href="https://github.com/FasterXML/jackson-dataformat-xml/issues/355">FasterXML/jackson-dataformat-xml#355</a>.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0.5
|
||||
*/
|
||||
@JsonInclude(NON_EMPTY)
|
||||
@JacksonXmlRootElement(localName = "problem", namespace = "urn:ietf:rfc:7807")
|
||||
public interface ProblemDetailJacksonXmlMixin {
|
||||
|
||||
@JacksonXmlProperty(namespace = "urn:ietf:rfc:7807")
|
||||
URI getType();
|
||||
|
||||
@JacksonXmlProperty(namespace = "urn:ietf:rfc:7807")
|
||||
String getTitle();
|
||||
|
||||
@JacksonXmlProperty(namespace = "urn:ietf:rfc:7807")
|
||||
int getStatus();
|
||||
|
||||
@JacksonXmlProperty(namespace = "urn:ietf:rfc:7807")
|
||||
String getDetail();
|
||||
|
||||
@JacksonXmlProperty(namespace = "urn:ietf:rfc:7807")
|
||||
URI getInstance();
|
||||
|
||||
@JsonAnySetter
|
||||
void setProperty(String name, @Nullable Object value);
|
||||
|
||||
@JsonAnyGetter
|
||||
@JacksonXmlProperty(namespace = "urn:ietf:rfc:7807")
|
||||
Map<String, Object> getProperties();
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
@@ -42,6 +44,10 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class MappingJackson2XmlHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
|
||||
|
||||
private static final List<MediaType> problemDetailMediaTypes =
|
||||
Collections.singletonList(MediaType.APPLICATION_PROBLEM_XML);
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code MappingJackson2XmlHttpMessageConverter} using default configuration
|
||||
* provided by {@code Jackson2ObjectMapperBuilder}.
|
||||
@@ -74,4 +80,9 @@ public class MappingJackson2XmlHttpMessageConverter extends AbstractJackson2Http
|
||||
super.setObjectMapper(objectMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<MediaType> getMediaTypesForProblemDetail() {
|
||||
return problemDetailMediaTypes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user