Skip to content

Commit 3eedda6

Browse files
artembilangaryrussell
authored andcommitted
GH- 3223: Build ResolvableType after mapping
Fixes #3223 The `json__TypeId__` header may have a value which cannot be resolved to the `Class` in the current classpath So, skip `ResolvableType` building logic until we really sure that end-user wants to map JSON headers * WARN a build exception that we can't load a class for the `json__TypeId__` when we try to build a `ResolvableType` in the `DefaultAmqpHeaderMapper` * Document the negation feature for JSON headers **Cherry-pick to 5.2.x**
1 parent eea29e7 commit 3eedda6

File tree

4 files changed

+82
-76
lines changed

4 files changed

+82
-76
lines changed

spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -151,26 +151,13 @@ protected Map<String, Object> extractStandardHeaders(MessageProperties amqpMessa
151151
headers.put(jsonHeader, value);
152152
}
153153
}
154-
155-
createJsonResolvableTypHeaderInAny(headers);
156154
}
157155
catch (Exception e) {
158-
if (logger.isWarnEnabled()) {
159-
logger.warn("error occurred while mapping from AMQP properties to MessageHeaders", e);
160-
}
156+
this.logger.warn("error occurred while mapping from AMQP properties to MessageHeaders", e);
161157
}
162158
return headers;
163159
}
164160

165-
private void createJsonResolvableTypHeaderInAny(Map<String, Object> headers) {
166-
Object typeIdHeader = headers.get(JsonHeaders.TYPE_ID);
167-
if (typeIdHeader != null) {
168-
headers.put(JsonHeaders.RESOLVABLE_TYPE,
169-
JsonHeaders.buildResolvableType(getClassLoader(), typeIdHeader,
170-
headers.get(JsonHeaders.CONTENT_TYPE_ID), headers.get(JsonHeaders.KEY_TYPE_ID)));
171-
}
172-
}
173-
174161
/**
175162
* Extract user-defined headers from an AMQP MessageProperties instance.
176163
*/

spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -26,12 +26,13 @@
2626
import java.util.Map;
2727
import java.util.Set;
2828

29-
import org.junit.Test;
29+
import org.junit.jupiter.api.Test;
3030

