Build spring integration release strategy using spring DSL - spring-integration

I am new to Spring integration. I am trying to split the message from a file using file splitter and then use .aggregate() to build a single message and send to output channel.
I have markers as true and hence apply-sequence is false by default now.
I have set correlationId to a constant "1" using enrichHeaders. I have trouble setting the realease strategy as I do not have a hold on the sequence end. Here is how my code looks.
IntegrationFlows
.from(s -> s.file(new File(fileDir))
.filter(getFileFilter(fileName)),
e -> e.poller(poller))
.split(Files.splitter(true, true)
.charset(StandardCharsets.US_ASCII),
e -> e.id(beanName)).enrichHeaders(h -> h.header("correlationId", "1"));
IntegrationFlow integrationFlow = integrationFlowBuilder
.<Object, Class<?>>route(Object::getClass, m -> m
.channelMapping(FileSplitter.FileMarker.class, "markers.input")
.channelMapping(String.class, "lines.input"))
.get();
#Bean
public IntegrationFlow itemExcludes() {
return flow -> flow.transform(new ItemExcludeRowMapper(itemExcludeRowUnmarshaller)) //This maps each line to ItemExclude object
.aggregate(aggregator -> aggregator
.outputProcessor(group -> group.getMessages()
.stream()
.map(message -> ((ItemExclude) message.getPayload()).getPartNumber())
.collect(Collectors.joining(","))))
.transform(Transformers.toJson())
.channel(customSource.itemExclude());
}
#Bean
public IntegrationFlow itemExcludeMarkers() {
return flow -> flow
.log(LoggingHandler.Level.INFO)
.<FileSplitter.FileMarker>filter(m -> m.getMark().equals(FileSplitter.FileMarker.Mark.END))
.<FileHandler>handle(new FileHandler(configProps))
.channel(NULL_CHANNEL);
}
Any help appreciated.

I would move your header enricher for the correlationId before splitter and make it like this:
.enrichHeaders(h -> h
.headerFunction(IntegrationMessageHeaderAccessor.CORRELATION_ID,
m -> m.getHeaders().getId()))
The constant correlationId is absolutely not good in the multi-threaded environment: different threads splits different files and send different lines to the same aggregator. So, with the "1" as correlation key you'd have always one group to aggregate and release. The default sequence behavior is to populate the original message id to the correlationId. Since you are not going to rely on the applySequence from the FileSplitter I suggest that simple solution to emulate that behavior.
As Gary pointed in his answer you need to think about custom ReleaseStrategy and send FileSplitter.FileMarker to the aggregator as well. The FileSplitter.FileMarker.END has lineCount property which can be compared with the MessageGroup.size to decide that we are good to release the group. The MessageGroupProcessor indeed has to filter FileSplitter.FileMarker messages during building the result for output.

Use a custom release strategy that looks for the END marker in the last message and, perhaps, a custom output processor that removes the markers from the collection.

Related

Spring Integration Gateway Without Reply in DSL

My question is very similar to this Stackoverflow Question in that I want to send to JMS and then carry on with my integration flow.
The response is totally asynchronous and is therefore handled on a separate Jms.messageDrivenChannelAdapter. So, I basically want to "fire and forget".
My code is this (Spring 5.3.14);
.enrichHeaders(
h -> h.headerFunction("JMSCorrelationID", m -> m.getHeaders().get(MessageHeaders.ID))
)
.handle(
Jms
.outboundGateway(connectionFactory)
.requestDestination(queueName)
)
.handle(p -> System.err.println("Do something else with ... " + p))
.get();
And I get this;
org.springframework.integration.MessageTimeoutException: failed to receive JMS response within timeout of: 5000ms,
The referenced answer to me implies that I need to listen to a dummy queue, which I don't want to have to do. So what do I need to fix in my code above?
Edit; final code using the solution below tested with/without "queue PUT inhibit" in order cause an exception.
.publishSubscribeChannel(s -> s
.subscribe(f -> f.handle(
Jms
.outboundAdapter(connectionFactory)
.destination(queueName)
))
.subscribe(f -> f.handle(
p -> System.err.println("Do something else with ... " + p)
))
)
You need to use Jms.outboundChannelAdapter() instead. And together with that next handle() wrap them into a publishSubscribeChannel() as two subscribers sub-flows. This way they are going to be called one after other, but in parallel and their individual flows.

What mappings is Automapper applying?

I have a problem where a mapping works when applied as the child of one object but not another and I am clueless how to debug this.
I map a complex object such as:
ParentType1
-> ChildType1
-> GrandchildType1
PropertyA
which is transformed to
ParentType2
-> ChildType2
-> GrandchildType2
PropertyB
PropertyB is populated
through a tonne of mapping files (I inherited this code) and works fine.
I have another object
ParentType3
-> ChildType3
-> GrandchildType1
PropertyA
which I am trying to map to another object
ParentType4
-> ChildType4
-> GrandchildType2
PropertyB
PropertyB is empty
which doesn't work. In both cases GrandchildType1 is being converted to GrandchildType2. The problem is the transform for Grandchild1 to GrandChild2 is not being applied in the second scenario. How can I tell which mappings are being applied in the first case so I can compare with the second. The application has thousands of lines of code so I am not about to ditch automapper. If I post the mappings here it will take up hundreds of lines of code.

Spring Integration aggregator's release strategy based on last modified

