Spring Integration read and process a file without polling - spring-integration

I'm currently trying to write and integration flow then reads a csv file and processes it in chunks (Calls API for enrichment) then writes in back out as a new csv. I currently have an example working perfectly except that it is polling a directory. What I would like to do is be able to pass the file-path and file-name to the integration flow in the headers and then just perform the operation on that one file.
Here is my code for the polling example that works great except for the polling.
#Bean
#SuppressWarnings("unchecked")
public IntegrationFlow getUIDsFromTTDandOutputToFile() {
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
return IntegrationFlows
.from(Files.inboundAdapter(new File(inputFilePath))
.filter(getFileFilters())
.preventDuplicates(true)
.autoCreateDirectory(true),
c -> c
.poller(Pollers.fixedRate(1000)
.maxMessagesPerPoll(1)
)
)
.log(Level.INFO, m -> "TTD UID 2.0 Integration Start" )
.split(Files.splitter())
.channel(c -> c.executor(Executors.newFixedThreadPool(7)))
.handle((p, h) -> new CSVUtils().csvColumnSelector((String) p, ttdColNum))
.channel("chunkingChannel")
.get();
}
#Bean
#ServiceActivator(inputChannel = "chunkingChannel")
public AggregatorFactoryBean chunker() {
log.info("Initializing Chunker");
AggregatorFactoryBean aggregator = new AggregatorFactoryBean();
aggregator.setReleaseStrategy(new MessageCountReleaseStrategy(batchSize));
aggregator.setExpireGroupsUponCompletion(true);
aggregator.setGroupTimeoutExpression(new ValueExpression<>(100L));
aggregator.setOutputChannelName("chunkingOutput");
aggregator.setProcessorBean(new DefaultAggregatingMessageGroupProcessor());
aggregator.setSendPartialResultOnExpiry(true);
aggregator.setCorrelationStrategy(new CorrelationStrategyIml());
return aggregator;
}
#Bean
public IntegrationFlow enrichFlow() {
return IntegrationFlows.from("chunkingOutput")
.handle((p, h) -> gson.toJson(new TradeDeskUIDRequestPayloadBean((Collection<String>) p)))
.enrichHeaders(eh -> eh.async(false)
.header("accept", "application/json")
.header("contentType", "application/json")
.header("Authorization", "Bearer [TOKEN]")
)
.log(Level.INFO, m -> "Sending request of size " + batchSize + " to: " + TTD_UID_IDENTITY_MAP)
.handle(Http.outboundGateway(TTD_UID_IDENTITY_MAP)
.requestFactory(
alliantPooledHttpConnection.get_httpComponentsClientHttpRequestFactory())
.httpMethod(HttpMethod.POST)
.expectedResponseType(TradeDeskUIDResponsePayloadBean.class)
.extractPayload(true)
)
.log(Level.INFO, m -> "Writing response to output file" )
.handle((p, h) -> ((TradeDeskUIDResponsePayloadBean) p).printMappedBodyAsCSV2())
.handle(Files.outboundAdapter(new File(outputFilePath))
.autoCreateDirectory(true)
.fileExistsMode(FileExistsMode.APPEND)
//.appendNewLine(true)
.fileNameGenerator(m -> m.getHeaders().getOrDefault("file_name", "outputFile") + "_out.csv")
)
.get();
}
public class CorrelationStrategyIml implements CorrelationStrategy {
#Override
public Object getCorrelationKey(Message<?> message) {
return message.getHeaders().getOrDefault("", 1);
}
}
#Component
public class CSVUtils {
#ServiceActivator
String csvColumnSelector(String inputStr, Integer colNum) {
return StringUtils.commaDelimitedListToStringArray(inputStr)[colNum];
}
}
private FileListFilter<File> getFileFilters(){
ChainFileListFilter<File> cflf = new ChainFileListFilter<>();
cflf.addFilter(new LastModifiedFileListFilter(30));
cflf.addFilter(new AcceptOnceFileListFilter<>());
cflf.addFilter(new SimplePatternFileListFilter(fileExtention));
return cflf;
}

If you know the file, then there is no reason in any special component from the framework. You just start your flow from a channel and send a message to it with File object as a payload. That message is going to be carried on to the slitter in your flow and everything is going to work OK.
If you really want to have a high-level API on the matter, you can expose a #MessagingGateway as a beginning of that flow and end-user is going to call your gateway method with desired file as an argument. The framework will create a message on your behalf and send it to the message channel in the flow for processing.
See more info in docs about gateways:
https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway
https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#integration-flow-as-gateway
And also a DSL definition starting from some explicit channel:
https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-channels

