2

I have a following flow that I would like to implement using Spring Integration Java DSL:

  1. Poll a table in a database every 2 hours which returns id of documents that need to be processed
  2. For each id, process a document through an HTTP gateway
  3. Store a response in a database

I have a working Java code that does exactly these steps. An additional requirement that I'm struggling with is that the polling for the next round of documents shouldn't happen until all the documents from the last polling has been processed and stored in the database.

Is there any pattern in Spring Integration that I could use for this additional requirement?

Here is a simplified code - it will get more complex and I'll split processing of the documents (HTTP outbound and persisting) into separate classes / flows:

return IntegrationFlows.from(Jpa.inboundAdapter(this.targetEntityManagerFactory)
                .entityClass(ProcessingMetadata.class)
                .jpaQuery("select max(p.modifiedDate) from ProcessingMetadata p " +
                        "where p.status = com.test.ProcessingStatus.PROCESSED")
                .maxResults(1)
                .expectSingleResult(true),
        e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(10))))
        .handle(Jpa.retrievingGateway(this.sourceEntityManagerFactory)
                .entityClass(DocumentHeader.class)
                .jpaQuery("from DocumentHeader d where d.modified > :modified")
                .parameterExpression("modified", "payload"))
        .handle(Http.outboundGateway(uri)
                .httpMethod(HttpMethod.POST)
                .expectedResponseType(String.class))
        .handle(Jpa.outboundAdapter(this.targetEntityManagerFactory)
                        .entityClass(ProcessingMetadata.class)
                        .persistMode(PersistMode.PERSIST),
                e -> e.transactional(true))
        .get();

UPDATE

Following Artem's suggestion, I'm trying to implement it using a SimpleActiveIdleMessageSourceAdvice

class WaitUntilCompleted extends SimpleActiveIdleMessageSourceAdvice {

    public WaitUntilCompleted(DynamicPeriodicTrigger trigger) {
        super(trigger);
    }

    @Override
    public boolean beforeReceive(MessageSource<?> source) {
        return false;
    }
}

If I understand it correctly, above code would stop polling. Now I have no idea how to attach this Advice to the Jpa.inboundAdapter... It doesn't seem to have a proper method (neither Advice nor Spec Handler). Do I miss something obvious here? I've tried attaching the Advice to the Jpa.retrievingGateway but it doesn't change the flow at all.

UPDATE2

Check this question for a complete solution: Spring Integration: how to unit test an advice

Blink
  • 1,408
  • 13
  • 21

1 Answers1

2

I have answered today for similar question: How to poll from a queue 1 message at a time after downstream flow is completed in Spring Integration.

You also may have a trick on database level do not let to see new records in the table while others are locked. Or you can have some UPDATE in the end of flow while your SELECT won't see appropriate records until they are updated respectively.

But anyway any of those approaches I suggest for that question should be applied here as well.

Also you indeed can consider to rely on the SimpleActiveIdleMessageSourceAdvice since your solution is already based on a MessageSource implementation.

UPDATE

For your use-case it is probably would be better to extend that SimpleActiveIdleMessageSourceAdvice and override its beforeReceive() to check some state that you are able to read more data or not. The idlePollPeriod and activePollPeriod could be the same value: doesn't look like it make sense to change it in between since you are going to the idle state just after reading the next set of data.

For the state to check it really might be a simple AtomicBoolean bean which you should change after you process the current set of documents. That might be something after an aggregator or anything else you can use in your solution.

UPDATE 2

To use a WaitUntilCompleted for your Jpa.inboundAdapter you should have a configuration like this:

IntegrationFlows.from(Jpa.inboundAdapter(this.targetEntityManagerFactory)
            .entityClass(ProcessingMetadata.class)
            .jpaQuery("select max(p.modifiedDate) from ProcessingMetadata p " +
                    "where p.status = com.test.ProcessingStatus.PROCESSED")
            .maxResults(1)
            .expectSingleResult(true),
    e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(10)).advice(waitUntilCompleted())))

Pay attention to the .advice(waitUntilCompleted()) which is a part of the pller configuration and points to your advice bean.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Thanks a lot for a quick reply. I'm trying to understand how to use the SimpleActiveIdleMessageSourceAdvice but I'm lost... Could you please elaborate a bit how exactly could I use this advice? Also, how would I know that all of the messages have been processed? At some stage in the above flow I'll receive a list of elements which I'll need to split. Do I need to aggregate it somehow and set an atomic boolean status value to true or do I need to keep counter of the list? – Blink Apr 22 '20 at 21:07
  • Works both way. Counter or boolean reset after an aggregator is OK. – Artem Bilan Apr 22 '20 at 21:11
  • Unfortunately it's still unclear to me how to use the SimpleActiveIdleMessageSourceAdvice... Any chance to point me to some additional documentation and a unit test example related to it in the spring-integration-java-dsl repo? – Blink Apr 23 '20 at 06:59
  • The `spring-integration-java-dsl` repo is deprecated. Everyone is encouraged to upgrade to the latest Spring Integration. And this `SimpleActiveIdleMessageSourceAdvice` has nothing to do with DSL. You just need to configure it as a bean and use from the `PollerSpec.advice()` when you configure that `Jpa.inboundAdapter()` in the `IntegrationFlows.from()`. – Artem Bilan Apr 23 '20 at 14:12
  • See an UPDATE in my answer, please. – Artem Bilan Apr 23 '20 at 14:25
  • Thanks a lot. It's clearer now :) Let me get back to it once I've implemented it. – Blink Apr 24 '20 at 16:03
  • Could you please look at my UPDATE in the post? I'm lost again... – Blink Apr 24 '20 at 20:21
  • See an UPDATE 2 in my answer. – Artem Bilan Apr 27 '20 at 14:29
  • Thanks a lot... For some reason I was trying to apply the advice on the adapter, not the poller. I'll accept the answer and post an implementation. Out of curiosity, why the jpaOutboundAdapter has an "advice" method directly, whereas the jpaInboundAdapter doesn't? – Blink Apr 27 '20 at 14:54
  • That's not true. See `JpaUpdatingOutboundEndpointSpec` - there is no an `advice()` method. It is a part of the `ConsumerEndpointSpec`, which is also a second lambda when you configure a `handle()`. You probably use too old `spring-integration-java-dsl` project which is deprecated now in favor of main Spring Integration project. – Artem Bilan Apr 27 '20 at 15:01
  • btw SimpleActiveIdleMessageSourceAdvice is now deprecated. What would be an alternative for it? Thanks – Blink Aug 13 '20 at 08:37
  • Well, I'm not sure what is wrong with JavaDocs of that deprecated class: `@deprecated since 5.3 in favor of {@link SimpleActiveIdleReceiveMessageAdvice} with the same (but more common) functionality.` – Artem Bilan Aug 13 '20 at 15:40