Spring Integration TCP: How to get local IP address - spring-integration

I'm new to Spring Integration and experimenting with its TCP support. I have implemented a very basic TCP server that accepts connections and just logs the messages from the clients. This works fine, but I'm stuck in finding out how to get the local IP address to which the client has connected (the server machine has multiple IP addresses). It seems that the MessageHeaders contain only the remote address.
Here's my configuration:
#EnableIntegration
#Configuration
public class SpringConfig {
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
public AbstractServerConnectionFactory serverConnectionFactory() {
TcpNetServerConnectionFactory connectionFactory =
new TcpNetServerConnectionFactory(2000);
return connectionFactory;
}
#Bean
public TcpReceivingChannelAdapter tcpReceivingChannelAdapter() {
TcpReceivingChannelAdapter adapter = new TcpReceivingChannelAdapter();
adapter.setOutputChannel(requestChannel());
adapter.setConnectionFactory(serverConnectionFactory());
return adapter;
}
#Bean
public PrintService printService() {
return new PrintService();
}
}
And my PrintService:
#MessageEndpoint
public class PrintService {
#ServiceActivator(inputChannel = "requestChannel")
public String print(Message<byte[]> message) {
// How can I get the local IP address here?
}
}
Edit 1
Basically, I would like to have the result from java.net.Socket.getLocalAddress() available in my print(...) function. Maybe like this: message.getHeaders().get(IpHeaders.LOCAL_IP_ADDRESS)
I also tried to add a custom message header by subclassing TcpMessageMapper and overriding supplyCustomHeaders(...), but I couldn't find out how to access the underlying socket.

Basically, I would like to have the result from java.net.Socket.getLocalAddress() available in my print(...) function. Maybe like this: message.getHeaders().get(IpHeaders.LOCAL_IP_ADDRESS)
Yeah, I'd better expose something like T getTarget() on the TcpConnection to allow to get access to underlying socket from the TcpMessageMapper.supplyCustomHeaders() implementation...
Feel free to raise a JIRA on the matter and we'll try to figure out what to do from the Framework perspective.
Meanwhile, as a workaround: I guess you should extend the TcpNetServerConnectionFactory a bit and override its initializeConnection(TcpConnectionSupport connection, Socket socket) to store the connectionId <-> socket relationship in some global map. Maybe even in your custom TcpMessageMapper? Having connectionId in the supplyCustomHeaders() you will be able to restore the particular socket for your purpose.

Related

No publisher available to publish TcpConnectionOpenEvent / TcpConnectionCloseEvent

I configured a TCP Client with the Java DSL of Spring Integration. It looks like this
#Bean
public TcpSendingMessageHandler tcpClient()
{
return Tcp
.outboundAdapter(
Tcp.nioClient("localhost", 9060)
.deserializer(new ByteArrayLfSerializer())
.soKeepAlive(false)
.leaveOpen(false)
.taskExecutor(Executors.newSingleThreadExecutor())
.get()
)
.clientMode(false)
.get();
}
And I am using it in a Service to send messages to the TCP socket the client is connected to:
#Slf4j
#Service
public class TcpClientConnectionService
{
private final TcpSendingMessageHandler messageHandler;
#Autowired
public TcpClientConnectionService(final TcpSendingMessageHandler messageHandler)
{
this.messageHandler = messageHandler;
this.messageHandler.start();
}
public void sendMessage(final String message)
{
messageHandler.handleMessage(new GenericMessage<>(message));
log.debug("Message: " + message + " send");
}
}
But in production I am getting the follwing warning rather regulary and I do not know what the issue is and how to fix it.
o.s.i.i.tcp.connection.TcpNioConnection : No publisher available to
publish TcpConnectionOpenEvent
o.s.i.i.tcp.connection.TcpNioConnection : No publisher available to
publish TcpConnectionCloseEvent
It would be great if somebody could help me out since I was not able to find anything by googling.
The nested factory is not initialized properly because you are incorrectly calling .get() on the spec, which subverts Spring initialization.
I configured a TCP Client with the Java DSL of Spring Integration. It looks like this
#Bean
public TcpSendingMessageHandler tcpClient()
{
return Tcp
.outboundAdapter(
Tcp.nioClient("localhost", 9060)
.deserializer(new ByteArrayLfSerializer())
.soKeepAlive(false)
.leaveOpen(false)
.taskExecutor(Executors.newSingleThreadExecutor()))
.clientMode(false)
.get();
}
Or move the factory definition to a top level #Bean.