Related

How to route using message headers in Spring Integration DSL Tcp

I have 2 server side services and I would like route messages to them using message headers, where remote clients put service identification into field type.
Is the code snippet, from server side config, the correct way? It throws cast exception indicating that route() see only payload, but not the message headers. Also all example in the Spring Integration manual shows only payload based decisioning.
#Bean
public IntegrationFlow serverFlow( // common flow for all my services, currently 2
TcpNetServerConnectionFactory serverConnectionFactory,
HeartbeatServer heartbeatServer,
FeedServer feedServer) {
return IntegrationFlows
.from(Tcp.inboundGateway(serverConnectionFactory))
.<Message<?>, String>route((m) -> m.getHeaders().get("type", String.class),
(routeSpec) -> routeSpec
.subFlowMapping("hearbeat", subflow -> subflow.handle(heartbeatServer::processRequest))
.subFlowMapping("feed", subflow -> subflow.handle(feedServer::consumeFeed)))
.get();
}
Client side config:
#Bean
public IntegrationFlow heartbeatClientFlow(
TcpNetClientConnectionFactory clientConnectionFactory,
HeartbeatClient heartbeatClient) {
return IntegrationFlows.from(heartbeatClient::send, e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(5))))
.enrichHeaders(c -> c.header("type", "heartbeat"))
.log()
.handle(outboundGateway(clientConnectionFactory))
.handle(heartbeatClient::receive)
.get();
}
#Bean
public IntegrationFlow feedClientFlow(
TcpNetClientConnectionFactory clientConnectionFactory) {
return IntegrationFlows.from(FeedClient.MessageGateway.class)
.enrichHeaders(c -> c.header("type", "feed"))
.log()
.handle(outboundGateway(clientConnectionFactory))
.get();
}
And as usual here is the full demo project code, ClientConfig and ServerConfig.
There is no standard way to send headers over raw TCP. You need to encode them into the payload somehow (and extract them on the server side).
The framework provides a mechanism to do this for you, but it requires extra configuration.
See the documentation.
Specifically...
The MapJsonSerializer uses a Jackson ObjectMapper to convert between a Map and JSON. You can use this serializer in conjunction with a MessageConvertingTcpMessageMapper and a MapMessageConverter to transfer selected headers and the payload in JSON.
I'll try to find some time to create an example of how to use it.
But, of course, you can roll your own encoding/decoding.
EDIT
Here's an example configuration to use JSON to convey message headers over TCP...
#SpringBootApplication
public class TcpWithHeadersApplication {
public static void main(String[] args) {
SpringApplication.run(TcpWithHeadersApplication.class, args);
}
// Client side
public interface TcpExchanger {
public String exchange(String data, #Header("type") String type);
}
#Bean
public IntegrationFlow client(#Value("${tcp.port:1234}") int port) {
return IntegrationFlows.from(TcpExchanger.class)
.handle(Tcp.outboundGateway(Tcp.netClient("localhost", port)
.deserializer(jsonMapping())
.serializer(jsonMapping())
.mapper(mapper())))
.get();
}
// Server side
#Bean
public IntegrationFlow server(#Value("${tcp.port:1234}") int port) {
return IntegrationFlows.from(Tcp.inboundGateway(Tcp.netServer(port)
.deserializer(jsonMapping())
.serializer(jsonMapping())
.mapper(mapper())))
.log(Level.INFO, "exampleLogger", "'Received type header:' + headers['type']")
.route("headers['type']", r -> r
.subFlowMapping("upper",
subFlow -> subFlow.transform(String.class, p -> p.toUpperCase()))
.subFlowMapping("lower",
subFlow -> subFlow.transform(String.class, p -> p.toLowerCase())))
.get();
}
// Common
#Bean
public MessageConvertingTcpMessageMapper mapper() {
MapMessageConverter converter = new MapMessageConverter();
converter.setHeaderNames("type");
return new MessageConvertingTcpMessageMapper(converter);
}
#Bean
public MapJsonSerializer jsonMapping() {
return new MapJsonSerializer();
}
// Console
#Bean
#DependsOn("client")
public ApplicationRunner runner(TcpExchanger exchanger,
ConfigurableApplicationContext context) {
return args -> {
System.out.println("Enter some text; if it starts with a lower case character,\n"
+ "it will be uppercased by the server; otherwise it will be lowercased;\n"
+ "enter 'quit' to end");
Scanner scanner = new Scanner(System.in);
String request = scanner.nextLine();
while (!"quit".equals(request.toLowerCase())) {
if (StringUtils.hasText(request)) {
String result = exchanger.exchange(request,
Character.isLowerCase(request.charAt(0)) ? "upper" : "lower");
System.out.println(result);
}
request = scanner.nextLine();
}
scanner.close();
context.close();
};
}
}

Spring Integration: MessageSource doesn't honor errorChannel header

I've the following flow:
#Resource(name = S3_CLIENT_BEAN)
private MessageSource<InputStream> messageSource;
public IntegrationFlow fileStreamingFlow() {
return IntegrationFlows.from(s3Properties.getFileStreamingInputChannel())
.enrichHeaders(spec -> spec.header(ERROR_CHANNEL, S3_ERROR_CHANNEL, true))
.handle(String.class, (fileName, h) -> {
if (messageSource instanceof S3StreamingMessageSource) {
S3StreamingMessageSource s3StreamingMessageSource = (S3StreamingMessageSource) messageSource;
ChainFileListFilter<S3ObjectSummary> chainFileListFilter = new ChainFileListFilter<>();
chainFileListFilter.addFilters(...);
s3StreamingMessageSource.setFilter(chainFileListFilter);
return s3StreamingMessageSource.receive();
}
return messageSource.receive();
}, spec -> spec
.requiresReply(false) // in case all messages got filtered out
)
.channel(s3Properties.getFileStreamingOutputChannel())
.get();
}
I found that if s3StreamingMessageSource.receive throws an exception, the error ends up in the error channel configured for the previous flow in the pipeline, not the S3_ERROR_CHANNEL that's configured for this flow. Not sure if it's related to this question.
The s3StreamingMessageSource.receive() is called from the SourcePollingChannelAdapter:
protected Message<?> receiveMessage() {
return this.source.receive();
}
This one called from the AbstractPollingEndpoint:
private boolean doPoll() {
message = this.receiveMessage();
...
this.handleMessage(message);
...
}
That handleMessage() does this:
this.messagingTemplate.send(getOutputChannel(), message);
So, that is definitely still far away from the mentioned .enrichHeaders(spec -> spec.header(ERROR_CHANNEL, S3_ERROR_CHANNEL, true)) downstream.
However you still can catch an exception in that S3_ERROR_CHANNEL. Pay attention to the second argument of the IntegrationFlows.from():
IntegrationFlows.from(s3Properties.getFileStreamingInputChannel(),
e -> e.poller(Pollers.fixedDelay(...)
.errorChannel(S3_ERROR_CHANNEL)))
Or, according your current you have somewhere a global poller, so configure an errorChannel there.

Remote directory for sftp outbound gateway with DSL

I'm having an issue with the SFTP outbound gateway using DSL.
I want to use a outbound gateway to send a file, then continue my flow.
The problem is that I have an exception telling me:
IllegalArgumentException: 'remoteDirectoryExpression' is required
I saw that I can use a RemoteFileTemplate where I can set the sftp session factory plus the remote directory information, but the directory I wan't is defined in my flow by the code put in the header just before the launch of the batch.
#Bean
public IntegrationFlow orderServiceFlow() {
return f -> f
.handleWithAdapter(h -> h.httpGateway("myUrl")
.httpMethod(HttpMethod.GET)
.expectedResponseType(List.class)
)
.split()
.channel(batchLaunchChannel());
}
#Bean
public DirectChannel batchLaunchChannel() {
return MessageChannels.direct("batchLaunchChannel").get();
}
#Bean
public IntegrationFlow batchLaunchFlow() {
return IntegrationFlows.from(batchLaunchChannel())
.enrichHeaders(h -> h
.headerExpression("oiCode", "payload")
)
.transform((GenericTransformer<String, JobLaunchRequest>) message -> {
JobParameters jobParameters = new JobParametersBuilder()
.addDate("exec_date", new Date())
.addString("oiCode", message)
.toJobParameters();
return new JobLaunchRequest(orderServiceJob, jobParameters);
})
.handle(new JobLaunchingMessageHandler(jobLauncher))
.enrichHeaders(h -> h
.headerExpression("jobExecution", "payload")
)
.handle((p, h) -> {
//Logic to retreive file...
return new File("");
})
.handle(Sftp.outboundGateway(sftpSessionFactory,
AbstractRemoteFileOutboundGateway.Command.PUT,
"payload")
)
.get();
}
I don't see how I can tell my outbound gateway which will be the directory depending what is in my header.
The Sftp.outboundGateway() has an overloaded version with the RemoteFileTemplate. So, you need to instantiate SftpRemoteFileTemplate bean and configure its:
/**
* Set the remote directory expression used to determine the remote directory to which
* files will be sent.
* #param remoteDirectoryExpression the remote directory expression.
*/
public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
This one can be like FunctionExpression:
setRemoteDirectoryExpression(m -> m.getHeaders().get("remoteDireHeader"))
Before I got an answer, I came with this solution but I'm not sure it's a good one.
// flow //
.handle((p, h) -> {
//Logic to retreive file...
return new File("");
})
.handle(
Sftp.outboundGateway(
remoteFileTemplate(new SpelExpressionParser().parseExpression("headers['oiCode']")),
AbstractRemoteFileOutboundGateway.Command.PUT,
"payload")
)
.handle(// next steps //)
.get();
public RemoteFileTemplate remoteFileTemplate(Expression directory) throws Exception {
RemoteFileTemplate template = new SftpRemoteFileTemplate(sftpSessionFactory);
template.setRemoteDirectoryExpression(directory);
template.setAutoCreateDirectory(true);
template.afterPropertiesSet();
return template;
}
But this provoke a warn because of exception thrown by the ExpresionUtils
java.lang.RuntimeException: No beanFactory

Spring Integration DSL Custom Error Channel Not Work

I use DSL implementation of Spring Integration.
I have code below and I can not use my custom error flow. When authenticate method throws Runtime Exception, the errorChannel starts to process. I enrich header to use my custom error flow, but not use.
// In Class - 1
#Bean
public MarshallingWebServiceInboundGateway marshallingWebServiceInboundGateway(BeanFactoryChannelResolver channelResolver, Jaxb2Marshaller marshaller) {
MarshallingWebServiceInboundGateway wsInboundGateway = new MarshallingWebServiceInboundGateway();
wsInboundGateway.setRequestChannel(channelResolver.resolveDestination("incomingRequest.input"));
wsInboundGateway.setReplyChannel(channelResolver.resolveDestination("outgoingResponse.input"));
wsInboundGateway.setErrorChannel(channelResolver.resolveDestination("errorChannel"));
wsInboundGateway.setMarshaller(marshaller);
wsInboundGateway.setUnmarshaller(marshaller);
return wsInboundGateway;
}
// In Class - 2
#Bean
public IntegrationFlow incomingRequest() {
return f -> f.<Object, Class<?>>route(t -> t.getClass(),
mapping -> mapping.subFlowMapping(payloadType1(),
sf -> sf.gateway("type1.input", ConsumerEndpointSpec::transactional))
.subFlowMapping(payloadType2(),
sf -> sf.gateway("type2.input", ConsumerEndpointSpec::transactional)),
conf -> conf.id("router:Incoming request router"));
}
// In Class - 3
#Bean
public IntegrationFlow type1() {
IntegrationFlow integrationFlow = f -> f
.enrichHeaders(h -> h.header(MessageHeaders.ERROR_CHANNEL, "error222", true))
.<Type1>handle((p, h) -> authentication.authenticate(p),
conf -> conf.id("service-activator:Authenticate"))
.transform(transformer::transformType1MsgToDataX,
conf -> conf.id("transform:Unmarshall type1 Message"))
.enrichHeaders(h -> h.headerExpression(TypeDataIntegrationMessageHeaderAccessor.MESSAGE_ID, "payload.id")
.headerExpression(TypeDataIntegrationMessageHeaderAccessor.MESSAGE_TYPE, "payload.messageType"))
.handle((GenericHandler<DataX>) repository::successResponseMessage,
conf -> conf.id("service-activator:return success"))
.channel("outgoingResponse.input")
;
return integrationFlow;
}
// In Class - 3
#Bean
public IntegrationFlow error222Flow() {
return IntegrationFlows.from("error222").handle("repository", "failureResponseMessage").get()
;
}
EDIT:
After Artem's answers, my code like below. But still, I can't access header parameter in the error flow. I get error - "No channel resolved by router 'router:error response prepare' "
// In Class - 1
#Bean
public MarshallingWebServiceInboundGateway marshallingWebServiceInboundGateway(BeanFactoryChannelResolver channelResolver, Jaxb2Marshaller marshaller) {
MarshallingWebServiceInboundGateway wsInboundGateway = new MarshallingWebServiceInboundGateway();
wsInboundGateway.setRequestChannel(channelResolver.resolveDestination("incomingRequest.input"));
wsInboundGateway.setReplyChannel(channelResolver.resolveDestination("outgoingResponse.input"));
wsInboundGateway.setErrorChannel(channelResolver.resolveDestination("errorResponse.input"));
wsInboundGateway.setMarshaller(marshaller);
wsInboundGateway.setUnmarshaller(marshaller);
return wsInboundGateway;
}
// In Class - 2
#Bean
public IntegrationFlow incomingRequest() {
return f -> f.<Object, Class<?>>route(t -> t.getClass(),
mapping -> mapping.subFlowMapping(payloadType1(),
sf -> sf.gateway("type1.input", ConsumerEndpointSpec::transactional))
.subFlowMapping(payloadType2(),
sf -> sf.gateway("type2.input", ConsumerEndpointSpec::transactional)),
conf -> conf.id("router:Incoming request router"));
}
// In Class - 2
#Bean
public IntegrationFlow errorResponse(){
return f -> f.<MessageHandlingException, Object>route(t -> t.getFailedMessage().getHeaders().get("ABCDEF"),
mapping -> mapping.subFlowMapping("ABCDEF",
sf -> sf.gateway("customError.input", ConsumerEndpointSpec::transactional)),
conf -> conf.id("router:error response prepare"));
}
// In Class - 3
#Bean
public IntegrationFlow type1() {
IntegrationFlow integrationFlow = f -> f
.enrichHeaders(h -> h.header("ABCDEF", "ABCDEF", true))
.<Type1>handle((p, h) -> authentication.authenticate(p),
conf -> conf.id("service-activator:Authenticate"))
.transform(transformer::transformType1MsgToDataX,
conf -> conf.id("transform:Unmarshall type1 Message"))
.enrichHeaders(h -> h.headerExpression(TypeDataIntegrationMessageHeaderAccessor.MESSAGE_ID, "payload.id")
.headerExpression(TypeDataIntegrationMessageHeaderAccessor.MESSAGE_TYPE, "payload.messageType"))
.handle((GenericHandler<DataX>) repository::successResponseMessage,
conf -> conf.id("service-activator:return success"))
.channel("outgoingResponse.input")
;
return integrationFlow;
}
// In Class - 3
#Bean
public IntegrationFlow customError(){
return f -> f.handle((GenericHandler<MessageHandlingException>)eventRepository::failureResponseMessage,
conf -> conf.id("service-activator:return failure"));
}
EDIT - 2:
I try Artem's test code, it works in this scenario. If I convert the type1 flow to the subflow mapping as below (I do it, because I'm doubtful my subflow code block), the error flow can't print ABCDEF parameter value.
After that, I add another header(XYZTWR) to the subflow mapping, but it can't be printed too.
#Bean
public IntegrationFlow type1() {
return f -> f.<String, String>route(t -> t.toString(), mapping -> mapping.subFlowMapping("foo",
sf -> sf.gateway("fooFlow.input", ConsumerEndpointSpec::transactional).enrichHeaders(h -> h.header("XYZTRW", "XYZTRW", true))));
}
#Bean
public IntegrationFlow fooFlow() {
return f -> f.enrichHeaders(h -> h.header("ABCDEF", "ABCDEF", true))
.handle((p, h) -> {
throw new RuntimeException("intentional");
});
}
My S.OUT is :
GenericMessage [payload=foo, headers={history=testGateway,type1.input, id=1fad7a65-4abe-c41d-0b22-36839a103269, timestamp=1503029553071}]
The errorChannel header starts work when we shift message to the different thread executor or queue channel. Otherwise standard throw and try...catch works in the same call stack.
So in your case the authentication exception is just thrown to the caller - WS Inbound Gateway. And here you have configured global error channel.
I did this testing:
#Configuration
#EnableIntegration
#IntegrationComponentScan
public static class ContextConfiguration {
#Bean
public IntegrationFlow errorResponse() {
return IntegrationFlows.from(errorChannel())
.<MessagingException, Message<?>>transform(MessagingException::getFailedMessage,
e -> e.poller(p -> p.fixedDelay(100)))
.get();
}
#Bean
public IntegrationFlow type1() {
return f -> f
.enrichHeaders(h -> h.header("ABCDEF", "ABCDEF", true))
.handle((p, h) -> { throw new RuntimeException("intentional"); });
}
#Bean
public PollableChannel errorChannel() {
return new QueueChannel();
}
}
#MessagingGateway(errorChannel = "errorChannel", defaultRequestChannel = "type1.input")
public interface TestGateway {
Message<?> sendTest(String payload);
}
...
#Autowired
private TestGateway testGateway;
#Test
public void testErrorChannel() {
Message<?> message = this.testGateway.sendTest("foo");
System.out.println(message);
}
And my SOUT shows me:
GenericMessage [payload=foo, headers={ABCDEF=ABCDEF, id=ae5d2d44-46b7-912d-17d4-bf2ee656140a, timestamp=1502999446725}]
Please, make DEBUG logging level for the org.springframework.integration category and observe in which step your message is losing desired headers.
UPDATE
OK. I see your problem. Since you use sf -> sf.gateway("fooFlow.input", ConsumerEndpointSpec::transactional), in other words you call the downstream via gateway, everything you've done there is behind the door and you can get as guilty to back in case of error only what you send there - gateway's request message. The downstream failedMessage is swallowed by default.
To fix the problem you should consider an addition errorChannel() option for that .gateway() and handle the downstream error there. Or... just don't use .gateway() in the router's subflow, but simple channel mapping.
The .transactional() can be configured also on the any .handle().

Spring Integration Queue Error Handling

I have a Spring Integration DSL flow which pulls in data from a rest API, transforms it and sends it through to a different rest API.
After data is fetched, it sends a message into a queue channel which does the rest of the processing. While the queue is working the original thread goes and fetches more data.
The issue that I am having is that any errors thrown from the queue are not processed until it has finished processing all the data, but I want it to stop the processing and throw the error right away because the whole process can take a long time but I want it to stop on the first error found.
Gateway:
#MessagingGateway(errorChannel = "syncErrorChannel")
#Service
public interface CrmGateway {
#Gateway(requestChannel = "departmentSyncInput", replyChannel = "departmentSyncOutput")
#Payload("new String()")
Object syncDepartments();
}
Flow:
/**
* Fetches data from the source api and passes it on to the split channel to process it If the
* response indicates it has more data to fetch then it is also loaded
*
* #return {#link IntegrationFlow}
*/
#Bean
IntegrationFlow sync() {
return IntegrationFlows
.from("departmentSyncInput")
.handle(this::fetchDomain)
.enrichHeaders(s -> s.headerExpressions(h -> h.put("nextLink", "payload.getNext()")))
.routeToRecipients(r -> r
.recipient("departmentSplitChannel")
.recipient(
"departmentSyncInput",
p -> p.getPayload() instanceof Wrapper
&& ((Wrapper) p.getPayload()).getNext() != null
))
.get();
}
/**
* Split data from the api into individual models and send them to the target service
*
* #return {#link IntegrationFlow}
*/
#Bean
IntegrationFlow split() {
return IntegrationFlows
.from("departmentSplitChannel")
.transform(Wrapper.class, Wrapper::getContent)
.split()
.channel(c -> c.executor(Executors.newScheduledThreadPool(100)))
.enrichHeaders(h -> h.header("errorChannel", "syncErrorChannel"))
.handle((payload, headers) -> log("Syncing", payload, payload))
.transform(Department.class, transformer)
// exception happens here
.handle(DepartmentDTO.class, (payload, headers) -> service.upsertDepartment(payload))
.handle((payload, headers) -> log("Synced", payload, payload))
.aggregate()
.get();
}
Error handler:
#Bean
IntegrationFlow errorHandler() {
return IntegrationFlows
.from("syncErrorChannel")
.handle(Exception.class, (payload, headers) -> {
payload.printStackTrace();
return payload;
})
.get();
}
I also tried using IntegrationFlows.from("errorChannel") with the same results.
I have tried using a Future too and it behaves the same so that when I call get() I get the error, but this is still happening at the end.
Thanks for any help.
There is no queue channel definition in your flow, but I guess you mean that .channel(c -> c.executor()). Would be better if you share logs on the matter as well.
What I can say that you try to override errorChannel header which is TemporaryReplyChannel in case of Gateway.
So, the error is send to the gateway's process and crashes it in case of split.
I suggest you to try with the h.header("errorChannel", "syncErrorChannel", true) to really override that header.

Resources