|
16 | 16 |
|
17 | 17 | package org.springframework.integration.ip.tcp.connection;
|
18 | 18 |
|
| 19 | +import java.util.ArrayList; |
19 | 20 | import java.util.Iterator;
|
20 | 21 | import java.util.List;
|
21 | 22 | import java.util.UUID;
|
|
34 | 35 | * Given a list of connection factories, serves up {@link TcpConnection}s
|
35 | 36 | * that can iterate over a connection from each factory until the write
|
36 | 37 | * succeeds or the list is exhausted.
|
| 38 | + * |
37 | 39 | * @author Gary Russell
|
38 | 40 | * @since 2.2
|
39 | 41 | *
|
40 | 42 | */
|
41 | 43 | public class FailoverClientConnectionFactory extends AbstractClientConnectionFactory {
|
42 | 44 |
|
| 45 | + private static final long DEFAULT_REFRESH_SHARED_INTERVAL = 0L; |
| 46 | + |
43 | 47 | private final List<AbstractClientConnectionFactory> factories;
|
44 | 48 |
|
| 49 | + private final boolean cachingDelegates; |
| 50 | + |
| 51 | + private long refreshSharedInterval = DEFAULT_REFRESH_SHARED_INTERVAL; |
| 52 | + |
| 53 | + private boolean closeOnRefresh; |
| 54 | + |
| 55 | + private volatile long creationTime; |
| 56 | + |
| 57 | + /** |
| 58 | + * Construct an instance with the provided delegate factories. |
| 59 | + * @param factories the delegates. |
| 60 | + */ |
45 | 61 | public FailoverClientConnectionFactory(List<AbstractClientConnectionFactory> factories) {
|
46 | 62 | super("", 0);
|
47 | 63 | Assert.notEmpty(factories, "At least one factory is required");
|
48 |
| - this.factories = factories; |
| 64 | + this.factories = new ArrayList<AbstractClientConnectionFactory>(factories); |
| 65 | + boolean cachingDelegates = false; |
| 66 | + for (AbstractClientConnectionFactory factory : factories) { |
| 67 | + if (factory instanceof CachingClientConnectionFactory) { |
| 68 | + cachingDelegates = true; |
| 69 | + break; |
| 70 | + } |
| 71 | + } |
| 72 | + this.cachingDelegates = cachingDelegates; |
| 73 | + } |
| 74 | + |
| 75 | + /** |
| 76 | + * When using a shared connection {@link #setSingleUse(boolean) singleUse} is false, |
| 77 | + * specify how long to wait before trying to fail back to start from the beginning of |
| 78 | + * the factory list. Default is 0 for backwards compatibility to always try to get a |
| 79 | + * connection to the primary server. If you don't want to fail back until the current |
| 80 | + * connection is closed, set this to {@link Long#MAX_VALUE}. |
| 81 | + * Cannot be changed when using {@link CachingClientConnectionFactory} delegates. |
| 82 | + * @param refreshSharedInterval the interval in milliseconds. |
| 83 | + * @since 4.3.22 |
| 84 | + * @see #setSingleUse(boolean) |
| 85 | + * @see #setCloseOnRefresh(boolean) |
| 86 | + */ |
| 87 | + public void setRefreshSharedInterval(long refreshSharedInterval) { |
| 88 | + Assert.isTrue(!this.cachingDelegates, |
| 89 | + "'refreshSharedInterval' cannot be changed when using 'CachingClientConnectionFactory` delegates"); |
| 90 | + this.refreshSharedInterval = refreshSharedInterval; |
| 91 | + } |
| 92 | + |
| 93 | + /** |
| 94 | + * When using a shared connection {@link #setSingleUse(boolean) singleUse} is false, |
| 95 | + * set this to true to close the old shared connection after a refresh. If this is |
| 96 | + * false, the connection will remain open, but unused until its connection factory is |
| 97 | + * again used to get a connection. Default is false for backwards compatibility. |
| 98 | + * Cannot be changed when using {@link CachingClientConnectionFactory} delegates. |
| 99 | + * @param closeOnRefresh true to close. |
| 100 | + * @since 4.3.22 |
| 101 | + * @see #setSingleUse(boolean) |
| 102 | + * @see #setRefreshSharedInterval(long) |
| 103 | + */ |
| 104 | + public void setCloseOnRefresh(boolean closeOnRefresh) { |
| 105 | + Assert.isTrue(!this.cachingDelegates, |
| 106 | + "'closeOnRefresh' cannot be changed when using 'CachingClientConnectionFactory` delegates"); |
| 107 | + this.closeOnRefresh = closeOnRefresh; |
49 | 108 | }
|
50 | 109 |
|
51 | 110 | @Override
|
@@ -93,27 +152,42 @@ public void registerSender(TcpSender sender) {
|
93 | 152 |
|
94 | 153 | @Override
|
95 | 154 | protected TcpConnectionSupport obtainConnection() throws Exception {
|
96 |
| - TcpConnectionSupport connection = this.getTheConnection(); |
97 |
| - if (connection != null && connection.isOpen()) { |
98 |
| - ((FailoverTcpConnection) connection).incrementEpoch(); |
99 |
| - return connection; |
| 155 | + FailoverTcpConnection sharedConnection = (FailoverTcpConnection) getTheConnection(); |
| 156 | + boolean shared = !isSingleUse() && !this.cachingDelegates; |
| 157 | + boolean refreshShared = shared |
| 158 | + && sharedConnection != null |
| 159 | + && System.currentTimeMillis() > this.creationTime + this.refreshSharedInterval; |
| 160 | + if (sharedConnection != null && sharedConnection.isOpen() && !refreshShared) { |
| 161 | + sharedConnection.incrementEpoch(); |
| 162 | + return sharedConnection; |
100 | 163 | }
|
101 | 164 | FailoverTcpConnection failoverTcpConnection = new FailoverTcpConnection(this.factories);
|
102 | 165 | if (getListener() != null) {
|
103 | 166 | failoverTcpConnection.registerListener(getListener());
|
104 | 167 | }
|
105 | 168 | failoverTcpConnection.incrementEpoch();
|
| 169 | + if (shared) { |
| 170 | + this.creationTime = System.currentTimeMillis(); |
| 171 | + /* |
| 172 | + * We may have simply wrapped the same connection in a new wrapper; don't close. |
| 173 | + */ |
| 174 | + if (refreshShared && this.closeOnRefresh |
| 175 | + && !sharedConnection.delegate.equals(failoverTcpConnection.delegate) |
| 176 | + && sharedConnection.isOpen()) { |
| 177 | + sharedConnection.close(); |
| 178 | + } |
| 179 | + setTheConnection(failoverTcpConnection); |
| 180 | + } |
106 | 181 | return failoverTcpConnection;
|
107 | 182 | }
|
108 | 183 |
|
109 |
| - |
110 | 184 | @Override
|
111 | 185 | public void start() {
|
112 | 186 | for (AbstractClientConnectionFactory factory : this.factories) {
|
113 | 187 | factory.enableManualListenerRegistration();
|
114 | 188 | factory.start();
|
115 | 189 | }
|
116 |
| - this.setActive(true); |
| 190 | + setActive(true); |
117 | 191 | super.start();
|
118 | 192 | }
|
119 | 193 |
|
@@ -150,16 +224,16 @@ private final class FailoverTcpConnection extends TcpConnectionSupport implement
|
150 | 224 |
|
151 | 225 | private final String connectionId;
|
152 | 226 |
|
| 227 | + private final AtomicLong epoch = new AtomicLong(); |
| 228 | + |
153 | 229 | private volatile Iterator<AbstractClientConnectionFactory> factoryIterator;
|
154 | 230 |
|
155 | 231 | private volatile AbstractClientConnectionFactory currentFactory;
|
156 | 232 |
|
157 |
| - private volatile TcpConnectionSupport delegate; |
| 233 | + volatile TcpConnectionSupport delegate; // NOSONAR visibility |
158 | 234 |
|
159 | 235 | private volatile boolean open = true;
|
160 | 236 |
|
161 |
| - private final AtomicLong epoch = new AtomicLong(); |
162 |
| - |
163 | 237 | private FailoverTcpConnection(List<AbstractClientConnectionFactory> factories) throws Exception {
|
164 | 238 | this.factories = factories;
|
165 | 239 | this.factoryIterator = factories.iterator();
|
|
0 commit comments