1
votes

I'm on a request/reply scenario and I receive requests that I need to reply through the same input connection. To give a response I need to do a websocket request to another system. Right now I'm connecting an output-channel to the reply channel of the gateway this way (I'm using a transformer to adapt the incoming messages):

<int:channel id="emrOutboundMessageTransformedChannel"/>
<int:channel id="emrInboundMessageTransformerChannel"/>
<int:channel id="emrOutboundChannel"/>
<int:channel id="cloudOutboundChannel"/>

<!-- Server WebSocket -->

<beans:bean id="webSocketClient"
  class="org.springframework.web.socket.client.standard.StandardWebSocketClient"/>

<int-websocket:client-container id="webSocketClientContainer"
  client="webSocketClient"
  uri="ws://${cloud.webSocket.host}:${cloud.webSocket.port}"/>

<!-- Client Inbound -->

<int:gateway id="emrInboundGateway"
  service-interface="com.roche.iconnect.emr.core.EmrMessageInjector">
  <int:method name="injectMessage" request-channel="emrInboundMessageTransformerChannel" reply-channel="emrOutboundMessageTransformedChannel"/>
</int:gateway>

<int:transformer
  id="emrInboundTransformer"
  input-channel="emrInboundMessageTransformerChannel"
  output-channel="cloudOutboundChannel"
  method="convertEmrMessageToGenericMessage">
  <beans:bean class="com.roche.iconnect.emr.EmrMessageAdapter"/>
</int:transformer>

<!-- Client Outbound -->

<int:transformer
  id="emrOutboundTransformer"
  input-channel="emrOutboundChannel"
  output-channel="emrOutboundMessageTransformedChannel"
  method="convertGenericMessageToEmrMessage">
  <beans:bean class="com.roche.iconnect.emr.EmrMessageAdapter"/>
</int:transformer>

<!-- Server Inbound -->

<int-websocket:inbound-channel-adapter id="webSocketClientInboundAdapter"
  container="webSocketClientContainer"
  channel="emrOutboundChannel"/>

<int-websocket:outbound-channel-adapter id="webSocketClientOutboundAdapter"
  container="webSocketClientContainer"
  channel="cloudOutboundChannel"/>

It basically is chanining componenets like this:

CLIENT -> gateway -> transformer -> wsClient -> SERVER -> wsClient -> transformer -> gateway-reply

The problem here is that I get an error:

org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available

Websockets are by nature bidirectional so I can get any message without sending anything, is not a real request/reply channel but I need to use it this way. The websocket server only sends "reply" messages to previous "request" messages.

I need to get through the return of the gateway the response of the websocket.


Update 1

Header enricher registry

I added a header enricher after the gateway to stringify and store into the registry the reply and error channels:

(Added "replyChannelRegistry" channel)

<int:gateway id="emrInboundGateway"
  service-interface="com.roche.iconnect.emr.core.EmrMessageInjector">
  <int:method name="injectMessage" request-channel="replyChannelRegistry" reply-channel="emrOutboundMessageTransformedChannel"/>
</int:gateway>

<beans:bean id="integrationHeaderChannelRegistry"
  class="org.springframework.integration.channel.DefaultHeaderChannelRegistry">
  <beans:constructor-arg index="0" value="${config.timeout}"/>
</beans:bean>

<int:header-enricher input-channel="replyChannelRegistry" output-channel="emrInboundMessageTransformerChannel">
  <int:header-channels-to-string time-to-live-expression="${config.timeout}"/>
  <int:correlation-id value="CORRELATION_ID_TEST"/>
</int:header-enricher>

Now the question is... How can I retrieve these reply and error channels? Inside the messages that go through the system I have a unique Id that I can add to the header, but how can I get the reply and error channels from this correlation Id that will come from the websocket?

Update 2 (SOLVED)

I guess that this scenario is pretty usual. A request arrives to you API and to respond to this request you must get the information from another system over a channel that breaks the request/reply pattern.

Somewhere you need to store a mapping between a unique id that you need to extract from the request and the channel from where it arrives. This unique id needs to be recreated by using the data that you'll get from the other system, otherwise you cannot recover the original channel.

Steps are:

  1. Request comes.
  2. Use the reply-channel property of the gateway component.
  3. Stringify the replyChannel by using the header-enricher component (see Update 1). You cannot have a Map<String, MessageChannel> mapping (id/channel), you need to stringify the channel.
  4. Ask for the response data.
  5. Get the response data.
  6. Now you need to send the reply through the same original connection.
  7. To do so, use another header-enricher component. The idea is to set the replyChannel of the message that will return to the gateway (ensure that the class of the message matches the return class of the gateway) and Spring automatically will link the "temporal internal point-to-point" (the one that we stringified previously) to the reply-channel of the gateway. Magic!

    <int:header-enricher input-channel="recoverOriginalChannel" output-channel="gatewayReplyChannel"> <int:reply-channel ref="yourBean" method="enrichReplyChannelHeader" /> </int:header-enricher>

  8. Done!

1

1 Answers

1
votes

You are losing the gateway's replyChannel header; it's a live object and can't be sent over websocket.

Try using a message channel registry - which converts the channel to a String, and stores the channel in a registry; the websocket server would have to return the String value header so it can be correlated properly to get the channel out of the registry.