How to config tcp server to receive data from multiple client using spring boot? - spring-integration

I would like to configure TCP server to receive and reply data from multiple clients. I searched many other thread but could not found exact way to do. I'm using spring integration first time and have no experience.
Server requirement
should be able to receive and reply data to specific client (can have multiple client, each client should processed separately)
should be able to send data to client and wait for response for specific timeout.
Should be able to detect client is disconnect or not. if Client is disconnect then connection should be closed to save memory. (In earlier method without spring integration I was able to do it by ping client and see sending is failed or not but don't know how to do with spring integration)
I tried below code, In which I'm able to send data to client but could achieve my above requirements
TCP Server Configuration:
#Configuration
public class TcpServerConfig {
private List<TcpConnectionOpenEvent> clientList = new ArrayList<>();
public List<TcpConnectionOpenEvent> getClientList() {
return clientList;
}
#Bean
public TcpReceivingChannelAdapter server(TcpNetServerConnectionFactory cf) {
TcpReceivingChannelAdapter adapter = new TcpReceivingChannelAdapter();
adapter.setConnectionFactory(cf);
adapter.setOutputChannel(inputChannel());
return adapter;
}
#Bean
public MessageChannel inputChannel() {
return new QueueChannel();
}
#Bean
public MessageChannel outputChannel() {
return new DirectChannel();
}
#Bean
public TcpNetServerConnectionFactory cf() {
return new TcpNetServerConnectionFactory(1001);
}
#Bean
public IntegrationFlow outbound() {
return IntegrationFlows.from(outputChannel())
.handle(sender())
.get();
}
#Bean
public MessageHandler sender() {
TcpSendingMessageHandler tcpSendingMessageHandler = new TcpSendingMessageHandler();
tcpSendingMessageHandler.setConnectionFactory(cf());
return tcpSendingMessageHandler;
}
#Bean
public ApplicationListener<TcpConnectionOpenEvent> listener() {
return new ApplicationListener<TcpConnectionOpenEvent>() {
#Override
public void onApplicationEvent(TcpConnectionOpenEvent event) {
outputChannel().send(MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, event.getConnectionId())
.build());
clientList.add(event);
}
};
}
}
Test Code:
#Service
public class Test {
private static final Logger LOGGER = LoggerFactory.getLogger(MessageServiceImpl.class);
#Autowired
TcpServerConfig tcpServerConfig;
#Autowired
private MessageChannel outputChannel;
#Autowired
private MessageChannel inputChannel;
#Scheduled(fixedRate = 1000)
void task() {
LOGGER.info("Client count: " + tcpServerConfig.getClientList().size());
for (TcpConnectionOpenEvent client : tcpServerConfig.getClientList()) {
outputChannel.send(MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, client.getConnectionId())
.build());
}
}
}
Any help would be appreciated.

