Skip to content

Commit 7bcf6a0

Browse files
artembilangaryrussell
authored andcommitted
INT-4446 Improve EmbeddedJsonHeadersMessageMapper (#2422)
* INT-4446 Improve EmbeddedJsonHeadersMessageMapper JIRA: https://jira.spring.io/browse/INT-4446 * Do not recreate message if not necessarily * Do not let to generate `id` and `timestamp` if they are not mapped * Use `smartMatch` to allow to configure negative patterns * Introduce `PatternMatchUtils.smartMatchIgnoreCase()` for convenience **Cherry-pick to 5.0.x** * * Polishing `EmbeddedJsonHeadersMessageMapper`
1 parent 15fc23c commit 7bcf6a0

File tree

3 files changed

+144
-53
lines changed

3 files changed

+144
-53
lines changed

spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapper.java

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 the original author or authors.
2+
* Copyright 2017-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,9 +19,7 @@
1919
import java.nio.ByteBuffer;
2020
import java.util.Arrays;
2121
import java.util.Collection;
22-
import java.util.Collections;
2322
import java.util.HashMap;
24-
import java.util.List;
2523
import java.util.Map;
2624
import java.util.stream.Collectors;
2725

@@ -31,10 +29,11 @@
3129
import org.springframework.integration.mapping.BytesMessageMapper;
3230
import org.springframework.integration.support.MutableMessage;
3331
import org.springframework.integration.support.MutableMessageHeaders;
32+
import org.springframework.integration.support.utils.PatternMatchUtils;
3433
import org.springframework.lang.Nullable;
3534
import org.springframework.messaging.Message;
35+
import org.springframework.messaging.MessageHeaders;
3636
import org.springframework.messaging.support.GenericMessage;
37-
import org.springframework.util.PatternMatchUtils;
3837

3938
import com.fasterxml.jackson.databind.ObjectMapper;
4039

@@ -82,7 +81,7 @@ public class EmbeddedJsonHeadersMessageMapper implements BytesMessageMapper {
8281

8382
private final ObjectMapper objectMapper;
8483

85-
private final List<String> headerPatterns;
84+
private final String[] headerPatterns;
8685

8786
private final boolean allHeaders;
8887

@@ -102,7 +101,7 @@ public EmbeddedJsonHeadersMessageMapper() {
102101
* Construct an instance that embeds headers matching the supplied patterns, using
103102
* the default JSON object mapper.
104103
* @param headerPatterns the patterns.
105-
* @see PatternMatchUtils#simpleMatch(String, String)
104+
* @see PatternMatchUtils#smartMatch(String, String...)
106105
*/
107106
public EmbeddedJsonHeadersMessageMapper(String... headerPatterns) {
108107
this(JacksonJsonUtils.messagingAwareMapper(), headerPatterns);
@@ -125,8 +124,8 @@ public EmbeddedJsonHeadersMessageMapper(ObjectMapper objectMapper) {
125124
*/
126125
public EmbeddedJsonHeadersMessageMapper(ObjectMapper objectMapper, String... headerPatterns) {
127126
this.objectMapper = objectMapper;
128-
this.headerPatterns = Arrays.asList(headerPatterns);
129-
this.allHeaders = this.headerPatterns.size() == 1 && this.headerPatterns.get(0).equals("*");
127+
this.headerPatterns = Arrays.copyOf(headerPatterns, headerPatterns.length);
128+
this.allHeaders = this.headerPatterns.length == 1 && this.headerPatterns[0].equals("*");
130129
}
131130

132131
/**
@@ -151,35 +150,54 @@ public void setCaseSensitive(boolean caseSensitive) {
151150
}
152151

153152
public Collection<String> getHeaderPatterns() {
154-
return Collections.unmodifiableCollection(this.headerPatterns);
153+
return Arrays.asList(this.headerPatterns);
155154
}
156155

157156
@SuppressWarnings("unchecked")
158157
@Override
159158
public byte[] fromMessage(Message<?> message) throws Exception {
160-
Message<?> messageToEncode = this.allHeaders ? message : pruneHeaders(message);
159+
Map<String, Object> headersToEncode =
160+
this.allHeaders
161+
? message.getHeaders()
162+
: pruneHeaders(message.getHeaders());
163+
161164
if (this.rawBytes && message.getPayload() instanceof byte[]) {
162-
return fromBytesPayload((Message<byte[]>) messageToEncode);
165+
return fromBytesPayload((byte[]) message.getPayload(), headersToEncode);
163166
}
164167
else {
168+
Message<?> messageToEncode = message;
169+
170+
if (!this.allHeaders) {
171+
if (!headersToEncode.containsKey(MessageHeaders.ID)) {
172+
headersToEncode.put(MessageHeaders.ID, MessageHeaders.ID_VALUE_NONE);
173+
}
174+
if (!headersToEncode.containsKey(MessageHeaders.TIMESTAMP)) {
175+
headersToEncode.put(MessageHeaders.TIMESTAMP, -1L);
176+
}
177+
178+
messageToEncode = new MutableMessage<>(message.getPayload(), headersToEncode);
179+
}
180+
165181
return this.objectMapper.writeValueAsBytes(messageToEncode);
166182
}
167183
}
168184

169-
private Message<?> pruneHeaders(Message<?> message) {
170-
Map<String, Object> headersToEmbed =
171-
message.getHeaders().entrySet().stream()
172-
.filter(e -> this.headerPatterns.stream().anyMatch(p ->
173-
this.caseSensitive
174-
? PatternMatchUtils.simpleMatch(p, e.getKey())
175-
: PatternMatchUtils.simpleMatch(p.toLowerCase(), e.getKey().toLowerCase())))
176-
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
177-
return new MutableMessage<>(message.getPayload(), headersToEmbed);
185+
private Map<String, Object> pruneHeaders(MessageHeaders messageHeaders) {
186+
return messageHeaders
187+
.entrySet()
188+
.stream()
189+
.filter(e -> matchHeader(e.getKey()))
190+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
191+
}
192+
193+
private boolean matchHeader(String header) {
194+
return Boolean.TRUE.equals(this.caseSensitive
195+
? PatternMatchUtils.smartMatch(header, this.headerPatterns)
196+
: PatternMatchUtils.smartMatchIgnoreCase(header, this.headerPatterns));
178197
}
179198

180-
private byte[] fromBytesPayload(Message<byte[]> message) throws Exception {
181-
byte[] headers = this.objectMapper.writeValueAsBytes(message.getHeaders());
182-
byte[] payload = message.getPayload();
199+
private byte[] fromBytesPayload(byte[] payload, Map<String, Object> headersToEncode) throws Exception {
200+
byte[] headers = this.objectMapper.writeValueAsBytes(headersToEncode);
183201
ByteBuffer buffer = ByteBuffer.wrap(new byte[8 + headers.length + payload.length]);
184202
buffer.putInt(headers.length);
185203
buffer.put(headers);
@@ -189,7 +207,7 @@ private byte[] fromBytesPayload(Message<byte[]> message) throws Exception {
189207
}
190208

191209
@Override
192-
public Message<?> toMessage(byte[] bytes, @Nullable Map<String, Object> headers) throws Exception {
210+
public Message<?> toMessage(byte[] bytes, @Nullable Map<String, Object> headers) {
193211
Message<?> message = null;
194212
try {
195213
message = decodeNativeFormat(bytes, headers);

spring-integration-core/src/main/java/org/springframework/integration/support/utils/PatternMatchUtils.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 the original author or authors.
2+
* Copyright 2017-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,12 +16,15 @@
1616

1717
package org.springframework.integration.support.utils;
1818

19+
import java.util.Arrays;
20+
1921
/**
2022
* Utility methods for pattern matching.
2123
* This utilities provide support of negative pattern matching as well
2224
* unlike {@link org.springframework.util.PatternMatchUtils}.
2325
*
2426
* @author Meherzad Lahewala
27+
* @author Artem Bilan
2528
*
2629
* @since 5.0
2730
*
@@ -31,6 +34,28 @@ public final class PatternMatchUtils {
3134

3235
private PatternMatchUtils() { }
3336

37+
/**
38+
* Pattern match against the supplied patterns ignoring case; also supports negated ('!')
39+
* patterns. First match wins (positive or negative).
40+
* To match the names starting with {@code !} symbol,
41+
* you have to escape it prepending with the {@code \} symbol in the pattern definition.
42+
* @param str the string to match.
43+
* @param patterns the patterns.
44+
* @return true for positive match; false for negative; null if no pattern matches.
45+
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String[], String)
46+
* @since 5.0.5
47+
*/
48+
public static Boolean smartMatchIgnoreCase(String str, String... patterns) {
49+
if (patterns != null) {
50+
return smartMatch(str.toLowerCase(),
51+
Arrays.stream(patterns)
52+
.map(String::toLowerCase)
53+
.toArray(String[]::new));
54+
}
55+
56+
return null; //NOSONAR - intentional null return
57+
}
58+
3459
/**
3560
* Pattern match against the supplied patterns; also supports negated ('!')
3661
* patterns. First match wins (positive or negative).
@@ -58,6 +83,7 @@ else if (pattern.startsWith("\\")) {
5883
}
5984
}
6085
}
86+
6187
return null; //NOSONAR - intentional null return
6288
}
6389

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 the original author or authors.
2+
* Copyright 2017-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,21 +16,25 @@
1616

1717
package org.springframework.integration.support.json;
1818

19-
import static org.hamcrest.CoreMatchers.containsString;
20-
import static org.hamcrest.CoreMatchers.equalTo;
21-
import static org.hamcrest.CoreMatchers.not;
22-
import static org.junit.Assert.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThat;
2320

2421
import java.nio.ByteBuffer;
2522
import java.util.Collections;
23+
import java.util.Map;
2624

2725
import org.junit.Test;
2826

2927
import org.springframework.messaging.Message;
28+
import org.springframework.messaging.MessageHeaders;
3029
import org.springframework.messaging.support.GenericMessage;
3130

31+
import com.fasterxml.jackson.core.type.TypeReference;
32+
import com.fasterxml.jackson.databind.ObjectMapper;
33+
3234
/**
3335
* @author Gary Russell
36+
* @author Artem Bilan
37+
*
3438
* @since 5.0
3539
*
3640
*/
@@ -40,17 +44,31 @@ public class EmbeddedJsonHeadersMessageMapperTests {
4044
public void testEmbedAll() throws Exception {
4145
EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper();
4246
GenericMessage<String> message = new GenericMessage<>("foo");
43-
assertThat(mapper.toMessage(mapper.fromMessage(message)), equalTo(message));
44-
47+
assertThat(mapper.toMessage(mapper.fromMessage(message))).isEqualTo(message);
4548
}
4649

4750
@Test
4851
public void testEmbedSome() throws Exception {
49-
EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper("id");
52+
EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper(MessageHeaders.ID);
5053
GenericMessage<String> message = new GenericMessage<>("foo");
51-
Message<?> decoded = mapper.toMessage(mapper.fromMessage(message));
52-
assertThat(decoded.getPayload(), equalTo(message.getPayload()));
53-
assertThat(decoded.getHeaders().getId(), equalTo(message.getHeaders().getId()));
54+
byte[] encodedMessage = mapper.fromMessage(message);
55+
Message<?> decoded = mapper.toMessage(encodedMessage);
56+
assertThat(decoded.getPayload()).isEqualTo(message.getPayload());
57+
assertThat(decoded.getHeaders().getTimestamp()).isNotEqualTo(message.getHeaders().getTimestamp());
58+
59+
ObjectMapper objectMapper = new ObjectMapper();
60+
Map<String, Object> encodedMessageToCheck =
61+
objectMapper.readValue(encodedMessage, new TypeReference<Map<String, Object>>() {
62+
63+
});
64+
65+
Object headers = encodedMessageToCheck.get("headers");
66+
assertThat(headers).isNotNull();
67+
assertThat(headers).isInstanceOf(Map.class);
68+
69+
@SuppressWarnings("unchecked")
70+
Map<String, Object> headersToCheck = (Map<String, Object>) headers;
71+
assertThat(headersToCheck).doesNotContainKey(MessageHeaders.TIMESTAMP);
5472
}
5573

5674
@Test
@@ -64,13 +82,13 @@ public void testBytesEmbedAll() throws Exception {
6482
byte[] headerBytes = new byte[headerLen];
6583
bb.get(headerBytes);
6684
String headers = new String(headerBytes);
67-
assertThat(headers, containsString(message.getHeaders().getId().toString()));
68-
assertThat(headers, containsString(String.valueOf(message.getHeaders().getTimestamp())));
69-
assertThat(bb.getInt(), equalTo(3));
70-
assertThat(bb.remaining(), equalTo(3));
71-
assertThat((char) bb.get(), equalTo('f'));
72-
assertThat((char) bb.get(), equalTo('o'));
73-
assertThat((char) bb.get(), equalTo('o'));
85+
assertThat(headers).contains(message.getHeaders().getId().toString());
86+
assertThat(headers).contains(String.valueOf(message.getHeaders().getTimestamp()));
87+
assertThat(bb.getInt()).isEqualTo(3);
88+
assertThat(bb.remaining()).isEqualTo(3);
89+
assertThat((char) bb.get()).isEqualTo('f');
90+
assertThat((char) bb.get()).isEqualTo('o');
91+
assertThat((char) bb.get()).isEqualTo('o');
7492
}
7593

7694
@Test
@@ -83,13 +101,14 @@ public void testBytesEmbedSome() throws Exception {
83101
byte[] headerBytes = new byte[headerLen];
84102
bb.get(headerBytes);
85103
String headers = new String(headerBytes);
86-
assertThat(headers, containsString(message.getHeaders().getId().toString()));
87-
assertThat(headers, not(containsString("bar")));
88-
assertThat(bb.getInt(), equalTo(3));
89-
assertThat(bb.remaining(), equalTo(3));
90-
assertThat((char) bb.get(), equalTo('f'));
91-
assertThat((char) bb.get(), equalTo('o'));
92-
assertThat((char) bb.get(), equalTo('o'));
104+
assertThat(headers).contains(message.getHeaders().getId().toString());
105+
assertThat(headers).doesNotContain(MessageHeaders.TIMESTAMP);
106+
assertThat(headers).doesNotContain("bar");
107+
assertThat(bb.getInt()).isEqualTo(3);
108+
assertThat(bb.remaining()).isEqualTo(3);
109+
assertThat((char) bb.get()).isEqualTo('f');
110+
assertThat((char) bb.get()).isEqualTo('o');
111+
assertThat((char) bb.get()).isEqualTo('o');
93112
}
94113

95114
@Test
@@ -99,10 +118,10 @@ public void testBytesEmbedAllJson() throws Exception {
99118
GenericMessage<byte[]> message = new GenericMessage<>("foo".getBytes());
100119
byte[] mappedBytes = mapper.fromMessage(message);
101120
String mapped = new String(mappedBytes);
102-
assertThat(mapped, containsString("[B\",\"Zm9v"));
121+
assertThat(mapped).contains("[B\",\"Zm9v");
103122
@SuppressWarnings("unchecked")
104123
Message<byte[]> decoded = (Message<byte[]>) mapper.toMessage(mappedBytes);
105-
assertThat(new String(decoded.getPayload()), equalTo("foo"));
124+
assertThat(new String(decoded.getPayload())).isEqualTo("foo");
106125

107126
}
108127

@@ -111,7 +130,35 @@ public void testBytesDecodeAll() throws Exception {
111130
EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper();
112131
GenericMessage<byte[]> message = new GenericMessage<>("foo".getBytes());
113132
Message<?> decoded = mapper.toMessage(mapper.fromMessage(message));
114-
assertThat(decoded, equalTo(message));
133+
assertThat(decoded).isEqualTo(message);
134+
}
135+
136+
@Test
137+
public void testDontMapIdButOthers() throws Exception {
138+
EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper("!" + MessageHeaders.ID, "*");
139+
GenericMessage<String> message = new GenericMessage<>("foo", Collections.singletonMap("bar", "baz"));
140+
byte[] encodedMessage = mapper.fromMessage(message);
141+
142+
ObjectMapper objectMapper = new ObjectMapper();
143+
Map<String, Object> encodedMessageToCheck =
144+
objectMapper.readValue(encodedMessage, new TypeReference<Map<String, Object>>() {
145+
146+
});
147+
148+
Object headers = encodedMessageToCheck.get("headers");
149+
assertThat(headers).isNotNull();
150+
assertThat(headers).isInstanceOf(Map.class);
151+
152+
@SuppressWarnings("unchecked")
153+
Map<String, Object> headersToCheck = (Map<String, Object>) headers;
154+
assertThat(headersToCheck).doesNotContainKey(MessageHeaders.ID);
155+
assertThat(headersToCheck).containsKey(MessageHeaders.TIMESTAMP);
156+
assertThat(headersToCheck).containsKey("bar");
157+
158+
Message<?> decoded = mapper.toMessage(mapper.fromMessage(message));
159+
assertThat(decoded.getHeaders().getTimestamp()).isEqualTo(message.getHeaders().getTimestamp());
160+
assertThat(decoded.getHeaders().getId()).isNotEqualTo(message.getHeaders().getId());
161+
assertThat(decoded.getHeaders().get("bar")).isEqualTo("baz");
115162
}
116163

117164
}

0 commit comments

Comments
 (0)