3131
import org.springframework.amqp.core.Message;
3232
import org.springframework.amqp.core.MessageDeliveryMode;
3333
import org.springframework.amqp.core.MessageProperties;
3434
import org.springframework.amqp.support.AmqpHeaders;
35+
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
3536
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
3637
import org.springframework.amqp.support.converter.MessageConverter;
3738
import org.springframework.core.ResolvableType;
@@ -68,7 +69,7 @@ public void fromHeadersFallbackIdTimestamp() {
6869
@Test
6970
public void fromHeaders() {
7071
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
71-
Map<String, Object> headerMap = new HashMap<String, Object>();
72+
Map<String, Object> headerMap = new HashMap<>();
7273
headerMap.put(AmqpHeaders.APP_ID, "test.appId");
7374
headerMap.put(AmqpHeaders.CLUSTER_ID, "test.clusterId");
7475
headerMap.put(AmqpHeaders.CONTENT_ENCODING, "test.contentEncoding");
@@ -130,7 +131,7 @@ public void fromHeaders() {
130131
@Test
131132
public void fromHeadersWithContentTypeAsMediaType() {
132133
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
133-
Map<String, Object> headerMap = new HashMap<String, Object>();
134+
Map<String, Object> headerMap = new HashMap<>();
134135

135136
MediaType contentType = MediaType.parseMediaType("text/html");
136137
headerMap.put(AmqpHeaders.CONTENT_TYPE, contentType);
@@ -152,7 +153,7 @@ public void fromHeadersWithContentTypeAsMediaType() {
152153
@Test
153154
public void fromHeadersWithContentTypeAsMimeType() {
154155
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
155-
Map<String, Object> headerMap = new HashMap<String, Object>();
156+
Map<String, Object> headerMap = new HashMap<>();
156157

157158
MimeType contentType = MimeType.valueOf("text/html");
158159
headerMap.put(AmqpHeaders.CONTENT_TYPE, contentType);
@@ -217,7 +218,7 @@ public void toHeaders() {
217218
}
218219

219220
@Test
220-
public void toHeadersNonContenType() {
221+
public void toHeadersNonContentType() {
221222
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
222223
MessageProperties amqpProperties = new MessageProperties();
223224
amqpProperties.setAppId("test.appId");
@@ -244,7 +245,7 @@ public void testToHeadersConsumerMetadata() {
244245
@Test
245246
public void messageIdNotMappedToAmqpProperties() {
246247
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
247-
Map<String, Object> headerMap = new HashMap<String, Object>();
248+
Map<String, Object> headerMap = new HashMap<>();
248249
headerMap.put(MessageHeaders.ID, "msg-id");
249250
MessageHeaders integrationHeaders = new MessageHeaders(headerMap);
250251
MessageProperties amqpProperties = new MessageProperties();
@@ -255,21 +256,21 @@ public void messageIdNotMappedToAmqpProperties() {
255256
@Test
256257
public void messageTimestampNotMappedToAmqpProperties() {
257258
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
258-
Map<String, Object> headerMap = new HashMap<String, Object>();
259+
Map<String, Object> headerMap = new HashMap<>();
259260
headerMap.put(MessageHeaders.TIMESTAMP, 1234L);
260261
MessageHeaders integrationHeaders = new MessageHeaders(headerMap);
261262
MessageProperties amqpProperties = new MessageProperties();
262263
headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties);
263264
assertThat(amqpProperties.getHeaders().get(MessageHeaders.TIMESTAMP)).isNull();
264265
}
265266

266-
@Test // INT-2090
267+
@Test
267268
public void jsonTypeIdNotOverwritten() {
268269
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
269270
MessageConverter converter = new Jackson2JsonMessageConverter();
270271
MessageProperties amqpProperties = new MessageProperties();
271272
converter.toMessage("123", amqpProperties);
272-
Map<String, Object> headerMap = new HashMap<String, Object>();
273+
Map<String, Object> headerMap = new HashMap<>();
273274
headerMap.put("__TypeId__", "java.lang.Integer");
274275
MessageHeaders integrationHeaders = new MessageHeaders(headerMap);
275276
headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties);
@@ -325,4 +326,16 @@ public void jsonHeadersResolvableTypeSkipped() {
325326
assertThat(amqpProperties.getHeaders()).doesNotContainKeys(JsonHeaders.RESOLVABLE_TYPE);
326327
}
327328

329+
@Test
330+
public void jsonHeadersNotMapped() {
331+
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.inboundMapper();
332+
headerMapper.setRequestHeaderNames("!json_*", "*");
333+
MessageProperties amqpProperties = new MessageProperties();
334+
amqpProperties.getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, "test.type");
335+
Map<String, Object> headers = headerMapper.toHeadersFromRequest(amqpProperties);
336+
assertThat(headers)
337+
.doesNotContainKeys(JsonHeaders.RESOLVABLE_TYPE, JsonHeaders.TYPE_ID)
338+
.containsKey(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME);
339+
}
340+
328341
}

spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.apache.commons.logging.LogFactory;
3030

3131
import org.springframework.beans.factory.BeanClassLoaderAware;
32+
import org.springframework.core.ResolvableType;
33+
import org.springframework.integration.mapping.support.JsonHeaders;
3234
import org.springframework.lang.Nullable;
3335
import org.springframework.messaging.MessageChannel;
3436
import org.springframework.messaging.MessageHeaders;
@@ -67,8 +69,8 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
6769
*/
6870
public static final String NON_STANDARD_HEADER_NAME_PATTERN = "NON_STANDARD_HEADERS";
6971

70-
private static final Collection<String> TRANSIENT_HEADER_NAMES = Arrays.asList(
71-
MessageHeaders.ID, MessageHeaders.TIMESTAMP);
72+
private static final Collection<String> TRANSIENT_HEADER_NAMES =
73+
Arrays.asList(MessageHeaders.ID, MessageHeaders.TIMESTAMP);
7274

7375
protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR final
7476

@@ -155,7 +157,7 @@ protected HeaderMatcher createDefaultHeaderMatcher(String standardHeaderPrefix,
155157
* @return a header mapper that match if any of the specified patters match
156158
*/
157159
protected HeaderMatcher createHeaderMatcher(Collection<String> patterns) {
158-
List<HeaderMatcher> matchers = new ArrayList<HeaderMatcher>();
160+
List<HeaderMatcher> matchers = new ArrayList<>();
159161
for (String pattern : patterns) {
160162
if (STANDARD_REQUEST_HEADER_NAME_PATTERN.equals(pattern)) {
161163
matchers.add(new ContentBasedHeaderMatcher(true, this.requestHeaderNames));
@@ -190,40 +192,38 @@ else if (pattern.startsWith("\\!")) {
190192

191193
@Override
192194
public void fromHeadersToRequest(MessageHeaders headers, T target) {
193-
this.fromHeaders(headers, target, this.requestHeaderMatcher);
195+
fromHeaders(headers, target, this.requestHeaderMatcher);
194196
}
195197

196198
@Override
197199
public void fromHeadersToReply(MessageHeaders headers, T target) {
198-
this.fromHeaders(headers, target, this.replyHeaderMatcher);
200+
fromHeaders(headers, target, this.replyHeaderMatcher);
199201
}
200202

201203
@Override
202204
public Map<String, Object> toHeadersFromRequest(T source) {
203-
return this.toHeaders(source, this.requestHeaderMatcher);
205+
return toHeaders(source, this.requestHeaderMatcher);
204206
}
205207

206208
@Override
207209
public Map<String, Object> toHeadersFromReply(T source) {
208-
return this.toHeaders(source, this.replyHeaderMatcher);
210+
return toHeaders(source, this.replyHeaderMatcher);
209211
}
210212

211213
private void fromHeaders(MessageHeaders headers, T target, HeaderMatcher headerMatcher) {
212214
try {
213-
Map<String, Object> subset = new HashMap<String, Object>();
215+
Map<String, Object> subset = new HashMap<>();
214216
for (Map.Entry<String, Object> entry : headers.entrySet()) {
215217
String headerName = entry.getKey();
216-
if (this.shouldMapHeader(headerName, headerMatcher)) {
218+
if (shouldMapHeader(headerName, headerMatcher)) {
217219
subset.put(headerName, entry.getValue());
218220
}
219221
}
220-
this.populateStandardHeaders(headers, subset, target);
221-
this.populateUserDefinedHeaders(subset, target);
222+
populateStandardHeaders(headers, subset, target);
223+
populateUserDefinedHeaders(subset, target);
222224
}
223225
catch (Exception e) {
224-
if (this.logger.isWarnEnabled()) {
225-
this.logger.warn("error occurred while mapping from MessageHeaders", e);
226-
}
226+
this.logger.warn("error occurred while mapping from MessageHeaders", e);
227227
}
228228
}
229229

@@ -234,8 +234,8 @@ private void populateUserDefinedHeaders(Map<String, Object> headers, T target) {
234234
if (value != null && !isMessageChannel(headerName, value)) {
235235
try {
236236
if (!headerName.startsWith(this.standardHeaderPrefix)) {
237-
String key = this.createTargetPropertyName(headerName, true);
238-
this.populateUserDefinedHeader(key, value, target);
237+
String key = createTargetPropertyName(headerName, true);
238+
populateUserDefinedHeader(key, value, target);
239239
}
240240
}
241241
catch (Exception e) {
@@ -262,21 +262,30 @@ private boolean isMessageChannel(String headerName, Object headerValue) {
262262
* a {@link org.springframework.messaging.Message}.
263263
*/
264264
private Map<String, Object> toHeaders(T source, HeaderMatcher headerMatcher) {
265-
Map<String, Object> headers = new HashMap<String, Object>();
265+
Map<String, Object> headers = new HashMap<>();
266266
Map<String, Object> standardHeaders = extractStandardHeaders(source);
267-
this.copyHeaders(standardHeaders, headers, headerMatcher);
267+
copyHeaders(standardHeaders, headers, headerMatcher);
268268
Map<String, Object> userDefinedHeaders = extractUserDefinedHeaders(source);
269-
this.copyHeaders(userDefinedHeaders, headers, headerMatcher);
269+
copyHeaders(userDefinedHeaders, headers, headerMatcher);
270270
return headers;
271271
}
272272

273-
private <V> void copyHeaders(Map<String, Object> source, Map<String, Object> target, HeaderMatcher headerMatcher) {
273+
private void copyHeaders(Map<String, Object> source, Map<String, Object> target, HeaderMatcher headerMatcher) {
274274
if (!CollectionUtils.isEmpty(source)) {
275275
for (Map.Entry<String, Object> entry : source.entrySet()) {
276276
try {
277-
String headerName = this.createTargetPropertyName(entry.getKey(), false);
278-
if (this.shouldMapHeader(headerName, headerMatcher)) {
279-
target.put(headerName, entry.getValue());
277+
String headerName = createTargetPropertyName(entry.getKey(), false);
278+
if (shouldMapHeader(headerName, headerMatcher)) {
279+
Object value = entry.getValue();
280+
target.put(headerName, value);
281+
if (JsonHeaders.TYPE_ID.equals(headerName) && value != null) {
282+
ResolvableType resolvableType =
283+
createJsonResolvableTypHeaderInAny(value, source.get(JsonHeaders.CONTENT_TYPE_ID),
284+
source.get(JsonHeaders.KEY_TYPE_ID));
285+
if (resolvableType != null) {
286+
target.put(JsonHeaders.RESOLVABLE_TYPE, resolvableType);
287+
}
288+
}
280289
}
281290
}
282291
catch (Exception e) {
@@ -289,6 +298,20 @@ private <V> void copyHeaders(Map<String, Object> source, Map<String, Object> tar
289298
}
290299
}
291300

301+
@Nullable
302+
private ResolvableType createJsonResolvableTypHeaderInAny(Object typeId, @Nullable Object contentId,
303+
@Nullable Object keyId) {
304+
305+
try {
306+
return JsonHeaders.buildResolvableType(getClassLoader(), typeId,
307+
contentId, keyId);
308+
}
309+
catch (Exception e) {
310+
this.logger.warn("Cannot build a ResolvableType from 'json__TypeId__' header", e);
311+
}
312+
return null;
313+
}
314+
292315
private boolean shouldMapHeader(String headerName, HeaderMatcher headerMatcher) {
293316
return !(!StringUtils.hasText(headerName) || getTransientHeaderNames().contains(headerName))
294317
&& headerMatcher.matchHeader(headerName);
@@ -303,8 +326,8 @@ protected <V> V getHeaderIfAvailable(Map<String, Object> headers, String name, C
303326
}
304327
if (!type.isAssignableFrom(value.getClass())) {
305328
if (this.logger.isWarnEnabled()) {
306-
this.logger.warn("skipping header '" + name + "' since it is not of expected type [" + type + "], it is [" +
307-
value.getClass() + "]");
329+
this.logger.warn("skipping header '" + name + "' since it is not of expected type [" + type + "], " +
330+
"it is [" + value.getClass() + "]");
308331
}
309332
return null;
310333
}
@@ -380,6 +403,7 @@ protected void populateStandardHeaders(@Nullable Map<String, Object> allHeaders,
380403
* Strategy interface to determine if a given header name matches.
381404
* @since 4.1
382405
*/
406+
@FunctionalInterface
383407
public interface HeaderMatcher {
384408

385409
/**
@@ -393,7 +417,9 @@ public interface HeaderMatcher {
393417
* Return true if this match should be explicitly excluded from the mapping.
394418
* @return true if negated.
395419
*/
396-
boolean isNegated();
420+
default boolean isNegated() {
421+
return false;
422+
}
397423

398424
}
399425

@@ -440,11 +466,6 @@ private boolean containsIgnoreCase(String name) {
440466
return false;
441467
}
442468

443-
@Override
444-
public boolean isNegated() {
445-
return false;
446-
}
447-
448469
}
449470

450471
/**
@@ -457,7 +478,7 @@ protected static class PatternBasedHeaderMatcher implements HeaderMatcher {
457478

458479
private static final Log logger = LogFactory.getLog(HeaderMatcher.class);
459480

460-
private final Collection<String> patterns = new ArrayList<String>();
481+
private final Collection<String> patterns = new ArrayList<>();
461482

462483
public PatternBasedHeaderMatcher(Collection<String> patterns) {
463484
Assert.notNull(patterns, "Patterns must no be null");
@@ -482,11 +503,6 @@ public boolean matchHeader(String headerName) {
482503
return false;
483504
}
484505

485-
@Override
486-
public boolean isNegated() {
487-
return false;
488-
}
489-
490506
}
491507

492508
/**
@@ -566,11 +582,6 @@ public boolean matchHeader(String headerName) {
566582
return result;
567583
}
568584

569-
@Override
570-
public boolean isNegated() {
571-
return false;
572-
}
573-
574585
}
575586

576587
/**
@@ -608,11 +619,6 @@ public boolean matchHeader(String headerName) {
608619
return false;
609620
}
610621

611-
@Override
612-
public boolean isNegated() {
613-
return false;
614-
}
615-
616622
}
617623

618624
}

0 commit comments

Comments
 (0)