I'm trying to implement the following scenario:
I get a bunch of files that have common file pattern, i.e. doc0001_page0001, doc0001_page0002, doc0001_page0003, doc0002_page0001 (where doc0001 would be one document consisting of 3 pages that I would need to merge, doc0002 would only have 1 page)
I want to aggregate them in a way that I will release a group only if all of the files for specific document are gathered (doc0001 after 3 files were picked up, doc0002 after 1 file)
My idea was to read the files in an alphabetical order and wait for 2 seconds after a group was last modified to release it (g.getLastModified() is smaller than the current time minus 2 seconds)
I've tried the following without success:
return IntegrationFlows.from(Files.inboundAdapter(tmpDir.getRoot())
.patternFilter("*.json")
.useWatchService(true)
.watchEvents(FileReadingMessageSource.WatchEventType.CREATE,
FileReadingMessageSource.WatchEventType.MODIFY),
e -> e.poller(Pollers.fixedDelay(100)
.errorChannel("filePollingErrorChannel")))
.enrichHeaders(h -> h.headerExpression("CORRELATION_PATTERN", "headers[" + FileHeaders.FILENAME + "].substring(0,7)")) // docxxxx.length()
.aggregate(a -> a.correlationExpression("headers['CORRELATION_PATTERN']")
.releaseStrategy(g -> g.getLastModified() < System.currentTimeMillis() - 2000)) .channel(MessageChannels.queue("fileReadingResultChannel"))
.get();
Changing the release strategy to the following also didn't work:
.aggregate(a -> a.correlationExpression("headers['CORRELATION_PATTERN']")
.releaseStrategy(g -> {
Stream<Message<?>> stream = g.getMessages()
.stream();
Long timestamp = (Long) stream.skip(stream.count() - 1)
.findFirst()
.get()
.getHeaders()
.get(MessageHeaders.TIMESTAMP);
System.out.println("Timestamp: " + timestamp);
return timestamp.longValue() < System.currentTimeMillis() - 2000;
}))
Am I misunderstanding the release strategy concept?
Also, is it possible to print something out from the releaseStrategy block? I wanted to compare the timestamp (see System.out.println("Timestamp: " + timestamp);)
Right, since you don't know the whole sequence for message group, you don't have any other choice unless to use a groupTimeout. The regular releaseStrategy works only when a message arrives to the aggregator. Since at the point of one message you don't have enough info to release the group, it is going to sit in the group store forever.
The groupTimeout option has been introduced to the aggregator especially for this kind of use-cases when we definitely would like to release a group without enough messages to group normally.
You may consider to use a groupTimeoutExpression instead of constant-based groupTimeout. The MessageGroup is a root evaluation context object for SpEL, so you will be able to get access to the mentioned lastModified for it.
The .sendPartialResultOnExpiry(true) is right option to deal with here.
See more info in the docs: https://docs.spring.io/spring-integration/reference/html/#agg-and-group-to
I found a solution to that with a different approach. I still don't understand why the above one wasn't working.
I've also found a cleaner way of defining the correlation function.
IntegrationFlows.from(Files.inboundAdapter(tmpDir.getRoot())
.patternFilter("*.json")
.useWatchService(true)
.watchEvents(FileReadingMessageSource.WatchEventType.CREATE, FileReadingMessageSource.WatchEventType.MODIFY), e -> e
.poller(Pollers.fixedDelay(100)))
.enrichHeaders(h -> h.headerFunction(IntegrationMessageHeaderAccessor.CORRELATION_ID, m -> ((String) m
.getHeaders()
.get(FileHeaders.FILENAME)).substring(0, 17)))
.aggregate(a -> a.groupTimeout(2000)
.sendPartialResultOnExpiry(true))
.channel(MessageChannels.queue("fileReadingResultChannel"))
.get();

Spring integration - aggregator from sftp inbound

What is the best solution to aggregate one message from sftp inbound message source that contains multiple files?
We have on remote machine 3 files that need to be received. After that we combine content of those files to one json message and send it forward.
public IntegrationFlow sftpIntegrationFlowBean() {
final Map<String, Object> headers = new HashMap<>();
headers.put("sftpFile", "sftpFile");
final Consumer<AggregatorSpec> aggregator = t -> {
t.sendPartialResultOnExpiry(true);
t.expireGroupsUponCompletion(true);
t.processor(new CustomMessageAggregator());
};
return IntegrationFlows
.from(sftpInboundMessageSource(),
e -> e.id("sftpIntegrationFlow").poller(pollerMetadataSftp))
.enrichHeaders(headers).aggregate(aggregator)
.handle(customMessageSender).get();
}
Poller polls every 15 minutes.
While running this code next thing happens:
Retrieve files and process one of them
After 15 minutes second file is processed
After another 15 minutes third file is processed
And finally after more 15 minutes message is sent to destination
How can this all be done in one operation without delays? I did try this with FileReadingMessageSource, but had a same result.
Thank you in advance.
Increase maxMessagesPerPoll in the PollerMetadata.

Camel custom component: perform two different actions

I just want to know if I can do below pertaining to custom component
1) I created a sample component
somComponent://foo ---> what this foo refers to?can i have any string there?
What does it denotes?
2) consider below route
from("some blah")
.to(someCustomComponent://action1)
.to(someCustomComponent://action2);
Idea - I want to perform two different actions on the above. Kind of two different methods.
Is the above possible?
The notation for your custom component in Apache Camel can be described as follows:
someComponent://instance?parm1=foo&parm2=bar
The instance part can be pretty much anything you want to uniquely identify the endpoint.
You can derive DefaultComponent and implement the methods. The signature for createEndpoint method looks like this:
protected Endpoint createEndpoint(final String uri, String remaining,
Map<String, Object> parameters) throws Exception
So for the endpoint someComponent://instance?parm1=foo&parm2=bar
uri = someComponent://instance?parm1=foo&parm2=bar
remaining = instance
parmeters = (Map) parm1 -> foo, parm2 -> bar
Therefore, yes! You can easily denote the action you want, for example as a parameter such as:
someComponent://instance?action=something

Resources