diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapper.java index b42a3cb443..2325d5f001 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 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. @@ -19,9 +19,7 @@ package org.springframework.integration.support.json; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -31,10 +29,11 @@ import org.apache.commons.logging.LogFactory; import org.springframework.integration.mapping.BytesMessageMapper; import org.springframework.integration.support.MutableMessage; import org.springframework.integration.support.MutableMessageHeaders; +import org.springframework.integration.support.utils.PatternMatchUtils; import org.springframework.lang.Nullable; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.PatternMatchUtils; import com.fasterxml.jackson.databind.ObjectMapper; @@ -82,7 +81,7 @@ public class EmbeddedJsonHeadersMessageMapper implements BytesMessageMapper { private final ObjectMapper objectMapper; - private final List headerPatterns; + private final String[] headerPatterns; private final boolean allHeaders; @@ -102,7 +101,7 @@ public class EmbeddedJsonHeadersMessageMapper implements BytesMessageMapper { * Construct an instance that embeds headers matching the supplied patterns, using * the default JSON object mapper. * @param headerPatterns the patterns. - * @see PatternMatchUtils#simpleMatch(String, String) + * @see PatternMatchUtils#smartMatch(String, String...) */ public EmbeddedJsonHeadersMessageMapper(String... headerPatterns) { this(JacksonJsonUtils.messagingAwareMapper(), headerPatterns); @@ -125,8 +124,8 @@ public class EmbeddedJsonHeadersMessageMapper implements BytesMessageMapper { */ public EmbeddedJsonHeadersMessageMapper(ObjectMapper objectMapper, String... headerPatterns) { this.objectMapper = objectMapper; - this.headerPatterns = Arrays.asList(headerPatterns); - this.allHeaders = this.headerPatterns.size() == 1 && this.headerPatterns.get(0).equals("*"); + this.headerPatterns = Arrays.copyOf(headerPatterns, headerPatterns.length); + this.allHeaders = this.headerPatterns.length == 1 && this.headerPatterns[0].equals("*"); } /** @@ -151,35 +150,54 @@ public class EmbeddedJsonHeadersMessageMapper implements BytesMessageMapper { } public Collection getHeaderPatterns() { - return Collections.unmodifiableCollection(this.headerPatterns); + return Arrays.asList(this.headerPatterns); } @SuppressWarnings("unchecked") @Override public byte[] fromMessage(Message message) throws Exception { - Message messageToEncode = this.allHeaders ? message : pruneHeaders(message); + Map headersToEncode = + this.allHeaders + ? message.getHeaders() + : pruneHeaders(message.getHeaders()); + if (this.rawBytes && message.getPayload() instanceof byte[]) { - return fromBytesPayload((Message) messageToEncode); + return fromBytesPayload((byte[]) message.getPayload(), headersToEncode); } else { + Message messageToEncode = message; + + if (!this.allHeaders) { + if (!headersToEncode.containsKey(MessageHeaders.ID)) { + headersToEncode.put(MessageHeaders.ID, MessageHeaders.ID_VALUE_NONE); + } + if (!headersToEncode.containsKey(MessageHeaders.TIMESTAMP)) { + headersToEncode.put(MessageHeaders.TIMESTAMP, -1L); + } + + messageToEncode = new MutableMessage<>(message.getPayload(), headersToEncode); + } + return this.objectMapper.writeValueAsBytes(messageToEncode); } } - private Message pruneHeaders(Message message) { - Map headersToEmbed = - message.getHeaders().entrySet().stream() - .filter(e -> this.headerPatterns.stream().anyMatch(p -> - this.caseSensitive - ? PatternMatchUtils.simpleMatch(p, e.getKey()) - : PatternMatchUtils.simpleMatch(p.toLowerCase(), e.getKey().toLowerCase()))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - return new MutableMessage<>(message.getPayload(), headersToEmbed); + private Map pruneHeaders(MessageHeaders messageHeaders) { + return messageHeaders + .entrySet() + .stream() + .filter(e -> matchHeader(e.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } - private byte[] fromBytesPayload(Message message) throws Exception { - byte[] headers = this.objectMapper.writeValueAsBytes(message.getHeaders()); - byte[] payload = message.getPayload(); + private boolean matchHeader(String header) { + return Boolean.TRUE.equals(this.caseSensitive + ? PatternMatchUtils.smartMatch(header, this.headerPatterns) + : PatternMatchUtils.smartMatchIgnoreCase(header, this.headerPatterns)); + } + + private byte[] fromBytesPayload(byte[] payload, Map headersToEncode) throws Exception { + byte[] headers = this.objectMapper.writeValueAsBytes(headersToEncode); ByteBuffer buffer = ByteBuffer.wrap(new byte[8 + headers.length + payload.length]); buffer.putInt(headers.length); buffer.put(headers); @@ -189,7 +207,7 @@ public class EmbeddedJsonHeadersMessageMapper implements BytesMessageMapper { } @Override - public Message toMessage(byte[] bytes, @Nullable Map headers) throws Exception { + public Message toMessage(byte[] bytes, @Nullable Map headers) { Message message = null; try { message = decodeNativeFormat(bytes, headers); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/utils/PatternMatchUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/support/utils/PatternMatchUtils.java index 1fa9946202..85a8392237 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/utils/PatternMatchUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/utils/PatternMatchUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 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. @@ -16,12 +16,15 @@ package org.springframework.integration.support.utils; +import java.util.Arrays; + /** * Utility methods for pattern matching. * This utilities provide support of negative pattern matching as well * unlike {@link org.springframework.util.PatternMatchUtils}. * * @author Meherzad Lahewala + * @author Artem Bilan * * @since 5.0 * @@ -31,6 +34,28 @@ public final class PatternMatchUtils { private PatternMatchUtils() { } + /** + * Pattern match against the supplied patterns ignoring case; also supports negated ('!') + * patterns. First match wins (positive or negative). + * To match the names starting with {@code !} symbol, + * you have to escape it prepending with the {@code \} symbol in the pattern definition. + * @param str the string to match. + * @param patterns the patterns. + * @return true for positive match; false for negative; null if no pattern matches. + * @see org.springframework.util.PatternMatchUtils#simpleMatch(String[], String) + * @since 5.0.5 + */ + public static Boolean smartMatchIgnoreCase(String str, String... patterns) { + if (patterns != null) { + return smartMatch(str.toLowerCase(), + Arrays.stream(patterns) + .map(String::toLowerCase) + .toArray(String[]::new)); + } + + return null; //NOSONAR - intentional null return + } + /** * Pattern match against the supplied patterns; also supports negated ('!') * patterns. First match wins (positive or negative). @@ -58,6 +83,7 @@ public final class PatternMatchUtils { } } } + return null; //NOSONAR - intentional null return } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapperTests.java b/spring-integration-core/src/test/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapperTests.java index 37ffb015b5..bc96259359 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapperTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/support/json/EmbeddedJsonHeadersMessageMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 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. @@ -16,21 +16,25 @@ package org.springframework.integration.support.json; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import java.nio.ByteBuffer; import java.util.Collections; +import java.util.Map; import org.junit.Test; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.GenericMessage; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * @author Gary Russell + * @author Artem Bilan + * * @since 5.0 * */ @@ -40,17 +44,31 @@ public class EmbeddedJsonHeadersMessageMapperTests { public void testEmbedAll() throws Exception { EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper(); GenericMessage message = new GenericMessage<>("foo"); - assertThat(mapper.toMessage(mapper.fromMessage(message)), equalTo(message)); - + assertThat(mapper.toMessage(mapper.fromMessage(message))).isEqualTo(message); } @Test public void testEmbedSome() throws Exception { - EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper("id"); + EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper(MessageHeaders.ID); GenericMessage message = new GenericMessage<>("foo"); - Message decoded = mapper.toMessage(mapper.fromMessage(message)); - assertThat(decoded.getPayload(), equalTo(message.getPayload())); - assertThat(decoded.getHeaders().getId(), equalTo(message.getHeaders().getId())); + byte[] encodedMessage = mapper.fromMessage(message); + Message decoded = mapper.toMessage(encodedMessage); + assertThat(decoded.getPayload()).isEqualTo(message.getPayload()); + assertThat(decoded.getHeaders().getTimestamp()).isNotEqualTo(message.getHeaders().getTimestamp()); + + ObjectMapper objectMapper = new ObjectMapper(); + Map encodedMessageToCheck = + objectMapper.readValue(encodedMessage, new TypeReference>() { + + }); + + Object headers = encodedMessageToCheck.get("headers"); + assertThat(headers).isNotNull(); + assertThat(headers).isInstanceOf(Map.class); + + @SuppressWarnings("unchecked") + Map headersToCheck = (Map) headers; + assertThat(headersToCheck).doesNotContainKey(MessageHeaders.TIMESTAMP); } @Test @@ -64,13 +82,13 @@ public class EmbeddedJsonHeadersMessageMapperTests { byte[] headerBytes = new byte[headerLen]; bb.get(headerBytes); String headers = new String(headerBytes); - assertThat(headers, containsString(message.getHeaders().getId().toString())); - assertThat(headers, containsString(String.valueOf(message.getHeaders().getTimestamp()))); - assertThat(bb.getInt(), equalTo(3)); - assertThat(bb.remaining(), equalTo(3)); - assertThat((char) bb.get(), equalTo('f')); - assertThat((char) bb.get(), equalTo('o')); - assertThat((char) bb.get(), equalTo('o')); + assertThat(headers).contains(message.getHeaders().getId().toString()); + assertThat(headers).contains(String.valueOf(message.getHeaders().getTimestamp())); + assertThat(bb.getInt()).isEqualTo(3); + assertThat(bb.remaining()).isEqualTo(3); + assertThat((char) bb.get()).isEqualTo('f'); + assertThat((char) bb.get()).isEqualTo('o'); + assertThat((char) bb.get()).isEqualTo('o'); } @Test @@ -83,13 +101,14 @@ public class EmbeddedJsonHeadersMessageMapperTests { byte[] headerBytes = new byte[headerLen]; bb.get(headerBytes); String headers = new String(headerBytes); - assertThat(headers, containsString(message.getHeaders().getId().toString())); - assertThat(headers, not(containsString("bar"))); - assertThat(bb.getInt(), equalTo(3)); - assertThat(bb.remaining(), equalTo(3)); - assertThat((char) bb.get(), equalTo('f')); - assertThat((char) bb.get(), equalTo('o')); - assertThat((char) bb.get(), equalTo('o')); + assertThat(headers).contains(message.getHeaders().getId().toString()); + assertThat(headers).doesNotContain(MessageHeaders.TIMESTAMP); + assertThat(headers).doesNotContain("bar"); + assertThat(bb.getInt()).isEqualTo(3); + assertThat(bb.remaining()).isEqualTo(3); + assertThat((char) bb.get()).isEqualTo('f'); + assertThat((char) bb.get()).isEqualTo('o'); + assertThat((char) bb.get()).isEqualTo('o'); } @Test @@ -99,10 +118,10 @@ public class EmbeddedJsonHeadersMessageMapperTests { GenericMessage message = new GenericMessage<>("foo".getBytes()); byte[] mappedBytes = mapper.fromMessage(message); String mapped = new String(mappedBytes); - assertThat(mapped, containsString("[B\",\"Zm9v")); + assertThat(mapped).contains("[B\",\"Zm9v"); @SuppressWarnings("unchecked") Message decoded = (Message) mapper.toMessage(mappedBytes); - assertThat(new String(decoded.getPayload()), equalTo("foo")); + assertThat(new String(decoded.getPayload())).isEqualTo("foo"); } @@ -111,7 +130,35 @@ public class EmbeddedJsonHeadersMessageMapperTests { EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper(); GenericMessage message = new GenericMessage<>("foo".getBytes()); Message decoded = mapper.toMessage(mapper.fromMessage(message)); - assertThat(decoded, equalTo(message)); + assertThat(decoded).isEqualTo(message); + } + + @Test + public void testDontMapIdButOthers() throws Exception { + EmbeddedJsonHeadersMessageMapper mapper = new EmbeddedJsonHeadersMessageMapper("!" + MessageHeaders.ID, "*"); + GenericMessage message = new GenericMessage<>("foo", Collections.singletonMap("bar", "baz")); + byte[] encodedMessage = mapper.fromMessage(message); + + ObjectMapper objectMapper = new ObjectMapper(); + Map encodedMessageToCheck = + objectMapper.readValue(encodedMessage, new TypeReference>() { + + }); + + Object headers = encodedMessageToCheck.get("headers"); + assertThat(headers).isNotNull(); + assertThat(headers).isInstanceOf(Map.class); + + @SuppressWarnings("unchecked") + Map headersToCheck = (Map) headers; + assertThat(headersToCheck).doesNotContainKey(MessageHeaders.ID); + assertThat(headersToCheck).containsKey(MessageHeaders.TIMESTAMP); + assertThat(headersToCheck).containsKey("bar"); + + Message decoded = mapper.toMessage(mapper.fromMessage(message)); + assertThat(decoded.getHeaders().getTimestamp()).isEqualTo(message.getHeaders().getTimestamp()); + assertThat(decoded.getHeaders().getId()).isNotEqualTo(message.getHeaders().getId()); + assertThat(decoded.getHeaders().get("bar")).isEqualTo("baz"); } }