/*
* Copyright (C) 2014 Roman Nazarenko <me@jtalk.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.jtalk.networking;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.LocalBean;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.LinkedList;
import javax.ejb.EJB;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Singleton
@Startup
@LocalBean
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class NettyController {
private static final Logger log = LogManager.getLogger(NettyController.class);
private static final String LISTENER_THREADS_PROPERTY = "me.jtalk.networking.ListenerThreadCount";
private static final String WORKER_THREADS_PROPERTY = "me.jtalk.networking.WorkerThreadCount";
private static final String BACKLOG_SIZE_PROPERTY = "me.jtalk.networking.BacklogSize";
private static final String IP_PORT_PROPERTY = "me.jtalk.networking.IpsPorts";
public static final String CHILD_HANDLER_NAME = "NettyChildHandler";
@EJB(name = CHILD_HANDLER_NAME)
NettyChannelInitializerFabric childHandler;
private ServerBootstrap server;
private NioEventLoopGroup listeners;
private NioEventLoopGroup workers;
private void setupServer() throws Exception {
log.info("Starting Netty Server Controller");
int listenerThreads = Integer.valueOf(System.getProperty(LISTENER_THREADS_PROPERTY));
int workerThreads = Integer.valueOf(System.getProperty(WORKER_THREADS_PROPERTY));
int backlog = Integer.valueOf(System.getProperty(BACKLOG_SIZE_PROPERTY));
this.listeners = new NioEventLoopGroup(listenerThreads);
this.workers = new NioEventLoopGroup(workerThreads);
this.server = new ServerBootstrap();
this.server.group(this.listeners, this.workers);
this.server.channel(NioServerSocketChannel.class);
this.server.childHandler(this.childHandler.get());
this.server.option(ChannelOption.SO_BACKLOG, backlog);
this.server.childOption(ChannelOption.SO_KEEPALIVE, true);
}
private void bind() {
log.info("Binding Netty Server Controller");
Iterable<InetSocketAddress> toBind = this.parseAddressPort();
GenericFutureListener<ChannelFuture> listener = new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
InetSocketAddress addr = (InetSocketAddress)future.channel().localAddress();
if (cause == null) {
log.info("Listening socket is bound to address {} successfully", addr.toString());
} else {
log.error("Listening socket for address {} not bound: {}", addr.toString(), cause.getMessage());
}
}
};
for (InetSocketAddress addr : toBind) {
ChannelFuture future = this.server.bind(addr);
future.addListener(listener);
}
}
@PostConstruct
void start() {
try {
this.setupServer();
this.bind();
log.info("Netty Controller is successfully initialized");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
@PreDestroy
void stop() {
Future<?> listenersFuture = this.listeners.shutdownGracefully();
Future<?> workersFuture = this.workers.shutdownGracefully();
listenersFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isSuccess()) {
log.info("Listeners are shut down");
} else {
log.catching(future.cause());
}
}
});
workersFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isSuccess()) {
log.info("Workers are shut down");
} else {
log.catching(future.cause());
}
}
});
}
private LinkedList<InetSocketAddress> parseAddressPort() {
String params = System.getProperty(IP_PORT_PROPERTY);
if (params == null || params.isEmpty())
throw new RuntimeException("No IPs and ports provided for Netty Controller");
String[] pairs = params.split(";");
if (pairs.length <= 0)
throw new RuntimeException("No data provided for Netty Controller in IP-Port property");
LinkedList<InetSocketAddress> result = new LinkedList<>();
for (String s : pairs) {
if (s.isEmpty()) {
continue;
}
String[] data = s.split(":");
if (data.length != 2) {
throw new RuntimeException("Error in IP-Port pairs parsing in Netty Controller: value given is not in format IP:Port");
}
try {
InetAddress addr = Inet4Address.getByName(data[0]);
int port = Integer.valueOf(data[1]);
result.add(new InetSocketAddress(addr, port));
} catch (UnknownHostException | NumberFormatException e) {
log.catching(e);
throw new RuntimeException(e);
}
}
if (result.isEmpty()) {
throw new RuntimeException("IP-Port list provided to Netty Controller consists of empty entries");
}
return result;
}
}