I have a spring integration flow for post message:
#Bean
public IntegrationFlow httpPost() {
return IntegrationFlows
.from("requestChannel")
.handle(Http.outboundGateway("http://uri")
.httpMethod(HttpMethod.POST)
.extractPayload(true))
.get();
}
How to change code to make http://uri dynamic or parametrized?
/**
* Create an {#link HttpMessageHandlerSpec} builder for request-reply gateway
* based on provided {#code Function} to evaluate target {#code uri} against request message.
* #param uriFunction the {#code Function} to evaluate {#code uri} at runtime.
* #param <P> the expected payload type.
* #return the HttpMessageHandlerSpec instance
*/
public static <P> HttpMessageHandlerSpec outboundGateway(Function<Message<P>, ?> uriFunction) {
return outboundGateway(new FunctionExpression<>(uriFunction));
}
so
.handle(Http.outboundGateway(message -> message.getHeaders().get("someHeaderWithURI"))
Alternativelt, you can use a SpEL expression.
Related
I have an integration flow that sinks DML queries at the end.
I have a config file to log transactions:
logging.level.org.springframework.transaction.interceptor=TRACE
logging.level.org.springframework.transaction.support=DEBUG
The sinkSql method is called but there is no transaction log.
If I just call e.transactional(true) I get an error because there are two transaction managers (one is from the source database).
#Bean(name = SYBASE_TRAN_MANAGER)
public PlatformTransactionManager transactionManager(#Qualifier(SYBASE_DS) final DataSource sybaseDataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(sybaseDataSource);
return dataSourceTransactionManager;
}
#Bean
public TransactionInterceptor sybaseTransactionInterceptor(#Qualifier(SYBASE_TRAN_MANAGER) final TransactionManager tm) {
return new TransactionInterceptorBuilder(true)
.transactionManager(tm)
.isolation(Isolation.READ_COMMITTED)
.propagation(Propagation.REQUIRES_NEW)
.readOnly(false)
.build();
}
#Bean
public IntegrationFlow sinkSqlFlow(final TransactionInterceptor sybaseTransactionInterceptor) {
return IntegrationFlows.from(SYBASE_SINK_SQL)
.enrichHeaders(h -> h.header(MessageHeaders.ERROR_CHANNEL, SYBASE_ERROR))
.handle(this::sinkSql, e -> e.transactional(sybaseTransactionInterceptor))
.get();
}
public void sinkSql(final Message<?> message) {
//jdbcTemplate logic here
}
Not sure why is the question since TransactionAspectSupport does just this plain fallback if we don't provide an explicit TransactionManager to the interceptor configuration:
if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
where that getBean(Class aClass) indeed is going to fail if several beans of type is present in the application context. So, what you did so far with a sybaseTransactionInterceptor bean definition is OK.
You can use an overloaded Java DSL method though:
/**
* Specify a {#link TransactionInterceptor} {#link Advice} with the provided
* {#code PlatformTransactionManager} and default
* {#link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
* for the {#link MessageHandler}.
* #param transactionManager the {#link TransactionManager} to use.
* #param handleMessageAdvice the flag to indicate the target {#link Advice} type:
* {#code false} - regular {#link TransactionInterceptor}; {#code true} -
* {#link org.springframework.integration.transaction.TransactionHandleMessageAdvice}
* extension.
* #return the spec.
*/
public S transactional(TransactionManager transactionManager, boolean handleMessageAdvice) {
Although having your sink contract as void sinkSql(final Message<?> message), there is no need in that true: the transaction is going to be applied for a handleMessage() method which is really the end of your flow anyway.
In my project I will use a process method in the handle method and based on the return type that will be boolean I will have to notify the advice success for true or failure for false.
Can someone please let me know how that could be done in the integration flow? is there any other implementation probably.
Code:
#Bean
public IntegrationFlow ftpInboundFlow() {
return IntegrationFlows
.from(Ftp.inboundAdapter(ftpSessionFactory())
.preserveTimestamp(true)
.remoteDirectory(appProperties.getFtp().getRemoteDirectory())
.patternFilter(appProperties.getFtp().getFilter())
.deleteRemoteFiles(true)
.localDirectory(new File("inbound"))
.temporaryFileSuffix(appProperties.getFtp().getTemporaryFileSuffix()),
e -> e.id("ftpInboundAdapter")
.poller(Pollers.fixedDelay(appProperties.getFtp().getPollerDelay()))
.autoStartup(true))
.transform(new FileToByteArrayTransformer())
.<byte[]>handle((p, h) -> {
log.info("After transform " + p);
log.info("Headers " + h);
CustomerFile customerFile = CustomerFile.builder()
.content(p)
.customerFileType(CustomerFileType.ORDER_BOOK)
.filename(appProperties.ftp.getFileName())
.build();
customerFileDataService.save(customerFile);
boolean isProssed = orderBookFileProcessor.process(customerFile);
log.info("is Processed : " + isProssed);
return isProssed;
})
.route(Boolean.class, p -> p.equals(false) ? "historyChannel" : "errorChannel")
.get();
}
#Bean
public IntegrationFlow history(){
return IntegrationFlows.from("historyChannel")
.transform("genericMessage.headers['file_originalFile']")
.handle(Ftp.outboundAdapter(ftpSessionFactory(), FileExistsMode.REPLACE)
.useTemporaryFileName(true)
.autoCreateDirectory(true)
.remoteDirectory("/ftp/ge/inbound/history"))
.get();
}
error:
Caused by: org.springframework.messaging.MessageHandlingException: Expression evaluation failed: genericMessage.headers['file_originalFile']; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'genericMessage' cannot be found on object of type 'org.springframework.messaging.support.GenericMessage' - maybe not public or not valid?, failedMessage=GenericMessage [payload=false, headers={file_remoteHostPort=bey-notes-fs.bey.ei:21, file_name=OA_ex_PK_2020_2023.csv, file_remoteDirectory=//ftp/GE/Inbound, file_originalFile=inbound\OA_ex_PK_2020_2023.csv, id=3a6ecf72-e9cd-43e6-bf0c-25020c0ab30d, file_relativePath=OA_ex_PK_2020_2023.csv, file_remoteFile=OA_ex_PK_2020_2023.csv, timestamp=1640030927539}]
I think you need to take a look to different handle() variant:
/**
* Populate a {#link ServiceActivatingHandler} for the
* {#link org.springframework.integration.handler.MethodInvokingMessageProcessor}
* to invoke the provided {#link GenericHandler} at runtime.
* Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {#code
* .<Integer>handle((p, h) -> p / 2)
* }
* </pre>
* Use {#link #handle(Class, GenericHandler)} if you need to access the entire
* message.
* #param handler the handler to invoke.
* #param <P> the payload type to expect.
* #return the current {#link IntegrationFlowDefinition}.
* #see org.springframework.integration.handler.LambdaMessageProcessor
*/
public <P> B handle(GenericHandler<P> handler) {
The one you have chosen comes with plain void handle(Message<?>) contract. So, no any return are expected from this call. Since you have a result from the process() method call and you want to process it, then you need to change your flow behavior from the consumer to function at this point:
.<byte[]>handle( (p, h) -> {
log.info("After transform " + p);
CustomerFile customerFile = CustomerFile.builder()
.content(p)
.customerFileType(CustomerFileType.ORDER_BOOK)
.build();
customerFileService.saveCustomerFile(customerFile);
return orderBookFileProcessor.process(customerFile);
})
Given I have IntegrationFlow and inbound adapter for AMQP queue:
#Bean
public IntegrationFlow readMessagesFlow(
ConnectionFactory rabbitConnectionFactory,
ObjectMapper jacksonObjectMapper) {
return IntegrationFlows.from(
Amqp.inboundAdapter(rabbitConnectionFactory, QUEUE)
.messageConverter(new Jackson2JsonMessageConverter(jacksonObjectMapper))
)
.log(INFO, AMQP_LOGGER_CATEGORY)
.get();
}
When some external system sends a message with JSON body, I found they use __TypeId__=THEIR_INTERNAL_CLASS.
I would like to map JSON body to my own class.
Currently, it fails on ClassCastException because of THEIR_INTERNAL_CLASS not being available.
How can I tell Jackson2JsonMessageConverter to use my own class?
This is one way how to do it:
Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter(jacksonObjectMapper);
DefaultClassMapper defaultClassMapper = new DefaultClassMapper();
defaultClassMapper.setDefaultType(MyOwnClass.class);
messageConverter.setClassMapper(defaultClassMapper);
and use messageConverter in Amqp.inboundAdapter
See this method of converter:
/**
* Set the precedence for evaluating type information in message properties.
* When using {#code #RabbitListener} at the method level, the framework attempts
* to determine the target type for payload conversion from the method signature.
* If so, this type is provided in the
* {#link MessageProperties#getInferredArgumentType() inferredArgumentType}
* message property.
* <p> By default, if the type is concrete (not abstract, not an interface), this will
* be used ahead of type information provided in the {#code __TypeId__} and
* associated headers provided by the sender.
* <p> If you wish to force the use of the {#code __TypeId__} and associated headers
* (such as when the actual type is a subclass of the method argument type),
* set the precedence to {#link Jackson2JavaTypeMapper.TypePrecedence#TYPE_ID}.
* #param typePrecedence the precedence.
* #see DefaultJackson2JavaTypeMapper#setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence)
*/
public void setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence typePrecedence) {
and here are docs on the matter: https://docs.spring.io/spring-amqp/docs/2.2.11.RELEASE/reference/html/#json-message-converter
I want to use Java DSL for Spring Integration, but I can't figure out how to use message headers during transformation.
My old implementation had a Transformer like this:
#Transformer(inputChannel = "inputChannel", outputChannel = "outputChannel")
public EventB transform(
EventA eventA,
#Header("a_header") String aHeader,
#Header("other_header") String otherHeader){
return new EventB(eventA.getSomeField(), aHeader, otherHeader);
}
Now I have the following DSL:
#Bean
public IntegrationFlow aFlow(){
return IntegrationFlows.from(EventASink.INPUT)
.filter("headers['operation'] == 'OPERATION_A'")
.transform() //<-- What should I do here?
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
I looked at the implementation of transform() method and I found that it can receive a GenericTransformer as parameter, but it seems to work only with message payload and I also need the headers.
I also saw that some kind of reflection can be used, but I don't like it because its not refactor-safe.
Any advice? Thanks in advance.
Since the DSL is a part of the Framework and it is compiled before you start to use it, we can't infer any custom POJO methods, therefore there is no so clean way to count with any custom headers like in your sample.
The closet way to re-use your transform() with those annotations on parameters is with this .transform() contract:
/**
* Populate the {#code MessageTransformingHandler} for the {#link MethodInvokingTransformer}
* to invoke the service method at runtime.
* #param service the service to use.
* #param methodName the method to invoke.
* #return the current {#link IntegrationFlowDefinition}.
* #see MethodInvokingTransformer
*/
public B transform(Object service, String methodName)
So, you would need to declare a bean with that method and use it in the service argument meanwhile mention the method in the methodName argument.
Another way to get access to headers is to request the whole Message type for lambda:
/**
* Populate the {#link MessageTransformingHandler} instance for the provided
* {#link GenericTransformer} for the specific {#code payloadType} to convert at
* runtime.
* Use {#link #transform(Class, GenericTransformer)} if you need access to the
* entire message.
* #param payloadType the {#link Class} for expected payload type. It can also be
* {#code Message.class} if you wish to access the entire message in the transformer.
* Conversion to this type will be attempted, if necessary.
* #param genericTransformer the {#link GenericTransformer} to populate.
* #param <P> the payload type - 'transform from' or {#code Message.class}.
* #param <T> the target type - 'transform to'.
* #return the current {#link IntegrationFlowDefinition}.
* #see MethodInvokingTransformer
* #see LambdaMessageProcessor
*/
public <P, T> B transform(Class<P> payloadType, GenericTransformer<P, T> genericTransformer) {
In this case the code could be like this:
.transform(Message.class, m -> m.getHeaders())
I had the same issue. I needed both headers and payload. After a lot of tinkering I found a solution. I used .handle instead of .transform. GenericHandler's handle method provides both payload and headers.
In your case it would look something like:
#Bean
public IntegrationFlow aFlow(){
return IntegrationFlows.from(EventASink.INPUT)
.filter("headers['operation'] == 'OPERATION_A'")
.<EventA>handle((eventA, h) -> new EventB(
eventA.getSomeField(),
h.get("a_header", String.class),
h.get("other_header", String.class)))
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
What is the Java DSL equivalent for int:gateway which has error channel and a default request channel.
The default request channel is input to a transformer which sends JMS message to outbound adaptor and returns a listenable future.
Something like this:
return IntegrationFlows.from(Gate.class)
/**
* Populate the {#link MessageChannel} to the new {#link IntegrationFlowBuilder}
* chain, which becomes as a {#code requestChannel} for the Messaging Gateway(s) built
* on the provided service interface.
* <p>A gateway proxy bean for provided service interface is registered under a name
* from the
* {#link org.springframework.integration.annotation.MessagingGateway#name()} if present
* or from the {#link IntegrationFlow} bean name plus {#code .gateway} suffix.
* #param serviceInterface the service interface class with an optional
* {#link org.springframework.integration.annotation.MessagingGateway} annotation.
* #return new {#link IntegrationFlowBuilder}.
*/
public static IntegrationFlowBuilder from(Class<?> serviceInterface) {
https://docs.spring.io/spring-integration/docs/current/reference/html/java-dsl.html#java-dsl-gateway