spring-integration: MessageProducer may only be referenced once - spring-integration

I want to use a gateway in multiple flows. My gateway definition is:
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MarshallingWebServiceOutboundGateway myServiceGateway() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("blah.*");
MarshallingWebServiceOutboundGateway gateway = new MarshallingWebServiceOutboundGateway(
serviceEndpoint, marshaller, messageFactory);
gateway.setMessageSender(messageSender);
gateway.setRequestCallback(messageCallback);
return gateway;
}
Note that I have defined the message gateway bean in scope prototype so that Spring should create multiple gateway instances. Nevertheless I get this message at startup:
Caused by: java.lang.IllegalArgumentException: A reply MessageProducer may only be referenced once (myServiceGateway) - use #Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) on #Bean definition.
Why does it insist that a gateway must not be referenced more than once and how can I use the same gateway from multiple flows?
Using spring-integration 5.0.4

I thing you have something like .handle(myServiceGateway()) several times.
In this case you have to remove #Bean and #Scope from this method. And it also can be just private. The Java DSL process will create beans for you on the matter. And each flow will have its own instance. As you requested.
Any Spring Integration components can't be #Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) at all. They are referenced from non-prototype beans (endpoints) anyway. So, essentially, the scope of your prototype beans are increased.

Related

How to test message-driven-channel-adapter with MockIntegrationContext

I am trying to test a Spring Integration flow that starts off from a message-driven-channel-adapter configured as:
<int-jms:message-driven-channel-adapter id="myAdapter" ... />
My test goes like:
#SpringJUnitConfig(locations = {"my-app-context.xml"})
#SpringIntegrationTest(noAutoStartup = {"myAdapter"})
public class MyIntegrationFlowTest {
#Autowired
private MockIntegrationContext mockIntegrationContext;
#Test
public void myTest() {
...
MessageSource<String> messageSource = () -> new GenericMessage<>("foo");
mockIntegrationContext.substituteMessageSourceFor("myAdapter", messageSource);
...
}
}
I am however getting the following error:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'myAdapter' is expected to be of type 'org.springframework.integration.endpoint.SourcePollingChannelAdapter' but was actually of type 'org.springframework.integration.jms.JmsMessageDrivenEndpoint'
How should one specify an alternate source for the channel adapter for testing using the MockIntegrationContext, or by some other method?
The Message Driver Channel Adapter is really not a Source Polling Channel Adapter. So, the substituteMessageSourceFor() is indeed cannot be used for that type of components, which, essentially is a MessageProducerSupport implementation, not a SourcePollingChannelAdapter for a MessageSource.
The difference exists because not all protocols provides a listener-like hooks to spawn some self-managed task to subscribe to. The good example is JDBC, which is only passive system expecting requests. Therefore a polling channel adapter with a JdbcPollingChannelAdapter (which is a MessageSource) implementation must be used to interact with DB in event-driven manner.
Other systems (like JMS in your case) provides some listener (or consumer) API for what we can spawn a while task (see MessageListenerContainer in spring-jms) and let its MessageProducerSupport to emit messages to the channel.
Therefore you need to distinguish for yourself with what type of component you interact before choosing a testing strategy.
Since there is no extra layer in case of message-driver channel adapter, but rather some specific, self-managed MessageProducerSupport impl, we not only provide a particular mocking API, but even don't require to know anything else, but just standard unit testing feature and a message channel this endpoint is producing in the configuration.
So, the solution for you is something like:
#SpringIntegrationTest(noAutoStartup = {"myAdapter"}) - that's fully correct in your code: we really have to stop the real channel adapter to not pollute our testing environment.
You just need to inject into your test class a MessageChannel that id="myAdapter" is producing to. In your test code you just build a Message and send it into this channel. No need to worry about a MockIntegrationContext at all.

SI subscription to multiple mqtt topics

