diff --git a/build.gradle b/build.gradle index 2aa23ebcb0..3fdd181b0b 100644 --- a/build.gradle +++ b/build.gradle @@ -490,7 +490,7 @@ project("spring-websocket") { repositories { maven { url "https://repository.apache.org/content/repositories/snapshots" } // tomcat-websocket-* snapshots maven { url "https://maven.java.net/content/repositories/releases" } // javax.websocket, tyrus - mavenLocal() // temporary workaround for locally installed (latest) reactor + mavenLocal() // temporary workaround for locally installed (latest) reactor maven { url 'http://repo.springsource.org/snapshot' } // reactor } } diff --git a/spring-context/src/main/java/org/springframework/messaging/MessageHeaders.java b/spring-context/src/main/java/org/springframework/messaging/MessageHeaders.java index c9c25fa20c..10be582c6f 100644 --- a/spring-context/src/main/java/org/springframework/messaging/MessageHeaders.java +++ b/spring-context/src/main/java/org/springframework/messaging/MessageHeaders.java @@ -35,8 +35,8 @@ import org.apache.commons.logging.LogFactory; /** * The headers for a {@link Message}.
- * 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} * *

To create MessageHeaders instance use fluent MessageBuilder API *

@@ -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, Serializable {
+public final class MessageHeaders implements Map, Serializable {
 
 	private static final long serialVersionUID = -4615750558355702881L;
 
@@ -74,26 +73,12 @@ public class MessageHeaders implements Map, 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 HEADER_NAMES = Arrays.asList(ID, TIMESTAMP);
 
@@ -121,28 +106,6 @@ public class MessageHeaders implements Map, 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);
     }
diff --git a/spring-context/src/main/java/org/springframework/messaging/support/GenericMessage.java b/spring-context/src/main/java/org/springframework/messaging/support/GenericMessage.java
index 7c0b6ada2a..ebbb972890 100644
--- a/spring-context/src/main/java/org/springframework/messaging/support/GenericMessage.java
+++ b/spring-context/src/main/java/org/springframework/messaging/support/GenericMessage.java
@@ -66,15 +66,11 @@ public class GenericMessage implements Message, Serializable {
 		else {
 			headers = new HashMap(headers);
 		}
-		this.headers = createMessageHeaders(headers);
+		this.headers = new MessageHeaders(headers);
 		this.payload = payload;
 	}
 
 
-	protected MessageHeaders createMessageHeaders(Map headers) {
-		return new MessageHeaders(headers);
-	}
-
 	public MessageHeaders getHeaders() {
 		return this.headers;
 	}
diff --git a/spring-context/src/main/java/org/springframework/messaging/support/MessageBuilder.java b/spring-context/src/main/java/org/springframework/messaging/support/MessageBuilder.java
index 90e529a7e2..2790951003 100644
--- a/spring-context/src/main/java/org/springframework/messaging/support/MessageBuilder.java
+++ b/spring-context/src/main/java/org/springframework/messaging/support/MessageBuilder.java
@@ -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 {
 
 	private final T payload;
 
-	private final Map headers = new HashMap();
+	private final MessageHeaderAccesssor headerAccessor;
 
 	private final Message originalMessage;
 
-	private volatile boolean modified;
 
 	/**
 	 * Private constructor to be invoked from the static factory methods only.
@@ -59,15 +46,13 @@ public final class MessageBuilder {
 		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 {
 	}
 
 	/**
-	 * Set the value for the given header name. If the provided value is null, the header will be removed.
+	 * Set the value for the given header name. If the provided value is null
+	 * , the header will be removed.
 	 */
 	public MessageBuilder 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 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 removeHeaders(String... headerPatterns) {
-		List headersToRemove = new ArrayList();
-		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 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 copyHeaders(Map headersToCopy) {
-		Set 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 not overwrite any existing values.
+	 * Copy the name-value pairs from the provided Map. This operation will not
+	 * overwrite any existing values.
 	 */
 	public MessageBuilder copyHeadersIfAbsent(Map headersToCopy) {
-		Set keys = headersToCopy.keySet();
-		for (String key : keys) {
-			if (!this.isReadOnly(key)) {
-				this.setHeaderIfAbsent(key, headersToCopy.get(key));
-			}
-		}
-		return this;
-	}
-
-	public MessageBuilder setExpirationDate(Long expirationDate) {
-		return this.setHeader(MessageHeaders.EXPIRATION_DATE, expirationDate);
-	}
-
-	public MessageBuilder setExpirationDate(Date expirationDate) {
-		if (expirationDate != null) {
-			return this.setHeader(MessageHeaders.EXPIRATION_DATE, expirationDate.getTime());
-		}
-		else {
-			return this.setHeader(MessageHeaders.EXPIRATION_DATE, null);
-		}
-	}
-
-	public MessageBuilder setCorrelationId(Object correlationId) {
-		return this.setHeader(MessageHeaders.CORRELATION_ID, correlationId);
-	}
-
-	public MessageBuilder pushSequenceDetails(Object correlationId, int sequenceNumber, int sequenceSize) {
-		Object incomingCorrelationId = headers.get(MessageHeaders.CORRELATION_ID);
-		@SuppressWarnings("unchecked")
-		List> incomingSequenceDetails = (List>) headers.get(MessageHeaders.SEQUENCE_DETAILS);
-		if (incomingCorrelationId != null) {
-			if (incomingSequenceDetails == null) {
-				incomingSequenceDetails = new ArrayList>();
-			}
-			else {
-				incomingSequenceDetails = new ArrayList>(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 popSequenceDetails() {
-		String key = MessageHeaders.SEQUENCE_DETAILS;
-		if (!headers.containsKey(key)) {
-			return this;
-		}
-		@SuppressWarnings("unchecked")
-		List> incomingSequenceDetails = new ArrayList>((List>) headers.get(key));
-		List 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 setReplyChannel(MessageChannel replyChannel) {
-		return this.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel);
+		this.headerAccessor.setReplyChannel(replyChannel);
+		return this;
 	}
 
 	public MessageBuilder setReplyChannelName(String replyChannelName) {
-		return this.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannelName);
+		this.headerAccessor.setReplyChannelName(replyChannelName);
+		return this;
 	}
 
 	public MessageBuilder setErrorChannel(MessageChannel errorChannel) {
-		return this.setHeader(MessageHeaders.ERROR_CHANNEL, errorChannel);
+		this.headerAccessor.setErrorChannel(errorChannel);
+		return this;
 	}
 
 	public MessageBuilder setErrorChannelName(String errorChannelName) {
-		return this.setHeader(MessageHeaders.ERROR_CHANNEL, errorChannelName);
-	}
-
-	public MessageBuilder setSequenceNumber(Integer sequenceNumber) {
-		return this.setHeader(MessageHeaders.SEQUENCE_NUMBER, sequenceNumber);
-	}
-
-	public MessageBuilder setSequenceSize(Integer sequenceSize) {
-		return this.setHeader(MessageHeaders.SEQUENCE_SIZE, sequenceSize);
-	}
-
-	public MessageBuilder setPriority(Integer priority) {
-		return this.setHeader(MessageHeaders.PRIORITY, priority);
+		this.headerAccessor.setErrorChannelName(errorChannelName);
+		return this;
 	}
 
 	@SuppressWarnings("unchecked")
 	public Message build() {
-		if (!this.modified && this.originalMessage != null) {
+		if ((this.originalMessage != null) && !this.headerAccessor.isModified()) {
 			return this.originalMessage;
 		}
 		if (this.payload instanceof Throwable) {
-			return (Message) new ErrorMessage((Throwable) this.payload, this.headers);
-		}
-		return new GenericMessage(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) new ErrorMessage((Throwable) this.payload, this.headerAccessor.toMap());
 		}
+		return new GenericMessage(this.payload, this.headerAccessor.toMap());
 	}
 
 }
diff --git a/spring-context/src/main/java/org/springframework/messaging/support/MessageHeaderAccesssor.java b/spring-context/src/main/java/org/springframework/messaging/support/MessageHeaderAccesssor.java
new file mode 100644
index 0000000000..25d380b1c8
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/messaging/support/MessageHeaderAccesssor.java
@@ -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.
+ * 

+ * 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 headers = new HashMap(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 toMap() { + Map result = new HashMap(); + 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 headersToRemove = new ArrayList(); + 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 headersToCopy) { + Set 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 not + * overwrite any existing values. + */ + public void copyHeadersIfAbsent(Map headersToCopy) { + Set 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 + "]"; + } + +} diff --git a/spring-context/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java b/spring-context/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java new file mode 100644 index 0000000000..800cecc1bb --- /dev/null +++ b/spring-context/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java @@ -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> originalNativeHeaders; + + // native header updates + private final MultiValueMap nativeHeaders = new LinkedMultiValueMap(4); + + + /** + * A constructor for creating new headers, accepting an optional native header map. + */ + public NativeMessageHeaderAccessor(Map> 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> initNativeHeaders(Message message) { + if (message != null) { + @SuppressWarnings("unchecked") + Map> headers = (Map>) message.getHeaders().get(NATIVE_HEADERS); + if (headers != null) { + return headers; + } + } + return null; + } + + + @Override + public Map toMap() { + Map 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> toNativeHeaderMap() { + Map> result = new HashMap>(); + if (this.originalNativeHeaders != null) { + result.putAll(this.originalNativeHeaders); + } + for (String key : this.nativeHeaders.keySet()) { + List value = this.nativeHeaders.get(key); + if (value == null) { + result.remove(key); + } + else { + result.put(key, value); + } + } + return result; + } + + protected List 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 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 value) { + if (!ObjectUtils.nullSafeEquals(value, getHeader(name))) { + this.nativeHeaders.put(name, value); + } + } + + protected void setNativeHeader(String name, String value) { + this.nativeHeaders.set(name, value); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractPubSubMessageHandler.java b/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractPubSubMessageHandler.java index 512466579e..48d972fbeb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractPubSubMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/service/AbstractPubSubMessageHandler.java @@ -30,7 +30,7 @@ import org.springframework.util.AntPathMatcher; import org.springframework.util.CollectionUtils; import org.springframework.util.PathMatcher; import org.springframework.web.messaging.MessageType; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; /** @@ -80,7 +80,7 @@ public abstract class AbstractPubSubMessageHandler implements MessageHandler { protected boolean isDestinationAllowed(Message message) { - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(message); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message); String destination = headers.getDestination(); if (destination == null) { @@ -116,7 +116,7 @@ public abstract class AbstractPubSubMessageHandler implements MessageHandler { @Override public final void handleMessage(Message message) throws MessagingException { - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(message); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message); MessageType messageType = headers.getMessageType(); if (!canHandle(message, messageType)) { diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/service/ReactorPubSubMessageHandler.java b/spring-websocket/src/main/java/org/springframework/web/messaging/service/ReactorPubSubMessageHandler.java index 8cf3ef3da9..1d2d54f497 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/service/ReactorPubSubMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/service/ReactorPubSubMessageHandler.java @@ -31,7 +31,7 @@ import org.springframework.web.messaging.MessageType; import org.springframework.web.messaging.PubSubChannelRegistry; import org.springframework.web.messaging.converter.CompositeMessageConverter; import org.springframework.web.messaging.converter.MessageConverter; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; import reactor.core.Reactor; import reactor.fn.Consumer; @@ -78,7 +78,7 @@ public class ReactorPubSubMessageHandler extends AbstractPubSubMessageHandler { logger.debug("Subscribe " + message); } - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(message); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message); String subscriptionId = headers.getSubscriptionId(); BroadcastingConsumer consumer = new BroadcastingConsumer(subscriptionId); @@ -107,7 +107,7 @@ public class ReactorPubSubMessageHandler extends AbstractPubSubMessageHandler { try { // Convert to byte[] payload before the fan-out - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(message); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message); byte[] payload = payloadConverter.convertToPayload(message.getPayload(), headers.getContentType()); Message m = MessageBuilder.withPayload(payload).copyHeaders(message.getHeaders()).build(); @@ -120,7 +120,7 @@ public class ReactorPubSubMessageHandler extends AbstractPubSubMessageHandler { @Override public void handleDisconnect(Message message) { - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(message); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message); removeSubscriptions(headers.getSessionId()); } @@ -149,11 +149,11 @@ public class ReactorPubSubMessageHandler extends AbstractPubSubMessageHandler { Message sentMessage = event.getData(); - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(sentMessage); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(sentMessage); headers.setSubscriptionId(this.subscriptionId); Message clientMessage = MessageBuilder.withPayload( - sentMessage.getPayload()).copyHeaders(headers.toHeaders()).build(); + sentMessage.getPayload()).copyHeaders(headers.toMap()).build(); clientChannel.send(clientMessage); } diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/AnnotationPubSubMessageHandler.java b/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/AnnotationPubSubMessageHandler.java index 04461a7b93..1c5c434ad1 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/AnnotationPubSubMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/AnnotationPubSubMessageHandler.java @@ -45,7 +45,7 @@ import org.springframework.web.messaging.annotation.UnsubscribeEvent; import org.springframework.web.messaging.converter.MessageConverter; import org.springframework.web.messaging.service.AbstractPubSubMessageHandler; import org.springframework.web.messaging.support.MessageHolder; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; @@ -185,7 +185,7 @@ public class AnnotationPubSubMessageHandler extends AbstractPubSubMessageHandler private void handleMessageInternal(final Message message, Map handlerMethods) { - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(message); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message); String destination = headers.getDestination(); HandlerMethod match = getHandlerMethod(destination, handlerMethods); diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageBodyArgumentResolver.java b/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageBodyArgumentResolver.java index a8cc3a03ae..ec8d88a9dc 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageBodyArgumentResolver.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageBodyArgumentResolver.java @@ -25,7 +25,7 @@ import org.springframework.web.messaging.annotation.MessageBody; import org.springframework.web.messaging.converter.CompositeMessageConverter; import org.springframework.web.messaging.converter.MessageConversionException; import org.springframework.web.messaging.converter.MessageConverter; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; /** @@ -52,7 +52,7 @@ public class MessageBodyArgumentResolver implements ArgumentResolver { Object arg = null; MessageBody annot = parameter.getParameterAnnotation(MessageBody.class); - MediaType contentType = (MediaType) message.getHeaders().get(PubSubHeaderAccesssor.CONTENT_TYPE); + MediaType contentType = (MediaType) message.getHeaders().get(WebMessageHeaderAccesssor.CONTENT_TYPE); if (annot == null || annot.required()) { Class sourceType = message.getPayload().getClass(); diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageReturnValueHandler.java b/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageReturnValueHandler.java index f5009775df..85e01e88b9 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageReturnValueHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/service/method/MessageReturnValueHandler.java @@ -21,7 +21,7 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.Assert; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; /** @@ -66,10 +66,10 @@ public class MessageReturnValueHandler implements ReturnValueHandler { return; } - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.wrap(message); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.wrap(message); Assert.notNull(headers.getSubscriptionId(), "No subscription id: " + message); - PubSubHeaderAccesssor returnHeaders = PubSubHeaderAccesssor.wrap(returnMessage); + WebMessageHeaderAccesssor returnHeaders = WebMessageHeaderAccesssor.wrap(returnMessage); returnHeaders.setSessionId(headers.getSessionId()); returnHeaders.setSubscriptionId(headers.getSubscriptionId()); @@ -78,7 +78,7 @@ public class MessageReturnValueHandler implements ReturnValueHandler { } returnMessage = MessageBuilder.withPayload( - returnMessage.getPayload()).copyHeaders(returnHeaders.toHeaders()).build(); + returnMessage.getPayload()).copyHeaders(returnHeaders.toMap()).build(); this.clientChannel.send(returnMessage); } diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompHeaderAccessor.java b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompHeaderAccessor.java index 54886206e6..9899abca20 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompHeaderAccessor.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompHeaderAccessor.java @@ -16,8 +16,8 @@ package org.springframework.web.messaging.stomp.support; +import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -26,12 +26,9 @@ import java.util.concurrent.atomic.AtomicLong; import org.springframework.http.MediaType; import org.springframework.messaging.Message; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.messaging.stomp.StompCommand; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; /** @@ -39,13 +36,13 @@ import org.springframework.web.messaging.support.PubSubHeaderAccesssor; * STOMP-specific headers of an existing message. *

* Use one of the static factory method in this class, then call getters and setters, and - * at the end if necessary call {@link #toHeaders()} to obtain the updated headers - * or call {@link #toStompMessageHeaders()} to obtain only the STOMP-specific headers. + * at the end if necessary call {@link #toMap()} to obtain the updated headers + * or call {@link #toNativeHeaderMap()} to obtain only the STOMP-specific headers. * * @author Rossen Stoyanchev * @since 4.0 */ -public class StompHeaderAccessor extends PubSubHeaderAccesssor { +public class StompHeaderAccessor extends WebMessageHeaderAccesssor { public static final String STOMP_ID = "id"; @@ -80,55 +77,40 @@ public class StompHeaderAccessor extends PubSubHeaderAccesssor { public static final String HEARTBEAT = "heart-beat"; - private static final String STOMP_HEADERS = "stompHeaders"; - private static final AtomicLong messageIdCounter = new AtomicLong(); - private final Map headers; - - /** * A constructor for creating new STOMP message headers. - * This constructor is private. See factory methods in this sub-classes. */ private StompHeaderAccessor(StompCommand command, Map> externalSourceHeaders) { super(command.getMessageType(), command, externalSourceHeaders); - this.headers = new HashMap(4); - updateMessageHeaders(); + initWebMessageHeaders(); } - private void updateMessageHeaders() { - if (getExternalSourceHeaders().isEmpty()) { - return; - } - String destination = getHeaderValue(DESTINATION); + private void initWebMessageHeaders() { + String destination = getFirstNativeHeader(DESTINATION); if (destination != null) { super.setDestination(destination); } - String contentType = getHeaderValue(CONTENT_TYPE); + String contentType = getFirstNativeHeader(CONTENT_TYPE); if (contentType != null) { super.setContentType(MediaType.parseMediaType(contentType)); } if (StompCommand.SUBSCRIBE.equals(getStompCommand())) { - if (getHeaderValue(STOMP_ID) != null) { - super.setSubscriptionId(getHeaderValue(STOMP_ID)); + if (getFirstNativeHeader(STOMP_ID) != null) { + super.setSubscriptionId(getFirstNativeHeader(STOMP_ID)); } } } /** - * A constructor for accessing and modifying existing message headers. This - * constructor is protected. See factory methods in this class. + * A constructor for accessing and modifying existing message headers. */ - @SuppressWarnings("unchecked") private StompHeaderAccessor(Message message) { super(message); - this.headers = (message.getHeaders() .get(STOMP_HEADERS) != null) ? - (Map) message.getHeaders().get(STOMP_HEADERS) : new HashMap(4); } - /** * Create {@link StompHeaderAccessor} for a new {@link Message}. */ @@ -152,53 +134,35 @@ public class StompHeaderAccessor extends PubSubHeaderAccesssor { /** - * Return the original, wrapped headers (i.e. unmodified) or a new Map including any - * updates made via setters. + * Return STOMP headers including original, wrapped STOMP headers (if any) plus + * additional header updates made through accessor methods. */ @Override - public Map toHeaders() { - Map result = super.toHeaders(); - if (isModified()) { - result.put(STOMP_HEADERS, this.headers); - } - return result; - } + public Map> toNativeHeaderMap() { - @Override - public boolean isModified() { - return (super.isModified() || !this.headers.isEmpty()); - } - - /** - * Return STOMP headers and any custom headers that may have been sent by - * a remote endpoint, if this message originated from outside. - */ - public Map> toStompMessageHeaders() { - - MultiValueMap result = new LinkedMultiValueMap(); - result.putAll(getExternalSourceHeaders()); - result.setAll(this.headers); + Map> result = super.toNativeHeaderMap(); String destination = super.getDestination(); if (destination != null) { - result.set(DESTINATION, destination); + result.put(DESTINATION, Arrays.asList(destination)); } MediaType contentType = getContentType(); if (contentType != null) { - result.set(CONTENT_TYPE, contentType.toString()); + result.put(CONTENT_TYPE, Arrays.asList(contentType.toString())); } if (StompCommand.MESSAGE.equals(getStompCommand())) { String subscriptionId = getSubscriptionId(); if (subscriptionId != null) { - result.set(SUBSCRIPTION, subscriptionId); + result.put(SUBSCRIPTION, Arrays.asList(subscriptionId)); } else { logger.warn("STOMP MESSAGE frame should have a subscription: " + this.toString()); } if ((getMessageId() == null)) { - result.set(MESSAGE_ID, getSessionId() + "-" + messageIdCounter.getAndIncrement()); + String messageId = getSessionId() + "-" + messageIdCounter.getAndIncrement(); + result.put(MESSAGE_ID, Arrays.asList(messageId)); } } @@ -216,42 +180,37 @@ public class StompHeaderAccessor extends PubSubHeaderAccesssor { } public Set getAcceptVersion() { - String rawValue = getHeaderValue(ACCEPT_VERSION); + String rawValue = getFirstNativeHeader(ACCEPT_VERSION); return (rawValue != null) ? StringUtils.commaDelimitedListToSet(rawValue) : Collections.emptySet(); } - private String getHeaderValue(String headerName) { - List values = getExternalSourceHeaders().get(headerName); - return !CollectionUtils.isEmpty(values) ? values.get(0) : this.headers.get(headerName); - } - public void setAcceptVersion(String acceptVersion) { - this.headers.put(ACCEPT_VERSION, acceptVersion); + setNativeHeader(ACCEPT_VERSION, acceptVersion); } public void setHost(String host) { - this.headers.put(HOST, host); + setNativeHeader(HOST, host); } public String getHost() { - return getHeaderValue(HOST); + return getFirstNativeHeader(HOST); } @Override public void setDestination(String destination) { super.setDestination(destination); - this.headers.put(DESTINATION, destination); + setNativeHeader(DESTINATION, destination); } @Override public void setDestinations(List destinations) { Assert.isTrue((destinations != null) && (destinations.size() == 1), "STOMP allows one destination per message"); super.setDestinations(destinations); - this.headers.put(DESTINATION, destinations.get(0)); + setNativeHeader(DESTINATION, destinations.get(0)); } public long[] getHeartbeat() { - String rawValue = getHeaderValue(HEARTBEAT); + String rawValue = getFirstNativeHeader(HEARTBEAT); if (!StringUtils.hasText(rawValue)) { return null; } @@ -263,99 +222,91 @@ public class StompHeaderAccessor extends PubSubHeaderAccesssor { public void setContentType(MediaType mediaType) { if (mediaType != null) { super.setContentType(mediaType); - this.headers.put(CONTENT_TYPE, mediaType.toString()); + setNativeHeader(CONTENT_TYPE, mediaType.toString()); } } public MediaType getContentType() { - String value = getHeaderValue(CONTENT_TYPE); + String value = getFirstNativeHeader(CONTENT_TYPE); return (value != null) ? MediaType.parseMediaType(value) : null; } public Integer getContentLength() { - String contentLength = getHeaderValue(CONTENT_LENGTH); + String contentLength = getFirstNativeHeader(CONTENT_LENGTH); return StringUtils.hasText(contentLength) ? new Integer(contentLength) : null; } public void setContentLength(int contentLength) { - this.headers.put(CONTENT_LENGTH, String.valueOf(contentLength)); + setNativeHeader(CONTENT_LENGTH, String.valueOf(contentLength)); } public void setHeartbeat(long cx, long cy) { - this.headers.put(HEARTBEAT, StringUtils.arrayToCommaDelimitedString(new Object[] {cx, cy})); + setNativeHeader(HEARTBEAT, StringUtils.arrayToCommaDelimitedString(new Object[] {cx, cy})); } public void setAck(String ack) { - this.headers.put(ACK, ack); + setNativeHeader(ACK, ack); } public String getAck() { - return getHeaderValue(ACK); + return getFirstNativeHeader(ACK); } public void setNack(String nack) { - this.headers.put(NACK, nack); + setNativeHeader(NACK, nack); } public String getNack() { - return getHeaderValue(NACK); + return getFirstNativeHeader(NACK); } public void setLogin(String login) { - this.headers.put(LOGIN, login); + setNativeHeader(LOGIN, login); } public String getLogin() { - return getHeaderValue(LOGIN); + return getFirstNativeHeader(LOGIN); } public void setPasscode(String passcode) { - this.headers.put(PASSCODE, passcode); + setNativeHeader(PASSCODE, passcode); } public String getPasscode() { - return getHeaderValue(PASSCODE); + return getFirstNativeHeader(PASSCODE); } public void setReceiptId(String receiptId) { - this.headers.put(RECEIPT_ID, receiptId); + setNativeHeader(RECEIPT_ID, receiptId); } public String getReceiptId() { - return getHeaderValue(RECEIPT_ID); + return getFirstNativeHeader(RECEIPT_ID); } public String getMessage() { - return getHeaderValue(MESSAGE); + return getFirstNativeHeader(MESSAGE); } public void setMessage(String content) { - this.headers.put(MESSAGE, content); + setNativeHeader(MESSAGE, content); } public String getMessageId() { - return getHeaderValue(MESSAGE_ID); + return getFirstNativeHeader(MESSAGE_ID); } public void setMessageId(String id) { - this.headers.put(MESSAGE_ID, id); + setNativeHeader(MESSAGE_ID, id); } public String getVersion() { - return getHeaderValue(VERSION); + return getFirstNativeHeader(VERSION); } public void setVersion(String version) { - this.headers.put(VERSION, version); - } - - @Override - public String toString() { - return "StompHeaders [" + "messageType=" + getMessageType() + ", protocolMessageType=" - + getProtocolMessageType() + ", destination=" + getDestination() - + ", subscriptionId=" + getSubscriptionId() + ", sessionId=" + getSessionId() - + ", externalSourceHeaders=" + getExternalSourceHeaders() + ", headers=" + this.headers + "]"; + setNativeHeader(VERSION, version); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompMessageConverter.java b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompMessageConverter.java index 1483dd6ae0..0f71d6cc33 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompMessageConverter.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompMessageConverter.java @@ -99,7 +99,7 @@ public class StompMessageConverter { byte[] payload = new byte[totalLength - payloadIndex]; System.arraycopy(byteContent, payloadIndex, payload, 0, totalLength - payloadIndex); - return MessageBuilder.withPayload(payload).copyHeaders(stompHeaders.toHeaders()).build(); + return MessageBuilder.withPayload(payload).copyHeaders(stompHeaders.toMap()).build(); } private int findIndexOfPayload(byte[] bytes) { @@ -146,7 +146,7 @@ public class StompMessageConverter { try { out.write(stompHeaders.getStompCommand().toString().getBytes("UTF-8")); out.write(LF); - for (Entry> entry : stompHeaders.toStompMessageHeaders().entrySet()) { + for (Entry> entry : stompHeaders.toNativeHeaderMap().entrySet()) { String key = entry.getKey(); key = replaceAllOutbound(key); for (String value : entry.getValue()) { diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompRelayPubSubMessageHandler.java b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompRelayPubSubMessageHandler.java index f9e9b3f247..abcbcec4e0 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompRelayPubSubMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompRelayPubSubMessageHandler.java @@ -39,7 +39,7 @@ import org.springframework.web.messaging.converter.CompositeMessageConverter; import org.springframework.web.messaging.converter.MessageConverter; import org.springframework.web.messaging.service.AbstractPubSubMessageHandler; import org.springframework.web.messaging.stomp.StompCommand; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; import reactor.core.Environment; import reactor.core.Promise; @@ -120,7 +120,7 @@ public class StompRelayPubSubMessageHandler extends AbstractPubSubMessageHandler this.tcpClient = new TcpClient.Spec(NettyTcpClient.class) .using(new Environment()) .codec(new DelimitedCodec((byte) 0, true, StandardCodecs.STRING_CODEC)) - .connect("127.0.0.1", 61613) + .connect("127.0.0.1", 61616) .get(); StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.CONNECT); @@ -129,7 +129,7 @@ public class StompRelayPubSubMessageHandler extends AbstractPubSubMessageHandler headers.setPasscode("guest"); headers.setHeartbeat(0, 0); Message message = MessageBuilder.withPayload( - new byte[0]).copyHeaders(headers.toStompMessageHeaders()).build(); + new byte[0]).copyHeaders(headers.toNativeHeaderMap()).build(); RelaySession session = new RelaySession(message, headers) { @Override @@ -206,7 +206,7 @@ public class StompRelayPubSubMessageHandler extends AbstractPubSubMessageHandler @Override public void handleOther(Message message) { - StompCommand command = (StompCommand) message.getHeaders().get(PubSubHeaderAccesssor.PROTOCOL_MESSAGE_TYPE); + StompCommand command = (StompCommand) message.getHeaders().get(WebMessageHeaderAccesssor.PROTOCOL_MESSAGE_TYPE); Assert.notNull(command, "Expected STOMP command: " + message.getHeaders()); forwardMessage(message, command); } @@ -326,7 +326,7 @@ public class StompRelayPubSubMessageHandler extends AbstractPubSubMessageHandler StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.ERROR); headers.setSessionId(sessionId); headers.setMessage(errorText); - Message errorMessage = MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toHeaders()).build(); + Message errorMessage = MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toMap()).build(); sendMessageToClient(errorMessage); } @@ -372,7 +372,7 @@ public class StompRelayPubSubMessageHandler extends AbstractPubSubMessageHandler MediaType contentType = headers.getContentType(); byte[] payload = payloadConverter.convertToPayload(message.getPayload(), contentType); - Message byteMessage = MessageBuilder.withPayload(payload).copyHeaders(headers.toHeaders()).build(); + Message byteMessage = MessageBuilder.withPayload(payload).copyHeaders(headers.toMap()).build(); if (logger.isTraceEnabled()) { logger.trace("Forwarding message " + byteMessage); diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompWebSocketHandler.java index 828c4a879b..ea73dcbdb6 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/stomp/support/StompWebSocketHandler.java @@ -38,7 +38,7 @@ import org.springframework.web.messaging.converter.CompositeMessageConverter; import org.springframework.web.messaging.converter.MessageConverter; import org.springframework.web.messaging.stomp.StompCommand; import org.springframework.web.messaging.stomp.StompConversionException; -import org.springframework.web.messaging.support.PubSubHeaderAccesssor; +import org.springframework.web.messaging.support.WebMessageHeaderAccesssor; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; @@ -159,7 +159,7 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement // TODO: security Message connectedMessage = MessageBuilder.withPayload(EMPTY_PAYLOAD).copyHeaders( - connectedHeaders.toHeaders()).build(); + connectedHeaders.toMap()).build(); byte[] bytes = getStompMessageConverter().fromMessage(connectedMessage); session.sendMessage(new TextMessage(new String(bytes, Charset.forName("UTF-8")))); } @@ -195,7 +195,7 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.ERROR); headers.setMessage(error.getMessage()); - Message message = MessageBuilder.withPayload(EMPTY_PAYLOAD).copyHeaders(headers.toHeaders()).build(); + Message message = MessageBuilder.withPayload(EMPTY_PAYLOAD).copyHeaders(headers.toMap()).build(); byte[] bytes = this.stompMessageConverter.fromMessage(message); try { @@ -209,9 +209,9 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { this.sessionInfos.remove(session.getId()); - PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.create(MessageType.DISCONNECT); + WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.create(MessageType.DISCONNECT); headers.setSessionId(session.getId()); - Message message = MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toHeaders()).build(); + Message message = MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toMap()).build(); this.outputChannel.send(message); } @@ -264,7 +264,7 @@ public class StompWebSocketHandler extends TextWebSocketHandlerAdapter implement } try { - Message byteMessage = MessageBuilder.withPayload(payload).copyHeaders(headers.toHeaders()).build(); + Message byteMessage = MessageBuilder.withPayload(payload).copyHeaders(headers.toMap()).build(); byte[] bytes = getStompMessageConverter().fromMessage(byteMessage); session.sendMessage(new TextMessage(new String(bytes, Charset.forName("UTF-8")))); } diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/support/PubSubHeaderAccesssor.java b/spring-websocket/src/main/java/org/springframework/web/messaging/support/PubSubHeaderAccesssor.java deleted file mode 100644 index e429dd16ee..0000000000 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/support/PubSubHeaderAccesssor.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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.web.messaging.support; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.http.MediaType; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.web.messaging.MessageType; - - -/** - * A base class for working with message headers in Web, messaging protocols that support - * the publish-subscribe message pattern. Provides uniform access to specific values - * common across protocols such as a destination, message type (publish, - * subscribe/unsubscribe), session id, and others. - *

- * This class can be used to prepare headers for a new pub-sub message, or to access - * and/or modify headers of an existing message. - *

- * Use one of the static factory method in this class, then call getters and setters, and - * at the end if necessary call {@link #toHeaders()} to obtain the updated headers. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class PubSubHeaderAccesssor { - - protected Log logger = LogFactory.getLog(getClass()); - - public static final String DESTINATIONS = "destinations"; - - public static final String CONTENT_TYPE = "contentType"; - - public static final String MESSAGE_TYPE = "messageType"; - - public static final String PROTOCOL_MESSAGE_TYPE = "protocolMessageType"; - - public static final String SESSION_ID = "sessionId"; - - public static final String SUBSCRIPTION_ID = "subscriptionId"; - - public static final String EXTERNAL_SOURCE_HEADERS = "extSourceHeaders"; - - - private static final Map> emptyMultiValueMap = - Collections.unmodifiableMap(new LinkedMultiValueMap(0)); - - - // wrapped read-only message headers - private final MessageHeaders originalHeaders; - - // header updates - private final Map headers = new HashMap(4); - - // saved headers from a message from a remote source - private final Map> externalSourceHeaders; - - - - /** - * A constructor for creating new message headers. - * This constructor is protected. See factory methods in this and sub-classes. - */ - protected PubSubHeaderAccesssor(MessageType messageType, Object protocolMessageType, - Map> externalSourceHeaders) { - - this.originalHeaders = null; - - Assert.notNull(messageType, "messageType is required"); - this.headers.put(MESSAGE_TYPE, messageType); - - if (protocolMessageType != null) { - this.headers.put(PROTOCOL_MESSAGE_TYPE, protocolMessageType); - } - - if (externalSourceHeaders == null) { - this.externalSourceHeaders = emptyMultiValueMap; - } - else { - this.externalSourceHeaders = Collections.unmodifiableMap(externalSourceHeaders); // TODO: list values must also be read-only - this.headers.put(EXTERNAL_SOURCE_HEADERS, this.externalSourceHeaders); - } - } - - /** - * A constructor for accessing and modifying existing message headers. This - * constructor is protected. See factory methods in this and sub-classes. - */ - @SuppressWarnings("unchecked") - protected PubSubHeaderAccesssor(Message message) { - Assert.notNull(message, "message is required"); - this.originalHeaders = message.getHeaders(); - this.externalSourceHeaders = (this.originalHeaders.get(EXTERNAL_SOURCE_HEADERS) != null) ? - (Map>) this.originalHeaders.get(EXTERNAL_SOURCE_HEADERS) : emptyMultiValueMap; - } - - - /** - * Create {@link PubSubHeaderAccesssor} for a new {@link Message} with - * {@link MessageType#MESSAGE}. - */ - public static PubSubHeaderAccesssor create() { - return new PubSubHeaderAccesssor(MessageType.MESSAGE, null, null); - } - - /** - * Create {@link PubSubHeaderAccesssor} for a new {@link Message} of a specific type. - */ - public static PubSubHeaderAccesssor create(MessageType messageType) { - return new PubSubHeaderAccesssor(messageType, null, null); - } - - /** - * Create {@link PubSubHeaderAccesssor} from the headers of an existing message. - */ - public static PubSubHeaderAccesssor wrap(Message message) { - return new PubSubHeaderAccesssor(message); - } - - - /** - * Return the original, wrapped headers (i.e. unmodified) or a new Map including any - * updates made via setters. - */ - public Map toHeaders() { - if (!isModified()) { - return this.originalHeaders; - } - Map result = new HashMap(); - if (this.originalHeaders != null) { - result.putAll(this.originalHeaders); - } - result.putAll(this.headers); - return result; - } - - public boolean isModified() { - return ((this.originalHeaders == null) || !this.headers.isEmpty()); - } - - public MessageType getMessageType() { - return (MessageType) getHeaderValue(MESSAGE_TYPE); - } - - private Object getHeaderValue(String headerName) { - if (this.headers.get(headerName) != null) { - return this.headers.get(headerName); - } - else if ((this.originalHeaders != null) && (this.originalHeaders.get(headerName) != null)) { - return this.originalHeaders.get(headerName); - } - return null; - } - - protected void setProtocolMessageType(Object protocolMessageType) { - this.headers.put(PROTOCOL_MESSAGE_TYPE, protocolMessageType); - } - - protected Object getProtocolMessageType() { - return getHeaderValue(PROTOCOL_MESSAGE_TYPE); - } - - public void setDestination(String destination) { - Assert.notNull(destination, "destination is required"); - this.headers.put(DESTINATIONS, Arrays.asList(destination)); - } - - @SuppressWarnings("unchecked") - public String getDestination() { - List destinations = (List) getHeaderValue(DESTINATIONS); - return CollectionUtils.isEmpty(destinations) ? null : destinations.get(0); - } - - @SuppressWarnings("unchecked") - public List getDestinations() { - List destinations = (List) getHeaderValue(DESTINATIONS); - return CollectionUtils.isEmpty(destinations) ? null : destinations; - } - - public void setDestinations(List destinations) { - Assert.notNull(destinations, "destinations are required"); - this.headers.put(DESTINATIONS, destinations); - } - - public MediaType getContentType() { - return (MediaType) getHeaderValue(CONTENT_TYPE); - } - - public void setContentType(MediaType contentType) { - Assert.notNull(contentType, "contentType is required"); - this.headers.put(CONTENT_TYPE, contentType); - } - - public String getSubscriptionId() { - return (String) getHeaderValue(SUBSCRIPTION_ID); - } - - public void setSubscriptionId(String subscriptionId) { - this.headers.put(SUBSCRIPTION_ID, subscriptionId); - } - - public String getSessionId() { - return (String) getHeaderValue(SESSION_ID); - } - - public void setSessionId(String sessionId) { - this.headers.put(SESSION_ID, sessionId); - } - - /** - * Return a read-only map of headers originating from a message received by the - * application from an external source (e.g. from a remote WebSocket endpoint). The - * header names and values are exactly as they were, and are protocol specific but may - * also be custom application headers if the protocol allows that. - */ - public Map> getExternalSourceHeaders() { - return this.externalSourceHeaders; - } - - @Override - public String toString() { - return "PubSubHeaders [originalHeaders=" + this.originalHeaders + ", headers=" - + this.headers + ", externalSourceHeaders=" + this.externalSourceHeaders + "]"; - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/support/PubSubMessageBuilder.java b/spring-websocket/src/main/java/org/springframework/web/messaging/support/PubSubMessageBuilder.java index 47144ba82f..4e298a2427 100644 --- a/spring-websocket/src/main/java/org/springframework/web/messaging/support/PubSubMessageBuilder.java +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/support/PubSubMessageBuilder.java @@ -29,7 +29,7 @@ import reactor.util.Assert; */ public class PubSubMessageBuilder { - private final PubSubHeaderAccesssor headers = PubSubHeaderAccesssor.create(); + private final WebMessageHeaderAccesssor headers = WebMessageHeaderAccesssor.create(); private final T payload; @@ -67,11 +67,11 @@ public class PubSubMessageBuilder { Message message = MessageHolder.getMessage(); if (message != null) { - String sessionId = PubSubHeaderAccesssor.wrap(message).getSessionId(); + String sessionId = WebMessageHeaderAccesssor.wrap(message).getSessionId(); this.headers.setSessionId(sessionId); } - return MessageBuilder.withPayload(this.payload).copyHeaders(this.headers.toHeaders()).build(); + return MessageBuilder.withPayload(this.payload).copyHeaders(this.headers.toMap()).build(); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/messaging/support/WebMessageHeaderAccesssor.java b/spring-websocket/src/main/java/org/springframework/web/messaging/support/WebMessageHeaderAccesssor.java new file mode 100644 index 0000000000..269983f199 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/messaging/support/WebMessageHeaderAccesssor.java @@ -0,0 +1,167 @@ +/* + * 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.web.messaging.support; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.springframework.http.MediaType; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.NativeMessageHeaderAccessor; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.web.messaging.MessageType; + + +/** + * A base class for working with message headers in Web, messaging protocols that support + * the publish-subscribe message pattern. Provides uniform access to specific values + * common across protocols such as a destination, message type (publish, + * subscribe/unsubscribe), session id, and others. + *

+ * Use one of the static factory method in this class, then call getters and setters, and + * at the end if necessary call {@link #toMap()} to obtain the updated headers. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class WebMessageHeaderAccesssor extends NativeMessageHeaderAccessor { + + public static final String DESTINATIONS = "destinations"; + + public static final String CONTENT_TYPE = "contentType"; + + public static final String MESSAGE_TYPE = "messageType"; + + public static final String PROTOCOL_MESSAGE_TYPE = "protocolMessageType"; + + public static final String SESSION_ID = "sessionId"; + + public static final String SUBSCRIPTION_ID = "subscriptionId"; + + + /** + * A constructor for creating new message headers. + * This constructor is protected. See factory methods in this and sub-classes. + */ + protected WebMessageHeaderAccesssor(MessageType messageType, Object protocolMessageType, + Map> externalSourceHeaders) { + + super(externalSourceHeaders); + + Assert.notNull(messageType, "messageType is required"); + setHeader(MESSAGE_TYPE, messageType); + + if (protocolMessageType != null) { + setHeader(PROTOCOL_MESSAGE_TYPE, protocolMessageType); + } + } + + /** + * A constructor for accessing and modifying existing message headers. This + * constructor is protected. See factory methods in this and sub-classes. + */ + protected WebMessageHeaderAccesssor(Message message) { + super(message); + Assert.notNull(message, "message is required"); + } + + + /** + * Create {@link WebMessageHeaderAccesssor} for a new {@link Message} with + * {@link MessageType#MESSAGE}. + */ + public static WebMessageHeaderAccesssor create() { + return new WebMessageHeaderAccesssor(MessageType.MESSAGE, null, null); + } + + /** + * Create {@link WebMessageHeaderAccesssor} for a new {@link Message} of a specific type. + */ + public static WebMessageHeaderAccesssor create(MessageType messageType) { + return new WebMessageHeaderAccesssor(messageType, null, null); + } + + /** + * Create {@link WebMessageHeaderAccesssor} from the headers of an existing message. + */ + public static WebMessageHeaderAccesssor wrap(Message message) { + return new WebMessageHeaderAccesssor(message); + } + + + public MessageType getMessageType() { + return (MessageType) getHeader(MESSAGE_TYPE); + } + + protected void setProtocolMessageType(Object protocolMessageType) { + setHeader(PROTOCOL_MESSAGE_TYPE, protocolMessageType); + } + + protected Object getProtocolMessageType() { + return getHeader(PROTOCOL_MESSAGE_TYPE); + } + + public void setDestination(String destination) { + Assert.notNull(destination, "destination is required"); + setHeader(DESTINATIONS, Arrays.asList(destination)); + } + + @SuppressWarnings("unchecked") + public String getDestination() { + List destinations = (List) getHeader(DESTINATIONS); + return CollectionUtils.isEmpty(destinations) ? null : destinations.get(0); + } + + @SuppressWarnings("unchecked") + public List getDestinations() { + List destinations = (List) getHeader(DESTINATIONS); + return CollectionUtils.isEmpty(destinations) ? null : destinations; + } + + public void setDestinations(List destinations) { + Assert.notNull(destinations, "destinations are required"); + setHeader(DESTINATIONS, destinations); + } + + public MediaType getContentType() { + return (MediaType) getHeader(CONTENT_TYPE); + } + + public void setContentType(MediaType contentType) { + Assert.notNull(contentType, "contentType is required"); + setHeader(CONTENT_TYPE, contentType); + } + + public String getSubscriptionId() { + return (String) getHeader(SUBSCRIPTION_ID); + } + + public void setSubscriptionId(String subscriptionId) { + setHeader(SUBSCRIPTION_ID, subscriptionId); + } + + public String getSessionId() { + return (String) getHeader(SESSION_ID); + } + + public void setSessionId(String sessionId) { + setHeader(SESSION_ID, sessionId); + } + +} diff --git a/spring-websocket/src/test/java/org/springframework/web/messaging/stomp/support/StompMessageConverterTests.java b/spring-websocket/src/test/java/org/springframework/web/messaging/stomp/support/StompMessageConverterTests.java index 8aaf2d766b..0fb729e08b 100644 --- a/spring-websocket/src/test/java/org/springframework/web/messaging/stomp/support/StompMessageConverterTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/messaging/stomp/support/StompMessageConverterTests.java @@ -53,7 +53,7 @@ public class StompMessageConverterTests { MessageHeaders headers = message.getHeaders(); StompHeaderAccessor stompHeaders = StompHeaderAccessor.wrap(message); - assertEquals(7, stompHeaders.toHeaders().size()); + assertEquals(7, stompHeaders.toMap().size()); assertEquals(Collections.singleton("1.1"), stompHeaders.getAcceptVersion()); assertEquals("github.org", stompHeaders.getHost()); @@ -84,7 +84,7 @@ public class StompMessageConverterTests { StompHeaderAccessor stompHeaders = StompHeaderAccessor.wrap(message); assertEquals(Collections.singleton("1.1"), stompHeaders.getAcceptVersion()); - assertEquals("st\nomp.gi:thu\\b.org", stompHeaders.getExternalSourceHeaders().get("ho:\ns\rt").get(0)); + assertEquals("st\nomp.gi:thu\\b.org", stompHeaders.toNativeHeaderMap().get("ho:\ns\rt").get(0)); String convertedBack = new String(this.converter.fromMessage(message), "UTF-8"); @@ -128,7 +128,7 @@ public class StompMessageConverterTests { StompHeaderAccessor stompHeaders = StompHeaderAccessor.wrap(message); assertEquals(Collections.singleton("1.1"), stompHeaders.getAcceptVersion()); - assertEquals("st\nomp.gi:thu\\b.org", stompHeaders.getExternalSourceHeaders().get("ho:\ns\rt").get(0)); + assertEquals("st\nomp.gi:thu\\b.org", stompHeaders.toNativeHeaderMap().get("ho:\ns\rt").get(0)); String convertedBack = new String(this.converter.fromMessage(message), "UTF-8");