Enhance MessageHeaderAccessor support

Refine semantics of ID and TIMESTAMP headers provided to protected
MessageHeaders constructor.

Refactor internal implementation of MessageHeaderAccessor.

Support mutating headers from a single thread while a message is being
built (e.g. StompDecoder creating message + then adding session id).

Improve immutablity in NativeMessageHeaderAccessor and in
StompHeaderAccessor.

Optimize object creation for initializing messages and subsequent
accessing their headers.

Introduce MessageHeaderAccessorFactory support to enable applying a
common strategies for ID and TIMESTAMP generation to every message.

Add MessageBuilder shortcut factory method for creating messages from
payload and a full-prepared MessageHeaders instance. Also add
equivalent constructors to GenericMessage and ErrorMessage.

Issue: SPR-11468
This commit is contained in:
Rossen Stoyanchev
2014-04-06 11:06:18 -04:00
parent b4995f7e4f
commit 4867546aec
29 changed files with 1825 additions and 489 deletions

View File

@@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -53,6 +54,25 @@ public class MessageHeadersTests {
assertNotSame(headers1.getTimestamp(), headers2.getTimestamp());
}
@Test
public void testTimestampProvided() throws Exception {
MessageHeaders headers = new MessageHeaders(null, null, 10L);
assertEquals(10L, (long) headers.getTimestamp());
}
@Test
public void testTimestampProvidedNullValue() throws Exception {
Map<String, Object> input = Collections.<String, Object>singletonMap(MessageHeaders.TIMESTAMP, 1L);
MessageHeaders headers = new MessageHeaders(input, null, null);
assertNotNull(headers.getTimestamp());
}
@Test
public void testTimestampNone() throws Exception {
MessageHeaders headers = new MessageHeaders(null, null, -1L);
assertNull(headers.getTimestamp());
}
@Test
public void testIdOverwritten() throws Exception {
MessageHeaders headers1 = new MessageHeaders(null);
@@ -66,6 +86,26 @@ public class MessageHeadersTests {
assertNotNull(headers.getId());
}
@Test
public void testIdProvided() {
UUID id = new UUID(0L, 25L);
MessageHeaders headers = new MessageHeaders(null, id, null);
assertEquals(id, headers.getId());
}
@Test
public void testIdProvidedNullValue() {
Map<String, Object> input = Collections.<String, Object>singletonMap(MessageHeaders.ID, new UUID(0L, 25L));
MessageHeaders headers = new MessageHeaders(input, null, null);
assertNotNull(headers.getId());
}
@Test
public void testIdNone() {
MessageHeaders headers = new MessageHeaders(null, MessageHeaders.ID_VALUE_NONE, null);
assertNull(headers.getId());
}
@Test
public void testNonTypedAccessOfHeaderValue() {
Integer value = new Integer(123);
@@ -148,7 +188,7 @@ public class MessageHeadersTests {
class MyMH extends MessageHeaders {
public MyMH() {
super(null, new UUID(0, id.incrementAndGet()), null);
super(null, new UUID(0, id.incrementAndGet()), -1L);
}
}

View File

@@ -17,6 +17,8 @@
package org.springframework.messaging.handler.annotation.support;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@@ -149,7 +151,7 @@ public class HeaderMethodArgumentResolverTests {
public static class TestMessageHeaderAccessor extends NativeMessageHeaderAccessor {
protected TestMessageHeaderAccessor() {
super((Message<?>) null);
super((Map<String, List<String>>) null);
}
}

View File

@@ -21,6 +21,7 @@ import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.StubMessageChannel;
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import org.springframework.util.LinkedMultiValueMap;
import java.util.*;
@@ -87,9 +88,9 @@ public class SimpMessagingTemplateTests {
@Test
public void convertAndSendWithCustomHeaderNonNative() {
Map<String, Object> headers = new HashMap<String, Object>();
Map<String, Object> headers = new HashMap<>();
headers.put("key", "value");
headers.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, Collections.emptyMap());
headers.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, new LinkedMultiValueMap<String, String>());
this.messagingTemplate.convertAndSend("/foo", "data", headers);
List<Message<byte[]>> messages = this.messageChannel.getMessages();

View File

@@ -19,6 +19,7 @@ package org.springframework.messaging.simp.stomp;
import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.util.LinkedMultiValueMap;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
@@ -175,6 +176,17 @@ public class BufferingStompDecoderTests {
stompDecoder.decode(toByteBuffer(payload));
}
@Test
public void incompleteCommand() throws InterruptedException {
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
String chunk = "MESSAG";
LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
List<Message<byte[]>> messages = stompDecoder.decode(toByteBuffer(chunk), headers);
assertEquals(0, messages.size());
}
private ByteBuffer toByteBuffer(String chunk) {
return ByteBuffer.wrap(chunk.getBytes(Charset.forName("UTF-8")));

View File

@@ -25,6 +25,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.InvalidMimeTypeException;
import reactor.function.Consumer;
import reactor.function.Function;
import reactor.io.Buffer;
@@ -48,7 +49,7 @@ public class StompCodecTests {
StompHeaderAccessor headers = StompHeaderAccessor.wrap(frame);
assertEquals(StompCommand.DISCONNECT, headers.getCommand());
assertEquals(0, headers.toStompHeaderMap().size());
assertEquals(0, headers.toNativeHeaderMap().size());
assertEquals(0, frame.getPayload().length);
}
@@ -58,7 +59,7 @@ public class StompCodecTests {
StompHeaderAccessor headers = StompHeaderAccessor.wrap(frame);
assertEquals(StompCommand.DISCONNECT, headers.getCommand());
assertEquals(0, headers.toStompHeaderMap().size());
assertEquals(0, headers.toNativeHeaderMap().size());
assertEquals(0, frame.getPayload().length);
}
@@ -72,7 +73,7 @@ public class StompCodecTests {
assertEquals(StompCommand.CONNECT, headers.getCommand());
assertEquals(2, headers.toStompHeaderMap().size());
assertEquals(2, headers.toNativeHeaderMap().size());
assertEquals("1.1", headers.getFirstNativeHeader("accept-version"));
assertEquals("github.org", headers.getHost());
@@ -86,7 +87,7 @@ public class StompCodecTests {
assertEquals(StompCommand.SEND, headers.getCommand());
assertEquals(1, headers.toStompHeaderMap().size());
assertEquals(headers.toNativeHeaderMap().toString(), 1, headers.toNativeHeaderMap().size());
assertEquals("test", headers.getDestination());
String bodyText = new String(frame.getPayload());
@@ -95,15 +96,15 @@ public class StompCodecTests {
@Test
public void decodeFrameWithContentLength() {
Message<byte[]> frame = decode("SEND\ncontent-length:23\n\nThe body of the message\0");
StompHeaderAccessor headers = StompHeaderAccessor.wrap(frame);
Message<byte[]> message = decode("SEND\ncontent-length:23\n\nThe body of the message\0");
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
assertEquals(StompCommand.SEND, headers.getCommand());
assertEquals(1, headers.toStompHeaderMap().size());
assertEquals(1, headers.toNativeHeaderMap().size());
assertEquals(Integer.valueOf(23), headers.getContentLength());
String bodyText = new String(frame.getPayload());
String bodyText = new String(message.getPayload());
assertEquals("The body of the message", bodyText);
}
@@ -111,15 +112,15 @@ public class StompCodecTests {
@Test
public void decodeFrameWithInvalidContentLength() {
Message<byte[]> frame = decode("SEND\ncontent-length:-1\n\nThe body of the message\0");
StompHeaderAccessor headers = StompHeaderAccessor.wrap(frame);
Message<byte[]> message = decode("SEND\ncontent-length:-1\n\nThe body of the message\0");
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
assertEquals(StompCommand.SEND, headers.getCommand());
assertEquals(1, headers.toStompHeaderMap().size());
assertEquals(1, headers.toNativeHeaderMap().size());
assertEquals(Integer.valueOf(-1), headers.getContentLength());
String bodyText = new String(frame.getPayload());
String bodyText = new String(message.getPayload());
assertEquals("The body of the message", bodyText);
}
@@ -130,7 +131,7 @@ public class StompCodecTests {
assertEquals(StompCommand.SEND, headers.getCommand());
assertEquals(1, headers.toStompHeaderMap().size());
assertEquals(1, headers.toNativeHeaderMap().size());
assertEquals(Integer.valueOf(0), headers.getContentLength());
String bodyText = new String(frame.getPayload());
@@ -144,7 +145,7 @@ public class StompCodecTests {
assertEquals(StompCommand.SEND, headers.getCommand());
assertEquals(1, headers.toStompHeaderMap().size());
assertEquals(1, headers.toNativeHeaderMap().size());
assertEquals(Integer.valueOf(23), headers.getContentLength());
String bodyText = new String(frame.getPayload());
@@ -158,7 +159,7 @@ public class StompCodecTests {
assertEquals(StompCommand.DISCONNECT, headers.getCommand());
assertEquals(1, headers.toStompHeaderMap().size());
assertEquals(1, headers.toNativeHeaderMap().size());
assertEquals("alpha:bravo\r\n\\", headers.getFirstNativeHeader("a:\r\n\\b"));
}
@@ -187,6 +188,11 @@ public class StompCodecTests {
assertEquals(StompCommand.DISCONNECT, StompHeaderAccessor.wrap(messages.get(1)).getCommand());
}
@Test
public void decodeFrameWithIncompleteCommand() {
assertIncompleteDecode("MESSAG");
}
@Test
public void decodeFrameWithIncompleteHeader() {
assertIncompleteDecode("SEND\ndestination");
@@ -206,6 +212,16 @@ public class StompCodecTests {
assertIncompleteDecode("SEND\ncontent-length:23\n\nThe body of the mess");
}
@Test
public void decodeFrameWithIncompleteContentType() {
assertIncompleteDecode("SEND\ncontent-type:text/plain;charset=U");
}
@Test(expected = InvalidMimeTypeException.class)
public void decodeFrameWithInvalidContentType() {
assertIncompleteDecode("SEND\ncontent-type:text/plain;charset=U\n\nThe body\0");
}
@Test(expected=StompConversionException.class)
public void decodeFrameWithIncorrectTerminator() {
decode("SEND\ncontent-length:23\n\nThe body of the message*");
@@ -233,7 +249,7 @@ public class StompCodecTests {
public void encodeFrameWithNoHeadersAndNoBody() {
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.DISCONNECT);
Message<byte[]> frame = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
Message<byte[]> frame = MessageBuilder.createMessage(new byte[0], headers.getMessageHeaders());
assertEquals("DISCONNECT\n\n\0", new StompCodec().encoder().apply(frame).asString());
}
@@ -244,7 +260,7 @@ public class StompCodecTests {
headers.setAcceptVersion("1.2");
headers.setHost("github.org");
Message<byte[]> frame = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
Message<byte[]> frame = MessageBuilder.createMessage(new byte[0], headers.getMessageHeaders());
String frameString = new StompCodec().encoder().apply(frame).asString();
@@ -257,9 +273,10 @@ public class StompCodecTests {
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.DISCONNECT);
headers.addNativeHeader("a:\r\n\\b", "alpha:bravo\r\n\\");
Message<byte[]> frame = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
Message<byte[]> frame = MessageBuilder.createMessage(new byte[0], headers.getMessageHeaders());
assertEquals("DISCONNECT\na\\c\\r\\n\\\\b:alpha\\cbravo\\r\\n\\\\\n\n\0", new StompCodec().encoder().apply(frame).asString());
assertEquals("DISCONNECT\na\\c\\r\\n\\\\b:alpha\\cbravo\\r\\n\\\\\n\n\0",
new StompCodec().encoder().apply(frame).asString());
}
@Test
@@ -267,9 +284,10 @@ public class StompCodecTests {
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SEND);
headers.addNativeHeader("a", "alpha");
Message<byte[]> frame = MessageBuilder.withPayload("Message body".getBytes()).setHeaders(headers).build();
Message<byte[]> frame = MessageBuilder.createMessage("Message body".getBytes(), headers.getMessageHeaders());
assertEquals("SEND\na:alpha\ncontent-length:12\n\nMessage body\0", new StompCodec().encoder().apply(frame).asString());
assertEquals("SEND\na:alpha\ncontent-length:12\n\nMessage body\0",
new StompCodec().encoder().apply(frame).asString());
}
private void assertIncompleteDecode(String partialFrame) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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,22 +16,28 @@
package org.springframework.messaging.simp.stomp;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* Test fixture for {@link StompHeaderAccessor}.
* Unit tests for {@link StompHeaderAccessor}.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -99,17 +105,18 @@ public class StompHeaderAccessorTests {
extHeaders.add(StompHeaderAccessor.STOMP_LOGIN_HEADER, "joe");
extHeaders.add(StompHeaderAccessor.STOMP_PASSCODE_HEADER, "joe123");
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.CONNECT, extHeaders);
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.CONNECT, extHeaders);
assertEquals(StompCommand.CONNECT, headers.getCommand());
assertEquals(SimpMessageType.CONNECT, headers.getMessageType());
assertNotNull(headers.getHeader("stompCredentials"));
assertEquals("joe", headers.getLogin());
assertEquals("PROTECTED", headers.getPasscode());
assertEquals(StompCommand.CONNECT, headerAccessor.getCommand());
assertEquals(SimpMessageType.CONNECT, headerAccessor.getMessageType());
assertNotNull(headerAccessor.getHeader("stompCredentials"));
assertEquals("joe", headerAccessor.getLogin());
assertEquals("joe123", headerAccessor.getPasscode());
assertThat(headerAccessor.toString(), CoreMatchers.containsString("passcode=[PROTECTED]"));
Map<String, List<String>> output = headers.toStompHeaderMap();
Map<String, List<String>> output = headerAccessor.toNativeHeaderMap();
assertEquals("joe", output.get(StompHeaderAccessor.STOMP_LOGIN_HEADER).get(0));
assertEquals("joe123", output.get(StompHeaderAccessor.STOMP_PASSCODE_HEADER).get(0));
assertEquals("PROTECTED", output.get(StompHeaderAccessor.STOMP_PASSCODE_HEADER).get(0));
}
@Test
@@ -145,10 +152,11 @@ public class StompHeaderAccessorTests {
headers.setSubscriptionId("s1");
headers.setDestination("/d");
headers.setContentType(MimeTypeUtils.APPLICATION_JSON);
headers.updateStompCommandAsServerMessage();
Map<String, List<String>> actual = headers.toNativeHeaderMap();
assertEquals(4, actual.size());
assertEquals(actual.toString(), 4, actual.size());
assertEquals("s1", actual.get(StompHeaderAccessor.STOMP_SUBSCRIPTION_HEADER).get(0));
assertEquals("/d", actual.get(StompHeaderAccessor.STOMP_DESTINATION_HEADER).get(0));
assertEquals("application/json", actual.get(StompHeaderAccessor.STOMP_CONTENT_TYPE_HEADER).get(0));
@@ -158,15 +166,30 @@ public class StompHeaderAccessorTests {
@Test
public void toNativeHeadersContentType() {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0])
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_ATOM_XML).build();
SimpMessageHeaderAccessor simpHeaderAccessor = SimpMessageHeaderAccessor.create();
simpHeaderAccessor.setContentType(MimeTypeUtils.APPLICATION_ATOM_XML);
Message<byte[]> message = MessageBuilder.createMessage(new byte[0], simpHeaderAccessor.getMessageHeaders());
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
Map<String, List<String>> map = headers.toNativeHeaderMap();
StompHeaderAccessor stompHeaderAccessor = StompHeaderAccessor.wrap(message);
Map<String, List<String>> map = stompHeaderAccessor.toNativeHeaderMap();
assertEquals("application/atom+xml", map.get(StompHeaderAccessor.STOMP_CONTENT_TYPE_HEADER).get(0));
}
@Test
public void encodeConnectWithLoginAndPasscode() throws UnsupportedEncodingException {
MultiValueMap<String, String> extHeaders = new LinkedMultiValueMap<>();
extHeaders.add(StompHeaderAccessor.STOMP_LOGIN_HEADER, "joe");
extHeaders.add(StompHeaderAccessor.STOMP_PASSCODE_HEADER, "joe123");
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.CONNECT, extHeaders);
Message<byte[]> message = MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders());
byte[] bytes = new StompEncoder().encode(message);
assertEquals("CONNECT\nlogin:joe\npasscode:joe123\n\n\0", new String(bytes, "UTF-8"));
}
@Test
public void modifyCustomNativeHeader() {
@@ -187,5 +210,34 @@ public class StompHeaderAccessorTests {
assertNotNull("abc123", actual.get("accountId").get(0));
}
@Test
public void messageIdAndTimestampDefaultBehavior() {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.SEND);
MessageHeaders headers = headerAccessor.getMessageHeaders();
assertNull(headers.getId());
assertNull(headers.getTimestamp());
}
@Test
public void messageIdAndTimestampEnabled() {
DefaultStompHeaderAccessorFactory factory = new DefaultStompHeaderAccessorFactory();
factory.setIdGenerator(new AlternativeJdkIdGenerator());
factory.setEnableTimestamp(true);
StompHeaderAccessor headerAccessor = factory.create(StompCommand.SEND);
MessageHeaders headers = headerAccessor.getMessageHeaders();
assertNotNull(headers.getId());
assertNotNull(headers.getTimestamp());
}
@Test
public void getAccessor() {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.CONNECT);
Message<byte[]> message = MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders());
assertSame(headerAccessor, MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -21,17 +21,25 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.IdGenerator;
import static org.junit.Assert.*;
/**
* @author Mark Fisher
* @author Rossen Stoyanchev
*/
public class MessageBuilderTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void testSimpleMessageCreation() {
Message<String> message = MessageBuilder.withPayload("foo").build();
@@ -42,7 +50,7 @@ public class MessageBuilderTests {
public void testHeaderValues() {
Message<String> message = MessageBuilder.withPayload("test")
.setHeader("foo", "bar")
.setHeader("count", new Integer(123))
.setHeader("count", 123)
.build();
assertEquals("bar", message.getHeaders().get("foo", String.class));
assertEquals(new Integer(123), message.getHeaders().get("count", Integer.class));
@@ -153,11 +161,11 @@ public class MessageBuilderTests {
@Test
public void testCopySameHeaderValuesNotModifiedSameMessage() throws Exception {
Date current = new Date();
Map<String, Object> originalHeaders = new HashMap<String, Object>();
Map<String, Object> originalHeaders = new HashMap<>();
originalHeaders.put("b", "xyz");
originalHeaders.put("c", current);
Message<?> original = MessageBuilder.withPayload("foo").setHeader("a", 123).copyHeaders(originalHeaders).build();
Map<String, Object> newHeaders = new HashMap<String, Object>();
Map<String, Object> newHeaders = new HashMap<>();
newHeaders.put("a", 123);
newHeaders.put("b", "xyz");
newHeaders.put("c", current);
@@ -165,4 +173,61 @@ public class MessageBuilderTests {
assertEquals(original, result);
}
@Test
public void testBuildMessageWithMutableHeaders() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setLeaveMutable(true);
MessageHeaders headers = accessor.getMessageHeaders();
Message<?> message = MessageBuilder.createMessage("payload", headers);
accessor.setHeader("foo", "bar");
assertEquals("bar", headers.get("foo"));
assertSame(accessor, MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class));
}
@Test
public void testBuildMessageWithDefaultMutability() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
MessageHeaders headers = accessor.getMessageHeaders();
Message<?> message = MessageBuilder.createMessage("foo", headers);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Already immutable");
accessor.setHeader("foo", "bar");
assertSame(accessor, MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class));
}
@Test
public void testBuildMessageWithoutIdAndTimestamp() {
MessageHeaderAccessor headerAccessor = new MessageHeaderAccessor();
headerAccessor.setIdGenerator(new IdGenerator() {
@Override
public UUID generateId() {
return MessageHeaders.ID_VALUE_NONE;
}
});
Message<?> message = MessageBuilder.createMessage("foo", headerAccessor.getMessageHeaders());
assertNull(message.getHeaders().getId());
assertNull(message.getHeaders().getTimestamp());
}
@Test
public void testBuildMultipleMessages() {
MessageHeaderAccessor headerAccessor = new MessageHeaderAccessor();
MessageBuilder messageBuilder = MessageBuilder.withPayload("payload").setHeaders(headerAccessor);
headerAccessor.setHeader("foo", "bar1");
Message<?> message1 = messageBuilder.build();
headerAccessor.setHeader("foo", "bar2");
Message<?> message2 = messageBuilder.build();
headerAccessor.setHeader("foo", "bar3");
Message<?> message3 = messageBuilder.build();
assertEquals("bar1", message1.getHeaders().get("foo"));
assertEquals("bar2", message2.getHeaders().get("foo"));
assertEquals("bar3", message3.getHeaders().get("foo"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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,14 +16,21 @@
package org.springframework.messaging.support;
import java.util.Collections;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.IdGenerator;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* Test fixture for {@link MessageHeaderAccessor}.
@@ -32,55 +39,229 @@ import static org.junit.Assert.*;
*/
public class MessageHeaderAccessorTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void empty() {
MessageHeaderAccessor headers = new MessageHeaderAccessor();
assertEquals(Collections.emptyMap(), headers.toMap());
public void newEmptyHeaders() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
assertEquals(0, accessor.toMap().size());
}
@Test
public void wrapMessage() {
Map<String, Object> original = new HashMap<>();
original.put("foo", "bar");
original.put("bar", "baz");
GenericMessage<String> message = new GenericMessage<>("payload", original);
public void existingHeaders() throws InterruptedException {
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
map.put("bar", "baz");
GenericMessage<String> message = new GenericMessage<>("payload", map);
MessageHeaderAccessor headers = new MessageHeaderAccessor(message);
Map<String, Object> actual = headers.toMap();
MessageHeaderAccessor accessor = new MessageHeaderAccessor(message);
MessageHeaders actual = accessor.getMessageHeaders();
assertEquals(4, actual.size());
assertNotNull(actual.get(MessageHeaders.ID));
assertNotNull(actual.get(MessageHeaders.TIMESTAMP));
assertEquals(3, actual.size());
assertEquals("bar", actual.get("foo"));
assertEquals("baz", actual.get("bar"));
}
@Test
public void wrapMessageAndModifyHeaders() {
Map<String, Object> original = new HashMap<>();
original.put("foo", "bar");
original.put("bar", "baz");
GenericMessage<String> message = new GenericMessage<>("payload", original);
public void existingHeadersModification() throws InterruptedException {
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
map.put("bar", "baz");
GenericMessage<String> message = new GenericMessage<>("payload", map);
MessageHeaderAccessor headers = new MessageHeaderAccessor(message);
headers.setHeader("foo", "BAR");
Map<String, Object> actual = headers.toMap();
Thread.sleep(50);
assertEquals(4, actual.size());
assertNotNull(actual.get(MessageHeaders.ID));
assertNotNull(actual.get(MessageHeaders.TIMESTAMP));
MessageHeaderAccessor accessor = new MessageHeaderAccessor(message);
accessor.setHeader("foo", "BAR");
MessageHeaders actual = accessor.getMessageHeaders();
assertEquals(3, actual.size());
assertNotEquals(message.getHeaders().getId(), actual.getId());
assertEquals("BAR", actual.get("foo"));
assertEquals("baz", actual.get("bar"));
}
@Test
public void copyHeadersNullMap() {
public void copyHeadersFromNullMap() {
MessageHeaderAccessor headers = new MessageHeaderAccessor();
headers.copyHeaders(null);
headers.copyHeadersIfAbsent(null);
assertEquals(0, headers.toMap().size());
assertEquals(1, headers.getMessageHeaders().size());
assertEquals(new HashSet<>(Arrays.asList("id")), headers.getMessageHeaders().keySet());
}
@Test
public void toMap() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setHeader("foo", "bar1");
Map<String, Object> map1 = accessor.toMap();
accessor.setHeader("foo", "bar2");
Map<String, Object> map2 = accessor.toMap();
accessor.setHeader("foo", "bar3");
Map<String, Object> map3 = accessor.toMap();
assertEquals(1, map1.size());
assertEquals(1, map2.size());
assertEquals(1, map3.size());
assertEquals("bar1", map1.get("foo"));
assertEquals("bar2", map2.get("foo"));
assertEquals("bar3", map3.get("foo"));
}
@Test
public void leaveMutable() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setHeader("foo", "bar");
accessor.setLeaveMutable(true);
MessageHeaders headers = accessor.getMessageHeaders();
Message<?> message = MessageBuilder.createMessage("payload", headers);
accessor.setHeader("foo", "baz");
assertEquals("baz", headers.get("foo"));
assertSame(accessor, MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class));
}
@Test
public void leaveMutableDefaultBehavior() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setHeader("foo", "bar");
MessageHeaders headers = accessor.getMessageHeaders();
Message<?> message = MessageBuilder.createMessage("payload", headers);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Already immutable");
accessor.setLeaveMutable(true);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Already immutable");
accessor.setHeader("foo", "baz");
assertEquals("bar", headers.get("foo"));
assertSame(accessor, MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class));
}
@Test
public void getAccessor() {
MessageHeaderAccessor expected = new MessageHeaderAccessor();
Message<?> message = MessageBuilder.createMessage("payload", expected.getMessageHeaders());
assertSame(expected, MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class));
}
@Test
public void getMutableAccessorSameInstance() {
TestMessageHeaderAccessor expected = new TestMessageHeaderAccessor();
expected.setLeaveMutable(true);
Message<?> message = MessageBuilder.createMessage("payload", expected.getMessageHeaders());
MessageHeaderAccessor actual = MessageHeaderAccessor.getMutableAccessor(message);
assertNotNull(actual);
assertTrue(actual.isMutable());
assertSame(expected, actual);
}
@Test
public void getMutableAccessorNewInstance() {
Message<?> message = MessageBuilder.withPayload("payload").build();
MessageHeaderAccessor actual = MessageHeaderAccessor.getMutableAccessor(message);
assertNotNull(actual);
assertTrue(actual.isMutable());
}
@Test
public void getMutableAccessorNewInstanceMatchingType() {
TestMessageHeaderAccessor expected = new TestMessageHeaderAccessor();
Message<?> message = MessageBuilder.createMessage("payload", expected.getMessageHeaders());
MessageHeaderAccessor actual = MessageHeaderAccessor.getMutableAccessor(message);
assertNotNull(actual);
assertTrue(actual.isMutable());
assertEquals(TestMessageHeaderAccessor.class, actual.getClass());
}
@Test
public void timestampEnabled() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setEnableTimestamp(true);
assertNotNull(accessor.getMessageHeaders().getTimestamp());
}
@Test
public void timestampDefaultBehavior() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
assertNull(accessor.getMessageHeaders().getTimestamp());
}
@Test
public void timestampBehaviorCopyFromExistingMessage() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setEnableTimestamp(true);
Message<?> message = MessageBuilder.createMessage("payload", accessor.getMessageHeaders());
MessageHeaderAccessor secondAccessor = new MessageHeaderAccessor(message);
assertNotNull(secondAccessor.getMessageHeaders().getTimestamp());
}
@Test
public void idGeneratorCustom() {
final UUID id = new UUID(0L, 23L);
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setIdGenerator(new IdGenerator() {
@Override
public UUID generateId() {
return id;
}
});
assertSame(id, accessor.getMessageHeaders().getId());
}
@Test
public void idGeneratorDefaultBehavior() {
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
assertNotNull(accessor.getMessageHeaders().getId());
}
@Test
public void idGeneratorCopyFromExistingMessage() {
final UUID id = new UUID(0L, 23L);
MessageHeaderAccessor accessor = new MessageHeaderAccessor();
accessor.setIdGenerator(new IdGenerator() {
@Override
public UUID generateId() {
return id;
}
});
Message<?> message = MessageBuilder.createMessage("payload", accessor.getMessageHeaders());
MessageHeaderAccessor secondAccessor = new MessageHeaderAccessor(message);
assertSame(id, secondAccessor.getMessageHeaders().getId());
}
public static class TestMessageHeaderAccessor extends MessageHeaderAccessor {
private TestMessageHeaderAccessor() {
}
private TestMessageHeaderAccessor(Message<?> message) {
super(message);
}
public static TestMessageHeaderAccessor wrap(Message<?> message) {
return new TestMessageHeaderAccessor(message);
}
@Override
protected TestMessageHeaderAccessor createAccessor(Message<?> message) {
return wrap(message);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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,16 +19,19 @@ package org.springframework.messaging.support;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* Test fixture for {@link NativeMessageHeaderAccessor}.
@@ -37,81 +40,78 @@ import static org.junit.Assert.*;
*/
public class NativeMessageHeaderAccessorTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void originalNativeHeaders() {
MultiValueMap<String, String> original = new LinkedMultiValueMap<>();
original.add("foo", "bar");
original.add("bar", "baz");
public void createFromNativeHeaderMap() {
MultiValueMap<String, String> inputNativeHeaders = new LinkedMultiValueMap<>();
inputNativeHeaders.add("foo", "bar");
inputNativeHeaders.add("bar", "baz");
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(original);
Map<String, Object> actual = headers.toMap();
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor(inputNativeHeaders);
Map<String, Object> actual = headerAccessor.toMap();
assertEquals(1, actual.size());
assertEquals(actual.toString(), 1, actual.size());
assertNotNull(actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
assertEquals(original, actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
assertEquals(inputNativeHeaders, actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
assertNotSame(inputNativeHeaders, actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
}
@Test
public void wrapMessage() {
public void createFromMessage() {
MultiValueMap<String, String> inputNativeHeaders = new LinkedMultiValueMap<>();
inputNativeHeaders.add("foo", "bar");
inputNativeHeaders.add("bar", "baz");
MultiValueMap<String, String> originalNativeHeaders = new LinkedMultiValueMap<>();
originalNativeHeaders.add("foo", "bar");
originalNativeHeaders.add("bar", "baz");
Map<String, Object> inputHeaders = new HashMap<String, Object>();
inputHeaders.put("a", "b");
inputHeaders.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, inputNativeHeaders);
Map<String, Object> original = new HashMap<String, Object>();
original.put("a", "b");
original.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, originalNativeHeaders);
GenericMessage<String> message = new GenericMessage<>("p", inputHeaders);
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor(message);
Map<String, Object> actual = headerAccessor.toMap();
GenericMessage<String> message = new GenericMessage<>("p", original);
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(message);
Map<String, Object> actual = headers.toMap();
assertEquals(4, actual.size());
assertNotNull(actual.get(MessageHeaders.ID));
assertNotNull(actual.get(MessageHeaders.TIMESTAMP));
assertEquals(2, actual.size());
assertEquals("b", actual.get("a"));
assertNotNull(actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
assertEquals(originalNativeHeaders, actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
assertEquals(inputNativeHeaders, actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
assertNotSame(inputNativeHeaders, actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
}
@Test
public void wrapNullMessage() {
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor((Message<?>) null);
Map<String, Object> actual = headers.toMap();
public void createFromMessageNull() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor((Message<?>) null);
assertEquals(1, actual.size());
Map<String, Object> actual = headerAccessor.toMap();
assertEquals(0, actual.size());
@SuppressWarnings("unchecked")
Map<String, List<String>> actualNativeHeaders =
(Map<String, List<String>>) actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS);
Map<String, List<String>> actualNativeHeaders = headerAccessor.toNativeHeaderMap();
assertEquals(Collections.emptyMap(), actualNativeHeaders);
}
@Test
public void wrapMessageAndModifyHeaders() {
public void createFromMessageAndModify() {
MultiValueMap<String, String> originalNativeHeaders = new LinkedMultiValueMap<>();
originalNativeHeaders.add("foo", "bar");
originalNativeHeaders.add("bar", "baz");
MultiValueMap<String, String> inputNativeHeaders = new LinkedMultiValueMap<>();
inputNativeHeaders.add("foo", "bar");
inputNativeHeaders.add("bar", "baz");
Map<String, Object> original = new HashMap<String, Object>();
original.put("a", "b");
original.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, originalNativeHeaders);
Map<String, Object> nativeHeaders = new HashMap<String, Object>();
nativeHeaders.put("a", "b");
nativeHeaders.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, inputNativeHeaders);
GenericMessage<String> message = new GenericMessage<>("p", original);
GenericMessage<String> message = new GenericMessage<>("p", nativeHeaders);
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(message);
headers.setHeader("a", "B");
headers.setNativeHeader("foo", "BAR");
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor(message);
headerAccessor.setHeader("a", "B");
headerAccessor.setNativeHeader("foo", "BAR");
Map<String, Object> actual = headers.toMap();
Map<String, Object> actual = headerAccessor.toMap();
assertEquals(4, actual.size());
assertNotNull(actual.get(MessageHeaders.ID));
assertNotNull(actual.get(MessageHeaders.TIMESTAMP));
assertEquals(2, actual.size());
assertEquals("B", actual.get("a"));
@SuppressWarnings("unchecked")
@@ -123,4 +123,112 @@ public class NativeMessageHeaderAccessorTests {
assertEquals(Arrays.asList("baz"), actualNativeHeaders.get("bar"));
}
}
@Test
public void setNativeHeader() {
MultiValueMap<String, String> nativeHeaders = new LinkedMultiValueMap<>();
nativeHeaders.add("foo", "bar");
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(nativeHeaders);
headers.setNativeHeader("foo", "baz");
assertEquals(Arrays.asList("baz"), headers.getNativeHeader("foo"));
}
@Test
public void setNativeHeaderNullValue() {
MultiValueMap<String, String> nativeHeaders = new LinkedMultiValueMap<>();
nativeHeaders.add("foo", "bar");
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(nativeHeaders);
headers.setNativeHeader("foo", null);
assertNull(headers.getNativeHeader("foo"));
}
@Test
public void setNativeHeaderLazyInit() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
headerAccessor.setNativeHeader("foo", "baz");
assertEquals(Arrays.asList("baz"), headerAccessor.getNativeHeader("foo"));
}
@Test
public void setNativeHeaderLazyInitNullValue() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
headerAccessor.setNativeHeader("foo", null);
assertNull(headerAccessor.getNativeHeader("foo"));
assertNull(headerAccessor.getMessageHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
}
@Test
public void setNativeHeaderImmutable() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
headerAccessor.setNativeHeader("foo", "bar");
headerAccessor.setImmutable();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Already immutable");
headerAccessor.setNativeHeader("foo", "baz");
}
@Test
public void addNativeHeader() {
MultiValueMap<String, String> nativeHeaders = new LinkedMultiValueMap<>();
nativeHeaders.add("foo", "bar");
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(nativeHeaders);
headers.addNativeHeader("foo", "baz");
assertEquals(Arrays.asList("bar", "baz"), headers.getNativeHeader("foo"));
}
@Test
public void addNativeHeaderNullValue() {
MultiValueMap<String, String> nativeHeaders = new LinkedMultiValueMap<>();
nativeHeaders.add("foo", "bar");
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(nativeHeaders);
headers.addNativeHeader("foo", null);
assertEquals(Arrays.asList("bar"), headers.getNativeHeader("foo"));
}
@Test
public void addNativeHeaderLazyInit() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
headerAccessor.addNativeHeader("foo", "bar");
assertEquals(Arrays.asList("bar"), headerAccessor.getNativeHeader("foo"));
}
@Test
public void addNativeHeaderLazyInitNullValue() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
headerAccessor.addNativeHeader("foo", null);
assertNull(headerAccessor.getNativeHeader("foo"));
assertNull(headerAccessor.getMessageHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS));
}
@Test
public void addNativeHeaderImmutable() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
headerAccessor.addNativeHeader("foo", "bar");
headerAccessor.setImmutable();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Already immutable");
headerAccessor.addNativeHeader("foo", "baz");
}
@Test
public void setImmutableIdempotent() {
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
headerAccessor.addNativeHeader("foo", "bar");
headerAccessor.setImmutable();
headerAccessor.setImmutable();
}
}