I have a spring-integration message channel which read from a database using a jpa inbound-channel-adapter.
<int:channel id="logChannel">
<int:priority-queue capacity="20" />
</int:channel>
<int-jpa:inbound-channel-adapter
channel="logChannel" entity-class="com.objects.Transactionlog"
entity-manager-factory="entityManagerFactory" auto-startup="true"
jpa-query="SELECT x FROM Transactionlog AS x WHERE x.status LIKE '1'" max-results="1">
<int:poller fixed-rate="5000">
<int:transactional propagation="REQUIRED"
transaction-manager="transactionManager" />
</int:poller>
</int-jpa:inbound-channel-adapter>
This always reads only the first row of the table transactionlog. So I want to update the status of each database entry just after read. Any body know how to do that?
If max-results="1" is OK for you and receive only one entity per 5 second is appropiate for your use-case, let it be.
Now how to update that entity to skip it on the next poll.
The <int-jpa:inbound-channel-adapter> has delete-after-poll="true" option, which allows to perform entityManager.remove(entity) after an entity retrival.
Right, it is the real removal from DB. To convert it to the UPDATE, you can mark your entity with:
#SQLDelete(sql = "UPDATE Transactionlog SET status = false WHERE id = ?")
Or something similar, that is appropiate for you.
Another feature is Transaction Synchronization, when you mark your <poller> with some before-commit factory and do UPDATE there. Something like:
<int-jpa:inbound-channel-adapter ...>
<int:poller fixed-rate="5000">
<int:transactional propagation="REQUIRED"
transaction-manager="transactionManager"
synchronization-factory="txSyncFactory" />
</int:poller>
<int-jpa:inbound-channel-adapter>
<int:transaction-synchronization-factory id="txSyncFactory">
<int:before-commit channel="updateEntityChannel" />
</int:transaction-synchronization-factory>
<int:chain input-channel="updateEntityChannel">
<int:enricher>
<int:property name="status" value="true"/>
</int:enricher>
<int-jpa:outbound-channel-adapter entity-manager="entityManager"/>
</int:chain/>
Something like that.
Related
Spring integration tcp gateway can be setup as follows:
<!-- Server side -->
<int-ip:tcp-connection-factory id="crLfServer"
type="server"
port="${availableServerSocket}"/>
<int-ip:tcp-inbound-gateway id="gatewayCrLf"
connection-factory="crLfServer"
request-channel="serverBytes2StringChannel"
error-channel="errorChannel"
reply-timeout="10000" />
<int:channel id="toSA" />
<int:service-activator input-channel="toSA"
ref="echoService"
method="test"/>
<bean id="echoService"
class="org.springframework.integration.samples.tcpclientserver.EchoService" />
<int:object-to-string-transformer id="serverBytes2String"
input-channel="serverBytes2StringChannel"
output-channel="toSA"/>
<int:transformer id="errorHandler"
input-channel="errorChannel"
expression="Error processing payload"/>
Notice the reply-timeout which is set as 10 seconds.
Does it mean that the TCP server will call the service and can wait for a maximum of 10 seconds? If the service does not reply within 10 seconds, Does the TCP server will send the message to errorChannel which in turn sends the client error message "Error processing payload"?
When I tested the TCP Server with a service that takes 20 seconds, client is taking 20 seconds to get the response. I am not seeing error message.
Can you please help in understanding the reply-timeout in TCP inbound-gateway?
Thanks
UPDATE:
Thanks for Artem to help out with this issue.
Best way to solve this problem is with the following config:
<beans>
<int-ip:tcp-connection-factory id="crLfServer" type="server" port="${availableServerSocket}"/>
<int-ip:tcp-inbound-gateway id="gatewayCrLf" connection-factory="crLfServer" request-channel="requestChannel" error-channel="errorChannel" reply-timeout="5000" />
<int:service-activator input-channel="requestChannel" ref="gateway" requires-reply="true"/>
<int:gateway id="gateway" default-request-channel="timeoutChannel" default-reply-timeout="5000" />
<int:object-to-string-transformer id="serverBytes2String" input-channel="timeoutChannel" output-channel="serviceChannel"/>
<int:channel id="timeoutChannel">
<int:dispatcher task-executor="executor"/>
</int:channel>
<bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
<property name="queueCapacity" value="25" />
</bean>
<int:service-activator input-channel="serviceChannel" ref="echoService" method="test"/>
<bean id="echoService" class="org.springframework.integration.samples.tcpclientserver.EchoService" />
<int:transformer id="errorHandler" input-channel="errorChannel" expression="payload.failedMessage.payload + ' errorHandleMsg: may be timeout error'"/>
</beans>
Thanks
Well, actually we should on that attribute a description like we have in other similar places, e.g. HTTP Inbound Gateway:
<xsd:attribute name="reply-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Used to set the receiveTimeout on the underlying MessagingTemplate instance
(org.springframework.integration.core.MessagingTemplate) for receiving messages
from the reply channel. If not specified this property will default to "1000"
(1 second).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
That timeout means how much to wait for reply from downstream flow. But! That is possible if you flow is shifted to another thread somewhere. Otherwise everything is performed in the caller's Thread and therefore the wait time isn't deterministic.
Anyway we return null there after timeout without reply. And it is reflected in the TcpInboundGateway:
Message<?> reply = this.sendAndReceiveMessage(message);
if (reply == null) {
if (logger.isDebugEnabled()) {
logger.debug("null reply received for " + message + " nothing to send");
}
return false;
}
We can reconsider a logic in the TcpInboundGateway for :
if (reply == null && this.errorOnTimeout) {
if (object instanceof Message) {
error = new MessageTimeoutException((Message<?>) object, "No reply received within timeout");
}
else {
error = new MessageTimeoutException("No reply received within timeout");
}
}
But seems for me it really would be better on to rely on the timeout from the client.
UPDATE
I think we can overcome the limitation and meet you requirements with the midflow <gateway>:
<gateway id="gateway" default-request-channel="timeoutChannel" default-reply-timeout="10000"/>
<channel id="timeoutChannel">
<dispatcher task-executor="executor"/>
</channel>
<service-activator input-channel="requestChannel"
ref="gateway"
requires-reply="true"/>
So, the <service-activator> calls <gateway> and waits for reply from there. Requiring the last one, of course, to end up with the ReplyRequiredException, which you can convert into desired MessageTimeoutException in your error flow on the error-channel="errorChannel".
The timeoutChannel is an executor one, making our default-reply-timeout="10000" very useful because we shift a message on the gateway into separate thread immediately and move right from there into reply waiting process wrapped with that timeout on the CountDonwLatch.
Hope that is clear.
Configuration details :
<int:publish-subscribe-channel id="toKafka"/>
<int:publish-subscribe-channel id="sendMessageToKafkaChannel"/>
<int:service-activator input-channel="toKafka" output-channel="sendMessageToKafkaChannel" order="1" ref="conditionalProducerService" method="producerCircuitBreaker">
<int:request-handler-advice-chain>
<ref bean="circuitBreakerAdvice" />
</int:request-handler-advice-chain>
</int:service-activator>
<int-kafka:outbound-channel-adapter id="kafkaOutboundChannelAdapter" kafka-producer-context-ref="producerContext"
auto-startup="true" channel="toKafka" message-key="kafka_messageKey"/>
<bean id="circuitBreakerAdvice" class="org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice">
<property name="threshold" value="2"/>
<property name="halfOpenAfter" value="15000" />
</bean>
public Message<?> producerCircuitBreaker(Message<?> payload) {
throw new RuntimeException("foo Pro");
}
for(int i=0;i<4;i++){
toKafka.send(MessageBuilder
.withPayload(messageVO.getMessageContentVO())
.setHeader(KafkaHeaders.TOPIC, topic)
.setHeader(KafkaHeaders.PARTITION_ID,Integer.parseInt(messageVO.getPartition())).
build());
APPLOGGER.info("sending message");
}
Expecting to get the process to fail 2 times with exception and then "circuit breaker open" exception but it is simply stopping after throwing the below exception in the console.
Also how can we configure error-channel here.
https://gist.github.com/anonymous/67aae50e548c78470cd0
updated config:
<int:service-activator input-channel="toKafka" ref="gw">
<int:request-handler-advice-chain> <ref bean="circuitBreakerAdvice"/>
</int:request-handler-advice-chain>
</int:service-activator>
<int:channel id="failedChannel1" />
<int:gateway id="gw" default-request-channel="toKafka" default-reply-timeout="0" error-channel="failedChannel1" />
<int:chain input-channel="failedChannel1">
<int:transformer expression="'failed:'+payload.failedMessage.payload+ ' with a' +payload.cause.message" />
<int-stream:stderr-channel-adapter append-newline="true"/>
</int:chain>
getting below exception.
failed:TestVo[data=sample message]] with Cannot process message.
https://gist.github.com/anonymous/921be7691c41d125dc84
however it is working with same message otherwise.(message content changed intentionally)
Also tried putting invalid value for the producer context: eg. broker- list/value-class-type as invalid class type than expected as below.
getting below error but expecting to get CB to come into picture and message should flow to the error channel.
in case of value-class-type : CB not invoked however message flowing to the error channel but there are many message are coming for 1 message published.
failed:TestVo [data={tes message}}] with No converter found capable of converting from type xx.xxx.vo.TestVo to type java.lang.String
these is occuring in the console many times.
in case of broker-list : it is simply throwing exception in the console.
https://gist.github.com/anonymous/6ece517fb5e82ac73492
Expected : CB to get invoked and message flow to the error channel in all cases.
<int-kafka:outbound-channel-adapter id="kafkaOutboundChannelAdapter" kafka-producer-context-ref="producerContext"
auto-startup="true" channel="toKafka" message-key="kafka_messageKey"/>
<int-kafka:producer-context id="producerContext" producer-properties="producerProperties">
<int-kafka:producer-configurations>
<int-kafka:producer-configuration
broker-list="1.2.3:9092" topic="headers['topic']" key-class-type="java.lang.String"
value-class-type="java.lang.String"
value-encoder="kafkaEncoder" key-encoder="kafkaKeyEncoder"
compression-type="none" />
</int-kafka:producer-configurations>
</int-kafka:producer-context>
With that code, you need try {...} around the send().
The first two attempts will catch your RuntimeException; the next will catch the circuit breaker exception.
Use a Messaging Gateway with an error channel instead of sending to the channel directly.
EDIT
This code...
<int:service-activator input-channel="toKafka" ref="gw">
<int:request-handler-advice-chain> <ref bean="circuitBreakerAdvice"/>
</int:request-handler-advice-chain>
</int:service-activator>
<int:gateway id="gw" default-request-channel="toKafka" default-reply-timeout="0" error-channel="failedChannel1" />
When you send a message to toKafka, the gateway will be invoked which will send the message to toKafka in a loop.
It will cause a stack overflow.
I would like to apply an Interceptor on the reply-channel of an http:inbound-gateway to save some event related data to a table. The flow continues in a chain which then goes to a header-value-router. As an example let's take a service-activator at the end of this flow, where the output-channel is not specified. In this case, the replyChannel header holds a TemporaryReplyChannel object (anonymous reply channel) instead of the gateway's reply-channel. This way the Interceptor is never called.
Is there a way to "force" the usage of the specified reply-channel? The Spring document states that
by defining a default-reply-channel you can point to a channel of your choosing, which in this case would be a publish-subscribe-channel. The Gateway would create a bridge from it to the temporary, anonymous reply channel that is stored in the header.
I've tried using a publish-subscribe-channel as reply-channel, but it didn't make any difference. Maybe I misunderstood the article...
Inside my chain I've also experimented with a header-enricher. I wanted to overwrite the value of the replyChannel with the id of the channel I want to intercept (submit.reply.channel). While debugging I was able to see "submit.reply.channel" in the header, but then I got an exception java.lang.NoClassDefFoundError: org/springframework/transaction/interceptor/NoRollbackRuleAttribute and stopped trying ;-)
Code snippets
<int-http:inbound-gateway id="submitHttpGateway"
request-channel="submit.request.channel" reply-channel="submit.reply.channel" path="/submit" supported-methods="GET">
<int-http:header name="requestAttributes" expression="#requestAttributes" />
<int-http:header name="requestParametersMap" expression="#requestParams" />
</int-http:inbound-gateway>
<int:channel id="submit.request.channel" />
<int:publish-subscribe-channel id="submit.reply.channel">
<int:interceptors>
<int:ref bean="replyChannelInterceptor" />
</int:interceptors>
</int:publish-subscribe-channel>
Thanks in advance for your help!
The only "easy" way is to explicitly send the reply via the output-channel on the last endpoint.
In fact, all that happens when you send to a declared channel is the reply channel is simply bridged to the replyChannel header.
You could do it by saving off the replyChannel header in another header, set the replyChannel header to some other channel (which you can intercept); then restore the replyChannel header to the saved-off channel before the reply is returned to the gateway.
EDIT:
Sample config...
<int:channel id="in" />
<int:header-enricher input-channel="in" output-channel="next">
<int:header name="origReplyChannel" expression="headers['replyChannel']"/>
<int:reply-channel ref="myReplies" overwrite="true" />
</int:header-enricher>
<int:router input-channel="next" expression="payload.equals('foo')">
<int:mapping value="true" channel="channel1" />
<int:mapping value="false" channel="channel2" />
</int:router>
<int:transformer input-channel="channel1" expression="payload.toUpperCase()" />
<int:transformer input-channel="channel2" expression="payload + payload" />
<int:channel id="myReplies" />
<!-- restore the reply channel -->
<int:header-enricher input-channel="myReplies" output-channel="tapped">
<int:reply-channel expression="headers['origReplyChannel']" overwrite="true" />
</int:header-enricher>
<int:channel id="tapped">
<int:interceptors>
<int:wire-tap channel="loggingChannel" />
</int:interceptors>
</int:channel>
<int:logging-channel-adapter id="loggingChannel" log-full-message="true" logger-name="tapInbound"
level="INFO" />
<!-- route reply -->
<int:bridge id="bridgeToNowhere" input-channel="tapped" />
Test:
MessageChannel channel = context.getBean("in", MessageChannel.class);
MessagingTemplate template = new MessagingTemplate(channel);
String reply = template.convertSendAndReceive("foo", String.class);
System.out.println(reply);
reply = template.convertSendAndReceive("bar", String.class);
System.out.println(reply); }
Result:
09:36:30.224 INFO [main][tapInbound] GenericMessage [payload=FOO, headers={replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#fba92d3, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#fba92d3, id=326a610f-80c6-5b74-0158-e3644b732aab, origReplyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#fba92d3, timestamp=1442496990223}]
FOO
09:36:30.227 INFO [main][tapInbound] GenericMessage [payload=barbar, headers={replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#662b4c69, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#662b4c69, id=d161917c-ca73-a5a9-d0f1-d7a4346a459e, origReplyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#662b4c69, timestamp=1442496990227}]
barbar
Can I send distinct messages (distinct serialize/deserialize) to the same tcp server (same host and port) and differentiate the tcp-inbound-gateway by some value of header or payload with a router???
(...)
I want to add a router for select the correct tcp-inbound-gateway depends on some field in header or payload (for example named typedata). In what order would enter the request (or message) between router, tcp-inbound-gateway, tcp-connection-factory, serialize/deserialize? Will I have problems with the serialization/deserialization for chose the tcp-inbound-gateway? What is the correct way to do this?
Thanks in advance.
EDIT
In server:
<!-- Common -->
<int:channel id="channelConnectionFactoryRequest" />
<int:channel id="channelConnectionFactoryResponse" />
<router method="getDestinationChannel"
input-channel="channelSConnectionFactoryRequest">
<beans:bean class="org.mbracero.integration.CustomRouter" />
</router>
<beans:bean id="customSerializerDeserializer"
class="org.mbracero.integration.serialization.CustomSerializerDeserializer" />
<int-ip:tcp-connection-factory id="customConnectionFactory"
type="server" port="${payment.port}" single-use="true"
serializer="customSerializerDeserializer"
deserializer="customSerializerDeserializer" />
<int-ip:tcp-inbound-gateway id="customInboundGateway"
connection-factory="customConnectionFactory"
request-channel="channelCustomConnectionFactoryRequest"
reply-channel="channelCustomConnectionFactoryResponse"
error-channel="errorChannel" />
<!-- Custom -->
<beans:bean id="operations"
class="org.mbracero.integration.applepay.impl.OperationsImpl" />
<!-- Operation One -->
<int:channel id="operationOneRequest" />
<int:service-activator input-channel="operationOneRequest"
output-channel="operationOneResponse" ref="operations" method="getOperationOne" />
<!-- Operation Two -->
<int:channel id="operationTwoRequest" />
<int:service-activator input-channel="operationTwoRequest"
output-channel="operationTwoResponse" ref="operations" method="getOperationTwo" />
OperationsImpl:
ResultOne getOperationOne(RequestOne request);
ResultTwo getOperationOne(RequestTwo request);
ResultOne & ResultTwo implements ResultBase. And in serialize of customSerializerDeserializer I have:
#Override
public void serialize(ResultBase arg0, OutputStream arg1) throws IOException {
byte[] xxx = XXX.getBytes();
arg1.write(xxx);
byte[] yyy = yyy.getBytes();
arg1.write(senderName);
// **Each custom object have a method for serialize their own data**
arg0.transformDataToByte(arg1);
arg1.flush();
}
In client:
<gateway id="tmGateway"
service-interface="org.mbracero.integration.CustomGateway" />
<beans:bean id="operationOneSerializerDeserializer"
class="org.mbracero.integration.serialization.OperationOneSerializerDeserializer" />
<int-ip:tcp-connection-factory id="operationOneFactory"
type="client" host="127.0.0.1" port="7878" single-use="true"
serializer="operationOneSerializerDeserializer" deserializer="operationOneSerializerDeserializer" />
<int-ip:tcp-outbound-gateway id="operationOneOutGateway"
request-channel="operationOneChannel" connection-factory="operationOneFactory"
request-timeout="5000" reply-timeout="5000" remote-timeout="5000" />
<beans:bean id="operationTwoSerializerDeserializer"
class="org.mbracero.integration.operationTwoRequestSerializerDeserializer"/>
<int-ip:tcp-connection-factory id="operationTwoFactory"
type="client" host="127.0.0.1" port="7878" single-use="true"
serializer="operationTwoSerializerDeserializer"
deserializer="operationTwoSerializerDeserializer" />
<int-ip:tcp-outbound-gateway id="operationTwoOutGateway"
request-channel="operationTwoChannel" connection-factory="operationTwoFactory"
request-timeout="50000" reply-timeout="50000" remote-timeout="50000" />
CustomGateway:
#Gateway(requestChannel="operationOneChannel")
OperationOneResponse sendOperationOne(OperationOneRequest request);
#Gateway(requestChannel="operationTwoChannel")
OperationTwoResponse sendOperationTwo(OperationTwo request);
You cannot have 2 server connection factories listening on the same port. TCP doesn't allow it - the network stack wouldn't know which server socket to route the request to.
There's no problem on the client side but, with a single socket, the server would have to understand how to deserialize both data types.
It's probably easier to combine the serializers/deserializers into one on both sides (add another header to the message so the deserializer knows what type of payload to decode).
I have a configured spring integration pipeline where xml files are parsed into various objects. The objects are going through several channel endpoints where they are slightly modified - nothing special, just some properties added.
The last endpoint from the pipeline is the persister, where the objects are persisted in DB. There might be duplicates, so in this endpoint there is also a check whether the object is already persisted or its a new one.
I use a message driven architecture, with simple direct channels.
<int:channel id="parsedObjects1" />
<int:channel id="parsedObjects2" />
<int:channel id="processedObjects" />
<int:service-activator input-channel="parsedObjects1" ref="processor1" method="process" />
<int:service-activator input-channel="parsedObjects2" ref="processor2" method="process" />
<int:service-activator input-channel="processedObjects" ref="persister" method="persist" />
In the moment there is only one data source, from where I get xml files, and everything is going smoothly. The problems begin when I need to attach a second data source. The files are coming in the same time so I want them processed in parallel. So, I've placed two parser instances, and every parser is sending messages through the pipeline.
The configuration with the direct channels that I have creates concurrency problems, so I've tried modifying it. I've tried several configuration from spring integration documentation, but so far with no success.
I've tried with dispatcher configured with max pool size of 1 - one thread per message in every channel endpoint.
<task:executor id="channelTaskExecutor" pool-size="1-1" keep-alive="10" rejection-policy="CALLER_RUNS" queue-capacity="1" />
<int:channel id="parsedObjects1" >
<int:dispatcher task-executor="channelTaskExecutor" />
</int:channel>
<int:channel id="parsedObjects2" >
<int:dispatcher task-executor="channelTaskExecutor" />
</int:channel>
<int:channel id="processedObjects" >
<int:dispatcher task-executor="channelTaskExecutor" />
</int:channel>
I have tried the queue-poller configuration also:
<task:executor id="channelTaskExecutor" pool-size="1-1" keep-alive="10" rejection-policy="CALLER_RUNS" queue-capacity="1" />
<int:channel id="parsedObjects1" >
<int:rendezvous-queue/>
</int:channel>
<int:channel id="parsedObjects2" >
<int:rendezvous-queue/>
</int:channel>
<int:channel id="processedObjects" >
<int:rendezvous-queue/>
</int:channel>
<int:service-activator input-channel="parsedObjects1" ref="processor1" method="process" >
<int:poller task-executor="channelTaskExecutor" max-messages-per-poll="1" fixed-rate="2" />
</int:service-activator>
<int:service-activator input-channel="parsedObjects2" ref="processor2" method="process" >
<int:poller task-executor="channelTaskExecutor" max-messages-per-poll="1" fixed-rate="2" />
</int:service-activator>
<int:service-activator input-channel="processedObjects" ref="persister" method="persist" >
<int:poller task-executor="channelTaskExecutor" max-messages-per-poll="1" fixed-rate="2" />
</int:service-activator>
Basically, I want to get rid of any race conditions in the channel endpoints - in my case in the persister. The persister channel endpoint should block for every message, because if it runs in parallel, I get many duplicates persisted in the DB.
EDIT:
After some debugging I've done, it seems that the problems are in the endpoints logic rather than the configuration. Some of the objects which are sent through the pipeline to the persister, are also stored in a local cache until parsing of the file is done - they are later sent through the pipeline as well to persist some join tables as a part of some other domain entities. It happens that with the above configurations, some of the objects were not yet persisted when they are sent for the second time in the pipeline, so at the end I get duplicates in the DB.
I'm fairly new at spring integration, so probably at this point I will ask more general questions. In a setup with multiple data sources - meaning multiple instances of parsers etc:
Is there a common way (best way) to go to configure the pipeline to enable parallelization?
If there is need, is there a way to serialize the message handling?
Any suggestions are welcomed. Thanks in advance.
First, can you describe what the "concurrency problems" are? Ideally you would not need to serialize the message handling, so that would be a good place to start.
Second, the thread pool as you've configured it will not completely serialize. You will have 1 thread available in the pool but the rejection policy you've chosen leads to a caller thread running the task itself (basically throttling) in the case that the queue is at capacity. That means you will get a caller-run thread concurrently with the one from the pool.
The best way that I can think of for your scenario would be along these lines:
Make your parsedObject1 and parsedObject2 be normal queue channels, the capacity of the queue can be set appropriately (say 25 at any time):
<int:channel id="parsedObjects1" >
<int:queue />
</int:channel>
Now at this point your xml processors against the 2 channels - parsedObjects1 and parsedObjects2, will process the xml's and should output to the processedObjects channel. You can use the configuration similar to what you have for this, except that I have explicitly specified the processedObjects channel -:
<int:service-activator input-channel="parsedObjects1" ref="processor1" method="process" output-channel="processedObjects">
<int:poller task-executor="channelTaskExecutor"/>
</int:service-activator>
The third step is where I will deviate from your configuration, at this point you said you want to serialize the persistence, the best way would be to do it through a DIFFERENT task executor with a pool size of 1, this way only 1 instance of your persister is running at any point in time:
<task:executor id="persisterpool" pool-size="1"/>
<int:service-activator input-channel="processedObjects" ref="persister" method="persist" >
<int:poller task-executor="persisterpool" fixed-delay="2"/>
</int:service-activator>
I managed to get the pipeline working. I'm not sure if I'll keep the current configuration, or experiment some more, but for now, this is the configuration I ended up with:
<task:executor id="channelTaskExecutor" pool-size="1-1" keep-alive="10" rejection-policy="CALLER_RUNS" queue-capacity="1" />
<int:channel id="parsedObjects1" >
<int:queue capacity="1000" />
</int:channel>
<int:channel id="parsedObjects2" >
<int:queue capacity="1000" />
</int:channel>
<int:channel id="processedObjects" >
<int:queue capacity="1000" />
</int:channel>
<int:service-activator input-channel="parsedObjects1" ref="processor1" method="process" >
<int:poller task-executor="channelTaskExecutor" max-messages-per-poll="100" fixed-rate="2" />
</int:service-activator>
<int:service-activator input-channel="parsedObjects2" ref="processor2" method="process" >
<int:poller task-executor="channelTaskExecutor" max-messages-per-poll="100" fixed-rate="2" />
</int:service-activator>
<int:service-activator input-channel="processedObjects" ref="persister" method="persist" >
<int:poller task-executor="channelTaskExecutor" max-messages-per-poll="1" fixed-rate="2" />
</int:service-activator>