I'm trying to learn how to handle MQTT Messages in Spring-Integration.
Have created a converter, that subscribes with a single MqttPahoMessageDrivenChannelAdapter per MQTT Topic for consuming and converting the messages.
The problem is our data provider is planning to "speed-up" publishing messages on his side. So instead of having a few(<=10) topics each of which has messages with about 150 fields it is planned to publish each of those fields to the separate MQTT topic.
This means my converter would have to consume ca. 1000 mqtt topics, but I do not know whether:
Is spring-integration still a good choice for it. Cause afaik. the mentioned adapter uses the PAHO MqttClient that will consume the messages from all of the topics it is subscribed to in one single thread and creating 1000 instances of those adapters is an overkill.
If we stick further to spring-integration and use the provided components, would it be a good idea to create a single inbound adapter for all of the fields, that previously were in messages of one topic but moving the conversion away from the adapter bean to a separate bean ( that does the conversion) connected with an executer-channel to the adapter and thus executing the conversion of those fields on some threadpool in parallel.
Thanks in advance for your answers!
I think your idea makes sense.
For that purpose you need to implement a passthrough MqttMessageConverter and provide an MqttMessage as a payload and topic as a header:
public class PassThroughMqttMessageConverter implements MqttMessageConverter {
#Override
public Message<?> toMessage(String topic, MqttMessage mqttMessage) {
return MessageBuilder.withPayload(mqttMessage)
.setHeader(MqttHeaders.RECEIVED_TOPIC, topic)
.build();
}
#Override
public Object fromMessage(Message<?> message, Class<?> targetClass) {
return null;
}
#Override
public Message<?> toMessage(Object payload, MessageHeaders headers) {
return null;
}
}
So, you really will be able to perform a target conversion downstream, after a mentioned ExecutorChannel in the custom transformer.
You also may consider to implement a custom MqttPahoClientFactory (an extension of the DefaultMqttPahoClientFactory may work as well) and provide a custom ScheduledExecutorService to be injected into the MqttClient you are going create in the getClientInstance().

How to make the call to service activator transactional after split

I am using following to define my integration flow:
#Bean
public IntegrationFlow pollingFlow(MessageSource<Object> jdbcMessageSource) {
return IntegrationFlows.from(jdbcMessageSource,
c -> c.poller(Pollers.fixedRate(250, TimeUnit.MILLISECONDS)
.maxMessagesPerPoll(1)
.transactional()))
.split()
.channel(taskSourceChannel())
.get();
}
I would like to make call to service activator that reads from taskSourceChannel as transactional. Also, I want to use following with my transaction.
#Bean
public TransactionSynchronizationFactory transactionSynchronizationFactory() {
ExpressionEvaluatingTransactionSynchronizationProcessor syncProcessor
= new ExpressionEvaluatingTransactionSynchronizationProcessor();
syncProcessor.setAfterCommitChannel(successChannel());
syncProcessor.setAfterRollbackChannel(failureChannel());
return new DefaultTransactionSynchronizationFactory(syncProcessor);
}
The taskSourceChannel is an executor channel.
#Bean
public MessageChannel taskSourceChannel() {
return new ExecutorChannel(executor());
}
How can I add transaction support after split while using TransactionSynchronizationFactory. I don't want to make polling transacational. The only solution I can think of is putting transactional on activator but that won't solve my problem. I would like to make it applicable to any service activator uses this channel.
You question is not so clear, but you definitely need to consider to add transaction into the service activator. Although you don't show what is the subscriber for that taskSourceChannel, but you need to think do not have several subscribers on it.
Nevertheless I think your point is to apply TX into the service activator on this taskSourceChannel and everything after that one.
For this purpose Spring Integration provides a TransactionHandleMessageAdvice. See more info the Reference Manual: https://docs.spring.io/spring-integration/reference/html/messaging-endpoints-chapter.html#tx-handle-message-advice.
The TransactionSynchronizationFactory is only used from the AbstractPollingEndpoint implementations. However you can still utilize it in your transactional context relying on the TransactionSynchronizationManager.registerSynchronization().

Spring Integration : listener of Inbound Gateway not working when implemented with #Gateway Interface

