/
Jackson2CodecSupport.java
176 lines (150 loc) · 5.91 KB
/
Jackson2CodecSupport.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
* Copyright 2002-2020 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.codec.json;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Hints;
import org.springframework.http.HttpLogging;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.util.ObjectUtils;
/**
* Base class providing support methods for Jackson 2.9 encoding and decoding.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
* @since 5.0
*/
public abstract class Jackson2CodecSupport {
/**
* The key for the hint to specify a "JSON View" for encoding or decoding
* with the value expected to be a {@link Class}.
* @see <a href="https://www.baeldung.com/jackson-json-view-annotation">Jackson JSON Views</a>
*/
public static final String JSON_VIEW_HINT = Jackson2CodecSupport.class.getName() + ".jsonView";
/**
* The key for the hint to access the actual ResolvableType passed into
* {@link org.springframework.http.codec.HttpMessageReader#read(ResolvableType, ResolvableType, ServerHttpRequest, ServerHttpResponse, Map)}
* (server-side only). Currently set when the method argument has generics because
* in case of reactive types, use of {@code ResolvableType.getGeneric()} means no
* MethodParameter source and no knowledge of the containing class.
*/
static final String ACTUAL_TYPE_HINT = Jackson2CodecSupport.class.getName() + ".actualType";
private static final String JSON_VIEW_HINT_ERROR =
"@JsonView only supported for write hints with exactly 1 class argument: ";
private static final List<MimeType> DEFAULT_MIME_TYPES = Collections.unmodifiableList(
Arrays.asList(
MediaType.APPLICATION_JSON,
new MediaType("application", "*+json"),
MediaType.APPLICATION_NDJSON));
protected final Log logger = HttpLogging.forLogName(getClass());
private final ObjectMapper objectMapper;
private final List<MimeType> mimeTypes;
/**
* Constructor with a Jackson {@link ObjectMapper} to use.
*/
protected Jackson2CodecSupport(ObjectMapper objectMapper, MimeType... mimeTypes) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
this.mimeTypes = !ObjectUtils.isEmpty(mimeTypes) ?
Collections.unmodifiableList(Arrays.asList(mimeTypes)) : DEFAULT_MIME_TYPES;
}
public ObjectMapper getObjectMapper() {
return this.objectMapper;
}
/**
* Subclasses should expose this as "decodable" or "encodable" mime types.
*/
protected List<MimeType> getMimeTypes() {
return this.mimeTypes;
}
protected boolean supportsMimeType(@Nullable MimeType mimeType) {
if (mimeType == null) {
return true;
}
for (MimeType supportedMimeType : this.mimeTypes) {
if (supportedMimeType.isCompatibleWith(mimeType)) {
return true;
}
}
return false;
}
/**
* Determine whether to log the given exception coming from a
* {@link ObjectMapper#canDeserialize} / {@link ObjectMapper#canSerialize} check.
* @param type the class that Jackson tested for (de-)serializability
* @param cause the Jackson-thrown exception to evaluate
* (typically a {@link JsonMappingException})
* @since 5.3.1
*/
protected void logWarningIfNecessary(Type type, @Nullable Throwable cause) {
if (cause == null) {
return;
}
if (logger.isDebugEnabled()) {
String msg = "Failed to evaluate Jackson " + (type instanceof JavaType ? "de" : "") +
"serialization for type [" + type + "]";
logger.debug(msg, cause);
}
}
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
return this.objectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass));
}
protected Map<String, Object> getHints(ResolvableType resolvableType) {
MethodParameter param = getParameter(resolvableType);
if (param != null) {
Map<String, Object> hints = null;
if (resolvableType.hasGenerics()) {
hints = new HashMap<>(2);
hints.put(ACTUAL_TYPE_HINT, resolvableType);
}
JsonView annotation = getAnnotation(param, JsonView.class);
if (annotation != null) {
Class<?>[] classes = annotation.value();
Assert.isTrue(classes.length == 1, JSON_VIEW_HINT_ERROR + param);
hints = (hints != null ? hints : new HashMap<>(1));
hints.put(JSON_VIEW_HINT, classes[0]);
}
if (hints != null) {
return hints;
}
}
return Hints.none();
}
@Nullable
protected MethodParameter getParameter(ResolvableType type) {
return (type.getSource() instanceof MethodParameter ? (MethodParameter) type.getSource() : null);
}
@Nullable
protected abstract <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType);
}