INT-1437: push sequence details into message builder
INT-1420: add javadocs
This commit is contained in:
@@ -70,6 +70,8 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
|
||||
|
||||
public static final String SEQUENCE_SIZE = PREFIX + "sequenceSize";
|
||||
|
||||
public static final String SEQUENCE_DETAILS = PREFIX + "sequenceDetails";
|
||||
|
||||
|
||||
private final Map<String, Object> headers;
|
||||
|
||||
|
||||
@@ -13,20 +13,15 @@
|
||||
|
||||
package org.springframework.integration.aggregator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
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.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.splitter.AbstractMessageSplitter;
|
||||
import org.springframework.integration.store.MessageGroup;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -44,19 +39,18 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag
|
||||
|
||||
private final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
|
||||
public final Object processMessageGroup(MessageGroup group) {
|
||||
Assert.notNull(group, "MessageGroup must not be null");
|
||||
Map<String, Object> headers = this.aggregateHeaders(group);
|
||||
Object payload = this.aggregatePayloads(group, headers);
|
||||
MessageBuilder<?> builder;
|
||||
if (payload instanceof Message<?>) {
|
||||
builder = MessageBuilder.fromMessage((Message<?>) payload);
|
||||
builder = MessageBuilder.fromMessage((Message<?>) payload).copyHeadersIfAbsent(headers);
|
||||
}
|
||||
else {
|
||||
builder = MessageBuilder.withPayload(payload).copyHeadersIfAbsent(headers);
|
||||
}
|
||||
return builder.build();
|
||||
return builder.popSequenceDetails().build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,28 +65,7 @@ public abstract class AbstractAggregatingMessageGroupProcessor implements Messag
|
||||
MessageHeaders currentHeaders = message.getHeaders();
|
||||
for (String key : currentHeaders.keySet()) {
|
||||
if (MessageHeaders.ID.equals(key) || MessageHeaders.TIMESTAMP.equals(key)
|
||||
|| MessageHeaders.SEQUENCE_SIZE.equals(key) || MessageHeaders.SEQUENCE_NUMBER.equals(key)
|
||||
|| MessageHeaders.CORRELATION_ID.equals(key)) {
|
||||
continue;
|
||||
}
|
||||
if (AbstractMessageSplitter.SEQUENCE_DETAILS.equals(key)
|
||||
&& !aggregatedHeaders.containsKey(MessageHeaders.CORRELATION_ID)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object[]> incomingSequenceDetails = new ArrayList<Object[]>(currentHeaders
|
||||
.get(key, List.class));
|
||||
Object[] sequenceDetails = incomingSequenceDetails.remove(incomingSequenceDetails.size() - 1);
|
||||
Assert.state(sequenceDetails.length == 3, "Wrong sequence details (not created by splitter?): "
|
||||
+ Arrays.asList(sequenceDetails));
|
||||
aggregatedHeaders.put(MessageHeaders.CORRELATION_ID, sequenceDetails[0]);
|
||||
Integer sequenceNumber = (Integer) sequenceDetails[1];
|
||||
Integer sequenceSize = (Integer) sequenceDetails[2];
|
||||
if (sequenceSize > 0) {
|
||||
aggregatedHeaders.put(MessageHeaders.SEQUENCE_NUMBER, sequenceNumber);
|
||||
aggregatedHeaders.put(MessageHeaders.SEQUENCE_SIZE, sequenceSize);
|
||||
}
|
||||
if (!incomingSequenceDetails.isEmpty()) {
|
||||
aggregatedHeaders.put(AbstractMessageSplitter.SEQUENCE_DETAILS, incomingSequenceDetails);
|
||||
}
|
||||
|| MessageHeaders.SEQUENCE_SIZE.equals(key) || MessageHeaders.SEQUENCE_NUMBER.equals(key)) {
|
||||
continue;
|
||||
}
|
||||
Object value = currentHeaders.get(key);
|
||||
|
||||
@@ -17,28 +17,22 @@
|
||||
package org.springframework.integration.dispatcher;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
/**
|
||||
* A broadcasting dispatcher implementation. If the 'ignoreFailures' property
|
||||
* is set to <code>false</code> (the default), it will fail fast such that any
|
||||
* Exception thrown by a MessageHandler may prevent subsequent handlers from
|
||||
* receiving the Message. However, when an Executor is provided, the Messages
|
||||
* may be dispatched in separate Threads so that other handlers are invoked even
|
||||
* when the 'ignoreFailures' flag is <code>false</code>.
|
||||
* A broadcasting dispatcher implementation. If the 'ignoreFailures' property is set to <code>false</code> (the
|
||||
* default), it will fail fast such that any Exception thrown by a MessageHandler may prevent subsequent handlers from
|
||||
* receiving the Message. However, when an Executor is provided, the Messages may be dispatched in separate Threads so
|
||||
* that other handlers are invoked even when the 'ignoreFailures' flag is <code>false</code>.
|
||||
* <p>
|
||||
* If the 'ignoreFailures' flag is set to <code>true</code> on the other hand,
|
||||
* it will make a best effort to send the message to each of its handlers. In
|
||||
* other words, when 'ignoreFailures' is <code>true</code>, if it fails to send
|
||||
* to any one handler, it will simply log a warn-level message but continue to
|
||||
* send the Message to any other handlers.
|
||||
* If the 'ignoreFailures' flag is set to <code>true</code> on the other hand, it will make a best effort to send the
|
||||
* message to each of its handlers. In other words, when 'ignoreFailures' is <code>true</code>, if it fails to send to
|
||||
* any one handler, it will simply log a warn-level message but continue to send the Message to any other handlers.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Iwein Fuld
|
||||
@@ -52,7 +46,6 @@ public class BroadcastingDispatcher extends AbstractDispatcher {
|
||||
|
||||
private final Executor executor;
|
||||
|
||||
|
||||
public BroadcastingDispatcher() {
|
||||
this.executor = null;
|
||||
}
|
||||
@@ -61,26 +54,22 @@ public class BroadcastingDispatcher extends AbstractDispatcher {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify whether failures for one or more of the handlers should be
|
||||
* ignored. By default this is <code>false</code> meaning that an
|
||||
* Exception will be thrown when a handler fails. To override this and
|
||||
* suppress Exceptions, set the value to <code>true</code>.
|
||||
* Specify whether failures for one or more of the handlers should be ignored. By default this is <code>false</code>
|
||||
* meaning that an Exception will be thrown when a handler fails. To override this and suppress Exceptions, set the
|
||||
* value to <code>true</code>.
|
||||
* <p>
|
||||
* Keep in mind that when using an Executor, even without ignoring the
|
||||
* failures, other handlers may be invoked after one throws an Exception.
|
||||
* Since the Executor is most likely using a different thread, this flag would
|
||||
* only affect whether an error Message is sent to the error channel or not in
|
||||
* the case that such an Executor has been configured.
|
||||
* Keep in mind that when using an Executor, even without ignoring the failures, other handlers may be invoked after
|
||||
* one throws an Exception. Since the Executor is most likely using a different thread, this flag would only affect
|
||||
* whether an error Message is sent to the error channel or not in the case that such an Executor has been
|
||||
* configured.
|
||||
*/
|
||||
public void setIgnoreFailures(boolean ignoreFailures) {
|
||||
this.ignoreFailures = ignoreFailures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to apply sequence numbers to the messages
|
||||
* prior to sending to the handlers. By default, sequence
|
||||
* Specify whether to apply sequence numbers to the messages prior to sending to the handlers. By default, sequence
|
||||
* numbers will <em>not</em> be applied
|
||||
*/
|
||||
public void setApplySequence(boolean applySequence) {
|
||||
@@ -93,13 +82,8 @@ public class BroadcastingDispatcher extends AbstractDispatcher {
|
||||
List<MessageHandler> handlers = this.getHandlers();
|
||||
int sequenceSize = handlers.size();
|
||||
for (final MessageHandler handler : handlers) {
|
||||
final Message<?> messageToSend = (!this.applySequence) ? message
|
||||
: MessageBuilder.fromMessage(message)
|
||||
.setSequenceNumber(sequenceNumber++)
|
||||
.setSequenceSize(sequenceSize)
|
||||
.setCorrelationId(message.getHeaders().getId())
|
||||
.setHeader(MessageHeaders.ID, UUID.randomUUID())
|
||||
.build();
|
||||
final Message<?> messageToSend = (!this.applySequence) ? message : MessageBuilder.fromMessage(message)
|
||||
.pushSequenceDetails(message.getHeaders().getId(), sequenceNumber++, sequenceSize).build();
|
||||
if (this.executor != null) {
|
||||
this.executor.execute(new Runnable() {
|
||||
public void run() {
|
||||
@@ -123,8 +107,7 @@ public class BroadcastingDispatcher extends AbstractDispatcher {
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (!this.ignoreFailures) {
|
||||
if (e instanceof MessagingException &&
|
||||
((MessagingException) e).getFailedMessage() == null) {
|
||||
if (e instanceof MessagingException && ((MessagingException) e).getFailedMessage() == null) {
|
||||
((MessagingException) e).setFailedMessage(message);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -114,7 +114,7 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
|
||||
}
|
||||
|
||||
private void handleResult(Object result, MessageHeaders requestHeaders) {
|
||||
if (result instanceof Iterable && this.shouldSplitReply((Iterable<?>) result)) {
|
||||
if (result instanceof Iterable<?> && this.shouldSplitReply((Iterable<?>) result)) {
|
||||
for (Object o : (Iterable<?>) result) {
|
||||
this.produceReply(o, requestHeaders);
|
||||
}
|
||||
@@ -131,7 +131,7 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
|
||||
|
||||
private Message<?> createReplyMessage(Object reply, MessageHeaders requestHeaders) {
|
||||
MessageBuilder<?> builder = null;
|
||||
if (reply instanceof Message) {
|
||||
if (reply instanceof Message<?>) {
|
||||
if (!this.shouldCopyRequestHeaders()) {
|
||||
return (Message<?>) reply;
|
||||
}
|
||||
|
||||
@@ -17,12 +17,10 @@
|
||||
package org.springframework.integration.router;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessageDeliveryException;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
@@ -45,39 +43,35 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
|
||||
|
||||
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
|
||||
|
||||
/**
|
||||
* Set the default channel where Messages should be sent if channel
|
||||
* resolution fails to return any channels. If no default channel is
|
||||
* provided, the router will either drop the Message or throw an Exception
|
||||
* depending on the value of {@link #resolutionRequired}.
|
||||
* Set the default channel where Messages should be sent if channel resolution fails to return any channels. If no
|
||||
* default channel is provided, the router will either drop the Message or throw an Exception depending on the value
|
||||
* of {@link #resolutionRequired}.
|
||||
*/
|
||||
public void setDefaultOutputChannel(MessageChannel defaultOutputChannel) {
|
||||
this.defaultOutputChannel = defaultOutputChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout for sending a message to the resolved channel. By
|
||||
* default, there is no timeout, meaning the send will block indefinitely.
|
||||
* Set the timeout for sending a message to the resolved channel. By default, there is no timeout, meaning the send
|
||||
* will block indefinitely.
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.messagingTemplate.setSendTimeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this router should always be required to resolve at least one
|
||||
* channel. The default is 'false'. To trigger an exception whenever the
|
||||
* resolver returns null or an empty channel list, and this endpoint has
|
||||
* no 'defaultOutputChannel' configured, set this value to 'true'.
|
||||
* Set whether this router should always be required to resolve at least one channel. The default is 'false'. To
|
||||
* trigger an exception whenever the resolver returns null or an empty channel list, and this endpoint has no
|
||||
* 'defaultOutputChannel' configured, set this value to 'true'.
|
||||
*/
|
||||
public void setResolutionRequired(boolean resolutionRequired) {
|
||||
this.resolutionRequired = resolutionRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether send failures for one or more of the recipients should be
|
||||
* ignored. By default this is <code>false</code> meaning that an Exception
|
||||
* will be thrown whenever a send fails. To override this and suppress
|
||||
* Specify whether send failures for one or more of the recipients should be ignored. By default this is
|
||||
* <code>false</code> meaning that an Exception will be thrown whenever a send fails. To override this and suppress
|
||||
* Exceptions, set the value to <code>true</code>.
|
||||
*/
|
||||
public void setIgnoreSendFailures(boolean ignoreSendFailures) {
|
||||
@@ -85,12 +79,10 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to apply the sequence number and size headers to the
|
||||
* messages prior to sending to the recipient channels. By default, this
|
||||
* value is <code>false</code> meaning that sequence headers will
|
||||
* <em>not</em> be applied. If planning to use an Aggregator downstream with
|
||||
* the default correlation and completion strategies, you should set this
|
||||
* flag to <code>true</code>.
|
||||
* Specify whether to apply the sequence number and size headers to the messages prior to sending to the recipient
|
||||
* channels. By default, this value is <code>false</code> meaning that sequence headers will <em>not</em> be
|
||||
* applied. If planning to use an Aggregator downstream with the default correlation and completion strategies, you
|
||||
* should set this flag to <code>true</code>.
|
||||
*/
|
||||
public void setApplySequence(boolean applySequence) {
|
||||
this.applySequence = applySequence;
|
||||
@@ -116,13 +108,8 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
|
||||
int sequenceSize = results.size();
|
||||
int sequenceNumber = 1;
|
||||
for (MessageChannel channel : results) {
|
||||
final Message<?> messageToSend = (!this.applySequence) ? message
|
||||
: MessageBuilder.fromMessage(message)
|
||||
.setSequenceNumber(sequenceNumber++)
|
||||
.setSequenceSize(sequenceSize)
|
||||
.setCorrelationId(message.getHeaders().getId())
|
||||
.setHeader(MessageHeaders.ID, UUID.randomUUID())
|
||||
.build();
|
||||
final Message<?> messageToSend = (!this.applySequence) ? message : MessageBuilder.fromMessage(message)
|
||||
.pushSequenceDetails(message.getHeaders().getId(), sequenceNumber++, sequenceSize).build();
|
||||
if (channel != null) {
|
||||
try {
|
||||
this.messagingTemplate.send(channel, messageToSend);
|
||||
@@ -151,8 +138,7 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must implement this method to return the target channels for
|
||||
* a given Message.
|
||||
* Subclasses must implement this method to return the target channels for a given Message.
|
||||
*/
|
||||
protected abstract Collection<MessageChannel> determineTargetChannels(Message<?> message);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.integration.splitter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
@@ -23,93 +27,60 @@ import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Base class for Message-splitting handlers.
|
||||
*
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Dave Syer
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public abstract class AbstractMessageSplitter extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
public static final String SEQUENCE_DETAILS = MessageHeaders.PREFIX + "sequenceDetails";
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final Object handleRequestMessage(Message<?> message) {
|
||||
Object result = this.splitMessage(message);
|
||||
// return null if 'null', empty Collection or empty Array
|
||||
if ( result == null ||
|
||||
(result instanceof Collection && CollectionUtils.isEmpty((Collection<?>)result)) ||
|
||||
(result.getClass().isArray() && ObjectUtils.isEmpty((Object[]) result)) ) {
|
||||
if (result == null || (result instanceof Collection && CollectionUtils.isEmpty((Collection) result))
|
||||
|| (result.getClass().isArray() && ObjectUtils.isEmpty((Object[]) result))) {
|
||||
return null;
|
||||
}
|
||||
MessageHeaders headers = message.getHeaders();
|
||||
Object incomingCorrelationId = headers.getCorrelationId();
|
||||
List<Object[]> incomingSequenceDetails = extractSequenceDetails(headers, incomingCorrelationId);
|
||||
Object correlationId = headers.getId();
|
||||
List<MessageBuilder> messageBuilders;
|
||||
List<MessageBuilder<?>> messageBuilders = new ArrayList<MessageBuilder<?>>();
|
||||
if (result instanceof Collection) {
|
||||
messageBuilders = messageBuildersForCollection(result, incomingSequenceDetails, correlationId);
|
||||
} else if (result.getClass().isArray()) {
|
||||
messageBuilders = messageBuildersForArray(result, incomingSequenceDetails, correlationId);
|
||||
} else {
|
||||
messageBuilders = Collections.singletonList(this.createBuilder(result, incomingSequenceDetails, correlationId, 1, 1));
|
||||
}
|
||||
return messageBuilders;
|
||||
}
|
||||
|
||||
private List<MessageBuilder> messageBuildersForArray(Object result, List<Object[]> incomingSequenceDetails, Object correlationId) {
|
||||
List<MessageBuilder> messageBuilders = new ArrayList<MessageBuilder>();
|
||||
Object[] items = (Object[]) result;
|
||||
int sequenceNumber = 0;
|
||||
int sequenceSize = items.length;
|
||||
for (Object item : items) {
|
||||
messageBuilders.add(this.createBuilder(
|
||||
item, incomingSequenceDetails, correlationId, ++sequenceNumber, sequenceSize));
|
||||
}
|
||||
return messageBuilders;
|
||||
}
|
||||
|
||||
private List<MessageBuilder> messageBuildersForCollection(Object result, List<Object[]> incomingSequenceDetails, Object correlationId) {
|
||||
List<MessageBuilder> messageBuilders = new ArrayList<MessageBuilder>();
|
||||
Collection<?> items = (Collection<?>) result;
|
||||
int sequenceNumber = 0;
|
||||
int sequenceSize = items.size();
|
||||
for (Object item : items) {
|
||||
messageBuilders.add(this.createBuilder(
|
||||
item, incomingSequenceDetails, correlationId, ++sequenceNumber, sequenceSize));
|
||||
}
|
||||
return messageBuilders;
|
||||
}
|
||||
|
||||
private List<Object[]> extractSequenceDetails(MessageHeaders headers, Object incomingCorrelationId) {
|
||||
List<Object[]> incomingSequenceDetails = headers.get(SEQUENCE_DETAILS, List.class);
|
||||
if (incomingCorrelationId != null) {
|
||||
if (incomingSequenceDetails == null) {
|
||||
incomingSequenceDetails = new ArrayList<Object[]>();
|
||||
} else {
|
||||
incomingSequenceDetails = new ArrayList<Object[]>(incomingSequenceDetails);
|
||||
Collection<?> items = (Collection<?>) result;
|
||||
int sequenceNumber = 0;
|
||||
int sequenceSize = items.size();
|
||||
for (Object item : items) {
|
||||
messageBuilders.add(this.createBuilder(item, headers, correlationId, ++sequenceNumber, sequenceSize));
|
||||
}
|
||||
incomingSequenceDetails.add(new Object[]{
|
||||
incomingCorrelationId, headers.getSequenceNumber(), headers.getSequenceSize()});
|
||||
incomingSequenceDetails = Collections.unmodifiableList(incomingSequenceDetails);
|
||||
}
|
||||
return incomingSequenceDetails;
|
||||
else if (result.getClass().isArray()) {
|
||||
Object[] items = (Object[]) result;
|
||||
int sequenceNumber = 0;
|
||||
int sequenceSize = items.length;
|
||||
for (Object item : items) {
|
||||
messageBuilders.add(this.createBuilder(item, headers, correlationId, ++sequenceNumber, sequenceSize));
|
||||
}
|
||||
}
|
||||
else {
|
||||
messageBuilders.add(this.createBuilder(result, headers, correlationId, 1, 1));
|
||||
}
|
||||
return messageBuilders;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private MessageBuilder createBuilder(Object item, List<Object[]> incomingSequenceDetails, Object correlationId,
|
||||
int sequenceNumber, int sequenceSize) {
|
||||
MessageBuilder builder = (item instanceof Message) ? MessageBuilder.fromMessage((Message) item)
|
||||
: MessageBuilder.withPayload(item);
|
||||
builder.setCorrelationId(correlationId).setSequenceNumber(sequenceNumber).setSequenceSize(sequenceSize)
|
||||
.setHeader(MessageHeaders.ID, UUID.randomUUID());
|
||||
if (incomingSequenceDetails != null) {
|
||||
builder.setHeader(SEQUENCE_DETAILS, incomingSequenceDetails);
|
||||
@SuppressWarnings( { "unchecked" })
|
||||
private MessageBuilder createBuilder(Object item, MessageHeaders headers, Object correlationId, int sequenceNumber,
|
||||
int sequenceSize) {
|
||||
MessageBuilder builder;
|
||||
if (item instanceof Message) {
|
||||
builder = MessageBuilder.fromMessage((Message) item);
|
||||
}
|
||||
else {
|
||||
builder = MessageBuilder.withPayload(item);
|
||||
builder.copyHeaders(headers);
|
||||
}
|
||||
builder.pushSequenceDetails(correlationId, sequenceNumber, sequenceSize);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,12 @@
|
||||
|
||||
package org.springframework.integration.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;
|
||||
@@ -34,6 +38,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Arjen Poutsma
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public final class MessageBuilder<T> {
|
||||
|
||||
@@ -45,7 +50,6 @@ public final class MessageBuilder<T> {
|
||||
|
||||
private volatile boolean modified;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor to be invoked from the static factory methods only.
|
||||
*/
|
||||
@@ -58,14 +62,11 @@ public final class MessageBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param message the Message from which the payload and all headers will be copied
|
||||
*/
|
||||
public static <T> MessageBuilder<T> fromMessage(Message<T> message) {
|
||||
Assert.notNull(message, "message must not be null");
|
||||
@@ -83,13 +84,12 @@ public final class MessageBuilder<T> {
|
||||
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) {
|
||||
if (StringUtils.hasLength(headerName) && !headerName.equals(MessageHeaders.ID) && !headerName.equals(MessageHeaders.TIMESTAMP)) {
|
||||
if (StringUtils.hasLength(headerName) && !headerName.equals(MessageHeaders.ID)
|
||||
&& !headerName.equals(MessageHeaders.TIMESTAMP)) {
|
||||
this.verifyType(headerName, headerValue);
|
||||
this.modified = true;
|
||||
if (headerValue == null) {
|
||||
@@ -103,8 +103,7 @@ public final class MessageBuilder<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -117,7 +116,8 @@ public final class MessageBuilder<T> {
|
||||
* Remove the value for the given header name.
|
||||
*/
|
||||
public MessageBuilder<T> removeHeader(String headerName) {
|
||||
if (StringUtils.hasLength(headerName) && !headerName.equals(MessageHeaders.ID) && !headerName.equals(MessageHeaders.TIMESTAMP)) {
|
||||
if (StringUtils.hasLength(headerName) && !headerName.equals(MessageHeaders.ID)
|
||||
&& !headerName.equals(MessageHeaders.TIMESTAMP)) {
|
||||
this.modified = true;
|
||||
this.headers.remove(headerName);
|
||||
}
|
||||
@@ -125,10 +125,9 @@ public final class MessageBuilder<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -142,8 +141,7 @@ public final class MessageBuilder<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Object> headersToCopy) {
|
||||
Set<String> keys = headersToCopy.keySet();
|
||||
@@ -170,6 +168,50 @@ public final class MessageBuilder<T> {
|
||||
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 (sequenceSize > 0) {
|
||||
setSequenceNumber(sequenceNumber);
|
||||
setSequenceSize(sequenceSize);
|
||||
}
|
||||
if (!incomingSequenceDetails.isEmpty()) {
|
||||
headers.put(MessageHeaders.SEQUENCE_DETAILS, incomingSequenceDetails);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageBuilder<T> setReplyChannel(MessageChannel replyChannel) {
|
||||
return this.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel);
|
||||
}
|
||||
@@ -212,27 +254,24 @@ public final class MessageBuilder<T> {
|
||||
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.");
|
||||
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.");
|
||||
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.");
|
||||
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.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.SEQUENCE_NUMBER.equals(headerName)
|
||||
|| MessageHeaders.SEQUENCE_SIZE.equals(headerName)) {
|
||||
Assert.isTrue(Integer.class.isAssignableFrom(headerValue.getClass()), "The '" + headerName
|
||||
+ "' header value must be an Integer.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd">
|
||||
|
||||
<channel id="input"/>
|
||||
<channel id="splitter" />
|
||||
|
||||
<splitter id="upstream-splitter"
|
||||
input-channel="input"
|
||||
output-channel="upstream-splits"/>
|
||||
<splitter id="upstream-splitter" input-channel="splitter" output-channel="upstream-splits" />
|
||||
|
||||
<splitter id="downstream-splitter"
|
||||
input-channel="upstream-splits"
|
||||
output-channel="downstream-splits"/>
|
||||
<splitter id="downstream-splitter" input-channel="upstream-splits" output-channel="downstream-splits" />
|
||||
|
||||
<aggregator id="first-aggregator"
|
||||
timeout="1000"
|
||||
input-channel="downstream-splits" output-channel="pre-output"/>
|
||||
<aggregator id="first-aggregator" timeout="1000" input-channel="downstream-splits" output-channel="pre-output" />
|
||||
|
||||
<aggregator id="second-aggregator"
|
||||
input-channel="pre-output"/>
|
||||
<aggregator id="second-aggregator" input-channel="pre-output" />
|
||||
|
||||
|
||||
|
||||
<channel id="router" />
|
||||
|
||||
<recipient-list-router id="upstream-router" input-channel="router" apply-sequence="true">
|
||||
<recipient channel="upstream-splits" />
|
||||
<recipient channel="upstream-another" />
|
||||
</recipient-list-router>
|
||||
|
||||
<splitter id="downstream-another" input-channel="upstream-another" output-channel="downstream-splits" />
|
||||
|
||||
</beans:beans>
|
||||
@@ -41,24 +41,36 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
public class NestedAggregationTests {
|
||||
|
||||
@Autowired
|
||||
DirectChannel input;
|
||||
DirectChannel splitter;
|
||||
|
||||
@Autowired
|
||||
DirectChannel router;
|
||||
|
||||
@Test
|
||||
public void testAggregatorWithNestedSplitter() throws Exception {
|
||||
List<String> result = sendAndReceiveMessage(input, 2000);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<?> input = new GenericMessage<List<List<String>>>(Arrays.asList(Arrays.asList("foo", "bar", "spam"),
|
||||
Arrays.asList("bar", "foo")));
|
||||
List<String> result = sendAndReceiveMessage(splitter, 2000, input);
|
||||
assertNotNull("Expected result and got null", result);
|
||||
assertEquals("[[foo, bar, spam], [bar, foo]]", result.toString());
|
||||
}
|
||||
|
||||
private List<String> sendAndReceiveMessage(DirectChannel channel, int timeout) {
|
||||
@Test
|
||||
public void testAggregatorWithNestedRouter() throws Exception {
|
||||
Message<?> input = new GenericMessage<List<String>>(Arrays.asList("bar", "foo"));
|
||||
List<String> result = sendAndReceiveMessage(router, 2000, input);
|
||||
assertNotNull("Expected result and got null", result);
|
||||
assertEquals("[[bar, foo], [bar, foo]]", result.toString());
|
||||
}
|
||||
|
||||
private List<String> sendAndReceiveMessage(DirectChannel channel, int timeout, Message<?> input) {
|
||||
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(timeout);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> message = (Message<List<String>>) messagingTemplate.sendAndReceive(channel,
|
||||
new GenericMessage<List<List<String>>>(Arrays.asList(Arrays.asList("foo", "bar", "spam"), Arrays.asList("bar",
|
||||
"foo"))));
|
||||
Message<List<String>> message = (Message<List<String>>) messagingTemplate.sendAndReceive(channel, input);
|
||||
|
||||
return message == null ? null : message.getPayload();
|
||||
|
||||
|
||||
@@ -20,13 +20,12 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.splitter.AbstractMessageSplitter;
|
||||
import org.springframework.integration.splitter.MethodInvokingSplitter;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
@@ -126,8 +125,8 @@ public class CorrelationIdTests {
|
||||
Message<?> reply2 = testChannel.receive(100);
|
||||
assertEquals(message.getHeaders().getId(), reply1.getHeaders().getCorrelationId());
|
||||
assertEquals(message.getHeaders().getId(), reply2.getHeaders().getCorrelationId());
|
||||
assertTrue("Sequence details missing", reply1.getHeaders().containsKey(AbstractMessageSplitter.SEQUENCE_DETAILS));
|
||||
assertTrue("Sequence details missing", reply2.getHeaders().containsKey(AbstractMessageSplitter.SEQUENCE_DETAILS));
|
||||
assertTrue("Sequence details missing", reply1.getHeaders().containsKey(MessageHeaders.SEQUENCE_DETAILS));
|
||||
assertTrue("Sequence details missing", reply2.getHeaders().containsKey(MessageHeaders.SEQUENCE_DETAILS));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
@@ -6,6 +6,6 @@ log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m
|
||||
|
||||
|
||||
log4j.category.org.springframework=WARN
|
||||
log4j.category.org.springframework.integration=DEBUG
|
||||
log4j.category.org.springframework.integration.jdbc=DEBUG
|
||||
# log4j.category.org.springframework.integration=DEBUG
|
||||
# log4j.category.org.springframework.integration.jdbc=DEBUG
|
||||
log4j.category.org.springframework.jdbc=DEBUG
|
||||
|
||||
@@ -33,7 +33,11 @@ import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* JMX-based Control Bus implementation. Exports all channel and endpoint beans from a given BeanFactory as MBeans.
|
||||
* JMX-based Control Bus implementation. Routes control messages on an operation channel to the other control points
|
||||
* (channels and handlers) via JMX. To use the control bus send a message to the operation channel with a header
|
||||
* {@link #TARGET_BEAN_NAME} equal to the bean name of the channel or endpoint you want to target. Include also a header
|
||||
* {@link JmxHeaders#OPERATION_NAME} to specify the operation you want to invoke and a message payload containing the
|
||||
* arguments (if any).
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @since 2.0
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.springframework.util.StopWatch;
|
||||
* @author Helena Edelson
|
||||
*/
|
||||
@ManagedResource
|
||||
public class SimpleMessageChannelMonitor implements MethodInterceptor, MessageChannelMonitor {
|
||||
public class DirectChannelMonitor implements MethodInterceptor, MessageChannelMonitor {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -43,16 +43,16 @@ public class SimpleMessageChannelMonitor implements MethodInterceptor, MessageCh
|
||||
|
||||
public static final int DEFAULT_MOVING_AVERAGE_WINDOW = 10;
|
||||
|
||||
private ExponentialMovingAverageCumulativeHistory sendDuration = new ExponentialMovingAverageCumulativeHistory(
|
||||
private ExponentialMovingAverage sendDuration = new ExponentialMovingAverage(
|
||||
DEFAULT_MOVING_AVERAGE_WINDOW);
|
||||
|
||||
private final ExponentialMovingAverageRateCumulativeHistory sendErrorRate = new ExponentialMovingAverageRateCumulativeHistory(
|
||||
private final ExponentialMovingAverageRate sendErrorRate = new ExponentialMovingAverageRate(
|
||||
ONE_SECOND_SECONDS, ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW);
|
||||
|
||||
private final ExponentialMovingAverageRatioCumulativeHistory sendSuccessRatio = new ExponentialMovingAverageRatioCumulativeHistory(
|
||||
private final ExponentialMovingAverageRatio sendSuccessRatio = new ExponentialMovingAverageRatio(
|
||||
ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW);
|
||||
|
||||
private final ExponentialMovingAverageRateCumulativeHistory sendRate = new ExponentialMovingAverageRateCumulativeHistory(
|
||||
private final ExponentialMovingAverageRate sendRate = new ExponentialMovingAverageRate(
|
||||
ONE_SECOND_SECONDS, ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW);
|
||||
|
||||
private final AtomicInteger sendCount = new AtomicInteger();
|
||||
@@ -61,7 +61,7 @@ public class SimpleMessageChannelMonitor implements MethodInterceptor, MessageCh
|
||||
|
||||
private final String name;
|
||||
|
||||
public SimpleMessageChannelMonitor(String name) {
|
||||
public DirectChannelMonitor(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -107,14 +107,20 @@ public class SimpleMessageChannelMonitor implements MethodInterceptor, MessageCh
|
||||
Object result = invocation.proceed();
|
||||
|
||||
timer.stop();
|
||||
sendSuccessRatio.success();
|
||||
sendDuration.append(timer.getTotalTimeSeconds());
|
||||
if ((Boolean)result) {
|
||||
sendSuccessRatio.success();
|
||||
sendDuration.append(timer.getTotalTimeSeconds());
|
||||
} else {
|
||||
sendSuccessRatio.failure();
|
||||
sendErrorCount.incrementAndGet();
|
||||
sendErrorRate.increment();
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (Throwable e) {
|
||||
sendErrorCount.incrementAndGet();
|
||||
sendSuccessRatio.failure();
|
||||
sendErrorCount.incrementAndGet();
|
||||
sendErrorRate.increment();
|
||||
throw e;
|
||||
}
|
||||
@@ -141,17 +147,17 @@ public class SimpleMessageChannelMonitor implements MethodInterceptor, MessageCh
|
||||
}
|
||||
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Rate per Second")
|
||||
public double getSendRate() {
|
||||
public double getMeanSendRate() {
|
||||
return sendRate.getMean();
|
||||
}
|
||||
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Error Rate per Second")
|
||||
public double getErrorRate() {
|
||||
public double getMeanErrorRate() {
|
||||
return sendErrorRate.getMean();
|
||||
}
|
||||
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Mean Channel Error Ratio per Minute")
|
||||
public double getErrorRatio() {
|
||||
public double getMeanErrorRatio() {
|
||||
return 1 - sendSuccessRatio.getMean();
|
||||
}
|
||||
|
||||
@@ -174,6 +180,18 @@ public class SimpleMessageChannelMonitor implements MethodInterceptor, MessageCh
|
||||
public double getStandardDeviationSendDuration() {
|
||||
return sendDuration.getStandardDeviation();
|
||||
}
|
||||
|
||||
public Statistics getSendDuration() {
|
||||
return sendDuration.getStatistics();
|
||||
}
|
||||
|
||||
public Statistics getSendRate() {
|
||||
return sendRate.getStatistics();
|
||||
}
|
||||
|
||||
public Statistics getErrorRate() {
|
||||
return sendErrorRate.getStatistics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -14,13 +14,16 @@ package org.springframework.integration.monitor;
|
||||
|
||||
/**
|
||||
* Cumulative statistics for a series of real numbers with higher weight given to recent data but without storing any
|
||||
* history. Older values are given exponentially smaller weight, with a decay factor determined by a "window" size
|
||||
* chosen by the client.
|
||||
* history. Clients call {@link #append(double)} every time there is a new measurement, and then can collect summary
|
||||
* statistics from the convenience getters (e.g. {@link #getStatistics()}). Older values are given exponentially smaller
|
||||
* weight, with a decay factor determined by a "window" size chosen by the caller. The result is a good approximation to
|
||||
* the statistics of the series but with more weight given to recent measurements, so if the statistics change over time
|
||||
* those trends can be approximately reflected.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExponentialMovingAverageCumulativeHistory {
|
||||
public class ExponentialMovingAverage {
|
||||
|
||||
private int count;
|
||||
|
||||
@@ -37,12 +40,20 @@ public class ExponentialMovingAverageCumulativeHistory {
|
||||
private final double decay;
|
||||
|
||||
/**
|
||||
* Create a moving average accumulator with decay lapse window provided. Measurements older than this will have
|
||||
* smaller weight than <code>1/e</code>.
|
||||
*
|
||||
* @param window the exponential lapse window (number of measurements)
|
||||
*/
|
||||
public ExponentialMovingAverageCumulativeHistory(int window) {
|
||||
public ExponentialMovingAverage(int window) {
|
||||
this.decay = 1 - 1. / window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new measurement to the series.
|
||||
*
|
||||
* @param value the measurement to append
|
||||
*/
|
||||
public void append(double value) {
|
||||
if (value > max || count == 0)
|
||||
max = value;
|
||||
@@ -54,32 +65,53 @@ public class ExponentialMovingAverageCumulativeHistory {
|
||||
count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of measurements recorded
|
||||
*/
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mean value
|
||||
*/
|
||||
public double getMean() {
|
||||
return weight > 0 ? sum / weight : 0.;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the approximate standard deviation
|
||||
*/
|
||||
public double getStandardDeviation() {
|
||||
double mean = getMean();
|
||||
double var = weight > 0 ? sumSquares / weight - mean * mean : 0.;
|
||||
return var > 0 ? Math.sqrt(var) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum value recorded (not weighted)
|
||||
*/
|
||||
public double getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minimum value recorded (not weighted)
|
||||
*/
|
||||
public double getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return summary statistics (count, mean, standard deviation etc.)
|
||||
*/
|
||||
public Statistics getStatistics() {
|
||||
return new Statistics(count, min, max, getMean(), getStandardDeviation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[N=%d, min=%f, max=%f, mean=%f, sigma=%f]", count, min, max, getMean(),
|
||||
getStandardDeviation());
|
||||
return getStatistics().toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,15 +13,24 @@
|
||||
package org.springframework.integration.monitor;
|
||||
|
||||
/**
|
||||
* Cumulative statistics for rate with higher weight given to recent data but without storing any history. Older values
|
||||
* are given exponentially smaller weight, with a decay factor determined by a duration chosen by the client.
|
||||
* Cumulative statistics for an event rate with higher weight given to recent data but without storing any history.
|
||||
* Clients call {@link #increment()} when a new event occurs, and then use convenience methods (e.g. {@link #getMean()})
|
||||
* to retrieve estimates of the rate of event arrivals and the statistics of the series. Older values are given
|
||||
* exponentially smaller weight, with a decay factor determined by a duration chosen by the client. The rate measurement
|
||||
* weights decay in two dimensions:
|
||||
* <ul>
|
||||
* <li>in time according to the lapse period supplied: <code>weight = exp((t0-t)/T)</code> where <code>t0</code> is the
|
||||
* last measurement time, <code>t</code> is the current time and <code>T</code> is the lapse period)</li>
|
||||
* <li>per measurement according to the lapse window supplied: <code>weight = exp(-i/L)</code> where <code>L</code> is
|
||||
* the lapse window and <code>i</code> is the sequence number of the measurement.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExponentialMovingAverageRateCumulativeHistory {
|
||||
public class ExponentialMovingAverageRate {
|
||||
|
||||
private final ExponentialMovingAverageCumulativeHistory rates;
|
||||
private final ExponentialMovingAverage rates;
|
||||
|
||||
private double weight;
|
||||
|
||||
@@ -42,12 +51,15 @@ public class ExponentialMovingAverageRateCumulativeHistory {
|
||||
* @param lapsePeriod the exponential lapse rate for the rate average (in seconds)
|
||||
* @param window the exponential lapse window (number of measurements)
|
||||
*/
|
||||
public ExponentialMovingAverageRateCumulativeHistory(double period, double lapsePeriod, int window) {
|
||||
rates = new ExponentialMovingAverageCumulativeHistory(10);
|
||||
public ExponentialMovingAverageRate(double period, double lapsePeriod, int window) {
|
||||
rates = new ExponentialMovingAverage(10);
|
||||
this.lapse = lapsePeriod > 0 ? 0.001 / lapsePeriod : 0; // convert to millisecs
|
||||
this.period = period * 1000; // convert to millisecs
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new event to the series.
|
||||
*/
|
||||
public void increment() {
|
||||
|
||||
long t = System.currentTimeMillis();
|
||||
@@ -66,6 +78,9 @@ public class ExponentialMovingAverageRateCumulativeHistory {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of measurements recorded
|
||||
*/
|
||||
public int getCount() {
|
||||
return rates.getCount();
|
||||
}
|
||||
@@ -77,9 +92,12 @@ public class ExponentialMovingAverageRateCumulativeHistory {
|
||||
return (System.currentTimeMillis() - t0) / 1000.;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mean value
|
||||
*/
|
||||
public double getMean() {
|
||||
int count = rates.getCount();
|
||||
if (count==0) {
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
long t = System.currentTimeMillis();
|
||||
@@ -87,22 +105,37 @@ public class ExponentialMovingAverageRateCumulativeHistory {
|
||||
return count / (count / rates.getMean() + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the approximate standard deviation
|
||||
*/
|
||||
public double getStandardDeviation() {
|
||||
return rates.getStandardDeviation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum value recorded (not weighted)
|
||||
*/
|
||||
public double getMax() {
|
||||
return min > 0 ? 1 / min : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minimum value recorded (not weighted)
|
||||
*/
|
||||
public double getMin() {
|
||||
return max > 0 ? 1 / max : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return summary statistics (count, mean, standard deviation etc.)
|
||||
*/
|
||||
public Statistics getStatistics() {
|
||||
return new Statistics(getCount(), min, max, getMean(), getStandardDeviation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[N=%d, min=%f, max=%f, mean=%f, sigma=%f, timeSinceLast=%f]", getCount(), getMin(),
|
||||
getMax(), getMean(), getStandardDeviation(), getTimeSinceLastMeasurement());
|
||||
return String.format("[%s, timeSinceLast=%f]", getStatistics(), getTimeSinceLastMeasurement());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,14 +13,20 @@
|
||||
package org.springframework.integration.monitor;
|
||||
|
||||
/**
|
||||
* Cumulative statistics for success rate (ratio) with higher weight given to recent data but without storing any
|
||||
* history. Older values are given exponentially smaller weight, with a decay factor determined by a duration chosen by
|
||||
* the client.
|
||||
* Cumulative statistics for success ratio with higher weight given to recent data but without storing any history.
|
||||
* Clients call {@link #success()} or {@link #failure()} when an event occurs, and the ratio of success to total events
|
||||
* is accumulated. Older values are given exponentially smaller weight, with a decay factor determined by a duration
|
||||
* chosen by the client. The rate measurement weights decay in two dimensions:
|
||||
* <ul>
|
||||
* <li>in time according to the lapse period supplied: <code>weight = exp((t0-t)/T)</code> where <code>t0</code> is the
|
||||
* last measurement time, <code>t</code> is the current time and <code>T</code> is the lapse period)</li>
|
||||
* <li>per measurement according to the lapse window supplied: <code>weight = exp(-i/L)</code> where <code>L</code> is
|
||||
* the lapse window and <code>i</code> is the sequence number of the measurement.</li>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExponentialMovingAverageRatioCumulativeHistory {
|
||||
public class ExponentialMovingAverageRatio {
|
||||
|
||||
private double weight;
|
||||
|
||||
@@ -30,21 +36,27 @@ public class ExponentialMovingAverageRatioCumulativeHistory {
|
||||
|
||||
private final double lapse;
|
||||
|
||||
private final ExponentialMovingAverageCumulativeHistory cumulative;
|
||||
private final ExponentialMovingAverage cumulative;
|
||||
|
||||
/**
|
||||
* @param lapsePeriod the exponential lapse rate for the rate average (in seconds)
|
||||
* @param window the exponential lapse window (number of measurements)
|
||||
*/
|
||||
public ExponentialMovingAverageRatioCumulativeHistory(double lapsePeriod, int window) {
|
||||
this.cumulative = new ExponentialMovingAverageCumulativeHistory(window);
|
||||
public ExponentialMovingAverageRatio(double lapsePeriod, int window) {
|
||||
this.cumulative = new ExponentialMovingAverage(window);
|
||||
this.lapse = lapsePeriod > 0 ? 0.001 / lapsePeriod : 0; // convert to millisecs
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new event with successful outcome.
|
||||
*/
|
||||
public void success() {
|
||||
append(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new event with failed outcome.
|
||||
*/
|
||||
public void failure() {
|
||||
append(0);
|
||||
}
|
||||
@@ -60,6 +72,9 @@ public class ExponentialMovingAverageRatioCumulativeHistory {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of measurements recorded
|
||||
*/
|
||||
public int getCount() {
|
||||
return cumulative.getCount();
|
||||
}
|
||||
@@ -71,6 +86,9 @@ public class ExponentialMovingAverageRatioCumulativeHistory {
|
||||
return (System.currentTimeMillis() - t0) / 1000.;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mean success rate
|
||||
*/
|
||||
public double getMean() {
|
||||
int count = cumulative.getCount();
|
||||
if (count == 0) {
|
||||
@@ -81,22 +99,37 @@ public class ExponentialMovingAverageRatioCumulativeHistory {
|
||||
return alpha * cumulative.getMean() + 1 - alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the approximate standard deviation of the success rate measurements
|
||||
*/
|
||||
public double getStandardDeviation() {
|
||||
return cumulative.getStandardDeviation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum value recorded of the exponential weighted average (per measurement) success rate
|
||||
*/
|
||||
public double getMax() {
|
||||
return cumulative.getMax();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minimum value recorded of the exponential weighted average (per measurement) success rate
|
||||
*/
|
||||
public double getMin() {
|
||||
return cumulative.getMin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return summary statistics (count, mean, standard deviation etc.)
|
||||
*/
|
||||
public Statistics getStatistics() {
|
||||
return new Statistics(getCount(), getMin(), getMax(), getMean(), getStandardDeviation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[N=%d, min=%f, max=%f, mean=%f, sigma=%f, timeSinceLast=%f]", getCount(), getMin(),
|
||||
getMax(), getMean(), getStandardDeviation(), getTimeSinceLastMeasurement());
|
||||
return String.format("[%s, timeSinceLast=%f]", getStatistics(), getTimeSinceLastMeasurement());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,7 +56,25 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* MBean exporter for Spring Integration components in an existing application.
|
||||
* <p>
|
||||
* MBean exporter for Spring Integration components in an existing application. Add an instance of this as a bean
|
||||
* definition in the same context as the components you need to monitor and all message channels and message handlers
|
||||
* will be exposed.
|
||||
* </p>
|
||||
* <p>
|
||||
* Channels will report metrics on send and receive (counts, rates, errors) and handlers will report metrics on
|
||||
* execution duration. Channels will be registered under their name (bean id), if explicit, or the last part of their
|
||||
* internal name (e.g. "nullChannel") if registered by the framework. A handler that is attached to an endpoint will be
|
||||
* registered with the endpoint name (bean id) if there is one, otherwise under the name of the input channel. Handler
|
||||
* object names contain a <code>bean</code> key that reports the source of the name: "endpoint" if the name is the
|
||||
* endpoint id; "anonymous" if it is the input channel; and "handler" as a fallback, where the object name is just the
|
||||
* <code>toString()</code> of the handler.
|
||||
* </p>
|
||||
* <p>
|
||||
* This component is itself an MBean, reporting attributes concerning the names and object names of the channels and
|
||||
* handlers. It doesn't register itself to avoid conflicts with the standard <code><context:mbean-export/></code>
|
||||
* from Spring (which should therefore be used any time you need to expose those features).
|
||||
* </p>
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Helena Edelson
|
||||
@@ -81,9 +99,9 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
|
||||
private Set<SimpleMessageHandlerMonitor> handlers = new HashSet<SimpleMessageHandlerMonitor>();
|
||||
|
||||
private Set<SimpleMessageChannelMonitor> channels = new HashSet<SimpleMessageChannelMonitor>();
|
||||
private Set<DirectChannelMonitor> channels = new HashSet<DirectChannelMonitor>();
|
||||
|
||||
private Map<String, SimpleMessageChannelMonitor> channelsByName = new HashMap<String, SimpleMessageChannelMonitor>();
|
||||
private Map<String, DirectChannelMonitor> channelsByName = new HashMap<String, DirectChannelMonitor>();
|
||||
|
||||
private Map<String, MessageHandlerMonitor> handlersByName = new HashMap<String, MessageHandlerMonitor>();
|
||||
|
||||
@@ -149,7 +167,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
return monitor;
|
||||
}
|
||||
if (bean instanceof MessageChannel) {
|
||||
SimpleMessageChannelMonitor monitor;
|
||||
DirectChannelMonitor monitor;
|
||||
if (bean instanceof PollableChannel) {
|
||||
Object target = extractTarget(bean);
|
||||
if (target instanceof QueueChannel) {
|
||||
@@ -160,7 +178,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
}
|
||||
}
|
||||
else {
|
||||
monitor = new SimpleMessageChannelMonitor(beanName);
|
||||
monitor = new DirectChannelMonitor(beanName);
|
||||
}
|
||||
Object advised = applyChannelInterceptor(bean, monitor, beanClassLoader);
|
||||
channels.add(monitor);
|
||||
@@ -289,31 +307,15 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
return Collections.unmodifiableMap(objectNamesByName);
|
||||
}
|
||||
|
||||
public double getHandlerMeanDuration(String name) {
|
||||
public Statistics getHandlerDuration(String name) {
|
||||
if (handlersByName.containsKey(name)) {
|
||||
return handlersByName.get(name).getMeanDuration();
|
||||
return handlersByName.get(name).getDuration();
|
||||
}
|
||||
logger.debug("No handler found for (" + name + ")");
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getChannelSendCount(String name) {
|
||||
if (channelsByName.containsKey(name)) {
|
||||
return channelsByName.get(name).getSendCount();
|
||||
}
|
||||
logger.debug("No channel found for (" + name + ")");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long getChannelSendErrorCount(String name) {
|
||||
if (channelsByName.containsKey(name)) {
|
||||
return channelsByName.get(name).getSendErrorCount();
|
||||
}
|
||||
logger.debug("No channel found for (" + name + ")");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long getChannelReceiveCount(String name) {
|
||||
public int getChannelReceiveCount(String name) {
|
||||
if (channelsByName.containsKey(name)) {
|
||||
if (channelsByName.get(name) instanceof PollableChannelMonitor) {
|
||||
return ((PollableChannelMonitor) channelsByName.get(name)).getReceiveCount();
|
||||
@@ -323,32 +325,24 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
return -1;
|
||||
}
|
||||
|
||||
public double getChannelSendRate(String name) {
|
||||
public Statistics getChannelSendRate(String name) {
|
||||
if (channelsByName.containsKey(name)) {
|
||||
return channelsByName.get(name).getSendRate();
|
||||
}
|
||||
logger.debug("No channel found for (" + name + ")");
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
public double getChannelErrorRate(String name) {
|
||||
public Statistics getChannelErrorRate(String name) {
|
||||
if (channelsByName.containsKey(name)) {
|
||||
return channelsByName.get(name).getErrorRate();
|
||||
}
|
||||
logger.debug("No channel found for (" + name + ")");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public double getChannelMeanSendDuration(String name) {
|
||||
if (channelsByName.containsKey(name)) {
|
||||
return channelsByName.get(name).getMeanSendDuration();
|
||||
}
|
||||
logger.debug("No channel found for (" + name + ")");
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void registerChannels() {
|
||||
for (SimpleMessageChannelMonitor monitor : channels) {
|
||||
for (DirectChannelMonitor monitor : channels) {
|
||||
String name = monitor.getName();
|
||||
// Only register once...
|
||||
if (!channelsByName.containsKey(name)) {
|
||||
@@ -379,8 +373,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
}
|
||||
}
|
||||
|
||||
private Object applyChannelInterceptor(Object bean, SimpleMessageChannelMonitor interceptor,
|
||||
ClassLoader beanClassLoader) {
|
||||
private Object applyChannelInterceptor(Object bean, DirectChannelMonitor interceptor, ClassLoader beanClassLoader) {
|
||||
NameMatchMethodPointcutAdvisor channelsAdvice = new NameMatchMethodPointcutAdvisor(interceptor);
|
||||
channelsAdvice.addMethodName("send");
|
||||
channelsAdvice.addMethodName("receive");
|
||||
|
||||
@@ -21,15 +21,19 @@ import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
|
||||
/**
|
||||
* A {@link MessageHandlerMonitor} that exposes in addition the {@link Lifecycle} interface. The lifecycle methods can
|
||||
* be used to stop and start polling endpoints, for instance, in a live system.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @since 2.0
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ManagedResource
|
||||
public class LifecycleMessageHandlerMonitor implements MessageHandlerMonitor {
|
||||
public class LifecycleMessageHandlerMonitor implements MessageHandlerMonitor, Lifecycle {
|
||||
|
||||
private final Lifecycle lifecycle;
|
||||
|
||||
private final MessageHandlerMonitor delegate;
|
||||
|
||||
public LifecycleMessageHandlerMonitor(Lifecycle lifecycle, MessageHandlerMonitor delegate) {
|
||||
@@ -76,6 +80,10 @@ public class LifecycleMessageHandlerMonitor implements MessageHandlerMonitor {
|
||||
return delegate.getStandardDeviationDuration();
|
||||
}
|
||||
|
||||
public Statistics getDuration() {
|
||||
return delegate.getDuration();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
@@ -19,39 +19,89 @@ import org.springframework.jmx.export.annotation.ManagedMetric;
|
||||
import org.springframework.jmx.support.MetricType;
|
||||
|
||||
/**
|
||||
* @author dsyer
|
||||
*
|
||||
* Interface for all message channel monitors containing accessors for various useful metrics that are generic for all
|
||||
* channel types.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
public interface MessageChannelMonitor {
|
||||
|
||||
/**
|
||||
* @return the number of successful sends
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Sends")
|
||||
int getSendCount();
|
||||
|
||||
/**
|
||||
* @return the number of failed sends (either throwing an exception or rejected by the channel)
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Send Errors")
|
||||
int getSendErrorCount();
|
||||
|
||||
/**
|
||||
* @return the time in seconds since the last send
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Time Since Last Send in Seconds")
|
||||
double getTimeSinceLastSend();
|
||||
|
||||
/**
|
||||
* @return the mean send rate (per second)
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Rate per Second")
|
||||
double getSendRate();
|
||||
double getMeanSendRate();
|
||||
|
||||
/**
|
||||
* @return the mean error rate (per second). Errors comprise all failed sends.
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Error Rate per Second")
|
||||
double getErrorRate();
|
||||
double getMeanErrorRate();
|
||||
|
||||
/**
|
||||
* @return the mean ratio of failed to successful sends in approximately the last minute
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Mean Channel Error Ratio per Minute")
|
||||
double getErrorRatio();
|
||||
double getMeanErrorRatio();
|
||||
|
||||
/**
|
||||
* @return the mean send duration (milliseconds)
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Mean Duration")
|
||||
double getMeanSendDuration();
|
||||
|
||||
/**
|
||||
* @return the minimum send duration (milliseconds) since startup
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Min Duration")
|
||||
double getMinSendDuration();
|
||||
|
||||
/**
|
||||
* @return the maximum send duration (milliseconds) since startup
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Max Duration")
|
||||
double getMaxSendDuration();
|
||||
|
||||
/**
|
||||
* @return the standard deviation send duration (milliseconds)
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Standard Deviation Duration")
|
||||
double getStandardDeviationSendDuration();
|
||||
|
||||
/**
|
||||
* @return summary statistics about the send duration (milliseconds)
|
||||
*/
|
||||
Statistics getSendDuration();
|
||||
|
||||
/**
|
||||
* @return summary statistics about the send rates (per second)
|
||||
*/
|
||||
Statistics getSendRate();
|
||||
|
||||
/**
|
||||
* @return summary statistics about the error rates (per second)
|
||||
*/
|
||||
Statistics getErrorRate();
|
||||
|
||||
}
|
||||
@@ -25,23 +25,43 @@ import org.springframework.jmx.support.MetricType;
|
||||
*/
|
||||
public interface MessageHandlerMonitor {
|
||||
|
||||
/**
|
||||
* @return the number of successful handler calls
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count", description = "rate=1h")
|
||||
int getHandleCount();
|
||||
|
||||
/**
|
||||
* @return the number of failed handler calls
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Error Count", description = "rate=1h")
|
||||
int getErrorCount();
|
||||
|
||||
/**
|
||||
* @return the maximum handler duration (milliseconds)
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Mean Duration")
|
||||
double getMeanDuration();
|
||||
|
||||
/**
|
||||
* @return the minimum handler duration (milliseconds)
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Min Duration")
|
||||
double getMinDuration();
|
||||
|
||||
/**
|
||||
* @return the standard deviation handler duration (milliseconds)
|
||||
*/
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Max Duration")
|
||||
double getMaxDuration();
|
||||
|
||||
@ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Standard Deviation Duration")
|
||||
double getStandardDeviationDuration();
|
||||
|
||||
/**
|
||||
* @return summary statistics about the handler duration (milliseconds)
|
||||
*/
|
||||
Statistics getDuration();
|
||||
|
||||
String getName();
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.integration.monitor;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Locator interface for mapping bean names to JMX object names.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @since 2.0
|
||||
@@ -25,8 +27,15 @@ import java.util.Map;
|
||||
*/
|
||||
public interface ObjectNameLocator {
|
||||
|
||||
/**
|
||||
* @param beanName the bean name to query
|
||||
* @return a String representation of the corresponding JMX object name (or null if there is none)
|
||||
*/
|
||||
String getObjectName(String beanName);
|
||||
|
||||
/**
|
||||
* @return a map of all the known bean and object names
|
||||
*/
|
||||
Map<String, String> getObjectNames();
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.integration.monitor;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
@@ -28,11 +28,11 @@ import org.springframework.jmx.support.MetricType;
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
public class PollableChannelMonitor extends SimpleMessageChannelMonitor {
|
||||
public class PollableChannelMonitor extends DirectChannelMonitor {
|
||||
|
||||
private final AtomicLong receiveCount = new AtomicLong();
|
||||
private final AtomicInteger receiveCount = new AtomicInteger();
|
||||
|
||||
private final AtomicLong receiveErrorCount = new AtomicLong();
|
||||
private final AtomicInteger receiveErrorCount = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* @param name
|
||||
@@ -68,12 +68,12 @@ public class PollableChannelMonitor extends SimpleMessageChannelMonitor {
|
||||
}
|
||||
|
||||
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Receives")
|
||||
public long getReceiveCount() {
|
||||
public int getReceiveCount() {
|
||||
return receiveCount.get();
|
||||
}
|
||||
|
||||
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Receive Errors")
|
||||
public long getReceiveErrorCount() {
|
||||
public int getReceiveErrorCount() {
|
||||
return receiveErrorCount.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl
|
||||
|
||||
private final AtomicInteger errorCount = new AtomicInteger();
|
||||
|
||||
private final ExponentialMovingAverageCumulativeHistory duration = new ExponentialMovingAverageCumulativeHistory(
|
||||
private final ExponentialMovingAverage duration = new ExponentialMovingAverage(
|
||||
DEFAULT_MOVING_AVERAGE_WINDOW);
|
||||
|
||||
private String name;
|
||||
@@ -128,6 +128,10 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl
|
||||
public double getStandardDeviationDuration() {
|
||||
return duration.getStandardDeviation();
|
||||
}
|
||||
|
||||
public Statistics getDuration() {
|
||||
return duration.getStatistics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.integration.monitor;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
public class Statistics {
|
||||
|
||||
private final int count;
|
||||
private final double min;
|
||||
private final double max;
|
||||
private final double mean;
|
||||
private final double standardDeviation;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public Statistics(int count, double min, double max, double mean, double standardDeviation) {
|
||||
this.count = count;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.mean = mean;
|
||||
this.standardDeviation = standardDeviation;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public double getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public double getMean() {
|
||||
return mean;
|
||||
}
|
||||
|
||||
public double getStandardDeviation() {
|
||||
return standardDeviation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[N=%d, min=%f, max=%f, mean=%f, sigma=%f]", count, min, max, getMean(),
|
||||
getStandardDeviation());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import org.springframework.integration.jmx.OperationInvokingMessageHandlerTests;
|
||||
import org.springframework.integration.jmx.config.NotificationListeningChannelAdapterParserTests;
|
||||
import org.springframework.integration.jmx.config.OperationInvokingChannelAdapterParserTests;
|
||||
import org.springframework.integration.jmx.config.OperationInvokingOutboundGatewayTests;
|
||||
import org.springframework.integration.monitor.ExponentialMovingAverageCumulativeHistoryTests;
|
||||
import org.springframework.integration.monitor.ExponentialMovingAverageRatioCumulativeHistoryTests;
|
||||
import org.springframework.integration.monitor.ExponentialMovingAverageTests;
|
||||
import org.springframework.integration.monitor.ExponentialMovingAverageRatioTests;
|
||||
import org.springframework.integration.monitor.HandlerMonitoringIntegrationTests;
|
||||
import org.springframework.integration.monitor.MessageChannelsMonitorIntegrationTests;
|
||||
|
||||
@@ -41,10 +41,10 @@ import org.springframework.integration.monitor.MessageChannelsMonitorIntegration
|
||||
*/
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses(value = { OperationInvokingMessageHandlerTests.class,
|
||||
ExponentialMovingAverageCumulativeHistoryTests.class, OperationInvokingChannelAdapterParserTests.class,
|
||||
ExponentialMovingAverageTests.class, OperationInvokingChannelAdapterParserTests.class,
|
||||
HandlerMonitoringIntegrationTests.class, NotificationListeningMessageProducerTests.class,
|
||||
OperationInvokingOutboundGatewayTests.class, NotificationListeningChannelAdapterParserTests.class,
|
||||
ControlBusXmlTests.class, ExponentialMovingAverageRatioCumulativeHistoryTests.class,
|
||||
ControlBusXmlTests.class, ExponentialMovingAverageRatioTests.class,
|
||||
AttributePollingMessageSourceTests.class, ControlBusTests.class, MessageChannelsMonitorIntegrationTests.class })
|
||||
@Ignore
|
||||
public class IgnoredTestSuite {
|
||||
|
||||
@@ -44,7 +44,7 @@ import org.springframework.integration.handler.BridgeHandler;
|
||||
import org.springframework.integration.monitor.IntegrationMBeanExporter;
|
||||
import org.springframework.integration.monitor.LifecycleMessageHandlerMonitor;
|
||||
import org.springframework.integration.monitor.QueueChannelMonitor;
|
||||
import org.springframework.integration.monitor.SimpleMessageChannelMonitor;
|
||||
import org.springframework.integration.monitor.DirectChannelMonitor;
|
||||
import org.springframework.jmx.support.MBeanServerFactoryBean;
|
||||
import org.springframework.jmx.support.ObjectNameManager;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
@@ -86,7 +86,7 @@ public class ControlBusTests {
|
||||
MBeanServer mbeanServer = context.getBean("mbeanServer", MBeanServer.class);
|
||||
ObjectInstance instance = mbeanServer.getObjectInstance(ObjectNameManager
|
||||
.getInstance("domain.test1:type=MessageChannel,name=directChannel"));
|
||||
assertEquals(SimpleMessageChannelMonitor.class.getName(), instance.getClassName());
|
||||
assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,7 +99,7 @@ public class ControlBusTests {
|
||||
ObjectInstance instance = mbeanServer
|
||||
.getObjectInstance(ObjectNameManager
|
||||
.getInstance("domain.test1b:type=MessageChannel,name=org.springframework.integration.generated#0,source=anonymous"));
|
||||
assertEquals(SimpleMessageChannelMonitor.class.getName(), instance.getClassName());
|
||||
assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,7 +111,7 @@ public class ControlBusTests {
|
||||
MBeanServer mbeanServer = context.getBean("mbeanServer", MBeanServer.class);
|
||||
ObjectInstance instance = mbeanServer.getObjectInstance(ObjectNameManager
|
||||
.getInstance("domain.test1a:type=MessageChannel,name=directChannel,foo=bar"));
|
||||
assertEquals(SimpleMessageChannelMonitor.class.getName(), instance.getClassName());
|
||||
assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.monitor.LifecycleMessageHandlerMonitor;
|
||||
import org.springframework.integration.monitor.QueueChannelMonitor;
|
||||
import org.springframework.integration.monitor.SimpleMessageChannelMonitor;
|
||||
import org.springframework.integration.monitor.DirectChannelMonitor;
|
||||
import org.springframework.jmx.support.ObjectNameManager;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -51,7 +51,7 @@ public class ControlBusXmlTests {
|
||||
public void directChannelRegistered() throws Exception {
|
||||
ObjectInstance instance = mbeanServer.getObjectInstance(
|
||||
ObjectNameManager.getInstance(DOMAIN + ":type=MessageChannel,name=testDirectChannel"));
|
||||
assertEquals(SimpleMessageChannelMonitor.class.getName(), instance.getClassName());
|
||||
assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -42,11 +42,11 @@ public class ChannelIntegrationTests {
|
||||
|
||||
requests.send(new GenericMessage<String>("foo"));
|
||||
|
||||
double duration = messageChannelsMonitor.getChannelMeanSendDuration("" + requests);
|
||||
assertTrue("No statistics for requests channel", duration >= 0);
|
||||
double rate = messageChannelsMonitor.getChannelSendRate("" + requests).getMean();
|
||||
assertTrue("No statistics for requests channel", rate >= 0);
|
||||
|
||||
duration = messageChannelsMonitor.getChannelMeanSendDuration("" + intermediate);
|
||||
assertTrue("No statistics for intermediate channel", duration >= 0);
|
||||
rate = messageChannelsMonitor.getChannelSendRate("" + intermediate).getMean();
|
||||
assertTrue("No statistics for intermediate channel", rate >= 0);
|
||||
|
||||
assertNotNull(intermediate.receive(100L));
|
||||
double count = messageChannelsMonitor.getChannelReceiveCount("" + intermediate);
|
||||
|
||||
@@ -24,9 +24,9 @@ import org.junit.Test;
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExponentialMovingAverageRateCumulativeHistoryTests {
|
||||
public class ExponentialMovingAverageRateTests {
|
||||
|
||||
private ExponentialMovingAverageRateCumulativeHistory history = new ExponentialMovingAverageRateCumulativeHistory(
|
||||
private ExponentialMovingAverageRate history = new ExponentialMovingAverageRate(
|
||||
1., 10., 10);
|
||||
|
||||
@Test
|
||||
@@ -71,7 +71,7 @@ public class ExponentialMovingAverageRateCumulativeHistoryTests {
|
||||
Thread.sleep(22L);
|
||||
history.increment();
|
||||
Thread.sleep(18L);
|
||||
assertEquals(0, Math.log10(history.getStandardDeviation()), 1);
|
||||
assertTrue("Standard deviation should be non-zero: "+history, history.getStandardDeviation()>0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import org.junit.Test;
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExponentialMovingAverageRatioCumulativeHistoryTests {
|
||||
public class ExponentialMovingAverageRatioTests {
|
||||
|
||||
private ExponentialMovingAverageRatioCumulativeHistory history = new ExponentialMovingAverageRatioCumulativeHistory(
|
||||
private ExponentialMovingAverageRatio history = new ExponentialMovingAverageRatio(
|
||||
0.5, 10);
|
||||
|
||||
@Test
|
||||
@@ -23,9 +23,9 @@ import org.junit.Test;
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExponentialMovingAverageCumulativeHistoryTests {
|
||||
public class ExponentialMovingAverageTests {
|
||||
|
||||
private ExponentialMovingAverageCumulativeHistory history = new ExponentialMovingAverageCumulativeHistory(10);
|
||||
private ExponentialMovingAverage history = new ExponentialMovingAverage(10);
|
||||
|
||||
@Test
|
||||
public void testGetCount() {
|
||||
@@ -82,8 +82,8 @@ public class HandlerMonitoringIntegrationTests {
|
||||
channel.send(new GenericMessage<String>("bar"));
|
||||
assertEquals(before + 1, service.getCounter());
|
||||
|
||||
double duration = messageHandlersMonitor.getHandlerMeanDuration(monitor);
|
||||
assertTrue("No statistics for input channel", duration > 0);
|
||||
int count = messageHandlersMonitor.getHandlerDuration(monitor).getCount();
|
||||
assertTrue("No statistics for input channel", count > 0);
|
||||
|
||||
} finally {
|
||||
context.close();
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
package org.springframework.integration.monitor;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -70,11 +69,8 @@ public class MessageChannelsMonitorIntegrationTests {
|
||||
assertEquals(before + 50, service.getCounter());
|
||||
|
||||
// The handler monitor is registered under the endpoint id (since it is explicit)
|
||||
double sends = messageChannelsMonitor.getChannelSendCount("" + channel);
|
||||
int sends = messageChannelsMonitor.getChannelSendRate("" + channel).getCount();
|
||||
assertEquals("No send statistics for input channel", 50, sends, 0.01);
|
||||
double rate = messageChannelsMonitor.getChannelSendRate("" + channel);
|
||||
assertTrue(String.format("Unexpected rate statistics for input channel %f %f", 60000. / 20L, rate),
|
||||
60000. / 20L > rate && rate > 0);
|
||||
|
||||
}
|
||||
finally {
|
||||
@@ -106,13 +102,10 @@ public class MessageChannelsMonitorIntegrationTests {
|
||||
assertEquals(before + 10, service.getCounter());
|
||||
|
||||
// The handler monitor is registered under the endpoint id (since it is explicit)
|
||||
double sends = messageChannelsMonitor.getChannelSendCount("" + channel);
|
||||
int sends = messageChannelsMonitor.getChannelSendRate("" + channel).getCount();
|
||||
assertEquals("No send statistics for input channel", 11, sends, 0.01);
|
||||
double errors = messageChannelsMonitor.getChannelSendErrorCount("" + channel);
|
||||
int errors = messageChannelsMonitor.getChannelErrorRate("" + channel).getCount();
|
||||
assertEquals("No error statistics for input channel", 1, errors, 0.01);
|
||||
double rate = messageChannelsMonitor.getChannelErrorRate("" + channel);
|
||||
assertTrue(String.format("Unexpected error statistics for input channel %f %f", 60000. / 20L, rate),
|
||||
60000. / 20L > rate && rate > 0);
|
||||
}
|
||||
finally {
|
||||
context.close();
|
||||
@@ -143,11 +136,11 @@ public class MessageChannelsMonitorIntegrationTests {
|
||||
assertEquals(before + 10, service.getCounter());
|
||||
|
||||
// The handler monitor is registered under the endpoint id (since it is explicit)
|
||||
long sends = messageChannelsMonitor.getChannelSendCount("" + channel);
|
||||
int sends = messageChannelsMonitor.getChannelSendRate("" + channel).getCount();
|
||||
assertEquals("No send statistics for input channel", 11, sends);
|
||||
long receives = messageChannelsMonitor.getChannelReceiveCount("" + channel);
|
||||
int receives = messageChannelsMonitor.getChannelReceiveCount("" + channel);
|
||||
assertEquals("No send statistics for input channel", 11, receives);
|
||||
long errors = messageChannelsMonitor.getChannelSendErrorCount("" + channel);
|
||||
int errors = messageChannelsMonitor.getChannelErrorRate("" + channel).getCount();
|
||||
assertEquals("Expect no errors for input channel (handler fails)", 0, errors);
|
||||
}
|
||||
finally {
|
||||
@@ -167,7 +160,7 @@ public class MessageChannelsMonitorIntegrationTests {
|
||||
assertEquals(before + 1, service.getCounter());
|
||||
|
||||
// The handler monitor is registered under the endpoint id (since it is explicit)
|
||||
double sends = messageChannelsMonitor.getChannelSendCount("" + channel);
|
||||
int sends = messageChannelsMonitor.getChannelSendRate("" + channel).getCount();
|
||||
assertEquals("No statistics for input channel", 1, sends, 0.01);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user