|
1 | 1 | /*
|
2 |
| - * Copyright 2002-2019 the original author or authors. |
| 2 | + * Copyright 2002-2020 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
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<>(factories); |
| 65 | + this.cachingDelegates = factories.stream() |
| 66 | + .anyMatch(factory -> factory instanceof CachingClientConnectionFactory); |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * When using a shared connection {@link #setSingleUse(boolean) singleUse} is false, |
| 71 | + * specify how long to wait before trying to fail back to start from the beginning of |
| 72 | + * the factory list. Default is 0 for backwards compatibility to always try to get a |
| 73 | + * connection to the primary server. If you don't want to fail back until the current |
| 74 | + * connection is closed, set this to {@link Long#MAX_VALUE}. |
| 75 | + * Cannot be changed when using {@link CachingClientConnectionFactory} delegates. |
| 76 | + * @param refreshSharedInterval the interval in milliseconds. |
| 77 | + * @since 4.3.22 |
| 78 | + * @see #setSingleUse(boolean) |
| 79 | + * @see #setCloseOnRefresh(boolean) |
| 80 | + */ |
| 81 | + public void setRefreshSharedInterval(long refreshSharedInterval) { |
| 82 | + Assert.isTrue(!this.cachingDelegates, |
| 83 | + "'refreshSharedInterval' cannot be changed when using 'CachingClientConnectionFactory` delegates"); |
| 84 | + this.refreshSharedInterval = refreshSharedInterval; |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * When using a shared connection {@link #setSingleUse(boolean) singleUse} is false, |
| 89 | + * set this to true to close the old shared connection after a refresh. If this is |
| 90 | + * false, the connection will remain open, but unused until its connection factory is |
| 91 | + * again used to get a connection. Default is false for backwards compatibility. |
| 92 | + * Cannot be changed when using {@link CachingClientConnectionFactory} delegates. |
| 93 | + * @param closeOnRefresh true to close. |
| 94 | + * @since 4.3.22 |
| 95 | + * @see #setSingleUse(boolean) |
| 96 | + * @see #setRefreshSharedInterval(long) |
| 97 | + */ |
| 98 | + public void setCloseOnRefresh(boolean closeOnRefresh) { |
| 99 | + Assert.isTrue(!this.cachingDelegates, |
| 100 | + "'closeOnRefresh' cannot be changed when using 'CachingClientConnectionFactory` delegates"); |
| 101 | + this.closeOnRefresh = closeOnRefresh; |
49 | 102 | }
|
50 | 103 |
|
51 | 104 | @Override
|
@@ -93,27 +146,42 @@ public void registerSender(TcpSender sender) {
|
93 | 146 |
|
94 | 147 | @Override
|
95 | 148 | protected TcpConnectionSupport obtainConnection() throws InterruptedException {
|
96 |
| - TcpConnectionSupport connection = this.getTheConnection(); |
97 |
| - if (connection != null && connection.isOpen()) { |
98 |
| - ((FailoverTcpConnection) connection).incrementEpoch(); |
99 |
| - return connection; |
| 149 | + FailoverTcpConnection sharedConnection = (FailoverTcpConnection) getTheConnection(); |
| 150 | + boolean shared = !isSingleUse() && !this.cachingDelegates; |
| 151 | + boolean refreshShared = shared |
| 152 | + && sharedConnection != null |
| 153 | + && System.currentTimeMillis() > this.creationTime + this.refreshSharedInterval; |
| 154 | + if (sharedConnection != null && sharedConnection.isOpen() && !refreshShared) { |
| 155 | + sharedConnection.incrementEpoch(); |
| 156 | + return sharedConnection; |
100 | 157 | }
|
101 | 158 | FailoverTcpConnection failoverTcpConnection = new FailoverTcpConnection(this.factories);
|
102 | 159 | if (getListener() != null) {
|
103 | 160 | failoverTcpConnection.registerListener(getListener());
|
104 | 161 | }
|
105 | 162 | failoverTcpConnection.incrementEpoch();
|
| 163 | + this.creationTime = System.currentTimeMillis(); |
| 164 | + if (shared) { |
| 165 | + /* |
| 166 | + * We may have simply wrapped the same connection in a new wrapper; don't close. |
| 167 | + */ |
| 168 | + if (refreshShared && this.closeOnRefresh |
| 169 | + && !sharedConnection.delegate.equals(failoverTcpConnection.delegate) |
| 170 | + && sharedConnection.isOpen()) { |
| 171 | + sharedConnection.close(); |
| 172 | + } |
| 173 | + setTheConnection(failoverTcpConnection); |
| 174 | + } |
106 | 175 | return failoverTcpConnection;
|
107 | 176 | }
|
108 | 177 |
|
109 |
| - |
110 | 178 | @Override
|
111 | 179 | public void start() {
|
112 | 180 | for (AbstractClientConnectionFactory factory : this.factories) {
|
113 | 181 | factory.enableManualListenerRegistration();
|
114 | 182 | factory.start();
|
115 | 183 | }
|
116 |
| - this.setActive(true); |
| 184 | + setActive(true); |
117 | 185 | super.start();
|
118 | 186 | }
|
119 | 187 |
|
@@ -150,16 +218,16 @@ private final class FailoverTcpConnection extends TcpConnectionSupport implement
|
150 | 218 |
|
151 | 219 | private final String connectionId;
|
152 | 220 |
|
| 221 | + private final AtomicLong epoch = new AtomicLong(); |
| 222 | + |
153 | 223 | private volatile Iterator<AbstractClientConnectionFactory> factoryIterator;
|
154 | 224 |
|
155 | 225 | private volatile AbstractClientConnectionFactory currentFactory;
|
156 | 226 |
|
157 |
| - private volatile TcpConnectionSupport delegate; |
| 227 | + volatile TcpConnectionSupport delegate; // NOSONAR visibility |
158 | 228 |
|
159 | 229 | private volatile boolean open = true;
|
160 | 230 |
|
161 |
| - private final AtomicLong epoch = new AtomicLong(); |
162 |
| - |
163 | 231 | private FailoverTcpConnection(List<AbstractClientConnectionFactory> factories) throws InterruptedException {
|
164 | 232 | this.connectionFactories = factories;
|
165 | 233 | this.factoryIterator = factories.iterator();
|
|
0 commit comments