Add SoapHeader to a Spring WS Endpoint - spring-integration

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.

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.

Spring Integration | Reactive Streams Support | Exception in creating a reactive Message Gateway

I am trying to upgrade the spring integration flow in one of my existing application with Reactive Streams Support. The approach taken is to change the parameter and return type of the Gateway method as Mono. The flow executes smoothly and when the reply reaches the Gateway, it results in java.lang.IllegalArgumentException: 'beanFactory' must not be null
I am using Spring Boot 2.3.0.
inputChannel is a DirectChannel
gatewayReplyChannel is a FluxMessageChannel
#MessagingGateway(name = "reactiveGateway")
public interface EntryGate {
#Gateway(requestChannel = "inputChannel", replyChannel = "gatewayReplyChannel")
Mono<String> process(final Mono<String> input);
}
Exceptions trace
Caused by: java.lang.IllegalArgumentException: 'beanFactory' must not be null
at org.springframework.util.Assert.notNull(Assert.java:198)
at org.springframework.integration.channel.ChannelUtils.getErrorHandler(ChannelUtils.java:51)
at org.springframework.integration.endpoint.ReactiveStreamsConsumer.onInit(ReactiveStreamsConsumer.java:155)
at org.springframework.integration.context.IntegrationObjectSupport.afterPropertiesSet(IntegrationObjectSupport.java:214)
at org.springframework.integration.gateway.MessagingGatewaySupport.registerReplyMessageCorrelatorIfNecessary(MessagingGatewaySupport.java:806)
at org.springframework.integration.gateway.MessagingGatewaySupport.sendAndReceiveMessageReactive(MessagingGatewaySupport.java:609)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.sendOrSendAndReceive(GatewayProxyFactoryBean.java:639)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:573)
Can anyone help?
The MessagingGatewaySupport.registerReplyMessageCorrelatorIfNecessary() has to be fixed to call endpoint.setBeanFactory(beanFactory); for the ReactiveStreamsConsumer. Apparently we just don't have a test case to cover the replyChannel as a FluxMessageChannel.
Consider to not use that replyChannel at all as a workaround. It is really doesn't matter for this kind of flow: it is already reactive by the Mono return type. No reason to shift to other reactive stream internally for the reply correlation.

How to inject a variable in threadlocal in spring integration flow

I need to inject a callContext object as thread Local in spring integration flow. A traceId which is received in jms header should be extracted n set in the callContext object so that it gets printed in log which is configured at project level.
IntegrationFlows.from(Jms.messageDrivenChannelAdapter(connectionFactory).destination(topicName))
.log(INFO, m-> “message received for: + ((Order)m.getPayload()).getOrderId())
.handle(orderService)
.get();
You can just do it in your orderService, or add another service just before it.
I would say a Function for log() operator provides you full control over what you would like to log in the message. Of course, also avoiding overhead with the ThreadLocal. But if you still would like to use it, I would suggest a .wireTap() before that log(), so you can store a value in your ThreadLocal (MDC?) without effecting the main flow.
Only the problem that you have to remember to clean that callContext somehow to avoid thread local pollution.

spring-integration: MessageProducer may only be referenced once

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.

Spring integration DSL :: facing issue when calling channel from the xml

I did one simple DSL which retrieves the data from database and doing simple conversion in the service activator.
#Bean
public IntegrationFlow mainFlow() {
return IntegrationFlows.from("userChannel")
.channel("queryChannel")
.handle("sampleConvertor","convertUser")
.get();
queryChannel is a jdbc outbound gateway and sampleConverter is the service Activator.
<int-jdbc:outbound-gateway query="select * from employee where employee_id=:payload"
request-channel="queryChannel" data-source="dataSource"/>
The issue is after retrieving the data from database, the flow is not going to serviceActivator and it simply returns back the database response.
In xml configuration, I used to invoke gateway inside the chain like below.
<int:gateway id="query.gateway" request-channel="queryChannel"/>
Please suggest what I am doing wrong here. Thanks in advance.
That's a bit unusual to combine Java DSL and XML configuration, but they still work together.
Your problem I think that you are missing the fact that your queryChannel has two subscriber at runtime, not a chain of call.
The first one is <int-jdbc:outbound-gateway> and the second is that .handle("sampleConvertor","convertUser"). Right, when you declare a channel in the IntegrationFlow, the next EIP-method produces a subscriber for this channel. At the same time when you use a channel like request-channel or input-channel in the XML configuration that brings a subscriber as well.
So, you have two subscriber on the DirectChannel with the RoundRobinLoadBalancingStrategy and therefore only one of them will handle a message and if it is a request-replly component, like that <int-jdbc:outbound-gateway> it will produce a message into the output-channel or to the replyChannel in the headers. In your case the story is exactly about a replyChannel and therefore you don't go to the .handle("sampleConvertor","convertUser") because it's not the next in the chain, but just a parallel universe by the round-robin algorithm.
If you really would like to reach that .handle("sampleConvertor","convertUser") after calling the <int-jdbc:outbound-gateway>, you should consider to use .gateway("queryChannel") instead of that .channel().

Resources