All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.bitsensor.plugins.shaded.org.springframework.messaging.tcp.reactor.Reactor2TcpClient Maven / Gradle / Ivy

The newest version!
/*
 * 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() { @Override public void operationComplete(Future future) throws Exception { if (future.isSuccess()) { eventLoopPromise.onComplete(); } else { eventLoopPromise.onError(future.cause()); } } }); } }); promise = eventLoopPromise; } if (this.environment != null) { promise.onComplete(new Consumer>() { @Override public void accept(Promise voidPromise) { environment.shutdown(); } }); } return new PassThroughPromiseToListenableFutureAdapter(promise); } private static Method initEventLoopGroupMethod() { for (Method method : NettyClientSocketOptions.class.getMethods()) { if (method.getName().equals("eventLoopGroup") && method.getParameterTypes().length == 1) { return method; } } throw new IllegalStateException("No compatible Reactor version found"); } private static class SynchronousDispatcherConfigReader implements ConfigurationReader { @Override public ReactorConfiguration read() { return new ReactorConfiguration( Collections.emptyList(), "sync", new Properties()); } } private static class MessageChannelStreamHandler

implements ReactorChannelHandler, Message

, ChannelStream, Message

>> { private final TcpConnectionHandler

connectionHandler; private final Runnable cleanupTask; public MessageChannelStreamHandler(TcpConnectionHandler

connectionHandler, Runnable cleanupTask) { this.connectionHandler = connectionHandler; this.cleanupTask = cleanupTask; } @Override public Publisher apply(ChannelStream, Message

> channelStream) { Promise closePromise = Promises.prepare(); this.connectionHandler.afterConnected(new Reactor2TcpConnection

(channelStream, closePromise)); channelStream .finallyDo(new Consumer>>() { @Override public void accept(Signal> signal) { cleanupTask.run(); if (signal.isOnError()) { connectionHandler.handleFailure(signal.getThrowable()); } else if (signal.isOnComplete()) { connectionHandler.afterConnectionClosed(); } } }) .consume(new Consumer>() { @Override public void accept(Message

message) { connectionHandler.handleMessage(message); } }); return closePromise; } } private static class ReactorReconnectAdapter implements Reconnect { private final ReconnectStrategy strategy; public ReactorReconnectAdapter(ReconnectStrategy strategy) { this.strategy = strategy; } @Override public Tuple2 reconnect(InetSocketAddress address, int attempt) { return Tuple.of(address, this.strategy.getTimeToNextAttempt(attempt)); } } }