/*
* Copyright 2016 higherfrequencytrading.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.openhft.chronicle.network.connection;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.network.TCPRegistry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* Provides support for the client to failover TCP connections to different servers, if the primary
* connection can not be establish, after retrying up to a timeout, see {@link
* SocketAddressSupplier#timeoutMS()} the other connections will be attempted. The order of these
* connections are determined by the order of the connectURIs
*
* @author Rob Austin.
*/
public class SocketAddressSupplier implements Supplier<SocketAddress> {
private static final Logger LOG = LoggerFactory.getLogger(SocketAddressSupplier.class);
@NotNull
private final String name;
private final List<RemoteAddressSupplier> remoteAddresses = new ArrayList<>();
private final long failoverTimeout = Integer.getInteger("tcp.failover.time", 2_000);
@Nullable
private RemoteAddressSupplier current;
private int addressCount = 0;
/**
* @param connectURIs the socket connections defined in order with the primary first
* @param name the name of this service
*/
public SocketAddressSupplier(@NotNull final String[] connectURIs, @NotNull final String name) {
this.name = name;
for (@NotNull String connectURI : connectURIs) {
this.remoteAddresses.add(new RemoteAddressSupplier(connectURI));
}
assert !this.remoteAddresses.isEmpty();
}
/**
* use this method if you only with to connect to a single server
*
* @param connectURI the uri of the server
* @return a SocketAddressSupplier containing the UIR you provide
*/
@NotNull
public static SocketAddressSupplier uri(String connectURI) {
return new SocketAddressSupplier(new String[]{connectURI}, "");
}
@NotNull
public List<RemoteAddressSupplier> all() {
return remoteAddresses;
}
@NotNull
public String name() {
return name;
}
public void failoverToNextAddress() {
if (LOG.isDebugEnabled())
Jvm.debug().on(getClass(), "failing over to next address");
next();
}
/**
* reset back to the primary
*/
public void resetToPrimary() {
addressCount = 0;
current = remoteAddresses.get(addressCount);
}
private void next() {
addressCount = (addressCount + 1) % remoteAddresses.size();
current = remoteAddresses.get(addressCount);
}
public int size() {
return remoteAddresses.size();
}
/**
* @return index ( primary has index of ZERO )
*/
public int index() {
return addressCount;
}
public long timeoutMS() {
return failoverTimeout;
}
@Nullable
@Override
public InetSocketAddress get() {
@Nullable final RemoteAddressSupplier current = this.current;
if (current == null)
return null;
return current.get();
}
@Nullable
public String getDescription() {
@Nullable final RemoteAddressSupplier current = this.current;
if (current == null)
return null;
return current.toString();
}
@Override
@NotNull
public String toString() {
return log(this.current);
}
public String remoteAddresses() {
List<String> result = new ArrayList<>();
remoteAddresses.forEach(r -> result.add(log(r)));
return result.toString();
}
@NotNull
public String log(RemoteAddressSupplier current) {
if (current == null)
return "(none)";
final SocketAddress socketAddress = current.get();
if (socketAddress == null)
return "(none)";
return socketAddress.toString().replaceAll("0:0:0:0:0:0:0:0", "localhost") + " - " +
current.toString();
}
private class RemoteAddressSupplier implements Supplier<SocketAddress> {
private final InetSocketAddress remoteAddress;
@NotNull
private final String description;
public RemoteAddressSupplier(@NotNull String description) {
this.description = description;
remoteAddress = TCPRegistry.lookup(description);
}
@Override
public InetSocketAddress get() {
return remoteAddress;
}
@NotNull
@Override
public String toString() {
return description;
}
}
}