#Transformer for ObjectToJson Not Working in Spring Integration

A POJO Message.java is to be Converted to JSON(JSON is to be sent to pubsub Topic,using Spring Integration MessageChannels.),using following:
#Bean
#Transformer(inputChannel = "pubsubOutputChannel", outputChannel = "handleOutChannel")
public ObjectToJsonTransformer transformOut() {
return new ObjectToJsonTransformer();
}
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(Messages msg);
}
#Bean
#ServiceActivator(inputChannel = "handleOutChannel")
public MessageHandler messageSender(PubSubOperations pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, "TestTopic");
}
When i call sendToPubsub() with an instance of Message.java with required properties set,i get an error "Null".
Is serviceActivator not able to receive the required data?
Any suggestions to fix this?.
Yes, it can't do that because you just don't tell it to do that.
Your gateway is configured for this:
#MessagingGateway(defaultRequestChannel = "handleOutChannel")
But that is not an input channel for the ObjectToJsonTransformer. So, whatever you send over that gateway is going directly to the messageSender service activator.
Try to configure your gateway like this:
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")

Spring Integration: reuse MessageProducer definition

I have an outbound gateway for soap calls (MarshallingWebServiceOutboundGateway) with elaborate setup. I need to use that gateway definition from multiple flows.
The question spring-integration: MessageProducer may only be referenced once is somewhat similar, but this question is about the proper use of the spring bean scope prototype for spring integration collaborators.
I have a separate config file which sets up the gateway and its dependencies:
#Bean
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;
}
This is how I initially tried to wire up the outbound gateway from two different flows in two different config files.
In one config file:
#Bean
public IntegrationFlow flow1() {
MarshallingWebServiceOutboundGateway myServiceGateway = context.getBean("myServiceGateway", MarshallingWebServiceOutboundGateway.class);
return IntegrationFlows
.from(Http.inboundGateway("/res1")
.requestMapping(r -> r.methods(HttpMethod.GET))
.transform(soapRequestTransformer)
.handle(myServiceGateway) // wrong: cannot be same bean
.transform(widgetTransformer)
.get();
}
In a separate config file:
#Bean
public IntegrationFlow flow2() {
MarshallingWebServiceOutboundGateway myServiceGateway = context.getBean("myServiceGateway", MarshallingWebServiceOutboundGateway.class);
return IntegrationFlows
.from(Http.inboundGateway("/res2")
.requestMapping(r -> r.methods(HttpMethod.GET))
.transform(soapRequestTransformer)
.handle(myServiceGateway) // wrong: cannot be same bean
.transform(widgetTransformer)
.handle(servicePojo)
.get();
}
This is a problem because - as I understand it - myServiceGateway cannot be the same instance, since that instance has only one outbound channel and cannot belong to two different flows.
In the related question spring-integration: MessageProducer may only be referenced once, #artem-bilan advised not to create the outbound gateway in an #Bean method, rather to use a plain method which creates new instances for every call.
That works, but it is inconvenient in my case. I need to reuse the outbound gateway from several flows in different config files and I would have to copy the code to create the gateway into each config file. Also, the gateway dependencies inflate my Configuration file constructors, making Sonar bail.
Since the error message coming out of IntegrationFlowDefinition.checkReuse() says A reply MessageProducer may only be referenced once (myServiceGateway) - use #Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) on #Bean definition. I wanted to give the scope prototype another try.
So I try to make spring integration look up a prototype gateway from the context by name, hoping to get a different gateway instance in flow1 and flow2:
.handle(context.getBean("myServiceGateway",
MarshallingWebServiceOutboundGateway.class))
And I annotated the outbound gateway #Bean definition with
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
But I can see that the myServiceGateway() method is only invoked once, despite the prototype scope, and application startup still fails with the error message which advises to use the prototype scope - quite confusing, actually ;-)
Based on Mystery around Spring Integration and prototype scope I also tried:
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
The application starts, but the responses never reach the step after the gateway, the widgetTransformer. (Even more strange, exactly the widgetTransformer is skipped: in flow1 the outcome is the untransformed gateway response and in flow2 the untransformed messages hit the step after the widgetTransformer, i.e. the servicePojo). Making a proxy out of a message producer seems not to be a good idea.
I really want to get to the bottom of this. Is the exception message wrong which asks to use the prototype scope or am I just getting it wrong? How can I avoid to repeat the bean definition for message producers if I need several such producers which are all set up the same way?
Using spring-integration 5.0.9.
I am not entirely sure why the #Scope is not working, but here is a work-around...
#SpringBootApplication
public class So52453934Application {
public static void main(String[] args) {
SpringApplication.run(So52453934Application.class, args);
}
#Autowired
private HandlerConfig config;
#Bean
public IntegrationFlow flow1() {
return f -> f.handle(this.config.myHandler())
.handle(System.out::println);
}
#Bean
public IntegrationFlow flow2() {
return f -> f.handle(this.config.myHandler())
.handle(System.out::println);
}
#Bean
public ApplicationRunner runner() {
return args -> {
context.getBean("flow1.input", MessageChannel.class).send(new GenericMessage<>("foo"));
context.getBean("flow2.input", MessageChannel.class).send(new GenericMessage<>("bar"));
};
}
}
#Configuration
class HandlerConfig {
public AbstractReplyProducingMessageHandler myHandler() {
return new AbstractReplyProducingMessageHandler() {
#Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return ((String) requestMessage.getPayload()).toUpperCase();
}
};
}
}
i.e. do as #artem suggested, but inject the bean with the factory method.

