Spring Integration read and process a file without polling

Viewed 28

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;
     
}
1 Answers

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