/* * Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and * contributors. All rights reserved. * * 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 com.noctarius.tengi.client; import com.noctarius.tengi.client.impl.ConnectorFactory; import com.noctarius.tengi.core.config.Configuration; import com.noctarius.tengi.core.connection.Connection; import com.noctarius.tengi.core.connection.Transport; import com.noctarius.tengi.core.exception.IllegalTransportException; import com.noctarius.tengi.core.impl.FutureUtil; import com.noctarius.tengi.core.impl.Validate; import com.noctarius.tengi.core.listener.ClosedListener; import com.noctarius.tengi.core.listener.ConnectedListener; import com.noctarius.tengi.spi.logging.Logger; import com.noctarius.tengi.spi.logging.LoggerManager; import com.noctarius.tengi.spi.serialization.Serializer; import com.noctarius.tengi.spi.statemachine.StateMachine; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.util.concurrent.DefaultThreadFactory; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; class ClientImpl implements Client, ClosedListener { private static final Logger LOGGER = LoggerManager.getLogger(ClientImpl.class); private final StateMachine<ClientState> clientState = createStateMachine(); private final List<Connection> connections = new CopyOnWriteArrayList<>(); private final EventLoopGroup clientGroup; private final Configuration configuration; private final Serializer serializer; ClientImpl(Configuration configuration) { Validate.notNull("configuration", configuration); // Validate all configured transports are also ConnectorFactory checkTransports(configuration.getTransports()); this.clientGroup = new NioEventLoopGroup(5, new DefaultThreadFactory("channel-client-")); this.serializer = createSerializer(configuration); this.configuration = configuration; } @Override public CompletableFuture<Connection> connect(String host, ConnectedListener connectedListener) throws UnknownHostException { Validate.notNull("host", host); Validate.notNull("connectedListener", connectedListener); if (clientState.currentState() == ClientState.Stopped) { throw new IllegalStateException("Client is shutdown"); } InetAddress address = InetAddress.getByName(host); return connect(address, connectedListener); } @Override public CompletableFuture<Connection> connect(InetAddress address, ConnectedListener connectedListener) { Validate.notNull("address", address); Validate.notNull("connectedListener", connectedListener); if (clientState.currentState() == ClientState.Stopped) { throw new IllegalStateException("Client is shutdown"); } LOGGER.info("tengi client is connecting, transport priority: %s", configuration.getTransports()); ConnectorContext connectorContext = new ConnectorContext(configuration, serializer, clientGroup); CompletableFuture<Connection> connectFuture = connectorContext.connect(address); return connectFuture.thenApply((connection) -> { registerConnection(connection); if (clientState.transit(ClientState.Started)) { connectedListener.onConnection(connection); } return connection; }); } @Override public CompletableFuture<Client> stop() { return FutureUtil.executeAsync(() -> { if (clientState.transit(ClientState.Shutdown)) { if (!clientGroup.isTerminated()) { clientGroup.shutdownGracefully(); } for (Connection connection : connections) { connection.close(); } clientState.transit(ClientState.Stopped); } return ClientImpl.this; }); } @Override public void onClose(Connection connection) { connections.remove(connection); } private void registerConnection(Connection connection) { connections.add(connection); connection.addConnectionListener(this); } private Serializer createSerializer(Configuration configuration) { return Serializer.create(configuration.getMarshallers()); } private StateMachine<ClientState> createStateMachine() { StateMachine.Builder<ClientState> builder = StateMachine.newBuilder(); builder.addTransition(ClientState.Prepared, ClientState.Started); builder.addTransition(ClientState.Started, ClientState.Shutdown); builder.addTransition(ClientState.Shutdown, ClientState.Stopped); return builder.build(ClientState.Prepared, false); } private void checkTransports(List<Transport> transports) { for (Transport transport : transports) { if (!(transport instanceof ConnectorFactory)) { throw new IllegalTransportException("Illegal Transport configured: " + transport); } } } }