divconq.bus.Bus.java Source code

Java tutorial

Introduction

Here is the source code for divconq.bus.Bus.java

Source

/* ************************************************************************
#
#  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);
    }
}