/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.ctp.net;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import divconq.net.ssl.SslHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import divconq.bus.net.SocketInfo;
import divconq.bus.net.SslContextFactory;
import divconq.ctp.CtpAdapter;
import divconq.ctp.s.CtpsHandler;
import divconq.hub.Hub;
import divconq.hub.ISystemWork;
import divconq.hub.SysReporter;
import divconq.lang.op.OperationContext;
import divconq.log.Logger;
import divconq.net.acl.AclFilter;
import divconq.util.StringUtil;
import divconq.xml.XElement;
public class CtpServices {
protected AclFilter acl = new AclFilter(); // TODO support
protected Lock connectLock = new ReentrantLock();
// desired listeners
protected List<SocketInfo> listeners = new CopyOnWriteArrayList<>();
protected final ConcurrentHashMap<SocketInfo, Channel> activelisteners = new ConcurrentHashMap<>();
/*
* set localHub before calling this
*/
public void init(XElement config) {
int conninterval = 5;
if (config != null) {
for(XElement node : config.selectAll("Acl"))
this.acl.loadConfig(node);
for(XElement node : config.selectAll("Listener")) {
SocketInfo si = new SocketInfo();
si.loadConfig(node);
this.addListener(si);
}
if (config.hasAttribute("ConnectingInterval"))
conninterval = (int) StringUtil.parseInt(config.getAttribute("ConnectingInterval"), conninterval);
}
int connint = conninterval;
ISystemWork busconnector = new ISystemWork() {
@Override
public void run(SysReporter reporter) {
reporter.setStatus("dcCtp Services Connect");
if (!Hub.instance.isStopping())
CtpServices.this.connect();
reporter.setStatus("After dcCtp Services Connect");
}
@Override
public int period() {
return connint;
}
};
Hub.instance.getClock().addSlowSystemWorker(busconnector);
}
public void addListener(SocketInfo info) {
if (info == null)
return;
this.listeners.add(info);
}
public void removeListener(SocketInfo info) {
this.listeners.remove(info);
}
public void connect() {
// never try to connect until init has run
if (Hub.instance.isStopping())
return;
// if connect method is already running then skip - it will try again later
if (!this.connectLock.tryLock())
return;
try {
// ==========================================================================
// Add server binding when missing
// ==========================================================================
for (SocketInfo info : this.listeners) {
// only if not currently bound
if (this.activelisteners.containsKey(info))
continue;
// -------------------------------------------------
// stream port
// -------------------------------------------------
ServerBootstrap b = new ServerBootstrap()
.group(Hub.instance.getEventLoopGroup())
.channel(NioServerSocketChannel.class)
.option(ChannelOption.ALLOCATOR, Hub.instance.getBufferAllocator())
//.option(ChannelOption.SO_BACKLOG, 125) // this is probably not needed but serves as note to research
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (info.isUseSsl())
pipeline.addLast("ssl", new SslHandler(SslContextFactory.getServerEngine())); // TODO this should be the external SSL not the BUS one
pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60)); // TODO config
pipeline.addLast("writeTimeoutHandler", new WriteTimeoutHandler(45)); // TODO config
//pipeline.addLast("logger", new LoggingHandler(LogLevel.INFO));
// start as guest until authenticated
CtpAdapter adapter = new CtpAdapter(OperationContext.allocateGuest());
adapter.setHandler(new CtpsHandler());
pipeline.addLast("ctp", new CtpHandler(adapter, true));
}
});
try {
// must wait here, both to keep the activelisteners listeners up to date
// and also to make sure we don't release connectLock too soon
ChannelFuture bfuture = b.bind(info.getAddress()).sync();
if (bfuture.isSuccess()) {
Logger.info("dcCtp Server listening");
this.activelisteners.put(info, bfuture.channel());
}
else
Logger.error("dcCtp Server unable to bind: " + bfuture.cause());
}
catch (InterruptedException x) {
Logger.warn("dcCtp Server interrupted while binding: " + x);
}
catch (Exception x) {
Logger.error("dcCtp Server unable to bind: " + x);
}
}
// ==========================================================================
// Remove server binding as needed
// ==========================================================================
for (final SocketInfo info : this.activelisteners.keySet()) {
// all is well if in the listeners list
if (this.listeners.contains(info))
continue;
// otherwise we don't want to bind anymore
this.stopSocketListener(info);
}
}
finally {
this.connectLock.unlock();
}
}
protected void stopSocketListener(SocketInfo info) {
// tear down message port
Channel ch = this.activelisteners.remove(info);
try {
// must wait here, both to keep the activelisteners listeners up to date
// but also to make sure we don't release connectLock too soon
ChannelFuture bfuture = ch.close().sync();
if (bfuture.isSuccess())
System.out.println("dcCtp Server unbound");
else
System.out.println("dcCtp Server unable to unbind: " + bfuture.cause());
}
catch (InterruptedException x) {
System.out.println("dcCtp Server unable to unbind: " + x);
}
}
public void stopMatrix() {
this.connectLock.lock();
try {
// we don't want to listen anymore
for (final SocketInfo info : this.activelisteners.keySet())
this.stopSocketListener(info);
}
finally {
this.connectLock.unlock();
}
}
public AclFilter getAcl() {
return this.acl ;
}
}