io.bitsensor.plugins.shaded.org.springframework.messaging.tcp.reactor.Reactor2TcpClient Maven / Gradle / Ivy
/*
* Copyright 2002-2016 the original author or authors.
*
* 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 io.bitsensor.plugins.shaded.org.springframework.messaging.tcp.reactor;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import org.reactivestreams.Publisher;
import reactor.Environment;
import reactor.core.config.ConfigurationReader;
import reactor.core.config.DispatcherConfiguration;
import reactor.core.config.ReactorConfiguration;
import reactor.core.support.NamedDaemonThreadFactory;
import reactor.fn.Consumer;
import reactor.fn.Function;
import reactor.fn.tuple.Tuple;
import reactor.fn.tuple.Tuple2;
import reactor.io.buffer.Buffer;
import reactor.io.codec.Codec;
import reactor.io.net.ChannelStream;
import reactor.io.net.NetStreams;
import reactor.io.net.NetStreams.TcpClientFactory;
import reactor.io.net.ReactorChannelHandler;
import reactor.io.net.Reconnect;
import reactor.io.net.Spec.TcpClientSpec;
import reactor.io.net.config.ClientSocketOptions;
import reactor.io.net.impl.netty.NettyClientSocketOptions;
import reactor.io.net.impl.netty.tcp.NettyTcpClient;
import reactor.io.net.tcp.TcpClient;
import reactor.rx.Promise;
import reactor.rx.Promises;
import reactor.rx.Stream;
import reactor.rx.Streams;
import reactor.rx.action.Signal;
import io.bitsensor.plugins.shaded.org.springframework.messaging.Message;
import io.bitsensor.plugins.shaded.org.springframework.messaging.tcp.ReconnectStrategy;
import io.bitsensor.plugins.shaded.org.springframework.messaging.tcp.TcpConnectionHandler;
import io.bitsensor.plugins.shaded.org.springframework.messaging.tcp.TcpOperations;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.org.springframework.util.ReflectionUtils;
import io.bitsensor.plugins.shaded.org.springframework.util.concurrent.ListenableFuture;
/**
* An implementation of {@link io.bitsensor.plugins.shaded.org.springframework.messaging.tcp.TcpOperations}
* based on the TCP client support of the Reactor project.
*
* This implementation wraps N (Reactor) clients for N {@link #connect} calls,
* i.e. a separate (Reactor) client instance for each connection.
*
* @author Rossen Stoyanchev
* @author Stephane Maldini
* @since 4.2
*/
public class Reactor2TcpClient
implements TcpOperations
{
@SuppressWarnings("rawtypes")
public static final Class REACTOR_TCP_CLIENT_TYPE = NettyTcpClient.class;
private static final Method eventLoopGroupMethod = initEventLoopGroupMethod();
private final EventLoopGroup eventLoopGroup;
private final Environment environment;
private final TcpClientFactory, Message> tcpClientSpecFactory;
private final List, Message>> tcpClients =
new ArrayList, Message>>();
private boolean stopping;
/**
* A constructor that creates a {@link TcpClientSpec TcpClientSpec} factory
* with a default {@link reactor.core.dispatch.SynchronousDispatcher}, i.e.
* relying on Netty threads. The number of Netty threads can be tweaked with
* the {@code reactor.tcp.ioThreadCount} System property. The network I/O
* threads will be shared amongst the active clients.
*
Also see the constructor accepting a ready Reactor
* {@link TcpClientSpec} {@link Function} factory.
* @param host the host to connect to
* @param port the port to connect to
* @param codec the codec to use for encoding and decoding the TCP stream
*/
public Reactor2TcpClient(final String host, final int port, final Codec, Message> codec) {
// Reactor 2.0.5 requires NioEventLoopGroup vs 2.0.6+ requires EventLoopGroup
final NioEventLoopGroup nioEventLoopGroup = initEventLoopGroup();
this.eventLoopGroup = nioEventLoopGroup;
this.environment = new Environment(new SynchronousDispatcherConfigReader());
this.tcpClientSpecFactory = new TcpClientFactory, Message>() {
@Override
public TcpClientSpec, Message> apply(TcpClientSpec, Message> spec) {
return spec
.env(environment)
.codec(codec)
.connect(host, port)
.options(createClientSocketOptions());
}
private ClientSocketOptions createClientSocketOptions() {
return (ClientSocketOptions) ReflectionUtils.invokeMethod(eventLoopGroupMethod,
new NettyClientSocketOptions(), nioEventLoopGroup);
}
};
}
/**
* A constructor with a pre-configured {@link TcpClientSpec} {@link Function}
* factory. This might be used to add SSL or specific network parameters to
* the generated client configuration.
*
NOTE: if the client is configured with a thread-creating
* dispatcher, you are responsible for cleaning them, e.g. using
* {@link reactor.core.Dispatcher#shutdown}.
* @param tcpClientSpecFactory the TcpClientSpec {@link Function} to use for each client creation
*/
public Reactor2TcpClient(TcpClientFactory, Message> tcpClientSpecFactory) {
Assert.notNull(tcpClientSpecFactory, "'tcpClientClientFactory' must not be null");
this.tcpClientSpecFactory = tcpClientSpecFactory;
this.eventLoopGroup = null;
this.environment = null;
}
public static NioEventLoopGroup initEventLoopGroup() {
int ioThreadCount;
try {
ioThreadCount = Integer.parseInt(System.getProperty("reactor.tcp.ioThreadCount"));
}
catch (Throwable ex) {
ioThreadCount = -1;
}
if (ioThreadCount <= 0) {
ioThreadCount = Runtime.getRuntime().availableProcessors();
}
return new NioEventLoopGroup(ioThreadCount, new NamedDaemonThreadFactory("reactor-tcp-io"));
}
@Override
public ListenableFuture connect(final TcpConnectionHandler connectionHandler) {
Assert.notNull(connectionHandler, "TcpConnectionHandler must not be null");
final TcpClient, Message> tcpClient;
final Runnable cleanupTask;
synchronized (this.tcpClients) {
if (this.stopping) {
IllegalStateException ex = new IllegalStateException("Shutting down.");
connectionHandler.afterConnectFailure(ex);
return new PassThroughPromiseToListenableFutureAdapter(Promises.error(ex));
}
tcpClient = NetStreams.tcpClient(REACTOR_TCP_CLIENT_TYPE, this.tcpClientSpecFactory);
this.tcpClients.add(tcpClient);
cleanupTask = new Runnable() {
@Override
public void run() {
synchronized (tcpClients) {
tcpClients.remove(tcpClient);
}
}
};
}
Promise promise = tcpClient.start(
new MessageChannelStreamHandler(connectionHandler, cleanupTask));
return new PassThroughPromiseToListenableFutureAdapter(
promise.onError(new Consumer() {
@Override
public void accept(Throwable ex) {
cleanupTask.run();
connectionHandler.afterConnectFailure(ex);
}
})
);
}
@Override
public ListenableFuture connect(TcpConnectionHandler connectionHandler, ReconnectStrategy strategy) {
Assert.notNull(connectionHandler, "TcpConnectionHandler must not be null");
Assert.notNull(strategy, "ReconnectStrategy must not be null");
final TcpClient, Message> tcpClient;
Runnable cleanupTask;
synchronized (this.tcpClients) {
if (this.stopping) {
IllegalStateException ex = new IllegalStateException("Shutting down.");
connectionHandler.afterConnectFailure(ex);
return new PassThroughPromiseToListenableFutureAdapter(Promises.error(ex));
}
tcpClient = NetStreams.tcpClient(REACTOR_TCP_CLIENT_TYPE, this.tcpClientSpecFactory);
this.tcpClients.add(tcpClient);
cleanupTask = new Runnable() {
@Override
public void run() {
synchronized (tcpClients) {
tcpClients.remove(tcpClient);
}
}
};
}
Stream> stream = tcpClient.start(
new MessageChannelStreamHandler(connectionHandler, cleanupTask),
new ReactorReconnectAdapter(strategy));
return new PassThroughPromiseToListenableFutureAdapter(stream.next().after());
}
@Override
public ListenableFuture shutdown() {
synchronized (this.tcpClients) {
this.stopping = true;
}
Promise promise = Streams.from(this.tcpClients)
.flatMap(new Function, Message>, Promise>() {
@Override
public Promise apply(final TcpClient, Message> client) {
return client.shutdown().onComplete(new Consumer>() {
@Override
public void accept(Promise voidPromise) {
tcpClients.remove(client);
}
});
}
})
.next();
if (this.eventLoopGroup != null) {
final Promise eventLoopPromise = Promises.prepare();
promise.onComplete(new Consumer>() {
@Override
public void accept(Promise voidPromise) {
eventLoopGroup.shutdownGracefully().addListener(new FutureListener