Here is one solution:
#SpringBootApplication
#EnableScheduling
public class So62877512ServerApplication {
public static void main(String[] args) {
SpringApplication.run(So62877512ServerApplication.class, args);
}
#Bean
public IntegrationFlow serverIn(Handler handler) {
return IntegrationFlows.from(Tcp.inboundAdapter(server()))
.transform(Transformers.objectToString())
.filter(handler, "existingConnection", spec -> spec
.discardFlow(f -> f
.handle(handler, "sendInitialReply")))
.handle(handler, "reply")
.get();
}
#Bean
public IntegrationFlow serverOut() {
return f -> f.handle(Tcp.outboundAdapter(server()));
}
#Bean
public TcpServerConnectionFactorySpec server() {
return Tcp.netServer(1234)
.serializer(TcpCodecs.lf())
.deserializer(TcpCodecs.lf()); // compatible with netcat
}
}
#Component
#DependsOn("serverOut")
class Handler {
private static final Logger LOG = LoggerFactory.getLogger(Handler.class);
private final ConcurrentMap<String, BlockingQueue<Message<?>>> clients = new ConcurrentHashMap<>();
private final MessageChannel out;
private final TcpNetServerConnectionFactory server;
public Handler(#Qualifier("serverOut.input") MessageChannel out, TcpNetServerConnectionFactory server) {
this.out = out;
this.server = server;
}
public boolean existingConnection(Message<?> message) {
String connectionId = message.getHeaders().get(IpHeaders.CONNECTION_ID, String.class);
boolean containsKey = this.clients.containsKey(connectionId);
if (!containsKey) {
this.clients.put(connectionId, new LinkedBlockingQueue<Message<?>>());
}
return containsKey;
}
public void sendInitialReply(Message<String> message) {
LOG.info("Replying to " + message.getPayload());
this.out.send(MessageBuilder.withPayload(message.getPayload().toUpperCase())
.copyHeaders(message.getHeaders()).build());
}
#Scheduled(fixedDelay = 5000)
public void sender() {
this.clients.forEach((key, queue) -> {
try {
this.out.send(MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, key).build());
Message<?> reply = queue.poll(10, TimeUnit.SECONDS);
if (reply == null) {
LOG.error("Timeout waiting for " + key);
this.server.closeConnection(key);
}
else {
LOG.info("Reply " + reply.getPayload() + " from " + key);
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.error("Interrupted");
}
catch (Exception e) {
LOG.error("Failed to send to " + key, e);
}
});
}
public void reply(Message<String> in) {
BlockingQueue<Message<?>> queue = this.clients.get(in.getHeaders().get(IpHeaders.CONNECTION_ID, String.class));
if (queue != null) {
queue.add(in);
}
}
#EventListener
public void closed(TcpConnectionCloseEvent event) {
this.clients.remove(event.getConnectionId());
LOG.info(event.getConnectionId() + " closed");
}
}
$ nc localhost 1234
foo <- typed
FOO
foo
bar <- typed
foo
bar <- typed
foo
$ <- closed by server - timeout
2020-07-14 14:41:04.906 INFO 64763 --- [pool-1-thread-2] com.example.demo.Handler : Replying to foo
2020-07-14 14:41:13.841 INFO 64763 --- [ scheduling-1] com.example.demo.Handler : Reply bar from localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153
2020-07-14 14:41:21.465 INFO 64763 --- [ scheduling-1] com.example.demo.Handler : Reply bar from localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153
2020-07-14 14:41:36.473 ERROR 64763 --- [ scheduling-1] com.example.demo.Handler : Timeout waiting for localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153
2020-07-14 14:41:36.474 INFO 64763 --- [ scheduling-1] com.example.demo.Handler : localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153 closed

Related

Spring integration TCP/IP close connection problem

