From 8c24bf1dd996a43b8a9b4413942a8a91d16a93d9 Mon Sep 17 00:00:00 2001 From: Stuart Williams Date: Fri, 13 Feb 2015 10:59:48 +0200 Subject: [PATCH] INT-3513: MutableMessage: Removes the use of DFA JIRA: https://jira.spring.io/browse/INT-3513 Removing the `rawHeader` access by the `DFA` improves performance. Tests are added to verify that `MutableMessageBuilderFactory` does what it purports to, and that the `MutableMessageHeaders` is now applied, permitting updates to individual headers. change `@since` to correct/probable release version INT-3513: Polishing Change @since to 4.1.5 --- .../integration/support/MutableMessage.java | 34 ++--- .../support/MutableMessageBuilder.java | 21 ++- .../support/MutableMessageHeaders.java | 64 +++++++++ .../MutableMessageBuilderFactoryTests.java | 134 ++++++++++++++++++ .../support/MutableMessageTests.java | 75 ++++++++++ 5 files changed, 295 insertions(+), 33 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageHeaders.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageBuilderFactoryTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessage.java b/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessage.java index e6af32ab84..69121cec98 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessage.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.support; import java.io.Serializable; import java.util.Map; -import org.springframework.beans.DirectFieldAccessor; import org.springframework.integration.store.SimpleMessageStore; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; @@ -36,6 +36,7 @@ import org.springframework.util.ObjectUtils; * * @author Gary Russell * @author Artem Bilan + * @author Stuart Williams * @since 4.0 * */ @@ -43,33 +44,29 @@ class MutableMessage implements Message, Serializable { private static final long serialVersionUID = -636635024258737500L; - private T payload; + private final T payload; - private final MessageHeaders headers; - - private final Map rawHeaders; + private final MutableMessageHeaders headers; MutableMessage(T payload) { this(payload, null); } - @SuppressWarnings("unchecked") MutableMessage(T payload, Map headers) { Assert.notNull(payload, "payload must not be null"); - this.headers = new MessageHeaders(headers); this.payload = payload; - // Needs SPR-11468 to avoid DFA and header manipulation - rawHeaders = (Map) new DirectFieldAccessor(this.headers) - .getPropertyValue("headers"); + + this.headers = new MutableMessageHeaders(headers); + if (headers != null) { - this.rawHeaders.put(MessageHeaders.ID, headers.get(MessageHeaders.ID)); - this.rawHeaders.put(MessageHeaders.TIMESTAMP, headers.get(MessageHeaders.TIMESTAMP)); + this.headers.put(MessageHeaders.ID, headers.get(MessageHeaders.ID)); + this.headers.put(MessageHeaders.TIMESTAMP, headers.get(MessageHeaders.TIMESTAMP)); } } @Override - public MessageHeaders getHeaders() { + public MutableMessageHeaders getHeaders() { return this.headers; } @@ -78,13 +75,8 @@ class MutableMessage implements Message, Serializable { return this.payload; } - public void setPayload(T payload) { - Assert.notNull(payload, "'payload' must not be null"); - this.payload = payload; - } - - public Map getRawHeaders() { - return this.rawHeaders; + Map getRawHeaders() { + return this.headers.getRawHeaders(); } @Override diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageBuilder.java b/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageBuilder.java index 66c2a57ae5..8d87e13530 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageBuilder.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.support; import java.util.ArrayList; @@ -34,7 +35,7 @@ import org.springframework.util.StringUtils; */ public class MutableMessageBuilder extends AbstractIntegrationMessageBuilder { - private MutableMessage mutableMessage; + private final MutableMessage mutableMessage; private final Map headers; @@ -55,27 +56,23 @@ public class MutableMessageBuilder extends AbstractIntegrationMessageBuilder< /** * Create a builder for a new {@link Message} instance pre-populated with all of the headers copied from the * provided message. The payload of the provided Message will also be used as the payload for the new message. - * * @param message the Message from which the payload and all headers will be copied * @param The type of the payload. * @return A MutableMessageBuilder. */ public static MutableMessageBuilder fromMessage(Message message) { Assert.notNull(message, "message must not be null"); - MutableMessageBuilder builder = new MutableMessageBuilder(message); - return builder; + return new MutableMessageBuilder(message); } /** * Create a builder for a new {@link Message} instance with the provided payload. - * * @param payload the payload for the new message * @param The type of the payload. * @return A MessageBuilder. */ public static MutableMessageBuilder withPayload(T payload) { - MutableMessageBuilder builder = new MutableMessageBuilder(new MutableMessage(payload)); - return builder; + return new MutableMessageBuilder(new MutableMessage(payload)); } @Override @@ -102,8 +99,8 @@ public class MutableMessageBuilder extends AbstractIntegrationMessageBuilder< public AbstractIntegrationMessageBuilder removeHeaders(String... headerPatterns) { List headersToRemove = new ArrayList(); for (String pattern : headerPatterns) { - if (StringUtils.hasLength(pattern)){ - if (pattern.contains("*")){ + if (StringUtils.hasLength(pattern)) { + if (pattern.contains("*")) { headersToRemove.addAll(getMatchingHeaderNames(pattern, this.headers)); } else { @@ -120,8 +117,8 @@ public class MutableMessageBuilder extends AbstractIntegrationMessageBuilder< private List getMatchingHeaderNames(String pattern, Map headers) { List matchingHeaderNames = new ArrayList(); if (headers != null) { - for (Map.Entry header: headers.entrySet()) { - if (PatternMatchUtils.simpleMatch(pattern, header.getKey())) { + for (Map.Entry header : headers.entrySet()) { + if (PatternMatchUtils.simpleMatch(pattern, header.getKey())) { matchingHeaderNames.add(header.getKey()); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageHeaders.java b/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageHeaders.java new file mode 100644 index 0000000000..3699415936 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/MutableMessageHeaders.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015 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 + * + * http://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.integration.support; + +import java.util.Map; + +import org.springframework.messaging.MessageHeaders; + + +/** + * A MessageHeaders that permits direct access to and modification of the + * header map. + * + * @author Stuart Williams + * @since 4.1.5 + */ +class MutableMessageHeaders extends MessageHeaders { + + private static final long serialVersionUID = 3084692953798643018L; + + MutableMessageHeaders(Map headers) { + super(headers); + } + + @Override + protected Map getRawHeaders() { + return super.getRawHeaders(); + } + + @Override + public void putAll(Map map) { + super.getRawHeaders().putAll(map); + } + + @Override + public Object put(String key, Object value) { + return super.getRawHeaders().put(key, value); + } + + @Override + public void clear() { + super.getRawHeaders().clear(); + } + + @Override + public Object remove(Object key) { + return super.getRawHeaders().remove(key); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageBuilderFactoryTests.java b/spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageBuilderFactoryTests.java new file mode 100644 index 0000000000..4a32f1cbdf --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageBuilderFactoryTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015 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 + * + * http://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.integration.support; + +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.Filter; +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + + +/** + * @author Stuart Williams + * @since 4.1.5 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class MutableMessageBuilderFactoryTests { + + @Autowired + ContextConfiguration.TestGateway gateway; + + @Autowired + CountDownLatch latch; + + @Test + public void test() throws InterruptedException { + + this.gateway.input("hello!"); + + boolean result = this.latch.await(2L, TimeUnit.SECONDS); + + assertTrue("A failure means that that MMBF wasn't used", result); + } + + @Configuration + @EnableIntegration + @IntegrationComponentScan + static class ContextConfiguration { + + @Bean + public MutableMessageBuilderFactory messageBuilderFactory() { + return new MutableMessageBuilderFactory(); + } + + @Bean + public DirectChannel input() { + return new DirectChannel(); + } + + @Bean + public DirectChannel output() { + return new DirectChannel(); + } + + @Bean + public CountDownLatch latch() { + return new CountDownLatch(1); + } + + @MessagingGateway + static interface TestGateway { + + @Gateway(requestChannel = "input") + void input(String payload); + + } + + @MessageEndpoint + static class TestFilter { + + @Filter(inputChannel = "input", outputChannel = "output") + public boolean filter(MessageHeaders headers) { + // headers are immutable, so if this passes without exception, + // the MutableMessageBuilderFactory *was* used... + try { + headers.put("foo", "bar"); + return true; + } + catch (UnsupportedOperationException e) { + return false; + } + } + + } + + @MessageEndpoint + static class Counter { + + @Autowired + CountDownLatch latch; + + @ServiceActivator(inputChannel = "output") + public void count(@Payload String message) { + latch.countDown(); + } + + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageTests.java b/spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageTests.java new file mode 100644 index 0000000000..64e659dc6f --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/support/MutableMessageTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015 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 + * + * http://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.integration.support; + +import static org.hamcrest.Matchers.hasEntry; +import static org.junit.Assert.assertThat; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.junit.Test; + +import org.springframework.messaging.MessageHeaders; + + +/** + * @author Stuart Williams + * @since 4.1.5 + */ +public class MutableMessageTests { + + @Test + public void testMessageIdTimestampRemains() { + + UUID uuid = UUID.randomUUID(); + long timestamp = System.currentTimeMillis(); + + Object payload = new Object(); + Map headerMap = new HashMap<>(); + + headerMap.put(MessageHeaders.ID, uuid); + headerMap.put(MessageHeaders.TIMESTAMP, timestamp); + + MutableMessage mutableMessage = new MutableMessage<>(payload, headerMap); + MutableMessageHeaders headers = mutableMessage.getHeaders(); + + assertThat(headers.getRawHeaders(), hasEntry(MessageHeaders.ID, (Object) uuid)); + assertThat(headers.getRawHeaders(), hasEntry(MessageHeaders.TIMESTAMP, (Object) timestamp)); + } + + @Test + public void testMessageHeaderIsSettable() { + + Object payload = new Object(); + Map headerMap = new HashMap<>(); + Map additional = new HashMap<>(); + + MutableMessage mutableMessage = new MutableMessage<>(payload, headerMap); + MutableMessageHeaders headers = mutableMessage.getHeaders(); + + // Should not throw an UnsupportedOperationException + headers.put("foo", "bar"); + headers.put("eep", "bar"); + headers.remove("eep"); + headers.putAll(additional); + + assertThat(headers.getRawHeaders(), hasEntry("foo", (Object) "bar")); + } + +}