Aggregator
Introduction Basically a mirror-image of the Splitter, the Aggregator is a type of Message Consumer that receives multiple Messages and combines them into a single Message. In fact, Aggregators are often downstream consumers in a pipeline that includes a Splitter. Technically, the Aggregator is more complex than a Splitter, because it is required to maintain state (the Messages to-be-aggregated), to decide when the complete group of Messages is available, and to timeout if necessary. Furthermore, in case of a timeout, the Aggregator needs to know whether to send the partial results or to discard them to a separate channel.
Functionality The Aggregator combines a group of related messages, by storing and grouping them, until the group is deemed complete. At that point, the Aggregator will create a single message by processing the whole group, and will send the result message further. As messages might arrive with a certain delay (or certain messages from the group might not arrive at all), the Aggregator can specify a timeout (counted from the moment when the first message in the group has arrived), and whether, in the case of a timeout, the group should be discarded, or the Aggregator should merely attempt to create a single message out of what has arrived so far. An important aspect of implementing an Aggregator is providing the logic that has to be executed when the aggregation (creation of a single message out of many) takes place. In Spring Integration, the grouping of the messages for Aggregation is done based on their CORRELATION_ID message header (i.e. the messages with the same CORRELATION_ID will be grouped together). An important concern with respect to the timeout is, what happens if late messages arrive after the aggregation has taken place? In this case, a configuration option allows the user to decide whether they should be discarded or not.
Programming model The Aggregation API consists of a number of classes: The base class AbstractMessageAggregator and its subclass MethodInvokingMessageAggregator The CompletionStrategy interface and its default implementation SequenceSizeCompletionStrategy The AbstractMessageAggregator is a MessageConsumer implementation, encapsulating the common functionalities of an Aggregator, which are: storing messages until the message sequence to aggregate is complete (and grouping them according to their CORRELATION_ID), and implementing the timeout functionality. The responsibility of deciding whether the message sequence is complete is delegated to a CompletionStrategy instance. A brief highlight of the base AbstractMessageAggregator (the responsibility of implementing the aggregateMessages method is left to the developer): public abstract class AbstractMessageAggregator extends AbstractMessageBarrierConsumer { private volatile CompletionStrategy completionStrategy = new SequenceSizeCompletionStrategy(); .... protected abstract Message<?> aggregateMessages(List<Message<?>> messages); } For implementing a specific aggregator object for an application, a developer can extend AbstractMessageAggregator and implement the aggregateMessages method. However, there are better suited (which reads, less coupled to the API) solutions for implementing the aggregation logic, which can be configured easily either through XML or through annotations. In general, any ordinary Java class (i.e. POJO) can implement the aggregation algorithm. For doing so, it must provide a method that accepts as an argument a single java.util.List (parametrized lists are supported as well). This method will be invoked for aggregating messages, as follows: if the argument is a parametrized java.util.List, and the parameter type is assignable to Message, then the whole list of messages accumulated for aggregation will be sent to the aggregator if the argument is a non-parametrized java.util.List or the parameter type is not assignable to Message, then the method will receive the payloads of the accumulated messages if the return type is not assignable to Message, then it will be treated as the payload for a Message that will be created automatically by the framework. In the interest of code simplicity, and promoting best practices such as low coupling, testability, etc., the preferred way of implementing the aggregation logic is through a POJO, and using the XML or annotation support for setting it up in the application. The CompletionStrategy interface is defined as follows: public interface CompletionStrategy { boolean isComplete(List<Message<?>> messages); } In general, any ordinary Java class (i.e. POJO) can implement the completion decision mechanism. For doing so, it must provide a method that accepts as an argument a single java.util.List (parametrized lists are supported as well), and returns a boolean value. This method will be invoked after the arrival of a new message, to decide whether the group is complete or not, as follows: if the argument is a parametrized java.util.List, and the parameter type is assignable to Message, then the whole list of messages accumulated in the group will be sent to the method if the argument is a non-parametrized java.util.List or the parameter type is not assignable to Message, then the method will receive the payloads of the accumulated messages the method must return true if the message group is complete and ready for aggregation, and false otherwise. Spring Integration provides an out-of-the box implementation for CompletionStrategy, the SequenceSizeCompletionStrategy This implementation uses the SEQUENCE_NUMBER and SEQUENCE_SIZE of the arriving messages for deciding when a message group is complete and ready to be aggregated.
Configuring an Aggregator with XML Spring Integration supports the configuration of an aggregator via XML through the <aggregator/> element. A completely defined sample on how to define such an element is presented below, as well as it: <channel id="inputChannel"/> <aggregator id="completelyDefinedAggregator" input-channel="inputChannel" output-channel="outputChannel" discard-channel="discardChannel" ref="aggregatorBean" method="add" completion-strategy="completionStrategyBean" completion-strategy-method="checkCompleteness" timeout="42" send-partial-result-on-timeout="true" reaper-interval="135" tracked-correlation-id-capacity="99" send-timeout="86420000" /> <channel id="outputChannel"/> <bean id="aggregatorBean" class="sample.PojoAggregator"/> <bean id="completionStrategyBean" class="sample.PojoCompletionStrategy"/> The id of the aggregator is optional. The input channel of the aggregator. Required. The channel where the aggregator will send the aggregation results. Optional (because incoming messages can specify a reply channel themselves). The channel where the aggregator will send the messages that timed out (if send-partial-results-on-timeout is false). Optional. A reference to a bean defined in the application context. The bean must implement the aggregation logic as described above. Required. A method defined on the bean referenced by ref, that implements the message aggregation algorithm. Optional, with restrictions (see above). A reference to a bean that implements the decision algorithm as to whether a given message group is complete. The bean can be an implementation of the CompletionStrategy interface or a POJO. In the latter case the completion-strategy-method attribute must be defined as well. Optional (by default, the aggregator . A method defined on the bean referenced by completion-strategy, that implements the completion decision algorithm. Optional, with restrictions (requires completion-strategy to be present). The timeout for aggregating messages (counted from the arrival of the first message). Optional. Whether upon the expiration of the timeout, the aggregator shall try to aggregate the already arrived messages. Optional (false by default). The interval (in milliseconds) at which a reaper task is executed, checking if there are any timed out groups. Optional. The capacity of the correlation id tracker. Remembers the already processed correlation ids, preventing the formation of new groups for messages that arrive after their group has been already processed (aggregated or discarded). Optional. The timeout for sending out messages. Optional. An implementation of the aggregator bean, for example, looks as follows: public class PojoAggregator { public Long add(List<Long> results) { long total = 0l; for (long partialResult: results) { total += partialResult; } return total; } } An implementation of the completion strategy bean for the example above may be as follows: public class PojoCompletionStrategy { ... public boolean checkCompleteness(List<Long> numbers) { int sum = 0; for (long number: numbers) { sum += number; } return sum >= maxValue; } }Wherever it makes sense, the completion strategy method and the aggregator method can be combined in a single bean.
Configuring an Aggregator with Annotations An aggregator configured using annotations can look like this. public class Waiter { ... @Aggregator public Delivery aggregatingMethod(List<OrderItem> items) { ... } @CompletionStrategy public boolean completionChecker(List<Message<?>> messages) { ... } } An annotation indicating that this method shall be used as an aggregator. Must be specified if this class will be used as an aggregator. An annotation indicating that this method shall be used as the completion strategy of an aggregator. If not present of the method, the aggregator will use the SequenceSizeCompletionStrategy. All the configuration options provided by xml element are available for the @Aggregator annotation. The aggregator can be either referenced explicitly from XML or, if the @MessageEndpoint is defined on the class, detected automatically through classpath scanning.