Why does a AmqpChannelFactoryBean with Jackson2JsonMessageConverter not store type?

I'm trying to use Spring integration with RabbitMQ, using RabbitMQ backed Spring integration channels. (Which seems almost not documented for some reason, is this new?).
To do this, it seems I can use AmqpChannelFactoryBean to create a channel.
To set up message conversion, I use a Jackson2JsonMessageConverter.
When I use a GenericMessage with a POJO payload, it refuses to de-serialize it from Java, basically because it doesn't know the type. I would have expected the type to be automagically be put on the header, but on the header there is only __TypeId__=org.springframework.messaging.support.GenericMessage.
In Spring boot my configuration class looks like this:
#Configuration
public class IntegrationConfiguration {
#Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public AmqpChannelFactoryBean myActivateOutChannel(CachingConnectionFactory connectionFactory,
MessageConverter messageConverter) {
AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean(true);
factoryBean.setConnectionFactory(connectionFactory);
factoryBean.setQueueName("myActivateOut");
factoryBean.setPubSub(false);
factoryBean.setAcknowledgeMode(AcknowledgeMode.AUTO);
factoryBean.setDefaultDeliveryMode(MessageDeliveryMode.PERSISTENT);
factoryBean.setMessageConverter(messageConverter);
return factoryBean;
}
#Bean
#ServiceActivator(inputChannel = "bsnkActivateOutChannel", autoStartup="true")
public MessageHandler mqttOutbound() {
return m -> System.out.println(m);
}
}
Sending is done like this:
private final MessageChannel myActivateOutChannel;
#Autowired
public MySender(MessageChannel myActivateOutChannel) {
this.myActivateOutChannel = myActivateOutChannel;
}
#Override
public void run(ApplicationArguments args) throws Exception {
MyPojo pojo = new MyPojo();
Message<MyPojo> msg = new GenericMessage<>(pojo);
myActivateOutChannel.send(msg);
}
If I set my own classmapper, things do work as they should. But I would have to use many MessageConverters if I set up things like that.
E.g.
converter.setClassMapper(new ClassMapper() {
#Override
public void fromClass(Class< ? > clazz, MessageProperties properties) {
}
#Override
public Class< ? > toClass(MessageProperties properties) {
return MyPojo.class;
}
});
Am I using this wrong? Am I missing some configuration? Any other suggestions?
Thanks!! :)
Note: Looking more at things, I'm guessing the 'Spring integration' way would be to add a Spring integration JSON transformer on each side, which means also adding two additional direct channels per RabbitMQ queue?
This feels wrong to me, since I've got triple the channels then (6! for in/out), but mayby that's how the framework is supposed to be used? Couple all the simple steps with direct channels? (Do I keep the persistence which the RabbitMQ channels offer in that case? Or do I need some transaction mechanism if I want that? Or is it inherent in how direct channels work?)
I've also noticed now there's both a Spring-integration MessageConverter, and a Spring-amqp MessageConverter. The latter being the one I've used. Would the other work the way I want it to? A quick glance at the code suggests it doesn't store the object type in the message header?
Prior to version 4.3, amqp-backed channels only supported serializable payloads; the work around was to use channel adapters instead (which support mapping).
INT-3975 introduced a new property extractPayload which causes the message headers to be mapped to rabbitmq headers and the message body is just the payload instead of a serialized GenericMessage.
Setting extractPayload to true should solve your problem.