In my application, sending the messages to Tibco Queue with the help of Spring JMS Integration Outbound Gateway. We are having configuration file to load the Connection factory and all the information during server start up.
But when we try to retrieve the messages from Inbound Gateway, the call gets into the doReceive() of GenericMessagingTemplate class and not returned from that method.
Below is the configuration of our application
The Gateway Class as:
#MessagingGateway
public Interface MessageGateway
{
#Gateway(requestChannel="InboundChannel")
public Object listent(#Headers Map<String,Object> cusHeader, #Payload Object obj)
}
Below is the config and registers listener class:
public Class Msgconfig {
#Bean
public MessageChannel InboundChannel(){
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel ="InboundChannel")
public AbstractMessageHandler listenMessage() {
// having the logic of DefaultMessageListenerContainer class to load connection factory and setting the message listener
// Have the logic of ChannelPublishingJmsMessageListener class to set the request channel
}
}
I have Custom Inbound class which overrides the handleMessageInternal() method which is actually used for handling the messages.
My client app or test call will call the MessageGateway.listen which has to return the JMS response which is not returning anything.
Can someone help me on this
The AbstractMessageHandler indeed doesn't return anything. It is one-way component. If you would like to return something from downstream you have to use request-reply component. In the Spring Integration all of them are extension of the AbstractReplyProducingMessageHandler. However that's full unclear why you should go so low level - the simple POJO with the method to return anything for the gateway call is fully enough. You still can use that #ServiceActivator annotation: https://docs.spring.io/spring-integration/docs/5.0.0.RELEASE/reference/html/configuration.html#annotations

Add SoapHeader to a Spring WS Endpoint

I have a spring ws endpoint as part of a Spring Integration project and I would like to access the Soap Header. When I add the SoapHeader to the method parameters i get the following exception:
[10/05/16 05:00:05:005 PDT] localhost-startStop-1 DEBUG
springframework.integration.util.MessagingMethodInvokerHelper.doWith():
Method [public
com.bstonetech.ptms.integration.model.ws.external.contract.GetContractResponse
com.bstonetech.ptms.integration.service.ws.GetContractEndpoint.getContract(com.bstonetech.ptms.integration.model.ws.external.contract.GetContractRequest,org.springframework.ws.context.MessageContext)
throws java.lang.Exception] is not eligible for Message handling Found
more than one parameter type candidate:
[#org.springframework.ws.server.endpoint.annotation.RequestPayload
com.bstonetech.ptms.integration.model.ws.external.contract.GetContractRequest]
and [org.springframework.ws.context.MessageContext]. [10/05/16
05:00:05:005 PDT] localhost-startStop-1 WARN
web.context.support.XmlWebApplicationContext.refresh(): Exception
encountered during context initialization - cancelling refresh attempt
java.lang.IllegalArgumentException: Target object of type [class
com.bstonetech.ptms.integration.service.ws.GetContractEndpoint] has no
eligible methods for handling Messages.
The same error occurs when using MessageContext messageContext too.
I am obviously missing something. Any help would be appreciated.
Integration is as follows:
<oxm:jaxb2-marshaller id="contractMarshaller" context-path="com.bstonetech.ptms.integration.model.ws.external.contract"/>
<ws:inbound-gateway id="getContractWs" request-channel="inboundGetContractChannel" mapped-request-headers="fileId" mapped-reply-headers="fileId"
marshaller="contractMarshaller" unmarshaller="contractMarshaller"/>
<int:service-activator id="contractEndpoint" input-channel="inboundGetContractChannel" ref="getContractEndpoint"/>
The endpoint looks as follows:
#Endpoint
public class GetContractEndpoint {
private static final String NAMESPACE_URI = "http://bstonetech.com/contract";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "GetContractRequest")
#ResponsePayload
public GetContractResponse getContract(#RequestPayload GetContractRequest request, SoapHeader soapHeader) throws Exception {
.....
}
Sorry for delay. We were busy with Spring Integration 4.3.0.RC1 release :-).
Well, looks like you have missed something and therefore ended up with the mix of concerns.
The Spring WS POJO method invocation annotations (#RequestPayload, #PayloadRoot) and SOAP-specific argument injection is really for the POJO case, when you have #Endpoint on the service and rely on the #EnableWs or similar Spring WS mechanism.
On the other hand, right, Spring Integration WS module is fully based on the Spring WS project, but it aims to convert everything into Spring Integration Messaging model as fast as possible. Therefore a result of the <ws:inbound-gateway> is Message sent to the request-channel="inboundGetContractChannel". In your case the payload will be already unmarshalled to some your domain object according to the JaxB mapping.
The <service-activator> can now deal only with the Messaging infrastructure and it already know nothing about SOAP. That is the general purpose of the Messaging, when you can switch to fully different source (e.g. DB), but still use the same <service-activator>.
To meet some POJO method invocation there are useful annotations like #Payload #Header etc.
To be consistent and still provide some SOAP info, the AbstractWebServiceInboundGateway consults with the DefaultSoapHeaderMapper and extract source.getSoapHeader() state as a separate MessageHeaders. So, you still have access to desired headers from request.

Resources