11/*
2- * Copyright 2001-2019 the original author or authors.
2+ * Copyright 2001-2020 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1717package org .springframework .integration .ip .tcp ;
1818
1919import java .io .IOException ;
20+ import java .util .Date ;
2021import java .util .Map ;
2122import java .util .concurrent .ConcurrentHashMap ;
2223import java .util .concurrent .CountDownLatch ;
3839import org .springframework .integration .ip .tcp .connection .AbstractConnectionFactory ;
3940import org .springframework .integration .ip .tcp .connection .TcpConnection ;
4041import org .springframework .integration .ip .tcp .connection .TcpConnectionFailedCorrelationEvent ;
42+ import org .springframework .integration .ip .tcp .connection .TcpConnectionSupport ;
4143import org .springframework .integration .ip .tcp .connection .TcpListener ;
44+ import org .springframework .integration .ip .tcp .connection .TcpNioConnectionSupport ;
4245import org .springframework .integration .ip .tcp .connection .TcpSender ;
4346import org .springframework .messaging .Message ;
4447import org .springframework .messaging .MessageChannel ;
4548import org .springframework .messaging .MessageHandlingException ;
4649import org .springframework .messaging .MessagingException ;
4750import org .springframework .messaging .support .ErrorMessage ;
4851import org .springframework .util .Assert ;
52+ import org .springframework .util .concurrent .SettableListenableFuture ;
4953
5054/**
5155 * TCP outbound gateway that uses a client connection factory. If the factory is configured
@@ -123,6 +127,21 @@ protected void doInit() {
123127 }
124128 Assert .state (!this .closeStreamAfterSend || this .isSingleUse ,
125129 "Single use connection needed with closeStreamAfterSend" );
130+ if (isAsync ()) {
131+ try {
132+ TcpConnectionSupport connection = this .connectionFactory .getConnection ();
133+ if (connection instanceof TcpNioConnectionSupport ) {
134+ setAsync (false );
135+ this .logger .warn ("Async replies are not supported with NIO; see the reference manual" );
136+ }
137+ if (this .isSingleUse ) {
138+ connection .close ();
139+ }
140+ }
141+ catch (Exception e ) {
142+ this .logger .error ("Could not check if async is supported" , e );
143+ }
144+ }
126145 }
127146
128147 /**
@@ -144,11 +163,12 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
144163 boolean haveSemaphore = false ;
145164 TcpConnection connection = null ;
146165 String connectionId = null ;
166+ boolean async = isAsync ();
147167 try {
148168 haveSemaphore = acquireSemaphoreIfNeeded (requestMessage );
149169 connection = this .connectionFactory .getConnection ();
150170 Long remoteTimeout = getRemoteTimeout (requestMessage );
151- AsyncReply reply = new AsyncReply (remoteTimeout );
171+ AsyncReply reply = new AsyncReply (remoteTimeout , connection , haveSemaphore , requestMessage , async );
152172 connectionId = connection .getConnectionId ();
153173 this .pendingReplies .put (connectionId , reply );
154174 if (logger .isDebugEnabled ()) {
@@ -158,7 +178,12 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
158178 if (this .closeStreamAfterSend ) {
159179 connection .shutdownOutput ();
160180 }
161- return getReply (requestMessage , connection , connectionId , reply );
181+ if (async ) {
182+ return reply .getFuture ();
183+ }
184+ else {
185+ return getReply (requestMessage , connection , connectionId , reply );
186+ }
162187 }
163188 catch (RuntimeException | IOException e ) {
164189 logger .error ("Tcp Gateway exception" , e );
@@ -172,7 +197,9 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
172197 throw new MessageHandlingException (requestMessage , "Interrupted in the [" + this + ']' , e );
173198 }
174199 finally {
175- cleanUp (haveSemaphore , connection , connectionId );
200+ if (!async ) {
201+ cleanUp (haveSemaphore , connection , connectionId );
202+ }
176203 }
177204 }
178205
@@ -264,7 +291,13 @@ public boolean onMessage(Message<?> message) {
264291 return false ;
265292 }
266293 }
267- reply .setReply (message );
294+ if (isAsync ()) {
295+ reply .getFuture ().set (message );
296+ cleanUp (reply .isHaveSemaphore (), reply .getConnection (), connectionId );
297+ }
298+ else {
299+ reply .setReply (message );
300+ }
268301 return false ;
269302 }
270303
@@ -365,19 +398,44 @@ private final class AsyncReply {
365398
366399 private final long remoteTimeout ;
367400
401+ private final TcpConnection connection ;
402+
403+ private final boolean haveSemaphore ;
404+
405+ private final SettableListenableFuture <Message <?>> future = new SettableListenableFuture <>();
406+
368407 private volatile Message <?> reply ;
369408
370- private AsyncReply (long remoteTimeout ) {
409+ AsyncReply (long remoteTimeout , TcpConnection connection , boolean haveSemaphore , Message <?> requestMessage ,
410+ boolean async ) {
411+
371412 this .latch = new CountDownLatch (1 );
372413 this .secondChanceLatch = new CountDownLatch (1 );
373414 this .remoteTimeout = remoteTimeout ;
415+ this .connection = connection ;
416+ this .haveSemaphore = haveSemaphore ;
417+ if (async && remoteTimeout > 0 ) {
418+ getTaskScheduler ().schedule (() -> {
419+ TcpOutboundGateway .this .pendingReplies .remove (connection .getConnectionId ());
420+ this .future .setException (
421+ new MessageTimeoutException (requestMessage , "Timed out waiting for response" ));
422+ }, new Date (System .currentTimeMillis () + remoteTimeout ));
423+ }
424+ }
425+
426+ TcpConnection getConnection () {
427+ return this .connection ;
428+ }
429+
430+ boolean isHaveSemaphore () {
431+ return this .haveSemaphore ;
374432 }
375433
376434 /**
377435 * Sender blocks here until the reply is received, or we time out
378436 * @return The return message or null if we time out
379437 */
380- public Message <?> getReply () {
438+ Message <?> getReply () {
381439 try {
382440 if (!this .latch .await (this .remoteTimeout , TimeUnit .MILLISECONDS )) {
383441 return null ;
@@ -411,6 +469,10 @@ public Message<?> getReply() {
411469 return this .reply ;
412470 }
413471
472+ SettableListenableFuture <Message <?>> getFuture () {
473+ return this .future ;
474+ }
475+
414476 private void doThrowErrorMessagePayload () {
415477 if (this .reply .getPayload () instanceof MessagingException ) {
416478 throw (MessagingException ) this .reply .getPayload ();
0 commit comments