Spring Integration 4 asynchronous request/response

I am trying to write a simple message flow using Spring Integration v4's DSL APIs which would look like this:
-> in.ch -> Processing -> JmsGatewayOut -> JMS_OUT_QUEUE
Gateway
<- out.ch <- Processing <- JmsGatewayIn <- JMS_IN_QUEUE
With the request/response being asynchronous, when I inject a message via the initial Gateway, the message goes all the way to JMS_OUT_QUEUE. Beyond this message flow, a reply message is put back into JMS_IN_QUEUE which it is then picked up by JmsGatewayIn. At this point, the message is Processed and placed into out.ch (I know the response gets to out.ch because I have a logger interceptor there which logs the message being placed there) but, the Gateway never receives the response.
Instead of a response, the system outside of this message flow which picked up the message from JMS_OUT_QUEUE and placed the response in JMS_IN_QUEUE, receives a javax.jms.MessageFormatException: MQJMS1061: Unable to deserialize object on its own JmsOutboundgateway (I think it is failing to deserialize a jms reply object from looking at the logs).
I have clearly not got something configured correctly but I don't know exactly what. Does anyone know what I am missing?
Working with spring-integration-core-4.0.3.RELEASE, spring-integration-jms-4.0.3.RELEASE, spring-integration-java-dsl-1.0.0.M2, spring-jms-4.0.6.RELEASE.
My Gateway is configured as follows:
#MessagingGateway
public interface WsGateway {
#Gateway(requestChannel = "in.ch", replyChannel = "out.ch",
replyTimeout = 45000)
AResponse process(ARequest request);
}
My Integration flow is configured as follows:
#Configuration
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
public class IntegrationConfig {
#Bean(name = "in.ch")
public DirectChannel inCh() {
return new DirectChannel();
}
#Bean(name = "out.ch")
public DirectChannel outCh() {
return new DirectChannel();
}
#Autowired
private MQQueueConnectionFactory mqConnectionFactory;
#Bean
public IntegrationFlow requestFlow() {
return IntegrationFlows.from("in.ch")
.handle("processor", "processARequest")
.handle(Jms.outboundGateway(mqConnectionFactory)
.requestDestination("JMS_OUT_QUEUE")
.correlationKey("JMSCorrelationID")
.get();
}
#Bean
public IntegrationFlow responseFlow() {
return IntegrationFlows.from(Jms.inboundGateway(mqConnectionFactory)
.destination("JMS_IN_QUEUE"))
.handle("processor", "processAResponse")
.channel("out.ch")
.get();
}
}
Thanks for any help on this,
PM.
First of all your configuration is bad:
Since you start the flow from WsGateway#process you really should wait reply there.
The gateway's request/reply capability is based on TemporaryReplyChannel, which is placed to the headers as non-serializable value.
As long as you wait rely on that gateway, actually there is no reason to provide the replyChannel, if you aren't going to do some publish-subscribe logic on the reply.
As you send message to the JMS queue, you should understand that consumer part might be a separete remote application. And the last one might know nothing about your out.ch.
The JMS request/reply capability is really based on JMSCorrelationID, but it isn't enough. The one more thing here is a ReplyTo JMS header. Hence, if you are going to send reply from the consumer you should really just rely on the JmsGatewayIn stuff.
So I'd change your code to this:
#MessagingGateway
public interface WsGateway {
#Gateway(requestChannel = "in.ch", replyTimeout = 45000)
AResponse process(ARequest request);
}
#Configuration
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
public class IntegrationConfig {
#Bean(name = "in.ch")
public DirectChannel inCh() {
return new DirectChannel();
}
#Autowired
private MQQueueConnectionFactory mqConnectionFactory;
#Bean
public IntegrationFlow requestFlow() {
return IntegrationFlows.from("in.ch")
.handle("processor", "processARequest")
.handle(Jms.outboundGateway(mqConnectionFactory)
.requestDestination("JMS_OUT_QUEUE")
.replyDestination("JMS_IN_QUEUE"))
.handle("processor", "processAResponse")
.get();
}
}
Let me know, if it is appropriate for you or try to explian why you use two-way gateways for one one-way cases. Maybe Jms.outboundAdapter() and Jms.inboundAdapter() are more good for you?
UPDATE
How to use <header-channels-to-string> from Java DSL:
.enrichHeaders(e -> e.headerChannelsToString())

Resources