Remove generic params from Message/MessageChannel

This commit is contained in:
Rossen Stoyanchev
2013-06-24 11:14:48 -04:00
parent f7f66f2e5c
commit 32cb2ca2e7
26 changed files with 461 additions and 356 deletions

View File

@@ -23,8 +23,7 @@ package org.springframework.messaging;
* @author Mark Fisher
* @since 4.0
*/
@SuppressWarnings("rawtypes")
public interface MessageChannel<M extends Message> {
public interface MessageChannel {
/**
* Send a {@link Message} to this channel. May throw a RuntimeException for
@@ -39,7 +38,7 @@ public interface MessageChannel<M extends Message> {
*
* @return whether or not the Message has been sent successfully
*/
boolean send(M message);
boolean send(Message<?> message);
/**
* Send a message, blocking until either the message is accepted or the
@@ -52,6 +51,6 @@ public interface MessageChannel<M extends Message> {
* <code>false</code> if the specified timeout period elapses or
* the send is interrupted
*/
boolean send(M message, long timeout);
boolean send(Message<?> message, long timeout);
}

View File

@@ -1,40 +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.messaging;
import java.util.Map;
/**
* A factory for creating messages, allowing for control of the concrete type of the message.
*
* @author Andy Wilkinson
* @since 4.0
*/
public interface MessageFactory<M extends Message<?>> {
/**
* Creates a new message with the given payload and headers
*
* @param payload The message payload
* @param headers The message headers
* @param <P> The payload's type
*
* @return the message
*/
<P> M createMessage(P payload, Map<String, Object> headers);
}

View File

@@ -24,11 +24,10 @@ package org.springframework.messaging;
* @author Iwein Fuld
* @since 4.0
*/
@SuppressWarnings("rawtypes")
public interface MessageHandler<M extends Message> {
public interface MessageHandler {
/**
* TODO: support exceptions?
* TODO: exceptions
*
* Handles the message if possible. If the handler cannot deal with the
* message this will result in a <code>MessageRejectedException</code> e.g.
@@ -47,6 +46,6 @@ public interface MessageHandler<M extends Message> {
* @throws org.springframework.integration.MessageDeliveryException when this handler failed to deliver the
* reply related to the handling of the message
*/
void handleMessage(M message) throws MessagingException;
void handleMessage(Message<?> message) throws MessagingException;
}

View File

@@ -38,9 +38,6 @@ import org.apache.commons.logging.LogFactory;
* IMPORTANT: MessageHeaders are immutable. Any mutating operation (e.g., put(..), putAll(..) etc.)
* will result in {@link UnsupportedOperationException}
*
* <p>
* TODO: update below instructions
*
* <p>To create MessageHeaders instance use fluent MessageBuilder API
* <pre>
* MessageBuilder.withPayload("foo").setHeader("key1", "value1").setHeader("key2", "value2");
@@ -61,7 +58,7 @@ import org.apache.commons.logging.LogFactory;
*/
public class MessageHeaders implements Map<String, Object>, Serializable {
private static final long serialVersionUID = 8946067357652612145L;
private static final long serialVersionUID = -4615750558355702881L;
private static final Log logger = LogFactory.getLog(MessageHeaders.class);
@@ -77,6 +74,27 @@ 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);
@@ -103,6 +121,36 @@ 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);
}
public Object getErrorChannel() {
return this.get(ERROR_CHANNEL);
}
@SuppressWarnings("unchecked")
public <T> T get(Object key, Class<T> type) {
Object value = this.headers.get(key);

View File

@@ -0,0 +1,46 @@
/*
* 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;
/**
* Interface for Message Channels from which Messages may be actively received through polling.
*
* @author Mark Fisher
* @since 4.0
*/
public interface PollableChannel extends MessageChannel {
/**
* Receive a message from this channel, blocking indefinitely if necessary.
*
* @return the next available {@link Message} or <code>null</code> if interrupted
*/
Message<?> receive();
/**
* Receive a message from this channel, blocking until either a message is
* available or the specified timeout period elapses.
*
* @param timeout the timeout in milliseconds
*
* @return the next available {@link Message} or <code>null</code> if the
* specified timeout period elapses or the message reception is interrupted
*/
Message<?> receive(long timeout);
}

View File

@@ -25,18 +25,16 @@ package org.springframework.messaging;
* @author Mark Fisher
* @since 4.0
*/
@SuppressWarnings("rawtypes")
public interface SubscribableChannel<M extends Message, H extends MessageHandler<M>>
extends MessageChannel<M> {
public interface SubscribableChannel extends MessageChannel {
/**
* Register a {@link MessageHandler} as a subscriber to this channel.
*/
boolean subscribe(H handler);
boolean subscribe(MessageHandler handler);
/**
* Remove a {@link MessageHandler} from the subscribers of this channel.
*/
boolean unsubscribe(H handler);
boolean unsubscribe(MessageHandler handler);
}

View File

@@ -5,7 +5,7 @@
* 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
* 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,
@@ -18,21 +18,24 @@ package org.springframework.messaging.support;
import java.util.Map;
import org.springframework.messaging.MessageFactory;
/**
* A {@link MessageFactory} that creates {@link GenericMessage GenericMessages}.
* A message implementation that accepts a {@link Throwable} payload.
* Once created this object is immutable.
*
* @author Andy Wilkinson
* @author Mark Fisher
* @author Oleg Zhurakousky
* @since 4.0
*/
public class GenericMessageFactory implements MessageFactory<GenericMessage<?>> {
public class ErrorMessage extends GenericMessage<Throwable> {
private static final long serialVersionUID = -5470210965279837728L;
@Override
public <P> GenericMessage<P> createMessage(P payload, Map<String, Object> headers) {
return new GenericMessage<P>(payload, headers);
public ErrorMessage(Throwable payload) {
super(payload);
}
public ErrorMessage(Throwable payload, Map<String, Object> headers) {
super(payload, headers);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -17,19 +17,25 @@
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.MessageFactory;
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
*
* @author Arjen Poutsma
* @author Mark Fisher
* @author Oleg Zhurakousky
@@ -44,46 +50,24 @@ public final class MessageBuilder<T> {
private final Message<T> originalMessage;
@SuppressWarnings("rawtypes")
private static volatile MessageFactory messageFactory = null;
private volatile boolean modified;
/**
* A constructor with payload and an optional message to copy headers from.
* This is a private constructor to be invoked from the static factory methods only.
*
* @param payload the message payload, never {@code null}
* @param originalMessage a message to copy from or re-use if no changes are made, can
* be {@code null}
* Private constructor to be invoked from the static factory methods only.
*/
private MessageBuilder(T payload, Message<T> originalMessage) {
Assert.notNull(payload, "payload is required");
Assert.notNull(payload, "payload must not be null");
this.payload = payload;
this.originalMessage = originalMessage;
if (originalMessage != null) {
this.headers.putAll(originalMessage.getHeaders());
this.copyHeaders(originalMessage.getHeaders());
this.modified = (!this.payload.equals(originalMessage.getPayload()));
}
}
/**
* Private constructor to be invoked from the static factory methods only.
*
* @param payload the message payload, never {@code null}
* @param originalMessage a message to copy from or re-use if no changes are made, can
* be {@code null}
*/
private MessageBuilder(T payload, Map<String, Object> headers) {
Assert.notNull(payload, "payload is required");
Assert.notNull(headers, "headers is required");
this.payload = payload;
this.headers.putAll(headers);
this.originalMessage = null;
}
/**
* 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
*/
@@ -99,70 +83,69 @@ public final class MessageBuilder<T> {
* @param payload the payload for the new message
*/
public static <T> MessageBuilder<T> withPayload(T payload) {
MessageBuilder<T> builder = new MessageBuilder<T>(payload, (Message<T>) null);
MessageBuilder<T> builder = new MessageBuilder<T>(payload, null);
return builder;
}
/**
* 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)) {
putOrRemove(headerName, headerValue);
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;
}
}
}
return this;
}
private boolean isReadOnly(String headerName) {
return MessageHeaders.ID.equals(headerName) || MessageHeaders.TIMESTAMP.equals(headerName);
}
private void putOrRemove(String headerName, Object headerValue) {
if (headerValue == null) {
this.headers.remove(headerName);
}
else {
this.headers.put(headerName, headerValue);
}
}
/**
* 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) {
putOrRemove(headerName, headerValue);
this.setHeader(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".
* 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
*/
public MessageBuilder<T> removeHeaders(String... headerPatterns) {
List<String> toRemove = new ArrayList<String>();
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)){
toRemove.add(headerName);
headersToRemove.add(headerName);
}
}
}
else {
toRemove.add(pattern);
headersToRemove.add(pattern);
}
}
}
for (String headerName : toRemove) {
this.headers.remove(headerName);
putOrRemove(headerName, null);
for (String headerToRemove : headersToRemove) {
this.removeHeader(headerToRemove);
}
return this;
}
@@ -170,63 +153,182 @@ public final class MessageBuilder<T> {
* Remove the value for the given header name.
*/
public MessageBuilder<T> removeHeader(String headerName) {
if (StringUtils.hasLength(headerName) && !isReadOnly(headerName)) {
this.headers.remove(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;
}
}
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.
* 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
*/
public MessageBuilder<T> copyHeaders(Map<String, ?> headersToCopy) {
Set<String> keys = headersToCopy.keySet();
for (String key : keys) {
if (!this.isReadOnly(key)) {
putOrRemove(key, headersToCopy.get(key));
this.setHeader(key, headersToCopy.get(key));
}
}
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.headers.get(key) == null)) {
putOrRemove(key, headersToCopy.get(key));
if (!this.isReadOnly(key)) {
this.setHeaderIfAbsent(key, headersToCopy.get(key));
}
}
return this;
}
@SuppressWarnings("unchecked")
public Message<T> build() {
public MessageBuilder<T> setExpirationDate(Long expirationDate) {
return this.setHeader(MessageHeaders.EXPIRATION_DATE, expirationDate);
}
if (this.originalMessage != null
&& this.headers.equals(this.originalMessage.getHeaders())
&& this.payload.equals(this.originalMessage.getPayload())) {
return this.originalMessage;
}
// if (this.payload instanceof Throwable) {
// return (Message<T>) new ErrorMessage((Throwable) this.payload, this.headers);
// }
this.headers.remove(MessageHeaders.ID);
this.headers.remove(MessageHeaders.TIMESTAMP);
if (messageFactory == null) {
return new GenericMessage<T>(this.payload, this.headers);
public MessageBuilder<T> setExpirationDate(Date expirationDate) {
if (expirationDate != null) {
return this.setHeader(MessageHeaders.EXPIRATION_DATE, expirationDate.getTime());
}
else {
return messageFactory.createMessage(payload, headers);
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);
}
return this;
}
public MessageBuilder<T> setReplyChannel(MessageChannel replyChannel) {
return this.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel);
}
public MessageBuilder<T> setReplyChannelName(String replyChannelName) {
return this.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannelName);
}
public MessageBuilder<T> setErrorChannel(MessageChannel errorChannel) {
return this.setHeader(MessageHeaders.ERROR_CHANNEL, errorChannel);
}
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);
}
@SuppressWarnings("unchecked")
public Message<T> build() {
if (!this.modified && this.originalMessage != null) {
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.");
}
}
}