Introduce MessageHeader accessor types

A new type MessageHeaderAccesssor provides read/write access to
MessageHeaders along with typed getter/setter methods along the lines
of the existing MessageBuilder methods (internally MessageBuilder
merely delegates to MessageHeaderAccessor). This class is extensible
with sub-classes expected to provide typed getter/setter methods for
specific categories of message headers.

NativeMessageHeaderAccessor is one specific sub-class that further
provides read/write access to headers from some external message
source (e.g. STOMP headers). Native headers are stored in a separate
MultiValueMap and kept under a specific key.
This commit is contained in:
Rossen Stoyanchev
2013-06-25 16:31:52 -04:00
parent ac23832e4d
commit 486b4101ec
19 changed files with 653 additions and 643 deletions

View File

@@ -35,8 +35,8 @@ import org.apache.commons.logging.LogFactory;
/**
* The headers for a {@link Message}.<br>
* IMPORTANT: MessageHeaders are immutable. Any mutating operation (e.g., put(..), putAll(..) etc.)
* will result in {@link UnsupportedOperationException}
* IMPORTANT: This class is immutable. Any mutating operation (e.g., put(..), putAll(..) etc.)
* will throw {@link UnsupportedOperationException}
*
* <p>To create MessageHeaders instance use fluent MessageBuilder API
* <pre>
@@ -52,11 +52,10 @@ import org.apache.commons.logging.LogFactory;
*
* @author Arjen Poutsma
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 4.0
*/
public class MessageHeaders implements Map<String, Object>, Serializable {
public final class MessageHeaders implements Map<String, Object>, Serializable {
private static final long serialVersionUID = -4615750558355702881L;
@@ -74,26 +73,12 @@ public class MessageHeaders implements Map<String, Object>, Serializable {
public static final String TIMESTAMP = "timestamp";
public static final String CORRELATION_ID = "correlationId";
public static final String REPLY_CHANNEL = "replyChannel";
public static final String ERROR_CHANNEL = "errorChannel";
public static final String EXPIRATION_DATE = "expirationDate";
public static final String PRIORITY = "priority";
public static final String SEQUENCE_NUMBER = "sequenceNumber";
public static final String SEQUENCE_SIZE = "sequenceSize";
public static final String SEQUENCE_DETAILS = "sequenceDetails";
public static final String CONTENT_TYPE = "contentType";
public static final String POSTPROCESS_RESULT = "postProcessResult";
public static final List<String> HEADER_NAMES = Arrays.asList(ID, TIMESTAMP);
@@ -121,28 +106,6 @@ public class MessageHeaders implements Map<String, Object>, Serializable {
return this.get(TIMESTAMP, Long.class);
}
public Long getExpirationDate() {
return this.get(EXPIRATION_DATE, Long.class);
}
public Object getCorrelationId() {
return this.get(CORRELATION_ID);
}
public Integer getSequenceNumber() {
Integer sequenceNumber = this.get(SEQUENCE_NUMBER, Integer.class);
return (sequenceNumber != null ? sequenceNumber : 0);
}
public Integer getSequenceSize() {
Integer sequenceSize = this.get(SEQUENCE_SIZE, Integer.class);
return (sequenceSize != null ? sequenceSize : 0);
}
public Integer getPriority() {
return this.get(PRIORITY, Integer.class);
}
public Object getReplyChannel() {
return this.get(REPLY_CHANNEL);
}

View File

@@ -66,15 +66,11 @@ public class GenericMessage<T> implements Message<T>, Serializable {
else {
headers = new HashMap<String, Object>(headers);
}
this.headers = createMessageHeaders(headers);
this.headers = new MessageHeaders(headers);
this.payload = payload;
}
protected MessageHeaders createMessageHeaders(Map<String, Object> headers) {
return new MessageHeaders(headers);
}
public MessageHeaders getHeaders() {
return this.headers;
}

View File

@@ -16,41 +16,28 @@
package org.springframework.messaging.support;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* TODO
* A builder for creating {@link GenericMessage} or {@link ErrorMessage} if the payload is
* {@link Throwable}.
*
* @author Arjen Poutsma
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Dave Syer
* @since 4.0
*/
public final class MessageBuilder<T> {
private final T payload;
private final Map<String, Object> headers = new HashMap<String, Object>();
private final MessageHeaderAccesssor headerAccessor;
private final Message<T> originalMessage;
private volatile boolean modified;
/**
* Private constructor to be invoked from the static factory methods only.
@@ -59,15 +46,13 @@ public final class MessageBuilder<T> {
Assert.notNull(payload, "payload must not be null");
this.payload = payload;
this.originalMessage = originalMessage;
if (originalMessage != null) {
this.copyHeaders(originalMessage.getHeaders());
this.modified = (!this.payload.equals(originalMessage.getPayload()));
}
this.headerAccessor = new MessageHeaderAccesssor(originalMessage);
}
/**
* 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.
* 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
*/
@@ -88,248 +73,88 @@ public final class MessageBuilder<T> {
}
/**
* Set the value for the given header name. If the provided value is <code>null</code>, the header will be removed.
* Set the value for the given header name. If the provided value is <code>null</code>
* , the header will be removed.
*/
public MessageBuilder<T> setHeader(String headerName, Object headerValue) {
Assert.isTrue(!this.isReadOnly(headerName), "The '" + headerName + "' header is read-only.");
if (StringUtils.hasLength(headerName) && !headerName.equals(MessageHeaders.ID)
&& !headerName.equals(MessageHeaders.TIMESTAMP)) {
this.verifyType(headerName, headerValue);
if (headerValue == null) {
Object removedValue = this.headers.remove(headerName);
if (removedValue != null) {
this.modified = true;
}
}
else {
Object replacedValue = this.headers.put(headerName, headerValue);
if (!headerValue.equals(replacedValue)) {
this.modified = true;
}
}
}
this.headerAccessor.setHeader(headerName, headerValue);
return this;
}
/**
* Set the value for the given header name only if the header name is not already associated with a value.
* Set the value for the given header name only if the header name is not already
* associated with a value.
*/
public MessageBuilder<T> setHeaderIfAbsent(String headerName, Object headerValue) {
if (this.headers.get(headerName) == null) {
this.setHeader(headerName, headerValue);
}
this.headerAccessor.setHeaderIfAbsent(headerName, headerValue);
return this;
}
/**
* Removes all headers provided via array of 'headerPatterns'. As the name suggests the array
* may contain simple matching patterns for header names. Supported pattern styles are:
* "xxx*", "*xxx", "*xxx*" and "xxx*yyy".
*
* @param headerPatterns
* Removes all headers provided via array of 'headerPatterns'. As the name suggests
* the array may contain simple matching patterns for header names. Supported pattern
* styles are: "xxx*", "*xxx", "*xxx*" and "xxx*yyy".
*/
public MessageBuilder<T> removeHeaders(String... headerPatterns) {
List<String> headersToRemove = new ArrayList<String>();
for (String pattern : headerPatterns) {
if (StringUtils.hasLength(pattern)){
if (pattern.contains("*")){
for (String headerName : this.headers.keySet()) {
if (PatternMatchUtils.simpleMatch(pattern, headerName)){
headersToRemove.add(headerName);
}
}
}
else {
headersToRemove.add(pattern);
}
}
}
for (String headerToRemove : headersToRemove) {
this.removeHeader(headerToRemove);
}
this.headerAccessor.removeHeaders(headerPatterns);
return this;
}
/**
* Remove the value for the given header name.
*/
public MessageBuilder<T> removeHeader(String headerName) {
if (StringUtils.hasLength(headerName) && !headerName.equals(MessageHeaders.ID)
&& !headerName.equals(MessageHeaders.TIMESTAMP)) {
Object removedValue = this.headers.remove(headerName);
if (removedValue != null) {
this.modified = true;
}
}
this.headerAccessor.removeHeader(headerName);
return this;
}
/**
* Copy the name-value pairs from the provided Map. This operation will overwrite any existing values. Use {
* {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values. Note that the 'id' and 'timestamp' header values
* will never be overwritten.
*
* @see MessageHeaders#ID
* @see MessageHeaders#TIMESTAMP
* Copy the name-value pairs from the provided Map. This operation will overwrite any
* existing values. Use { {@link #copyHeadersIfAbsent(Map)} to avoid overwriting
* values. Note that the 'id' and 'timestamp' header values will never be overwritten.
*/
public MessageBuilder<T> copyHeaders(Map<String, ?> headersToCopy) {
Set<String> keys = headersToCopy.keySet();
for (String key : keys) {
if (!this.isReadOnly(key)) {
this.setHeader(key, headersToCopy.get(key));
}
}
this.headerAccessor.copyHeaders(headersToCopy);
return this;
}
/**
* Copy the name-value pairs from the provided Map. This operation will <em>not</em> overwrite any existing values.
* Copy the name-value pairs from the provided Map. This operation will <em>not</em>
* overwrite any existing values.
*/
public MessageBuilder<T> copyHeadersIfAbsent(Map<String, ?> headersToCopy) {
Set<String> keys = headersToCopy.keySet();
for (String key : keys) {
if (!this.isReadOnly(key)) {
this.setHeaderIfAbsent(key, headersToCopy.get(key));
}
}
return this;
}
public MessageBuilder<T> setExpirationDate(Long expirationDate) {
return this.setHeader(MessageHeaders.EXPIRATION_DATE, expirationDate);
}
public MessageBuilder<T> setExpirationDate(Date expirationDate) {
if (expirationDate != null) {
return this.setHeader(MessageHeaders.EXPIRATION_DATE, expirationDate.getTime());
}
else {
return this.setHeader(MessageHeaders.EXPIRATION_DATE, null);
}
}
public MessageBuilder<T> setCorrelationId(Object correlationId) {
return this.setHeader(MessageHeaders.CORRELATION_ID, correlationId);
}
public MessageBuilder<T> pushSequenceDetails(Object correlationId, int sequenceNumber, int sequenceSize) {
Object incomingCorrelationId = headers.get(MessageHeaders.CORRELATION_ID);
@SuppressWarnings("unchecked")
List<List<Object>> incomingSequenceDetails = (List<List<Object>>) headers.get(MessageHeaders.SEQUENCE_DETAILS);
if (incomingCorrelationId != null) {
if (incomingSequenceDetails == null) {
incomingSequenceDetails = new ArrayList<List<Object>>();
}
else {
incomingSequenceDetails = new ArrayList<List<Object>>(incomingSequenceDetails);
}
incomingSequenceDetails.add(Arrays.asList(incomingCorrelationId,
headers.get(MessageHeaders.SEQUENCE_NUMBER), headers.get(MessageHeaders.SEQUENCE_SIZE)));
incomingSequenceDetails = Collections.unmodifiableList(incomingSequenceDetails);
}
if (incomingSequenceDetails != null) {
setHeader(MessageHeaders.SEQUENCE_DETAILS, incomingSequenceDetails);
}
return setCorrelationId(correlationId).setSequenceNumber(sequenceNumber).setSequenceSize(sequenceSize);
}
public MessageBuilder<T> popSequenceDetails() {
String key = MessageHeaders.SEQUENCE_DETAILS;
if (!headers.containsKey(key)) {
return this;
}
@SuppressWarnings("unchecked")
List<List<Object>> incomingSequenceDetails = new ArrayList<List<Object>>((List<List<Object>>) headers.get(key));
List<Object> sequenceDetails = incomingSequenceDetails.remove(incomingSequenceDetails.size() - 1);
Assert.state(sequenceDetails.size() == 3, "Wrong sequence details (not created by MessageBuilder?): "
+ sequenceDetails);
setCorrelationId(sequenceDetails.get(0));
Integer sequenceNumber = (Integer) sequenceDetails.get(1);
Integer sequenceSize = (Integer) sequenceDetails.get(2);
if (sequenceNumber != null) {
setSequenceNumber(sequenceNumber);
}
if (sequenceSize != null) {
setSequenceSize(sequenceSize);
}
if (!incomingSequenceDetails.isEmpty()) {
headers.put(MessageHeaders.SEQUENCE_DETAILS, incomingSequenceDetails);
}
else {
headers.remove(MessageHeaders.SEQUENCE_DETAILS);
}
this.headerAccessor.copyHeadersIfAbsent(headersToCopy);
return this;
}
public MessageBuilder<T> setReplyChannel(MessageChannel replyChannel) {
return this.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel);
this.headerAccessor.setReplyChannel(replyChannel);
return this;
}
public MessageBuilder<T> setReplyChannelName(String replyChannelName) {
return this.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannelName);
this.headerAccessor.setReplyChannelName(replyChannelName);
return this;
}
public MessageBuilder<T> setErrorChannel(MessageChannel errorChannel) {
return this.setHeader(MessageHeaders.ERROR_CHANNEL, errorChannel);
this.headerAccessor.setErrorChannel(errorChannel);
return this;
}
public MessageBuilder<T> setErrorChannelName(String errorChannelName) {
return this.setHeader(MessageHeaders.ERROR_CHANNEL, errorChannelName);
}
public MessageBuilder<T> setSequenceNumber(Integer sequenceNumber) {
return this.setHeader(MessageHeaders.SEQUENCE_NUMBER, sequenceNumber);
}
public MessageBuilder<T> setSequenceSize(Integer sequenceSize) {
return this.setHeader(MessageHeaders.SEQUENCE_SIZE, sequenceSize);
}
public MessageBuilder<T> setPriority(Integer priority) {
return this.setHeader(MessageHeaders.PRIORITY, priority);
this.headerAccessor.setErrorChannelName(errorChannelName);
return this;
}
@SuppressWarnings("unchecked")
public Message<T> build() {
if (!this.modified && this.originalMessage != null) {
if ((this.originalMessage != null) && !this.headerAccessor.isModified()) {
return this.originalMessage;
}
if (this.payload instanceof Throwable) {
return (Message<T>) new ErrorMessage((Throwable) this.payload, this.headers);
}
return new GenericMessage<T>(this.payload, this.headers);
}
private boolean isReadOnly(String headerName) {
return MessageHeaders.ID.equals(headerName) || MessageHeaders.TIMESTAMP.equals(headerName);
}
private void verifyType(String headerName, Object headerValue) {
if (headerName != null && headerValue != null) {
if (MessageHeaders.ID.equals(headerName)) {
Assert.isTrue(headerValue instanceof UUID, "The '" + headerName + "' header value must be a UUID.");
}
else if (MessageHeaders.TIMESTAMP.equals(headerName)) {
Assert.isTrue(headerValue instanceof Long, "The '" + headerName + "' header value must be a Long.");
}
else if (MessageHeaders.EXPIRATION_DATE.equals(headerName)) {
Assert.isTrue(headerValue instanceof Date || headerValue instanceof Long, "The '" + headerName
+ "' header value must be a Date or Long.");
}
else if (MessageHeaders.ERROR_CHANNEL.equals(headerName)
|| MessageHeaders.REPLY_CHANNEL.endsWith(headerName)) {
Assert.isTrue(headerValue instanceof MessageChannel || headerValue instanceof String, "The '"
+ headerName + "' header value must be a MessageChannel or String.");
}
else if (MessageHeaders.SEQUENCE_NUMBER.equals(headerName)
|| MessageHeaders.SEQUENCE_SIZE.equals(headerName)) {
Assert.isTrue(Integer.class.isAssignableFrom(headerValue.getClass()), "The '" + headerName
+ "' header value must be an Integer.");
}
else if (MessageHeaders.PRIORITY.equals(headerName)) {
Assert.isTrue(Integer.class.isAssignableFrom(headerValue.getClass()), "The '" + headerName
+ "' header value must be an Integer.");
}
return (Message<T>) new ErrorMessage((Throwable) this.payload, this.headerAccessor.toMap());
}
return new GenericMessage<T>(this.payload, this.headerAccessor.toMap());
}
}

View File

@@ -0,0 +1,219 @@
/*
* Copyright 2002-2013 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.messaging.support;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* A base class for read/write access to {@link MessageHeaders}. Supports creation of new
* headers or modification of existing message headers.
* <p>
* Sub-classes can provide additinoal typed getters and setters for convenient access to
* specific headers. Getters and setters should delegate to {@link #getHeader(String)} or
* {@link #setHeader(String, Object)} respectively. At the end {@link #toMap()} can be
* used to obtain the resulting headers.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class MessageHeaderAccesssor {
protected Log logger = LogFactory.getLog(getClass());
// wrapped read-only message headers
private final MessageHeaders originalHeaders;
// header updates
private final Map<String, Object> headers = new HashMap<String, Object>(4);
/**
* A constructor for creating new message headers.
*/
public MessageHeaderAccesssor() {
this.originalHeaders = null;
}
/**
* A constructor for accessing and modifying existing message headers.
*/
public MessageHeaderAccesssor(Message<?> message) {
this.originalHeaders = (message != null) ? message.getHeaders() : null;
}
/**
* Return a header map including original, wrapped headers (if any) plus additional
* header updates made through accessor methods.
*/
public Map<String, Object> toMap() {
Map<String, Object> result = new HashMap<String, Object>();
if (this.originalHeaders != null) {
result.putAll(this.originalHeaders);
}
for (String key : this.headers.keySet()) {
Object value = this.headers.get(key);
if (value == null) {
result.remove(key);
}
else {
result.put(key, value);
}
}
return result;
}
public boolean isModified() {
return (!this.headers.isEmpty());
}
public Object getHeader(String headerName) {
if (this.headers.containsKey(headerName)) {
return this.headers.get(headerName);
}
else if (this.originalHeaders != null) {
return this.originalHeaders.get(headerName);
}
return null;
}
/**
* Set the value for the given header name. If the provided value is {@code null} the
* header will be removed.
*/
public void setHeader(String name, Object value) {
Assert.isTrue(!isReadOnly(name), "The '" + name + "' header is read-only.");
if (!ObjectUtils.nullSafeEquals(value, getHeader(name))) {
this.headers.put(name, value);
}
}
protected boolean isReadOnly(String headerName) {
return MessageHeaders.ID.equals(headerName) || MessageHeaders.TIMESTAMP.equals(headerName);
}
/**
* Set the value for the given header name only if the header name is not already associated with a value.
*/
public void setHeaderIfAbsent(String name, Object value) {
if (getHeader(name) == null) {
setHeader(name, value);
}
}
/**
* Removes all headers provided via array of 'headerPatterns'. As the name suggests
* the array may contain simple matching patterns for header names. Supported pattern
* styles are: "xxx*", "*xxx", "*xxx*" and "xxx*yyy".
*/
public void removeHeaders(String... headerPatterns) {
List<String> headersToRemove = new ArrayList<String>();
for (String pattern : headerPatterns) {
if (StringUtils.hasLength(pattern)){
if (pattern.contains("*")){
for (String headerName : this.headers.keySet()) {
if (PatternMatchUtils.simpleMatch(pattern, headerName)){
headersToRemove.add(headerName);
}
}
}
else {
headersToRemove.add(pattern);
}
}
}
for (String headerToRemove : headersToRemove) {
removeHeader(headerToRemove);
}
}
/**
* Remove the value for the given header name.
*/
public void removeHeader(String headerName) {
if (StringUtils.hasLength(headerName) && !isReadOnly(headerName)) {
setHeader(headerName, null);
}
}
/**
* Copy the name-value pairs from the provided Map. This operation will overwrite any
* existing values. Use { {@link #copyHeadersIfAbsent(Map)} to avoid overwriting
* values.
*/
public void copyHeaders(Map<String, ?> headersToCopy) {
Set<String> keys = headersToCopy.keySet();
for (String key : keys) {
if (!isReadOnly(key)) {
setHeader(key, headersToCopy.get(key));
}
}
}
/**
* Copy the name-value pairs from the provided Map. This operation will <em>not</em>
* overwrite any existing values.
*/
public void copyHeadersIfAbsent(Map<String, ?> headersToCopy) {
Set<String> keys = headersToCopy.keySet();
for (String key : keys) {
if (!this.isReadOnly(key)) {
setHeaderIfAbsent(key, headersToCopy.get(key));
}
}
}
public void setReplyChannel(MessageChannel replyChannel) {
setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel);
}
public void setReplyChannelName(String replyChannelName) {
setHeader(MessageHeaders.REPLY_CHANNEL, replyChannelName);
}
public void setErrorChannel(MessageChannel errorChannel) {
setHeader(MessageHeaders.ERROR_CHANNEL, errorChannel);
}
public void setErrorChannelName(String errorChannelName) {
setHeader(MessageHeaders.ERROR_CHANNEL, errorChannelName);
}
@Override
public String toString() {
return getClass().getSimpleName() + " [originalHeaders=" + this.originalHeaders
+ ", updated headers=" + this.headers + "]";
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2002-2013 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.messaging.support;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.messaging.Message;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
/**
* An extension of {@link MessageHeaderAccesssor} that also provides read/write access to
* message headers from an external message source. Native message headers are kept
* in a {@link MultiValueMap} under the key {@link #NATIVE_HEADERS}.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class NativeMessageHeaderAccessor extends MessageHeaderAccesssor {
public static final String NATIVE_HEADERS = "nativeHeaders";
// wrapped native headers
private final Map<String, List<String>> originalNativeHeaders;
// native header updates
private final MultiValueMap<String, String> nativeHeaders = new LinkedMultiValueMap<String, String>(4);
/**
* A constructor for creating new headers, accepting an optional native header map.
*/
public NativeMessageHeaderAccessor(Map<String, List<String>> nativeHeaders) {
super();
this.originalNativeHeaders = nativeHeaders;
}
/**
* A constructor for accessing and modifying existing message headers.
*/
public NativeMessageHeaderAccessor(Message<?> message) {
super(message);
this.originalNativeHeaders = initNativeHeaders(message);
}
private static Map<String, List<String>> initNativeHeaders(Message<?> message) {
if (message != null) {
@SuppressWarnings("unchecked")
Map<String, List<String>> headers = (Map<String, List<String>>) message.getHeaders().get(NATIVE_HEADERS);
if (headers != null) {
return headers;
}
}
return null;
}
@Override
public Map<String, Object> toMap() {
Map<String, Object> result = super.toMap();
result.put(NATIVE_HEADERS, toNativeHeaderMap());
return result;
}
@Override
public boolean isModified() {
return (super.isModified() || (!this.nativeHeaders.isEmpty()));
}
/**
* Return a map with native headers including original, wrapped headers (if any) plus
* additional header updates made through accessor methods.
*/
public Map<String, List<String>> toNativeHeaderMap() {
Map<String, List<String>> result = new HashMap<String, List<String>>();
if (this.originalNativeHeaders != null) {
result.putAll(this.originalNativeHeaders);
}
for (String key : this.nativeHeaders.keySet()) {
List<String> value = this.nativeHeaders.get(key);
if (value == null) {
result.remove(key);
}
else {
result.put(key, value);
}
}
return result;
}
protected List<String> getNativeHeader(String headerName) {
if (this.nativeHeaders.containsKey(headerName)) {
return this.nativeHeaders.get(headerName);
}
else if (this.originalNativeHeaders != null) {
return this.originalNativeHeaders.get(headerName);
}
return null;
}
protected String getFirstNativeHeader(String headerName) {
List<String> values = getNativeHeader(headerName);
return CollectionUtils.isEmpty(values) ? null : values.get(0);
}
/**
* Set the value for the given header name. If the provided value is {@code null} the
* header will be removed.
*/
protected void putNativeHeader(String name, List<String> value) {
if (!ObjectUtils.nullSafeEquals(value, getHeader(name))) {
this.nativeHeaders.put(name, value);
}
}
protected void setNativeHeader(String name, String value) {
this.nativeHeaders.set(name, value);
}
}