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
This commit is contained in:
Stuart Williams
2015-02-13 10:59:48 +02:00
committed by Gary Russell
parent 5b395a8c5c
commit 8c24bf1dd9
5 changed files with 295 additions and 33 deletions

View File

@@ -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<T> implements Message<T>, Serializable {
private static final long serialVersionUID = -636635024258737500L;
private T payload;
private final T payload;
private final MessageHeaders headers;
private final Map<String, Object> rawHeaders;
private final MutableMessageHeaders headers;
MutableMessage(T payload) {
this(payload, null);
}
@SuppressWarnings("unchecked")
MutableMessage(T payload, Map<String, Object> 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<String, Object>) 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<T> implements Message<T>, Serializable {
return this.payload;
}
public void setPayload(T payload) {
Assert.notNull(payload, "'payload' must not be null");
this.payload = payload;
}
public Map<String, Object> getRawHeaders() {
return this.rawHeaders;
Map<String, Object> getRawHeaders() {
return this.headers.getRawHeaders();
}
@Override

View File

@@ -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<T> extends AbstractIntegrationMessageBuilder<T> {
private MutableMessage<T> mutableMessage;
private final MutableMessage<T> mutableMessage;
private final Map<String, Object> headers;
@@ -55,27 +56,23 @@ public class MutableMessageBuilder<T> 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 <T> The type of the payload.
* @return A MutableMessageBuilder.
*/
public static <T> MutableMessageBuilder<T> fromMessage(Message<T> message) {
Assert.notNull(message, "message must not be null");
MutableMessageBuilder<T> builder = new MutableMessageBuilder<T>(message);
return builder;
return new MutableMessageBuilder<T>(message);
}
/**
* Create a builder for a new {@link Message} instance with the provided payload.
*
* @param payload the payload for the new message
* @param <T> The type of the payload.
* @return A MessageBuilder.
*/
public static <T> MutableMessageBuilder<T> withPayload(T payload) {
MutableMessageBuilder<T> builder = new MutableMessageBuilder<T>(new MutableMessage<T>(payload));
return builder;
return new MutableMessageBuilder<T>(new MutableMessage<T>(payload));
}
@Override
@@ -102,8 +99,8 @@ public class MutableMessageBuilder<T> extends AbstractIntegrationMessageBuilder<
public AbstractIntegrationMessageBuilder<T> removeHeaders(String... headerPatterns) {
List<String> headersToRemove = new ArrayList<String>();
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<T> extends AbstractIntegrationMessageBuilder<
private List<String> getMatchingHeaderNames(String pattern, Map<String, Object> headers) {
List<String> matchingHeaderNames = new ArrayList<String>();
if (headers != null) {
for (Map.Entry<String, Object> header: headers.entrySet()) {
if (PatternMatchUtils.simpleMatch(pattern, header.getKey())) {
for (Map.Entry<String, Object> header : headers.entrySet()) {
if (PatternMatchUtils.simpleMatch(pattern, header.getKey())) {
matchingHeaderNames.add(header.getKey());
}
}

View File

@@ -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<String, Object> headers) {
super(headers);
}
@Override
protected Map<String, Object> getRawHeaders() {
return super.getRawHeaders();
}
@Override
public void putAll(Map<? extends String, ? extends Object> 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);
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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<String, Object> headerMap = new HashMap<>();
headerMap.put(MessageHeaders.ID, uuid);
headerMap.put(MessageHeaders.TIMESTAMP, timestamp);
MutableMessage<Object> 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<String, Object> headerMap = new HashMap<>();
Map<String, Object> additional = new HashMap<>();
MutableMessage<Object> 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"));
}
}