parent
b322cbd625
commit
4b9c3fa739
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -39,76 +39,62 @@ import org.springframework.util.PatternMatchUtils;
|
|||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A base for classes providing strongly typed getters and setters as well as
|
||||
* behavior around specific categories of headers (e.g. STOMP headers).
|
||||
* Supports creating new headers, modifying existing headers (when still mutable),
|
||||
* or copying and modifying existing headers.
|
||||
*
|
||||
* <p>The method {@link #getMessageHeaders()} provides access to the underlying,
|
||||
* fully-prepared {@link MessageHeaders} that can then be used as-is (i.e.
|
||||
* without copying) to create a single message as follows:
|
||||
* Wrapper around {@link MessageHeaders} that provides extra features such as
|
||||
* strongly typed accessors for specific headers, the ability to leave headers
|
||||
* in a {@link Message} mutable, and the option to suppress automatic generation
|
||||
* of {@link MessageHeaders#ID id} and {@link MessageHeaders#TIMESTAMP
|
||||
* timesteamp} headers. Sub-classes such as {@link NativeMessageHeaderAccessor}
|
||||
* and others provide support for managing processing vs external source headers
|
||||
* as well as protocol specific headers.
|
||||
*
|
||||
* <p>Below is a workflow to initialize headers via {@code MessageHeaderAccessor},
|
||||
* or one of its sub-classes, then create a {@link Message}, and then re-obtain
|
||||
* the accessor possibly from a different component:
|
||||
* <pre class="code">
|
||||
* // Create a message with headers
|
||||
* MessageHeaderAccessor accessor = new MessageHeaderAccessor();
|
||||
* accessor.setHeader("foo", "bar");
|
||||
* Message message = MessageBuilder.createMessage("payload", accessor.getMessageHeaders());
|
||||
* MessageHeaders headers = accessor.getMessageHeaders();
|
||||
* Message message = MessageBuilder.createMessage("payload", headers);
|
||||
*
|
||||
* // Later on
|
||||
* MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message);
|
||||
* Assert.notNull(accessor, "No MessageHeaderAccessor");
|
||||
* </pre>
|
||||
*
|
||||
* <p>After the above, by default the {@code MessageHeaderAccessor} becomes
|
||||
* immutable. However it is possible to leave it mutable for further initialization
|
||||
* in the same thread, for example:
|
||||
*
|
||||
* <p>In order for the above to work, all participating components must use
|
||||
* {@code MessageHeaders} to create, access, or modify headers, or otherwise
|
||||
* {@link MessageHeaderAccessor#getAccessor(Message, Class)} will return null.
|
||||
* Below is a workflow that shows how headers are created and left mutable,
|
||||
* then modified possibly by a different component, and finally made immutable
|
||||
* perhaps before the possibility of being accessed on a different thread:
|
||||
* <pre class="code">
|
||||
* // Create a message with mutable headers
|
||||
* MessageHeaderAccessor accessor = new MessageHeaderAccessor();
|
||||
* accessor.setHeader("foo", "bar");
|
||||
* accessor.setLeaveMutable(true);
|
||||
* Message message = MessageBuilder.createMessage("payload", accessor.getMessageHeaders());
|
||||
*
|
||||
* // later on in the same thread...
|
||||
* MessageHeaders headers = accessor.getMessageHeaders();
|
||||
* Message message = MessageBuilder.createMessage("payload", headers);
|
||||
*
|
||||
* // Later on
|
||||
* MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message);
|
||||
* if (accessor.isMutable()) {
|
||||
* // It's mutable, just change the headers
|
||||
* accessor.setHeader("bar", "baz");
|
||||
* }
|
||||
* else {
|
||||
* // It's not, so get a mutable copy, change and re-create
|
||||
* accessor = MessageHeaderAccessor.getMutableAccessor(message);
|
||||
* accessor.setHeader("bar", "baz");
|
||||
* accessor.setLeaveMutable(true); // leave mutable again or not?
|
||||
* message = MessageBuilder.createMessage(message.getPayload(), accessor);
|
||||
* }
|
||||
*
|
||||
* // Make the accessor immutable
|
||||
* MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message);
|
||||
* accessor.setImmutable();
|
||||
* </pre>
|
||||
*
|
||||
* <p>The method {@link #toMap()} returns a copy of the underlying headers. It can
|
||||
* be used to prepare multiple messages from the same {@code MessageHeaderAccessor}
|
||||
* instance:
|
||||
* <pre class="code">
|
||||
* MessageHeaderAccessor accessor = new MessageHeaderAccessor();
|
||||
* MessageBuilder builder = MessageBuilder.withPayload("payload").setHeaders(accessor);
|
||||
*
|
||||
* accessor.setHeader("foo", "bar1");
|
||||
* Message message1 = builder.build();
|
||||
*
|
||||
* accessor.setHeader("foo", "bar2");
|
||||
* Message message2 = builder.build();
|
||||
*
|
||||
* accessor.setHeader("foo", "bar3");
|
||||
* Message message3 = builder.build();
|
||||
* </pre>
|
||||
*
|
||||
* <p>However note that with the above style, the header accessor is shared and
|
||||
* cannot be re-obtained later on. Alternatively it is also possible to create
|
||||
* one {@code MessageHeaderAccessor} per message:
|
||||
*
|
||||
* <pre class="code">
|
||||
* MessageHeaderAccessor accessor1 = new MessageHeaderAccessor();
|
||||
* accessor.set("foo", "bar1");
|
||||
* Message message1 = MessageBuilder.createMessage("payload", accessor1.getMessageHeaders());
|
||||
*
|
||||
* MessageHeaderAccessor accessor2 = new MessageHeaderAccessor();
|
||||
* accessor.set("foo", "bar2");
|
||||
* Message message2 = MessageBuilder.createMessage("payload", accessor2.getMessageHeaders());
|
||||
*
|
||||
* MessageHeaderAccessor accessor3 = new MessageHeaderAccessor();
|
||||
* accessor.set("foo", "bar3");
|
||||
* Message message3 = MessageBuilder.createMessage("payload", accessor3.getMessageHeaders());
|
||||
* </pre>
|
||||
*
|
||||
* <p>Note that the above examples aim to demonstrate the general idea of using
|
||||
* header accessors. The most likely usage however is through subclasses.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.0
|
||||
|
@ -568,6 +554,21 @@ public class MessageHeaderAccessor {
|
|||
|
||||
// Static factory methods
|
||||
|
||||
/**
|
||||
* Return the original {@code MessageHeaderAccessor} used to create the headers
|
||||
* of the given {@code Message}, or {@code null} if that's not available or if
|
||||
* its type does not match the required type.
|
||||
* <p>This is for cases where the existence of an accessor is strongly expected
|
||||
* (followed up with an assertion) or where an accessor will be created otherwise.
|
||||
* @param message the message to get an accessor for
|
||||
* @return an accessor instance of the specified type, or {@code null} if none
|
||||
* @since 5.1.19
|
||||
*/
|
||||
@Nullable
|
||||
public static MessageHeaderAccessor getAccessor(Message<?> message) {
|
||||
return getAccessor(message.getHeaders(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the original {@code MessageHeaderAccessor} used to create the headers
|
||||
* of the given {@code Message}, or {@code null} if that's not available or if
|
||||
|
@ -627,6 +628,11 @@ public class MessageHeaderAccessor {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extension of {@link MessageHeaders} that helps to preserve the link to
|
||||
* the outer {@link MessageHeaderAccessor} instance that created it as well
|
||||
* as keeps track of whether headers are still mutable.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private class MutableMessageHeaders extends MessageHeaders {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -30,40 +30,34 @@ import org.springframework.util.MultiValueMap;
|
|||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* An extension of {@link MessageHeaderAccessor} that also stores and provides read/write
|
||||
* access to message headers from an external source -- e.g. a Spring {@link Message}
|
||||
* created to represent a STOMP message received from a STOMP client or message broker.
|
||||
* Native message headers are kept in a {@code Map<String, List<String>>} under the key
|
||||
* {@link #NATIVE_HEADERS}.
|
||||
* {@link MessageHeaderAccessor} sub-class that supports storage and access of
|
||||
* headers from an external source such as a message broker. Headers from the
|
||||
* external source are kept separate from other headers, in a sub-map under the
|
||||
* key {@link #NATIVE_HEADERS}. This allows separating processing headers from
|
||||
* headers that need to be sent to or received from the external source.
|
||||
*
|
||||
* <p>This class is not intended for direct use but is rather expected to be used
|
||||
* indirectly through protocol-specific sub-classes such as
|
||||
* {@link org.springframework.messaging.simp.stomp.StompHeaderAccessor StompHeaderAccessor}.
|
||||
* Such sub-classes may provide factory methods to translate message headers from
|
||||
* an external messaging source (e.g. STOMP) to Spring {@link Message} headers and
|
||||
* reversely to translate Spring {@link Message} headers to a message to send to an
|
||||
* external source.
|
||||
* <p>This class is likely to be used through indirectly through a protocol
|
||||
* specific sub-class that also provide factory methods to translate
|
||||
* message headers to an from an external messaging source.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
||||
|
||||
/**
|
||||
* The header name used to store native headers.
|
||||
*/
|
||||
/** The header name used to store native headers. */
|
||||
public static final String NATIVE_HEADERS = "nativeHeaders";
|
||||
|
||||
|
||||
/**
|
||||
* A protected constructor to create new headers.
|
||||
* Protected constructor to create a new instance.
|
||||
*/
|
||||
protected NativeMessageHeaderAccessor() {
|
||||
this((Map<String, List<String>>) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A protected constructor to create new headers.
|
||||
* Protected constructor to create an instance with the given native headers.
|
||||
* @param nativeHeaders native headers to create the message with (may be {@code null})
|
||||
*/
|
||||
protected NativeMessageHeaderAccessor(@Nullable Map<String, List<String>> nativeHeaders) {
|
||||
|
@ -73,7 +67,7 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
}
|
||||
|
||||
/**
|
||||
* A protected constructor accepting the headers of an existing message to copy.
|
||||
* Protected constructor that copies headers from another message.
|
||||
*/
|
||||
protected NativeMessageHeaderAccessor(@Nullable Message<?> message) {
|
||||
super(message);
|
||||
|
@ -88,6 +82,10 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sub-classes can use this method to access the "native" headers sub-map.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
protected Map<String, List<String>> getNativeHeaders() {
|
||||
|
@ -95,7 +93,7 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the native header values or an empty map.
|
||||
* Return a copy of the native headers sub-map, or an empty map.
|
||||
*/
|
||||
public Map<String, List<String>> toNativeHeaderMap() {
|
||||
Map<String, List<String>> map = getNativeHeaders();
|
||||
|
@ -124,8 +122,7 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return all values for the specified native header.
|
||||
* or {@code null} if none.
|
||||
* Return the values for the specified native header, if present.
|
||||
*/
|
||||
@Nullable
|
||||
public List<String> getNativeHeader(String headerName) {
|
||||
|
@ -134,8 +131,7 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the first value for the specified native header,
|
||||
* or {@code null} if none.
|
||||
* Return the first value for the specified native header, if present.
|
||||
*/
|
||||
@Nullable
|
||||
public String getFirstNativeHeader(String headerName) {
|
||||
|
@ -151,6 +147,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
|
||||
/**
|
||||
* Set the specified native header value replacing existing values.
|
||||
* <p>In order for this to work, the accessor must be {@link #isMutable()
|
||||
* mutable}. See {@link MessageHeaderAccessor} for details.
|
||||
*/
|
||||
public void setNativeHeader(String name, @Nullable String value) {
|
||||
Assert.state(isMutable(), "Already immutable");
|
||||
|
@ -176,6 +174,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
|
||||
/**
|
||||
* Add the specified native header value to existing values.
|
||||
* <p>In order for this to work, the accessor must be {@link #isMutable()
|
||||
* mutable}. See {@link MessageHeaderAccessor} for details.
|
||||
*/
|
||||
public void addNativeHeader(String name, @Nullable String value) {
|
||||
Assert.state(isMutable(), "Already immutable");
|
||||
|
@ -199,6 +199,11 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
|||
headers.forEach((key, values) -> values.forEach(value -> addNativeHeader(key, value)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified native header value replacing existing values.
|
||||
* <p>In order for this to work, the accessor must be {@link #isMutable()
|
||||
* mutable}. See {@link MessageHeaderAccessor} for details.
|
||||
*/
|
||||
@Nullable
|
||||
public List<String> removeNativeHeader(String name) {
|
||||
Assert.state(isMutable(), "Already immutable");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -112,8 +112,8 @@ public class NativeMessageHeaderAccessorTests {
|
|||
(Map<String, List<String>>) actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS);
|
||||
|
||||
assertThat(actualNativeHeaders).isNotNull();
|
||||
assertThat(actualNativeHeaders.get("foo")).isEqualTo(Arrays.asList("BAR"));
|
||||
assertThat(actualNativeHeaders.get("bar")).isEqualTo(Arrays.asList("baz"));
|
||||
assertThat(actualNativeHeaders.get("foo")).isEqualTo(Collections.singletonList("BAR"));
|
||||
assertThat(actualNativeHeaders.get("bar")).isEqualTo(Collections.singletonList("baz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -124,7 +124,7 @@ public class NativeMessageHeaderAccessorTests {
|
|||
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(nativeHeaders);
|
||||
headers.setNativeHeader("foo", "baz");
|
||||
|
||||
assertThat(headers.getNativeHeader("foo")).isEqualTo(Arrays.asList("baz"));
|
||||
assertThat(headers.getNativeHeader("foo")).isEqualTo(Collections.singletonList("baz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -143,7 +143,7 @@ public class NativeMessageHeaderAccessorTests {
|
|||
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
|
||||
headerAccessor.setNativeHeader("foo", "baz");
|
||||
|
||||
assertThat(headerAccessor.getNativeHeader("foo")).isEqualTo(Arrays.asList("baz"));
|
||||
assertThat(headerAccessor.getNativeHeader("foo")).isEqualTo(Collections.singletonList("baz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -161,8 +161,8 @@ public class NativeMessageHeaderAccessorTests {
|
|||
headerAccessor.setNativeHeader("foo", "bar");
|
||||
headerAccessor.setImmutable();
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
headerAccessor.setNativeHeader("foo", "baz"))
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> headerAccessor.setNativeHeader("foo", "baz"))
|
||||
.withMessageContaining("Already immutable");
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ public class NativeMessageHeaderAccessorTests {
|
|||
NativeMessageHeaderAccessor headers = new NativeMessageHeaderAccessor(nativeHeaders);
|
||||
headers.addNativeHeader("foo", null);
|
||||
|
||||
assertThat(headers.getNativeHeader("foo")).isEqualTo(Arrays.asList("bar"));
|
||||
assertThat(headers.getNativeHeader("foo")).isEqualTo(Collections.singletonList("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -193,7 +193,7 @@ public class NativeMessageHeaderAccessorTests {
|
|||
NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor();
|
||||
headerAccessor.addNativeHeader("foo", "bar");
|
||||
|
||||
assertThat(headerAccessor.getNativeHeader("foo")).isEqualTo(Arrays.asList("bar"));
|
||||
assertThat(headerAccessor.getNativeHeader("foo")).isEqualTo(Collections.singletonList("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -211,8 +211,8 @@ public class NativeMessageHeaderAccessorTests {
|
|||
headerAccessor.addNativeHeader("foo", "bar");
|
||||
headerAccessor.setImmutable();
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
headerAccessor.addNativeHeader("foo", "baz"))
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> headerAccessor.addNativeHeader("foo", "baz"))
|
||||
.withMessageContaining("Already immutable");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue