/*
* 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.server;
import com.noctarius.tengi.core.config.Configuration;
import com.noctarius.tengi.core.connection.HandshakeHandler;
import com.noctarius.tengi.core.connection.Transport;
import com.noctarius.tengi.core.impl.FutureUtil;
import com.noctarius.tengi.core.impl.Validate;
import com.noctarius.tengi.core.impl.VersionUtil;
import com.noctarius.tengi.core.listener.ConnectedListener;
import com.noctarius.tengi.server.impl.ConnectionManager;
import com.noctarius.tengi.server.impl.EventManager;
import com.noctarius.tengi.server.spi.transport.Endpoint;
import com.noctarius.tengi.server.spi.transport.ServerChannel;
import com.noctarius.tengi.server.spi.transport.ServerChannelFactory;
import com.noctarius.tengi.server.spi.transport.ServerTransportLayer;
import com.noctarius.tengi.spi.connection.packets.Handshake;
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.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.stream.Collectors;
class ServerImpl
implements Server {
private static final Logger LOGGER = LoggerManager.getLogger(ServerImpl.class);
private final StateMachine<ServerState> serverState = createStateMachine();
private final Map<Endpoint, ServerChannel> channelEngpoints = new HashMap<>();
private final ConnectionManager connectionManager;
private final EventManager eventManager;
private final ExecutorService executor;
private final Configuration configuration;
private final Serializer serializer;
ServerImpl(Configuration configuration)
throws Exception {
Validate.notNull("configuration", configuration);
LOGGER.info("tengi Server [version: %s, build-date: %s] is starting", //
VersionUtil.VERSION, VersionUtil.BUILD_DATE);
this.configuration = configuration;
this.executor = Executors.newFixedThreadPool(16);
this.serializer = createSerializer(configuration);
HandshakeHandler handshakeHandler = createHandshakeHandler(configuration);
this.connectionManager = new ConnectionManager(configuration, createSslContext(), serializer, handshakeHandler);
this.eventManager = new EventManager();
}
@Override
public CompletableFuture<Server> start(ConnectedListener connectedListener) {
Validate.notNull("connectedListener", connectedListener);
return FutureUtil.executeAsync(() -> {
if (serverState.transit(ServerState.Started)) {
bindChannels();
connectionManager.registerConnectedListener(connectedListener);
connectionManager.start();
eventManager.start();
}
return ServerImpl.this;
});
}
@Override
public CompletableFuture<Server> stop() {
return FutureUtil.executeAsync(() -> {
if (serverState.transit(ServerState.Shutdown)) {
for (ServerChannel channel : channelEngpoints.values()) {
channel.shutdown();
}
executor.shutdown();
connectionManager.stop();
eventManager.stop();
serverState.transit(ServerState.Stopped);
}
return ServerImpl.this;
});
}
private void bindChannels()
throws Throwable {
Map<Endpoint, Set<Transport>> transportLayers = collectTransportLayers(configuration);
for (Map.Entry<Endpoint, Set<Transport>> entry : transportLayers.entrySet()) {
Endpoint endpoint = entry.getKey();
Set<Transport> transports = entry.getValue();
ServerChannel channel = createServerChannel(endpoint, transports);
channel.start();
}
}
// TODO: Remove netty dependency
private SslContext createSslContext()
throws Exception {
SelfSignedCertificate certificate = new SelfSignedCertificate("localhost");
return SslContextBuilder.forServer(certificate.certificate(), certificate.privateKey()).build();
}
private HandshakeHandler createHandshakeHandler(Configuration configuration) {
HandshakeHandler handshakeHandler = configuration.getHandshakeHandler();
if (handshakeHandler == null) {
handshakeHandler = (connectionId, handshake) -> new Handshake();
}
return handshakeHandler;
}
private Map<Endpoint, Set<Transport>> collectTransportLayers(Configuration configuration) {
Function<Transport, Endpoint> coordinater = (transport) -> {
int port = configuration.getTransportPort(transport);
return new Endpoint(port, (ServerTransportLayer) transport.getTransportLayer());
};
Map<Endpoint, Set<Transport>> transports = configuration.getTransports().stream().filter(t -> t
.getTransportLayer() instanceof ServerTransportLayer).collect(
Collectors.groupingBy(coordinater, IdentityHashMap::new, Collectors.mapping(t -> t, Collectors.toSet())));
// TODO: Check multiple socket types, same port number
return transports;
}
private ServerChannel createServerChannel(Endpoint endpoint, Set<Transport> transports)
throws Throwable {
ServerChannelFactory channelFactory = endpoint.getTransportLayer().serverChannelFactory();
ServerChannel channel = channelFactory.newServerChannel(endpoint, executor, connectionManager, serializer);
channelEngpoints.put(endpoint, channel);
return channel;
}
private Serializer createSerializer(Configuration configuration) {
return Serializer.create(configuration.getMarshallers());
}
private StateMachine<ServerState> createStateMachine() {
StateMachine.Builder<ServerState> builder = StateMachine.newBuilder();
builder.addTransition(ServerState.Prepared, ServerState.Started);
builder.addTransition(ServerState.Started, ServerState.Shutdown);
builder.addTransition(ServerState.Shutdown, ServerState.Stopped);
return builder.build(ServerState.Prepared, false);
}
}