Skip to content

Commit 56b0e93

Browse files
artembilangaryrussell
authored andcommitted
GH-892: Reinstate MimeTypeJsonDeserializer
Fixes #892 For compatibility with older Spring Kafka producer, it is better to keep a `MimeTypeJsonDeserializer` when they can send `MimeType` headers as a serialized version, not string representation
1 parent 4ea6d8d commit 56b0e93

File tree

2 files changed

+69
-3
lines changed

2 files changed

+69
-3
lines changed

spring-kafka/src/main/java/org/springframework/kafka/support/DefaultKafkaHeaderMapper.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@
3434
import org.springframework.messaging.MessageHeaders;
3535
import org.springframework.util.Assert;
3636
import org.springframework.util.ClassUtils;
37+
import org.springframework.util.MimeType;
3738

3839
import com.fasterxml.jackson.core.JsonProcessingException;
40+
import com.fasterxml.jackson.databind.DeserializationContext;
41+
import com.fasterxml.jackson.databind.JsonNode;
3942
import com.fasterxml.jackson.databind.ObjectMapper;
43+
import com.fasterxml.jackson.databind.deser.std.StdNodeBasedDeserializer;
44+
import com.fasterxml.jackson.databind.module.SimpleModule;
45+
import com.fasterxml.jackson.databind.type.TypeFactory;
4046

4147
/**
4248
* Default header mapper for Apache Kafka.
@@ -46,6 +52,8 @@
4652
* Header types are added to a special header {@link #JSON_TYPES}.
4753
*
4854
* @author Gary Russell
55+
* @author Artem Bilan
56+
*
4957
* @since 1.3
5058
*
5159
*/
@@ -54,7 +62,8 @@ public class DefaultKafkaHeaderMapper extends AbstractKafkaHeaderMapper {
5462
private static final List<String> DEFAULT_TRUSTED_PACKAGES =
5563
Arrays.asList(
5664
"java.util",
57-
"java.lang"
65+
"java.lang",
66+
"org.springframework.util"
5867
);
5968

6069
private static final List<String> DEFAULT_TO_STRING_CLASSES =
@@ -136,6 +145,8 @@ public DefaultKafkaHeaderMapper(ObjectMapper objectMapper, String... patterns) {
136145
Assert.notNull(objectMapper, "'objectMapper' must not be null");
137146
Assert.noNullElements(patterns, "'patterns' must not have null elements");
138147
this.objectMapper = objectMapper;
148+
this.objectMapper
149+
.registerModule(new SimpleModule().addDeserializer(MimeType.class, new MimeTypeJsonDeserializer()));
139150
}
140151

141152
/**
@@ -233,7 +244,6 @@ public void fromHeaders(MessageHeaders headers, Headers target) {
233244
}
234245
}
235246

236-
@SuppressWarnings("unchecked")
237247
@Override
238248
public void toHeaders(Headers source, final Map<String, Object> headers) {
239249
final Map<String, String> jsonTypes = decodeJsonTypes(source);
@@ -257,7 +267,8 @@ public void toHeaders(Headers source, final Map<String, Object> headers) {
257267
headers.put(h.key(), getObjectMapper().readValue(h.value(), type));
258268
}
259269
catch (IOException e) {
260-
logger.error("Could not decode json type: " + new String(h.value()) + " for key: " + h.key(),
270+
logger.error("Could not decode json type: " + new String(h.value()) + " for key: " + h
271+
.key(),
261272
e);
262273
headers.put(h.key(), h.value());
263274
}
@@ -310,6 +321,34 @@ protected boolean trusted(String requestedType) {
310321
return true;
311322
}
312323

324+
325+
/**
326+
* The {@link StdNodeBasedDeserializer} extension for {@link MimeType} deserialization.
327+
* It is presented here for backward compatibility when older producers send {@link MimeType}
328+
* headers as serialization version.
329+
*/
330+
private class MimeTypeJsonDeserializer extends StdNodeBasedDeserializer<MimeType> {
331+
332+
private static final long serialVersionUID = 1L;
333+
334+
MimeTypeJsonDeserializer() {
335+
super(MimeType.class);
336+
}
337+
338+
@Override
339+
public MimeType convert(JsonNode root, DeserializationContext ctxt) throws IOException {
340+
JsonNode type = root.get("type");
341+
JsonNode subType = root.get("subtype");
342+
JsonNode parameters = root.get("parameters");
343+
Map<String, String> params =
344+
DefaultKafkaHeaderMapper.this.objectMapper.readValue(parameters.traverse(),
345+
TypeFactory.defaultInstance()
346+
.constructMapType(HashMap.class, String.class, String.class));
347+
return new MimeType(type.asText(), subType.asText(), params);
348+
}
349+
350+
}
351+
313352
/**
314353
* Represents a header that could not be decoded due to an untrusted type.
315354
*/

spring-kafka/src/test/java/org/springframework/kafka/support/DefaultKafkaHeaderMapperTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020

2121
import java.nio.charset.Charset;
22+
import java.util.Collections;
2223
import java.util.HashMap;
2324
import java.util.Map;
25+
import java.util.Set;
2426

2527
import org.apache.kafka.common.header.internals.RecordHeaders;
2628
import org.junit.Test;
2729

2830
import org.springframework.kafka.support.DefaultKafkaHeaderMapper.NonTrustedHeaderType;
31+
import org.springframework.kafka.test.utils.KafkaTestUtils;
2932
import org.springframework.messaging.Message;
3033
import org.springframework.messaging.MessageHeaders;
3134
import org.springframework.messaging.support.ExecutorSubscribableChannel;
@@ -83,6 +86,30 @@ public void test() {
8386
assertThat(headers).hasSize(7);
8487
}
8588

89+
@Test
90+
public void testMimeBackwardsCompat() {
91+
DefaultKafkaHeaderMapper mapper = new DefaultKafkaHeaderMapper();
92+
MessageHeaders headers = new MessageHeaders(
93+
Collections.singletonMap("foo", MimeType.valueOf("application/json")));
94+
95+
RecordHeaders recordHeaders = new RecordHeaders();
96+
mapper.fromHeaders(headers, recordHeaders);
97+
Map<String, Object> receivedHeaders = new HashMap<>();
98+
mapper.toHeaders(recordHeaders, receivedHeaders);
99+
Object fooHeader = receivedHeaders.get("foo");
100+
assertThat(fooHeader).isInstanceOf(String.class);
101+
assertThat(fooHeader).isEqualTo("application/json");
102+
103+
KafkaTestUtils.getPropertyValue(mapper, "toStringClasses", Set.class).clear();
104+
recordHeaders = new RecordHeaders();
105+
mapper.fromHeaders(headers, recordHeaders);
106+
receivedHeaders = new HashMap<>();
107+
mapper.toHeaders(recordHeaders, receivedHeaders);
108+
fooHeader = receivedHeaders.get("foo");
109+
assertThat(fooHeader).isInstanceOf(MimeType.class);
110+
assertThat(fooHeader).isEqualTo(MimeType.valueOf("application/json"));
111+
}
112+
86113
public static final class Foo {
87114

88115
private String bar = "bar";

0 commit comments

Comments
 (0)