/* ************************************************************************
#
# 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.bus;
import io.netty.bootstrap.Bootstrap;
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.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import divconq.net.ssl.SslHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.util.concurrent.GenericFutureListener;
import java.util.ArrayList;
import java.util.Collection;
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.ClientHandler;
import divconq.bus.net.Session;
import divconq.bus.net.StreamHandler;
import divconq.bus.net.ServerHandler;
import divconq.bus.net.SocketInfo;
import divconq.bus.net.SslContextFactory;
import divconq.bus.net.StreamDecoder;
import divconq.bus.net.StreamEncoder;
import divconq.bus.net.StreamMessage;
import divconq.bus.net.StreamSession;
import divconq.hub.DomainInfo;
import divconq.hub.Hub;
import divconq.hub.ISystemWork;
import divconq.hub.SysReporter;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationResult;
import divconq.log.Logger;
import divconq.net.acl.AclFilter;
import divconq.struct.RecordStruct;
import divconq.util.StringUtil;
import divconq.xml.XElement;
/*
* Service, Attachments, Extension are all reserved headers - Feature and Op semi-reserved. All other headers are fine as long as the don't start with _.
*/
public class Bus {
// info for this Hub
protected HubRouter localhub = null;
protected boolean proxymode = false;
// includes Hubs exposed through PP2 connectors
protected final ConcurrentHashMap<String, ServiceRouter> servicerouters = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<String, HubRouter> hubrouters = new ConcurrentHashMap<>();
protected AclFilter acl = new AclFilter(); // TODO support
protected Lock connectLock = new ReentrantLock();
protected Lock hubLock = new ReentrantLock();
// desired listeners
protected List<SocketInfo> listeners = new CopyOnWriteArrayList<>();
protected final ConcurrentHashMap<SocketInfo, Channel> activelisteners = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<SocketInfo, Channel> activestreamlisteners = new ConcurrentHashMap<>();
// desired connectors
protected ConcurrentHashMap<String,SocketInfo> connectors = new ConcurrentHashMap<>();
// bus event group is separate from rest
protected EventLoopGroup eventLoopGroup = null;
protected boolean stopped = false;
/*
* set localHub before calling this
*/
public void init(OperationResult or, XElement config) {
// TODO this should be separate from keep alive. Sync Services should happen on demand as services change on Hub
// - update based on timestamp, so keep alive may have the timestamp but not the services payload - other side then needs to poll services if out of date.
// keep alive can be more like 1 minute or so, except when pushed to indicate status change (Hub Status or Service Status changes)
int syncperiodsec = 15;
int conninterval = 5;
this.localhub = new HubRouter();
this.hubrouters.put(this.localhub.getHubId(), this.localhub);
if (config != null) {
this.proxymode = "True".equals(config.getAttribute("Proxy"));
SslContextFactory.init(config);
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);
}
for(XElement node : config.selectAll("Connector")) {
SocketInfo si = new SocketInfo();
si.loadConfig(node);
this.addConnector(si);
}
/*
// TODO how many threads for eventGroup?
// as of yet, nothing to do - get settings as/when required
for(XElement node : config.selectAll("Pool"))
this.sessionpool.init(node);
*/
if (config.hasAttribute("SyncServiceList"))
syncperiodsec = (int) StringUtil.parseInt(config.getAttribute("SyncServiceList"), syncperiodsec);
if (config.hasAttribute("ConnectingInterval"))
conninterval = (int) StringUtil.parseInt(config.getAttribute("ConnectingInterval"), conninterval);
}
final int connint = conninterval;
ISystemWork busconnector = new ISystemWork() {
@Override
public void run(SysReporter reporter) {
reporter.setStatus("dcBus Connect");
if (!Hub.instance.isStopping())
Bus.this.connect();
reporter.setStatus("after dcBus Connect");
}
@Override
public int period() {
return connint;
}
};
Hub.instance.getClock().addSlowSystemWorker(busconnector);
final int syncperiodsec2 = syncperiodsec;
// keep the connected hubs up to date with our services list
Hub.instance.getClock().addSlowSystemWorker(new ISystemWork() {
@Override
public void run(SysReporter reporter) {
reporter.setStatus("dcBus keep alive");
for (HubRouter hub : Bus.this.hubrouters.values()) {
hub.keepAlive();
hub.cleanup();
}
reporter.setStatus("after dcBus keep alive");
}
@Override
public int period() {
return syncperiodsec2;
}
});
}
private EventLoopGroup getEventLoopGroup() {
if (this.eventLoopGroup == null)
this.eventLoopGroup = new NioEventLoopGroup();
return this.eventLoopGroup;
}
public boolean isProxyMode() {
return this.proxymode;
}
public HubRouter getLocalHub() {
return this.localhub;
}
public Collection<HubRouter> getHubs() {
return this.hubrouters.values();
}
public OperationResult sendMessage(Message msg) {
return this.sendMessage(msg, (ServiceResult)null);
}
public OperationResult sendMessage(Message msg, IReply r) {
return this.sendMessage(msg, new ServiceResult() {
@Override
public void callback() {
r.result(this);
}
});
}
// designed to be used either with callback or OpResult
public OperationResult sendMessage(Message msg, ServiceResult callback) {
OperationResult or = callback;
if (or == null)
or = new OperationResult();
OperationContext tc = OperationContext.get();
if (tc == null) {
or.errorTr(219, msg);
if (callback != null)
callback.abandon();
return or;
}
if (this.stopped) {
or.error("Unable to send message, bus stopped.");
if (callback != null)
callback.abandon();
return or;
}
String srv = msg.getFieldAsString("Service");
if (srv == null) {
or.errorTr(220, msg);
if (callback != null)
callback.abandon();
return or;
}
DomainInfo di = tc.getDomain();
ServiceRouter router = (di != null) ? di.getServiceRouter(srv) : null;
if (router == null)
router = this.servicerouters.get(srv);
if (router == null) {
or.errorTr(221, msg);
if (callback != null)
callback.abandon();
return or;
}
// if no FromHub then add myself
if (msg.isFieldEmpty("FromHub"))
msg.setField("FromHub", OperationContext.getHubId());
if (callback != null)
callback.setReplyTag(this.localhub.registerForReply(msg, callback));
tc.freeze(msg);
OperationResult routeres = router.sendMessage(msg);
if (routeres.hasErrors()) {
if (callback != null)
// put the routing errors into the callback
callback.abandon();
return or;
}
// message was sent, record it here
Hub.instance.getCountManager().countObjects("dcBusMessageSent", msg);
return or;
}
public boolean isServiceAvailable(String service) {
ServiceRouter router = this.servicerouters.get(service);
if (router == null)
return false;
return router.isAvailable();
}
// used on gateways to lookup the hub id of the one hub they are connected to
// return the hub if of the single, active, hub that provides backend services for us
public String getTetherId() {
for (HubRouter hub : this.hubrouters.values()) {
// is direct detects if the hub is active and not local - if so we use that hub
if (hub.isDirect())
return hub.getHubId();
}
return null;
}
/* TODO restore but no waits
public void sendMessages(ServiceResult callback, TimeoutPlan timeout, Message... msgs) {
final Semaphore flag = new Semaphore(0);
final CountDownLatch latch = new CountDownLatch(msgs.length);
final RecordStruct responses = new RecordStruct();
for (Message msg : msgs) {
this.sendMessage(msg, new ServiceResult(timeout) {
@Override
public void callback() {
Message rmsg = this.getResult();
// flag is just for fair play with responses - if it fails still need to count down
try {
flag.acquire();
String org = rmsg.getFieldAsString("Tag");
if (org != null)
responses.setField(org, rmsg);
flag.release();
}
catch (InterruptedException e) {
}
latch.countDown();
}
});
}
flag.release();
try {
latch.await();
callback.setReply(MessageUtil.success(new RecordStruct(new FieldStruct("Responses", responses))));
}
catch (InterruptedException e) {
callback.error(1, "Latch failed"); // TODO code
}
callback.complete();
}
*/
public void sendReply(Message msg, Message original) {
if (msg == null)
return;
// sender didn't want a reply
if (!MessageUtil.addressReply(msg, original))
return;
// TODO consider a setting to disable response checks
OperationResult mr = OperationContext.get().getSchema().validateResponse(msg, original);
if (mr.hasErrors()) {
System.out.println("Bad Message Content: " + msg);
msg = mr.toLogMessage(); // explain problem to original requester
// TODO temp
System.out.println("Bad Message Error: " + mr);
MessageUtil.addressReply(msg, original);
}
this.sendMessage(msg);
}
// for when original Service/Feature/Op don't match your validation needs
public void sendReply(Message msg, Message original, String serv, String feat, String op) {
if (msg == null)
return;
// sender didn't want a reply
if (!MessageUtil.addressReply(msg, original))
return;
// TODO consider a setting to disable response checks
OperationResult mr = OperationContext.get().getSchema().validateResponse(msg, serv, feat, op);
if (mr.hasErrors()) {
System.out.println("Bad Message Content: " + msg);
msg = mr.toLogMessage(); // explain problem to original requester
// TODO temp
System.out.println("Bad Message Error: " + mr);
MessageUtil.addressReply(msg, original);
}
this.sendMessage(msg);
}
/*
// TODO replace with dcEvents
public void whenAvailable(String service, OperationCallback callback) {
// TODO support fabric/peers and delayed discovery
ServiceRouter router = (service != null)
? this.servicerouters.get(service)
: null;
if (router == null)
callback.error(1, "Unable to find service: " + service); // TODO code
callback.completed();
}
*/
public HubRouter allocateOrGetHub(String id, boolean gateway) {
this.hubLock.lock();
try {
HubRouter hr = this.hubrouters.get(id);
if (hr == null) {
hr = new HubRouter(id, gateway);
this.hubrouters.put(id, hr);
Hub.instance.getCountManager().allocateSetNumberCounter("dcBusHubCount", this.hubrouters.size());
}
return hr;
}
finally {
this.hubLock.unlock();
}
}
public void indexServices(HubRouter hub) {
//System.out.println(" ****************************************** ");
//System.out.println(" SERVICES INDEXED!!! ");
//System.out.println(" ****************************************** ");
for (String srv : hub.getServices())
if (!this.servicerouters.containsKey(srv))
this.servicerouters.put(srv, new ServiceRouter(srv));
for (ServiceRouter rt : this.servicerouters.values())
rt.index(hub);
}
public void removeServices(HubRouter hub) {
for (ServiceRouter rt : this.servicerouters.values())
rt.remove(hub);
}
public void addConnector(SocketInfo info) {
if (info == null)
return;
this.connectors.putIfAbsent(info.getHubId(), info);
}
/**
* Unlike removeListener this does not remove any "binding"
* just won't try connecting again anymore
*
* @param info descriptor of connector to remove
*/
public void removeConnector(SocketInfo info) {
this.connectors.remove(info.getHubId());
HubRouter router = this.hubrouters.get(info.getHubId());
if (router.isLocal())
return;
router.remove(info);
}
public SocketInfo getHubConnector(String hubid) {
return this.connectors.get(hubid);
}
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 client connections when not enough
// ==========================================================================
for (final SocketInfo info : this.connectors.values()) {
HubRouter router = this.allocateOrGetHub(info.getHubId(), info.isGateway());
if (router.isLocal())
continue;
// -------------------------------------------------
// message port
// -------------------------------------------------
int conncount = router.getCountSessions(info);
// add a coonection only once per call to connect (should be between 2 - 15 seconds between calls)
if (conncount < info.getCount()) {
Bootstrap b = new Bootstrap();
b.group(this.getEventLoopGroup())
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 250)
.option(ChannelOption.ALLOCATOR, Hub.instance.getBufferAllocator())
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (info.isUseSsl())
pipeline.addLast("ssl", new SslHandler(SslContextFactory.getClientEngine()));
pipeline.addLast("http-codec", new HttpClientCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(8192)); // TODO is this too small?
pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60)); // TODO config
pipeline.addLast("writeTimeoutHandler", new WriteTimeoutHandler(30)); // TODO config
pipeline.addLast("ws-handler", new ClientHandler(info));
}
});
Logger.debug("dcBus Client connecting");
try {
// must wait here to make sure we don't release connectLock too soon
// we want channel init (above) to complete before we try connect again
b.connect(info.getAddress()).sync();
}
catch (InterruptedException x) {
Logger.warn("dcBus Client interrupted while connecting: " + x);
}
catch (Exception x) {
Logger.debug("dcBus Client unable to connect: " + x);
}
}
// -------------------------------------------------
// stream port
// -------------------------------------------------
conncount = router.getCountStreamSessions(info);
// add a coonection only once per call to connect (should be between 2 - 15 seconds between calls)
if (conncount < info.getStreamCount()) {
Bootstrap b = new Bootstrap();
b.group(this.getEventLoopGroup())
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 250)
.option(ChannelOption.ALLOCATOR, Hub.instance.getBufferAllocator())
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (info.isUseSsl())
pipeline.addLast("ssl", new SslHandler(SslContextFactory.getClientEngine()));
// TODO consider compression
pipeline.addLast("decoder", new StreamDecoder());
pipeline.addLast("encoder", new StreamEncoder());
pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60)); // TODO config
pipeline.addLast("writeTimeoutHandler", new WriteTimeoutHandler(30)); // TODO config
pipeline.addLast("handler", new StreamHandler(info, false));
}
});
Logger.debug("dcBus Client stream connecting");
try {
// must wait here to make sure we don't release connectLock too soon
// we want chanel init (above) to complete before we try connect again
b.connect(info.getStreamAddress()).addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(ChannelFuture cf) throws Exception {
if (!cf.isSuccess()) {
Logger.debug("dcBus Stream unable to connect: " + cf.cause());
return;
}
// client starts the HELLO thing once connected!
StreamMessage icmd = Hub.instance.getBus().getLocalHub().buildStreamHello(info.getHubId());
cf.channel().writeAndFlush(icmd);
}
}).sync();
}
catch (InterruptedException x) {
Logger.warn("dcBus Client stream interrupted while connecting: " + x);
}
catch (Exception x) {
Logger.debug("dcBus Client stream unable to connect: " + x);
}
}
}
// ==========================================================================
// Add server binding when missing
// ==========================================================================
for (final SocketInfo info : this.listeners) {
// only if not currently bound
if (this.activelisteners.containsKey(info))
continue;
// -------------------------------------------------
// message port
// -------------------------------------------------
ServerBootstrap b = new ServerBootstrap();
b.group(this.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()));
pipeline.addLast("codec-http", new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60)); // TODO config
pipeline.addLast("writeTimeoutHandler", new WriteTimeoutHandler(30)); // TODO config
pipeline.addLast("handler", new ServerHandler(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 = b.bind(info.getAddress()).sync();
if (bfuture.isSuccess()) {
Logger.info("dcBus Message Server listening - now listening for dcMessages on TCP port " + info.getPort());
this.activelisteners.put(info, bfuture.channel());
}
else
Logger.error("dcBus Server unable to bind: " + bfuture.cause());
}
catch (InterruptedException x) {
Logger.warn("dcBus Server interrupted while binding: " + x);
}
catch (Exception x) {
Logger.error("dcBus Server unable to bind: " + x);
}
// -------------------------------------------------
// stream port
// -------------------------------------------------
b = new ServerBootstrap();
b.group(this.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 consider compression
pipeline.addLast("decoder", new StreamDecoder());
pipeline.addLast("encoder", new StreamEncoder());
pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60)); // TODO config
pipeline.addLast("writeTimeoutHandler", new WriteTimeoutHandler(30)); // TODO config
pipeline.addLast("handler", new StreamHandler(info, true));
}
});
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 = b.bind(info.getStreamAddress()).sync();
if (bfuture.isSuccess()) {
Logger.info("dcBus Stream Server listening - now listening for dcStreams on TCP port " + info.getPort());
this.activestreamlisteners.put(info, bfuture.channel());
}
else
Logger.error("dcBus Stream Server unable to bind: " + bfuture.cause());
}
catch (InterruptedException x) {
Logger.warn("dcBus Stream Server interrupted while binding: " + x);
}
catch (Exception x) {
Logger.error("dcBus Stream 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();
}
}
public OperationResult sendMessage(StreamMessage msg) {
OperationResult res = new OperationResult();
if (msg == null) {
res.error(1, "Message is missing"); // TODO log codes
return res;
}
if (msg.isFieldEmpty("ToHub") || msg.isFieldEmpty("ToSession") || msg.isFieldEmpty("ToChannel")) {
res.error(1, "Message is missing addressing"); // TODO log codes
msg.release();
return res;
}
String hub = msg.getFieldAsString("ToHub");
HubRouter router = this.hubrouters.get(hub);
if (router == null) {
res.error(1, "No network path to hub"); // TODO log codes
msg.release();
return res;
}
OperationResult routeres = router.deliverMessage(msg);
if (routeres.hasErrors())
return res;
// message was sent, record it here
Hub.instance.getCountManager().countObjects("dcBusStreamMessageSent", msg);
return res;
}
public OperationResult sendReply(StreamMessage msg, RecordStruct original) {
//if (msg == null) {
// return ;
MessageUtil.streamAddressReply(msg, original);
return this.sendMessage(msg);
}
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("dcBus Server unbound");
else
System.out.println("dcBus Server unable to unbind: " + bfuture.cause());
}
catch (InterruptedException x) {
System.out.println("dcBus Server unable to unbind: " + x);
}
// tear down stream port
ch = this.activestreamlisteners.remove(info);
try {
if (ch != null) {
ChannelFuture bfuture = ch.close().sync();
if (bfuture.isSuccess())
System.out.println("dcBus Stream Server unbound");
else
System.out.println("dcBus Stream Server unable to unbind: " + bfuture.cause());
}
else
System.out.println("dcBus Stream Server missing channel");
}
catch (InterruptedException x) {
System.out.println("dcBus Stream Server unable to unbind: " + x);
}
}
public void stopMatrix(OperationResult or) {
this.connectLock.lock();
try {
// we don't want to listen anymore
for (final SocketInfo info : this.activelisteners.keySet())
this.stopSocketListener(info);
for (HubRouter router : this.hubrouters.values())
router.close();
}
finally {
this.connectLock.unlock();
}
}
public boolean isConnected() {
for (HubRouter hub : this.hubrouters.values())
if (hub.isDirect())
return true;
return false;
}
public void stopFinal(OperationResult or) {
// TODO sync these guys
this.stopped = true;
try {
if (this.eventLoopGroup != null)
this.eventLoopGroup.shutdownGracefully().await();
}
catch (InterruptedException x) {
}
}
public void dumpInfo() {
System.out.println("End-points and connections: ");
System.out.println();
System.out.println();
System.out.println("Hubs: ");
System.out.println();
for (HubRouter hub : this.hubrouters.values()) {
String connectkind = hub.isLocal()
? "Self"
: hub.isDirect()
? "Direct"
: hub.isTunneled()
? "Tunneled"
: "???";
System.out.println("- " + hub.getHubId() + " - " + (hub.isActive() ? "Active" : "Inactive") + " - " + connectkind);
System.out.println(" >>> " + StringUtil.join(hub.services, ","));
if (hub.isDirect()) {
System.out.println(" $$$ cmd: " + hub.sessions.size() + " - data: " + hub.streamsessions.size());
for (Session sess : hub.sessions) {
System.out.println(" +++ msg sess " + sess.getChannel()
+ " - open: " + sess.getChannel().isOpen()
+ " - active: " + sess.getChannel().isActive()
+ " - write: " + sess.getChannel().isWritable()
+ " - regist: " + sess.getChannel().isRegistered());
}
for (StreamSession sess : hub.streamsessions) {
System.out.println(" +++ data sess " + sess.getChannel() + " written: " + sess.getWritten() + " - read: " + sess.getRead()
+ " - open: " + sess.getChannel().isOpen()
+ " - active: " + sess.getChannel().isActive()
+ " - write: " + sess.getChannel().isWritable()
+ " - regist: " + sess.getChannel().isRegistered());
}
}
String proxied = StringUtil.join(hub.proxied.keySet(), ",");
if (StringUtil.isNotEmpty(proxied))
System.out.println(" ||| " + proxied);
}
System.out.println();
System.out.println("Services: ");
System.out.println();
for (ServiceRouter router : this.servicerouters.values()) {
System.out.println("- " + router.getName());
List<String> al = new ArrayList<String>();
for (HubRouter a : router.hubList())
al.add(a.getHubId());
System.out.println(" >>> " + StringUtil.join(al, ","));
}
Hub.instance.getDomains().dumpDomainNames();
}
public AclFilter getAcl() {
return this.acl ;
}
public ServiceRouter getServiceRouter(String srv) {
return this.servicerouters.get(srv);
}
}