I use spring integration as a Full Duplex communication system's gateway module.
that flow is client app <--> spring-integration-ip-module (siid) <--> server app
The problem is when client app closed, ssid can't closed connection with server app side?
here is my code
// siid connet to client
#Bean
public TcpNetServerConnectionFactory server(){
TcpNetServerConnectionFactory server=new TcpNetServerConnectionFactory(1234);
server.setMapper(new TcpSerMapper()); // use 'mapper' attribute in XML
MySerializer mySeri=new MySerializer();
server.setDeserializer(mySeri);
server.setSerializer(mySeri);
return server;
}
// inboundGateway, inChannel as reqeustChannel
#Bean
public TcpInboundGateway inGate(){
TcpInboundGateway inGate=new TcpInboundGateway();
inGate.setConnectionFactory(server());
inGate.setRequestChannelName("inChannel");
inGate.setReplyChannelName("outputChannel");
return inGate;
}
// serviceActivator to get inChannel's payload msg and send though a gateway.
#ServiceActivator(inputChannel = "inChannel")
public byte[]doClientForward(Message<?> msg){
byte[]msgPayload=(byte[])(msg.getPayload());
byte[]sendResult=null;
ToTCP toTcp=(ToTCP)contextBean.get("toTcpBean"); // ToTCP is a gateway
sendResult=toTcp.sends((msgPayload),"localhost",7779);
QueueChannel outputChannel=(QueueChannel)contextBean.get("outputChannel");
return sendResult;
}
public static class DynamicSerSeri extends AbstractPooledBufferByteArraySerializer {
protected byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
byte[] bytes = this.copyBuffer(inputStream, buffer);
return bytes;
}
public void serialize(byte[] object, OutputStream outputStream) throws IOException {
outputStream.write(object);
}
public byte[] copyBuffer(InputStream inputStream, byte[] buffer) throws IOException {
int n = 0;
int bite = 0;
try {
while (true) {
bite = inputStream.read(); // blocked here
this.setMaxMessageSize(inputStream.available() + 1);
buffer = new byte[inputStream.available() + 1];
if (bite < 0 && n == 0) {
throw new SoftEndOfStreamException("Stream closed between payloads");
}
checkClosure(bite);
buffer[n++] = (byte) bite;
if (bite == -1) {
break;
}
if (n == this.maxMessageSize) {
break;
}
}
return buffer;
} catch (SoftEndOfStreamException e) {
throw e; // I was stuck here. when client closed, cf can't receive this exception and send close singnal to server side
} catch (IOException e) {
publishEvent(e, buffer, n);
throw e;
} catch (RuntimeException e) {
publishEvent(e, buffer, n);
throw e;
}
}
}
#MessagingGateway()
public interface ToTCP {
#Gateway(requestChannel = "toTcp.input", replyChannel = "outputChannel")
public byte[] sends(byte[] data, #Header("host") String host, #Header("port") int port);
}
#Bean
public IntegrationFlow toTcp() {
return f -> f.route(new ClientTcpRouter());
}
// I am not sure I understand IntegrationFlowContext,but it works
public static class ClientTcpRouter extends AbstractMessageRouter {
#Autowired
private IntegrationFlowContext flowContext;
#Override
protected synchronized Collection<MessageChannel> determineTargetChannels(Message<?> message) {
// connection to server side.
TcpNetClientConnectionFactory cf = new TcpNetClientConnectionFactory(host, port); //?? this connection factory does's closed when inGate's connection factory throw SoftEndOfStreamException
TcpOutboundGateway handler = new TcpOutboundGateway();
handler.setConnectionFactory(cf);
cf.setDeserializer(new DynamicSerSeri());
cf.setSerializer(new DynamicSerSeri());
IntegrationFlow flow = f -> f.handle(handler);
IntegrationFlowContext.IntegrationFlowRegistration flowRegistration =
this.flowContext.registration(flow)
.addBean(cf)
.id(hostPort + ".flow")
.register();
MessageChannel inputChannel = flowRegistration.getInputChannel();
this.subFlows.put(hostPort, inputChannel);
return inputChannel;
}
}
TcpInboundGateway get the connection from client into the inputChannel, and I use a serviceActivator to get inputChannel's payload and send to server side by a TcpOutboundGateway which has a connection factory with server side.
when the client closed the connection with spring-integration-ip-module, TcpInboundGateway can get the exception in SoftEndOfStreamException, but I don't known how to closed TcpOutboundGateway's connection to the server side.
Use an ApplicationListener bean or #EventListener method to listen for TCP Events.
When you first open an outbound connection, you will get a TcpConnectionOpenEvent. It is published on (and will be receive on) the calling thread by default. You can associate the outbound connection id with the inbound.
Listen for TcpConnectionCloseEvent from the inbound connection factory; you can then close the outbound connection using its connectionId.
outboundFactory.closeConnection(connectionId);
EDIT
Since you are using a TcpNetServerConnectionFactory, you can use a ThreadAffinityClientConnectionFactory which will automatically associate the outgoing connection with the incoming connection.
When you get the event for the incoming connection close, it will be on the same thread, so you can simply call releaseConnection() on that thread and the outgoing connection will close.
Here is an example
#SpringBootApplication
public class So55207274Application {
public static void main(String[] args) {
SpringApplication.run(So55207274Application.class, args);
}
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(Tcp.inboundGateway(server()))
.log()
.handle(Tcp.outboundGateway(threadBoundClient()))
.get();
}
#Bean
public TcpNetServerConnectionFactory server() {
return new TcpNetServerConnectionFactory(1234);
}
#Bean
public ThreadAffinityClientConnectionFactory threadBoundClient() {
return new ThreadAffinityClientConnectionFactory(client());
}
public TcpNetClientConnectionFactory client() {
TcpNetClientConnectionFactory client = new TcpNetClientConnectionFactory("localhost", 1235);
client.setSingleUse(true);
return client;
}
#EventListener
public void listen(TcpConnectionCloseEvent event) {
if (event.getConnectionFactoryName().equals("server")) {
try {
threadBoundClient().releaseConnection();
}
catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(event);
}
// Test server
#Bean
public IntegrationFlow test() {
return IntegrationFlows.from(Tcp.inboundGateway(Tcp.netServer(1235)))
.transform(Transformers.objectToString())
.<String, String>transform(p -> p.toUpperCase())
.get();
}
}

publishSubscribeChannel unit test can't work well

my integration config class is below,when i do some unit test on them,found that:
when i send message to UserRecipientSubscribeCacheChannel,it work well;
when i send a message to an upper level of channel userReportWriteCompletedRouteChannel, it work failed,and it don't throws any exceptions yet. i can't understand it. the messages that i sent is same,of course.
because of the fail section, the next handler can't work ok.
ty!!
it work ok below, it print ===>ip location channel message:GenericMessage [payload=[MailRecipientActionDocumen...and ===>user recipient channel message:GenericMessage [payload=[UserRecipientSubscribeDataRedisStructure...
#Test
public void test_sendMessageUserRecipientSubscribeCacheChannel(){
UserRecipientSubscribeCacheChannel.send(createMessageWithIp());
}
it work fail below, it print ===>ip location channel message:GenericMessage [payload=[MailRecipientActionDocumen... only
notice that: the fail section, In front of handler has a transformer.
#Test
public void test_sendMessageToRouteChannel() {
userReportWriteCompletedRouteChannel.send(createMessageWithIp());
}
my code config below:
#Bean
public SubscribableChannel userReportWriteCompletedSubscribeChannel() {
return new DirectChannel();
}
#Bean
public QueueChannel userReportWriteCompletedRouteChannel() {
return new QueueChannel();
}
#Bean
public MessageChannel ipLocationResolveCacheChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel userRecipientSubscribeCacheChannel() {
return new DirectChannel();
}
#MessagingGateway(name = "userReportWriteCompletedListener",
defaultRequestChannel = "userReportWriteCompletedRouteChannel")
public interface UserReportWriteCompletedListener {
#Gateway
void receive(List<UserMailRecipientActionDocument> docs);
}
#Bean
public IntegrationFlow bridgeFlow() {
return flow -> flow.channel("userReportWriteCompletedRouteChannel")
.bridge(bridgeSpe -> bridgeSpe
.poller(pollerFactory -> pollerFactory.fixedRate(500).maxMessagesPerPoll(1)))
.channel("userReportWriteCompletedSubscribeChannel")
;
}
#Bean
public IntegrationFlow subscribeFlow() {
return IntegrationFlows.from("userReportWriteCompletedSubscribeChannel")
.publishSubscribeChannel(publishSubscribeSpec -> publishSubscribeSpec
.subscribe(flow -> flow
.channel(IP_LOCATION_RESOLVE_CACHE_CHANNEL)
)
.subscribe(flow -> flow
.channel(USER_RECIPIENT_SUBSCRIBE_CACHE_CHANNEL)
))
.get();
}
#Bean
public RedisStoreWritingMessageHandler ipLocationResolveCacheHandler(RedisTemplate<String, ?> redisTemplate) {
final RedisStoreWritingMessageHandler ipLocationResolveCacheHandler =
new RedisStoreWritingMessageHandler(redisTemplate);
ipLocationResolveCacheHandler.setKey("IP_LOCATION_RESOLVE_CACHE");
return ipLocationResolveCacheHandler;
}
#Bean
public RedisStoreWritingMessageHandler userRecipientSubscribeCacheHandler(RedisTemplate<String, ?> redisTemplate) {
final RedisStoreWritingMessageHandler userRecipientSubscribeCacheHandler =
new RedisStoreWritingMessageHandler(redisTemplate);
userRecipientSubscribeCacheHandler.setKey("USER_RECIPIENT_SUBSCRIBE_CACHE");
return userRecipientSubscribeCacheHandler;
}
#Bean
public IpLocationResolveRedisStructureFilterAndTransformer recipientActionHasIpFilterAndTransformer() {
return new IpLocationResolveRedisStructureFilterAndTransformer();
}
#Bean
public UserRecipientSubscribeDataRedisStructureTransformer subscribeDataRedisStructureTransformer(
IpLocationClient ipLocationClient) {
return new UserRecipientSubscribeDataRedisStructureTransformer(ipLocationClient);
}
#Bean
public IntegrationFlow ipLocationResolveCacheFlow(
#Qualifier("ipLocationResolveCacheHandler") RedisStoreWritingMessageHandler writingMessageHandler) {
return flow -> flow.channel(IP_LOCATION_RESOLVE_CACHE_CHANNEL)
.handle(message -> {
System.out.println("===>ip location channel message:" + message);
})
;
}
#Bean
public IntegrationFlow userRecipientActionDataCacheFlow(
#Qualifier("userRecipientSubscribeCacheHandler") RedisStoreWritingMessageHandler messageHandler,
UserRecipientSubscribeDataRedisStructureTransformer transformer) {
return flow -> flow.channel(USER_RECIPIENT_SUBSCRIBE_CACHE_CHANNEL)
.transform(transformer)
.handle(message -> {
System.out.println("===>user recipient channel message:" + message);
})
}
i expect 2 print message info ,but print 1 only.
Today, i found that the bridge flow may had some problem, When I move the handler behind the channeluserReportWriteCompletedSubscribeChannel, it can't print any message;
when i remove channel and add handler directly, it will print message.
does i use the bridge wrong?
#Bean
public IntegrationFlow bridgeFlow() {
return flow -> flow.channel("userReportWriteCompletedRouteChannel")
.bridge(bridgeSpe -> bridgeSpe
.poller(pollerFactory -> pollerFactory.fixedRate(100).maxMessagesPerPoll(1)))
.handle(message -> {
System.out.println("===>route channel message:" + message);
}) // handle ok , will print message
.channel("userReportWriteCompletedSubscribeChannel")
// .handle(message -> {
// System.out.println("===>route channel message:" + message);
// }) // handle fail , will not printing message
;
}
test:
#Test
//invalid
public void test_sendMessageToRouteChannel() {
userReportWriteCompletedRouteChannel.send(createMessageWithIp());
}

Spring Integration Load Balance to JMS Queues

I would like to take JMS msgs from a single input queue and fan them out onto N output queues.
I have a simple flow that will forward messages to a single destination but can not figure out how to apply LoadBalancer to allow for multiple destinations in round-robin fashion.
Any ideas how to do this?
#Configuration
public class TestLoadBalance {
public static final String INPUT_QUEUE = "_dev.lb.input";
public static final String OUTPUT_QUEUE_PREFIX = "_dev.lb.output-";
#Bean
public IntegrationFlow testLoadBalanceFlow(
ConnectionFactory jmsConnectionFactory) {
IntegrationFlow flow = IntegrationFlows.from(
Jms.messageDrivenChannelAdapter(jmsConnectionFactory)
.destination(INPUT_QUEUE)
)
.handle(buildOutput(jmsConnectionFactory, 1))
// cant have 2nd handle. gets warn & flow end:
// The 'currentComponent' (org.springframework.integration.jms.JmsSendingMessageHandler#516462cc)
// is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'
//.handle(buildOutput(jmsConnectionFactory, 2))
.get();
return flow;
}
private JmsSendingMessageHandler buildOutput(ConnectionFactory jmsConnectionFactory, int i){
return Jms.outboundAdapter(jmsConnectionFactory)
.destination(OUTPUT_QUEUE_PREFIX + i).get();
}
}
There are a couple of ways to do it; you can either have multiple subscribers on the channel...
#Bean
public IntegrationFlow inbound(ConnectionFactory cf) {
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(cf)
.destination("foo"))
.channel(roundRobin())
.get();
}
#Bean
public DirectChannel roundRobin() {
return new DirectChannel();
}
#Bean
public IntegrationFlow outbound1(ConnectionFactory cf) {
return IntegrationFlows.from(roundRobin())
.bridge() // otherwise log() will wire tap the roundRobin channel
.log()
.log(new LiteralExpression("Sending to bar"))
.handle(Jms.outboundAdapter(cf)
.destination("bar"))
.get();
}
#Bean
public IntegrationFlow outbound2(ConnectionFactory cf) {
return IntegrationFlows.from(roundRobin())
.bridge() // otherwise log() will wire tap the roundRobin channel
.log()
.log(new LiteralExpression("Sending to baz"))
.handle(Jms.outboundAdapter(cf)
.destination("baz"))
.get();
}
Or, you can use a destination expression:
#Bean
public AtomicInteger toggle() {
return new AtomicInteger();
}
#Bean
public IntegrationFlow inbound(ConnectionFactory cf) {
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(cf)
.destination("foo"))
.handle(Jms.outboundAdapter(cf)
.destinationExpression("#toggle.getAndIncrement() % 2 == 0 ? 'bar' : 'baz'"))
.get();
}
#JmsListener(destination = "bar")
public void bar(String in) {
System.out.println("received " + in + " from bar");
}
#JmsListener(destination = "baz")
public void baz(String in) {
System.out.println("received " + in + " from baz");
}
Result:
received test1 from bar
received test2 from baz
Based on Gary's examples I went with the destinationExpression approach as:
#Configuration
public class TestLoadBalance {
public static final String INPUT_QUEUE = "_dev.lb.input";
public static final String OUTPUT_QUEUE_PREFIX = "_dev.lb.output-";
#Bean
public JmsDestinationPartitioner partitioner() {
return new JmsDestinationPartitioner(OUTPUT_QUEUE_PREFIX,1,3);
}
#Bean
public IntegrationFlow testLoadBalanceFlow(
ConnectionFactory jmsConnectionFactory) {
IntegrationFlow flow = IntegrationFlows.from(
Jms.messageDrivenChannelAdapter(jmsConnectionFactory)
.destination(INPUT_QUEUE)
)
.handle(Jms.outboundAdapter((jmsConnectionFactory))
.destinationExpression("#partitioner.nextDestination()"))
.get();
return flow;
}
}
With a wrapper around AtomicInt to handling naming with a prefix:
public class JmsDestinationPartitioner {
private int min;
private int max;
private String prefix;
private AtomicInteger current;
public JmsDestinationPartitioner(String prefix, int min, int max){
this.prefix = prefix;
this.min = min;
this.max = max;
current = new AtomicInteger(min);
}
public int getAndIncrement(){
int i = current.get();
current.getAndIncrement();
if (current.get() > max){
current.set(min);
}
return i;
}
public String nextDestination(){
return prefix + getAndIncrement();
}
}

Spring Integration redelivery via errorChannel throw with JmsTransactionManager doesnt honor maximumRedeliveries

Related to SO question: Spring Integration Java DSL using JMS retry/redlivery
Using a transacted poller and JmsTransactionManager on a connectionFactory with maximumRedeliveries set to 3 results in a doubling of the actual redlievery attempts.
How can I get this to honor the redelivery settings of the connection factory?
My connectionFactory is built as:
#Bean (name="spring-int-connection-factory")
ActiveMQConnectionFactory jmsConnectionFactory(){
return buildConnectionFactory(
brokerUrl,
DELAY_2_SECS,
MAX_REDELIVERIES,
"spring-int");
}
public static ActiveMQConnectionFactory buildConnectionFactory(String brokerUrl, Long retryDelay, Integer maxRedeliveries, String clientIdPrefix){
ActiveMQConnectionFactory amqcf = new ActiveMQConnectionFactory();
amqcf.setBrokerURL(brokerUrl);
amqcf.setClientIDPrefix(clientIdPrefix);
if (maxRedeliveries != null) {
if (retryDelay == null) {
retryDelay = 500L;
}
RedeliveryPolicy rp = new org.apache.activemq.RedeliveryPolicy();
rp.setInitialRedeliveryDelay(retryDelay);
rp.setRedeliveryDelay(retryDelay);
rp.setMaximumRedeliveries(maxRedeliveries);
}
return amqcf;
}
My flow with poller is as:
#Bean
public IntegrationFlow flow2(#Qualifier("spring-int-connection-factory") ConnectionFactory connectionFactory) {
IntegrationFlow flow = IntegrationFlows.from(
Jms.inboundAdapter(connectionFactory)
.configureJmsTemplate(t -> t.receiveTimeout(1000).sessionTransacted(true))
.destination(INPUT_DIRECT_QUEUE),
e -> e.poller(Pollers
.fixedDelay(5000)
.transactional()
.errorChannel("customErrorChannel")
.maxMessagesPerPoll(2))
).handle(this.msgHandler).get();
return flow;
}
My errorChannel handler simply re-throws which causes JMS redelivery to happen.
When I run this with the handler set to always throw an exception, I see that the message handler actually receives the message 7 times (1 initial and 6 redeliveries).
I expected only 3 redeliveries according to my connectionFactory config.
Any ideas what is causing the doubling of attempts and how to mitigate it?
This works fine for me - stops at 4...
#SpringBootApplication
public class So51792909Application {
private static final Logger logger = LoggerFactory.getLogger(So51792909Application.class);
public static void main(String[] args) {
SpringApplication.run(So51792909Application.class, args);
}
#Bean
public ApplicationRunner runner(JmsTemplate template) {
return args -> {
for (int i = 0; i < 1; i++) {
template.convertAndSend("foo", "test");
}
};
}
#Bean
public IntegrationFlow flow(ConnectionFactory connectionFactory) {
return IntegrationFlows.from(Jms.inboundAdapter(connectionFactory)
.destination("foo"), e -> e
.poller(Pollers
.fixedDelay(5000)
.transactional()
.maxMessagesPerPoll(2)))
.handle((p, h) -> {
System.out.println(h.get("JMSXDeliveryCount"));
try {
Thread.sleep(2000);
}
catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
throw new RuntimeException("foo");
})
.get();
}
#Bean
public JmsTransactionManager transactionManager(ConnectionFactory cf) {
return new JmsTransactionManager(cf);
}
#Bean
public ActiveMQConnectionFactory amqCF() {
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
RedeliveryPolicy rp = new RedeliveryPolicy();
rp.setMaximumRedeliveries(3);
cf.setRedeliveryPolicy(rp);
return cf;
}
public CachingConnectionFactory connectionFactory() {
return new CachingConnectionFactory(amqCF());
}
#JmsListener(destination = "ActiveMQ.DLQ")
public void listen(String in) {
logger.info(in);
}
}

Spring Integration TcpInboundGateway sending conditional reply

I have configured TcpInboundGateway to receive requests from client and my configuration is as follows. So as per below configuration every client requested is responded back,but what i want is response should be send back only if certain condition is true,not the every time, what changes needs to be done in configuration?
#SpringBootApplication
#IntegrationComponentScan
public class SpringIntegrationApplication extends SpringBootServletInitializer{
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext ctx = SpringApplication.run(SpringIntegrationApplication.class, args);
System.in.read();
ctx.close();
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringIntegrationApplication.class);
}
private static Class<SpringIntegrationApplication> applicationClass = SpringIntegrationApplication.class;
#Bean
TcpNetServerConnectionFactory cf(){
TcpNetServerConnectionFactory connectionFactory=new TcpNetServerConnectionFactory(8765);
return connectionFactory;
}
#Bean
TcpInboundGateway tcpGate(){
TcpInboundGateway gateway=new TcpInboundGateway();
gateway.setConnectionFactory(cf());
gateway.setRequestChannel(requestChannel());
return gateway;
}
#Bean
public MessageChannel requestChannel(){
return new DirectChannel();
}
#MessageEndpoint
public class Echo {
#ServiceActivator(inputChannel="requestChannel")
public byte[] echo(byte[] in,#SuppressWarnings("deprecation") #Header("ip_address") String ip){
byte[] rawbytes = gosDataSerivce.byteArrayToHex(in,ip);//Process bytes and returns result
return rawbytes;
}
}
}
Not sure where is your problem, but you can just simply return null from your echo(). In that case the ServiceActivatingHandler doesn't care and stops it work. Just because of requiresReply = false.
From other side the TcpInboundGateway doesn't care about null, too:
Message<?> reply = this.sendAndReceiveMessage(message);
if (reply == null) {
if (logger.isDebugEnabled()) {
logger.debug("null reply received for " + message + " nothing to send");
}
return false;
}
That is possible because of replyTimeout option for the MessagingTemplate on the background. By default it is 1 sec. After that the sendAndReceiveMessage() just returns null to the caller.
You can adjust this option on the TcpInboundGateway.

Resources