org.springframework.messaging.tcp.reactor.Reactor2TcpClient.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.messaging.tcp.reactor.Reactor2TcpClient.java

Source

/*
 * Copyright 2002-2015 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 org.springframework.messaging.tcp.reactor;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

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.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 org.springframework.messaging.Message;
import org.springframework.messaging.tcp.ReconnectStrategy;
import org.springframework.messaging.tcp.TcpConnectionHandler;
import org.springframework.messaging.tcp.TcpOperations;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;

/**
 * An implementation of {@link org.springframework.messaging.tcp.TcpOperations}
 * based on the TCP client support of the Reactor project.
 *
 * <p>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<P> implements TcpOperations<P> {

    @SuppressWarnings("rawtypes")
    public static final Class<NettyTcpClient> REACTOR_TCP_CLIENT_TYPE = NettyTcpClient.class;

    private final NioEventLoopGroup eventLoopGroup;

    private final TcpClientFactory<Message<P>, Message<P>> tcpClientSpecFactory;

    private final List<TcpClient<Message<P>, Message<P>>> tcpClients = new ArrayList<TcpClient<Message<P>, Message<P>>>();

    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.
     * <p>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<Buffer, Message<P>, Message<P>> codec) {
        this.eventLoopGroup = initEventLoopGroup();

        this.tcpClientSpecFactory = new TcpClientFactory<Message<P>, Message<P>>() {
            @Override
            public TcpClientSpec<Message<P>, Message<P>> apply(TcpClientSpec<Message<P>, Message<P>> spec) {
                return spec.env(new Environment(new SynchronousDispatcherConfigReader())).codec(codec)
                        .connect(host, port).options(new NettyClientSocketOptions().eventLoopGroup(eventLoopGroup));
            }
        };
    }

    /**
     * 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.
     * <p><strong>NOTE:</strong> 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<P>, Message<P>> tcpClientSpecFactory) {
        Assert.notNull(tcpClientSpecFactory, "'tcpClientClientFactory' must not be null");
        this.tcpClientSpecFactory = tcpClientSpecFactory;
        this.eventLoopGroup = null;
    }

    private static NioEventLoopGroup initEventLoopGroup() {
        int ioThreadCount;
        try {
            ioThreadCount = Integer.parseInt(System.getProperty("reactor.tcp.ioThreadCount"));
        } catch (Exception ex) {
            ioThreadCount = -1;
        }
        if (ioThreadCount <= 0l) {
            ioThreadCount = Runtime.getRuntime().availableProcessors();
        }

        return new NioEventLoopGroup(ioThreadCount, new NamedDaemonThreadFactory("reactor-tcp-io"));
    }

    @Override
    public ListenableFuture<Void> connect(final TcpConnectionHandler<P> connectionHandler) {
        Assert.notNull(connectionHandler, "TcpConnectionHandler must not be null");

        TcpClient<Message<P>, Message<P>> tcpClient;
        synchronized (this.tcpClients) {
            if (this.stopping) {
                IllegalStateException ex = new IllegalStateException("Shutting down.");
                connectionHandler.afterConnectFailure(ex);
                return new PassThroughPromiseToListenableFutureAdapter<Void>(Promises.<Void>error(ex));
            }
            tcpClient = NetStreams.tcpClient(REACTOR_TCP_CLIENT_TYPE, this.tcpClientSpecFactory);
            this.tcpClients.add(tcpClient);
        }

        Promise<Void> promise = tcpClient.start(new MessageChannelStreamHandler<P>(connectionHandler));

        return new PassThroughPromiseToListenableFutureAdapter<Void>(promise.onError(new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) {
                connectionHandler.afterConnectFailure(throwable);
            }
        }));
    }

    @Override
    public ListenableFuture<Void> connect(TcpConnectionHandler<P> connectionHandler, ReconnectStrategy strategy) {
        Assert.notNull(connectionHandler, "TcpConnectionHandler must not be null");
        Assert.notNull(strategy, "ReconnectStrategy must not be null");

        TcpClient<Message<P>, Message<P>> tcpClient;
        synchronized (this.tcpClients) {
            if (this.stopping) {
                IllegalStateException ex = new IllegalStateException("Shutting down.");
                connectionHandler.afterConnectFailure(ex);
                return new PassThroughPromiseToListenableFutureAdapter<Void>(Promises.<Void>error(ex));
            }
            tcpClient = NetStreams.tcpClient(REACTOR_TCP_CLIENT_TYPE, this.tcpClientSpecFactory);
            this.tcpClients.add(tcpClient);
        }

        Stream<Tuple2<InetSocketAddress, Integer>> stream = tcpClient.start(
                new MessageChannelStreamHandler<P>(connectionHandler), new ReactorReconnectAdapter(strategy));

        return new PassThroughPromiseToListenableFutureAdapter<Void>(stream.next().after());
    }

    @Override
    public ListenableFuture<Void> shutdown() {
        synchronized (this.tcpClients) {
            this.stopping = true;
        }

        Promise<Void> promise = Streams.from(this.tcpClients)
                .flatMap(new Function<TcpClient<Message<P>, Message<P>>, Promise<Void>>() {
                    @Override
                    public Promise<Void> apply(final TcpClient<Message<P>, Message<P>> client) {
                        return client.shutdown().onComplete(new Consumer<Promise<Void>>() {
                            @Override
                            public void accept(Promise<Void> voidPromise) {
                                tcpClients.remove(client);
                            }
                        });
                    }
                }).next();

        if (this.eventLoopGroup != null) {
            final Promise<Void> eventLoopPromise = Promises.prepare();
            promise.onComplete(new Consumer<Promise<Void>>() {
                @Override
                public void accept(Promise<Void> voidPromise) {
                    eventLoopGroup.shutdownGracefully().addListener(new FutureListener<Object>() {
                        @Override
                        public void operationComplete(Future<Object> future) throws Exception {
                            if (future.isSuccess()) {
                                eventLoopPromise.onComplete();
                            } else {
                                eventLoopPromise.onError(future.cause());
                            }
                        }
                    });
                }
            });
            promise = eventLoopPromise;
        }
        return new PassThroughPromiseToListenableFutureAdapter<Void>(promise);
    }

    private static class SynchronousDispatcherConfigReader implements ConfigurationReader {

        @Override
        public ReactorConfiguration read() {
            return new ReactorConfiguration(Arrays.<DispatcherConfiguration>asList(), "sync", new Properties());
        }
    }

    private static class MessageChannelStreamHandler<P>
            implements ReactorChannelHandler<Message<P>, Message<P>, ChannelStream<Message<P>, Message<P>>> {

        private final TcpConnectionHandler<P> connectionHandler;

        public MessageChannelStreamHandler(TcpConnectionHandler<P> connectionHandler) {
            this.connectionHandler = connectionHandler;
        }

        @Override
        public Publisher<Void> apply(ChannelStream<Message<P>, Message<P>> channelStream) {
            Promise<Void> closePromise = Promises.prepare();
            this.connectionHandler.afterConnected(new Reactor2TcpConnection<P>(channelStream, closePromise));
            channelStream.finallyDo(new Consumer<Signal<Message<P>>>() {
                @Override
                public void accept(Signal<Message<P>> signal) {
                    if (signal.isOnError()) {
                        connectionHandler.handleFailure(signal.getThrowable());
                    } else if (signal.isOnComplete()) {
                        connectionHandler.afterConnectionClosed();
                    }
                }
            }).consume(new Consumer<Message<P>>() {
                @Override
                public void accept(Message<P> 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<InetSocketAddress, Long> reconnect(InetSocketAddress address, int attempt) {
            return Tuple.of(address, this.strategy.getTimeToNextAttempt(attempt));
        }
    }

}