/* ************************************************************************ # # 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.hub; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import java.io.File; import java.lang.management.ClassLoadingMXBean; import java.lang.management.CompilationMXBean; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; import java.security.Security; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.joda.time.DateTime; import divconq.api.ApiSession; import divconq.api.IApiSessionFactory; import divconq.bus.Bus; import divconq.count.CountManager; import divconq.ctp.net.CtpServices; import divconq.db.IDatabaseManager; import divconq.db.ObjectResult; import divconq.db.DataRequest; import divconq.io.LocalFileStore; import divconq.lang.op.OperationContext; import divconq.lang.op.OperationResult; import divconq.locale.LocaleDefinition; import divconq.log.HubLog; import divconq.log.Logger; import divconq.mod.IModule; import divconq.mod.ModuleLoader; import divconq.scheduler.Scheduler; import divconq.schema.SchemaManager; import divconq.script.ActivityManager; import divconq.service.simple.AuthService; import divconq.service.simple.DomainsService; import divconq.session.Sessions; import divconq.sql.SqlManager; import divconq.sql.SqlManager.SqlDatabase; import divconq.struct.CompositeStruct; import divconq.struct.FieldStruct; import divconq.struct.RecordStruct; import divconq.util.FileUtil; import divconq.util.MimeUtil; import divconq.util.StringUtil; import divconq.work.WorkQueue; import divconq.work.WorkPool; import divconq.xml.XAttribute; import divconq.xml.XElement; /** * Hub is the center of activity for DivConq applications. Most of the built-in resources/features are available via the Hub. * The Hub must be initialized by creating a HubResource and passing it to "start". When the application quits it is best * to call "stop". * * There is only one Hub object per process (per JVM), the way to access the Hub object is through Hub.instance. * * @author Andy White * */ public class Hub { // ============ STATIC ============ static public Hub instance = new Hub(); static { // java 6 does not do TTL correctly, change it java.security.Security.setProperty("networkaddress.cache.ttl" , "0"); //PooledByteBufAllocator.DEFAULT. } // ============ INSTANCE ============ protected long starttime = System.currentTimeMillis(); protected Clock clock = new Clock(); protected String[] libpaths = null; protected Map<String,ModuleLoader> modules = new HashMap<>(); protected List<ModuleLoader> orderedModules = new ArrayList<>(); protected Bus bus = new Bus(); protected CtpServices ctp = new CtpServices(); protected WorkPool workpool = null; protected WorkQueue workqueue = new WorkQueue(); protected Scheduler scheduler = new Scheduler(); protected SqlManager sqldbman = new SqlManager(); protected LocalFileStore publicfilestore = null; protected LocalFileStore privatefilestore = null; protected Sessions sessions = new Sessions(); protected HubResources resources = null; protected SecurityPolicy policy = new SecurityPolicy(); protected IDatabaseManager db = null; protected DomainsManager domainman = new DomainsManager(); // api session managers protected ConcurrentHashMap<String, IApiSessionFactory> apimans = new ConcurrentHashMap<>(); // hub events protected ConcurrentHashMap<Integer, Set<IEventSubscriber>> subscribers = new ConcurrentHashMap<>(); protected ActivityManager actman = null; protected ByteBufAllocator bufferAllocator = PooledByteBufAllocator.DEFAULT; protected EventLoopGroup eventLoopGroup = null; protected CountManager countman = new CountManager(); protected HubState state = HubState.Booting; protected boolean idleflag = false; // set if Run should really be Idle or reverse protected int dependencyCntBoot = 0; protected int dependencyCntConn = 0; protected int dependencyCntRun = 0; protected ReentrantLock depedencyLock = new ReentrantLock(); protected HashMap<String, HubDependency> dependencies = new HashMap<>(); public HubState getState() { return this.state; } public void dependencyChanged() { OperationContext.useHubContext(); if (Logger.isDebug()) Logger.debug("Dependency channged"); this.depedencyLock.lock(); try { this.dependencyCntBoot = 0; this.dependencyCntConn = 0; this.dependencyCntRun = 0; for (HubDependency d : this.dependencies.values()) { if (Logger.isDebug()) Logger.debug("Dependency " + d.source + " - boot: " + d.passBoot + " connect: " + d.passConnected + " run: " + d.passRun); if (!d.passBoot) this.dependencyCntBoot++; if (!d.passConnected) this.dependencyCntConn++; if (!d.passRun) this.dependencyCntRun++; } // not ready to start if (this.dependencyCntBoot > 0) { // not ready is ok if we are still starting if (this.state == HubState.Booting) { Logger.info("Waiting on boot dependencies: " + this.dependencyCntBoot); return; } Logger.error("Illegal hub state, higher than booting but missing boot dependencies: " + this.dependencyCntBoot); // if in any other state and we are not ready then not good - stop this.stop(); return; } if (this.state == HubState.Booting) { if (Logger.isDebug()) Logger.debug("Switch hub to Booted"); this.booted(); // set status return; } if (this.dependencyCntConn > 0) { // not ready is ok if we are just starting if (this.state == HubState.Booted) { Logger.info("Waiting on connect dependencies: " + this.dependencyCntConn); return; } Logger.warn("Hub state higher than connected but missing connected dependencies: " + this.dependencyCntConn); this.booted(); // update status return; } if (this.state == HubState.Booted) { if (Logger.isDebug()) Logger.debug("Switch hub to Connected"); this.connected(); // go into Connected status return; } if (this.dependencyCntRun > 0) { // not ready is ok if we are just starting if (this.state == HubState.Connected) { Logger.info("Waiting on run dependencies: " + this.dependencyCntRun); return; } Logger.warn("Hub state higher than booted but missing running dependencies: " + this.dependencyCntRun); this.connected(); // update status return; } if (this.state == HubState.Connected) { if (Logger.isDebug()) Logger.debug("Switch hub to Running"); this.running(); // go into Running or Idle status return; } } finally { this.depedencyLock.unlock(); } } protected void running() { if (this.idleflag) { this.state = HubState.Idle; Logger.info("Hub entered Idled state"); this.fireEvent(HubEvents.Idling, null); } else { this.state = HubState.Running; Logger.info("Hub entered Running state"); this.fireEvent(HubEvents.Running, null); } } protected void booted() { this.state = HubState.Booted; Logger.info("Hub entered Booted state"); this.fireEvent(HubEvents.Booted, null); this.dependencyChanged(); } public void connected() { // if unconnected, gateway will get a message later //if (this.resources.isGateway() && !this.bus.isConnected()) // return; this.state = HubState.Connected; Logger.info("Hub entered Connected state"); this.fireEvent(HubEvents.Connected, null); // TODO add prep functions to load from database or do automatic self updates // TODO check for repo updates if connected via db and if in Production mode (not dev mode) // if true then restart with resume code // gateways always use Auth and Domain service from internal server if (!this.resources.isGateway()) { // these two services are required, if service not available and we are not on Gateway then load them // load default dcDomains if (!this.bus.isServiceAvailable("dcDomains")) { DomainsService s = new DomainsService(); s.init(null); this.bus.getLocalHub().registerService(s); } // load default dcAuth if (!this.bus.isServiceAvailable("dcAuth")) { AuthService s = new AuthService(); s.init(null); this.bus.getLocalHub().registerService(s); } } this.domainman.init(); this.dependencyChanged(); } public void setIdled(boolean v) { if (this.idleflag == v) return; this.idleflag = v; if ((this.state == HubState.Running) || (this.state == HubState.Idle)) this.running(); } public boolean isStopping() { return (this.state == HubState.Stopping) || (this.state == HubState.Stopped); } public boolean isIdled() { return (this.state == HubState.Idle); } public boolean isRunning() { return (this.state == HubState.Running); } public boolean isBooted() { return (this.state.getCode() > HubState.Booting.getCode()) && (this.state.getCode() < HubState.Stopped.getCode()); } public void addDependency(HubDependency v) { if (Logger.isDebug()) Logger.debug("Adding Dependency " + v.source + " - boot: " + v.passBoot + " connect: " + v.passConnected + " run: " + v.passRun); this.depedencyLock.lock(); try { this.dependencies.put(v.source, v); v.added = true; } finally { this.depedencyLock.unlock(); } this.dependencyChanged(); } public void removeDependency(String v) { if (Logger.isDebug()) Logger.debug("Removing Dependency " + v); this.depedencyLock.lock(); try { this.dependencies.remove(v); } finally { this.depedencyLock.unlock(); } this.dependencyChanged(); } public HubDependency getDependency(String v) { this.depedencyLock.lock(); try { return this.dependencies.get(v); } finally { this.depedencyLock.unlock(); } } public Bus getBus() { return this.bus; } public SecurityPolicy getSecurityPolicy() { return this.policy; } public CtpServices getCtp() { return this.ctp; } public LocalFileStore getPublicFileStore() { return this.publicfilestore; } public LocalFileStore getPrivateFileStore() { return this.privatefilestore; } public ActivityManager getActivityManager() { return this.actman; } public CountManager getCountManager() { return this.countman; } public ByteBufAllocator getBufferAllocator() { return this.bufferAllocator; } public EventLoopGroup getEventLoopGroup() { if (this.eventLoopGroup == null) this.eventLoopGroup = new NioEventLoopGroup(); return this.eventLoopGroup; } /** * DivConq uses a specialized type system that provides type consistency across services * (including web services), database fields and stored procedures, as well as scripting. * * All scalars (including primitives) and composites (collections) are wrapped by some * subclass of Struct. List/array collections are expressed by this class. * This class is analogous to an Array in JSON but may contain type information as well, * similar to Yaml. * * There are schema files (written in Xml and stored in the Packages repository) that define * all the known data types, including complex data types. * * @return the master collection of all known simple and complex data types */ public SchemaManager getSchema() { if (this.resources != null) return this.resources.getSchema(); return null; } /** * The WorkPool is a general purpose thread pool that should be used to execute tasks. * DivConq does not recommend using your own threads as you'll lose the TaskContext and * many features will cease to work correctly. However, do not use WorkPool to run tasks that * are known to block for long periods (seconds). WorkPool is for tasks that execute non-stop * and that use async calls when encountering a potentially blocking situation. (note the * async result should come back on a WorkPool thread also - not necessarily the same * thread). * * @see OperationContext for addition notes. * * @return the WorkPool manager */ public WorkPool getWorkPool() { return this.workpool; } public WorkQueue getWorkQueue() { return this.workqueue; } /** * Along the same lines as WorkPool (see "getWorkPool") the scheduler provides a way to * run tasks. Scheduler uses the WorkPool for running tasks. However, Scheduler has * features to schedule the task at some future time and to optionally make the task recurring. * * See the "schedule" methods in Clock for an alternative. Scheduler is best for tasks that * need to retain TaskContext (which is often) and that occur once or every 10 seconds or more. * Tasks that need to occur very frequently or need to be on the system clock may be candidates * for Clock scheduling. * * @return the scheduling manager */ public Scheduler getScheduler() { return this.scheduler; } public Sessions getSessions() { return this.sessions; } /** * The clock tracks the application's time (time zone and date time). The application's time * can be altered or even sped up. There are also some scheduling methods in Clock but consider * using Scheduler over Clock, see "getScheduler". * * @return the clock manager */ public Clock getClock() { return this.clock; } /** * The whole of Hub is started based off the settings from a single config file. * * @return the root element of the Hub config file */ public XElement getConfig() { if (this.resources != null) return this.resources.getConfig(); return null; } /** * @return time the Hub started in ms since 1970 */ public long getStartTime() { return this.starttime; } public IDatabaseManager getDatabase() { return this.db; } public SqlDatabase getSQLDatabase() { return this.getSQLDatabase("default"); } public SqlDatabase getSQLDatabase(String name) { return this.sqldbman.getDatabase(name); } public SqlManager getSQLManager() { return this.sqldbman; } /** * @return the HubResources object that was used to start the Hub */ public HubResources getResources() { return this.resources; } public DomainsManager getDomains() { return this.domainman; } public DomainInfo getDomainInfo(String id) { return this.domainman.getDomainInfo(id); } /** * Before many of the features of DivConq can be used the Hub must be started. This requires that a HubResources * object be created and hold valid paths/config/etc. * * @param resources the resources to use in startup * @return a log of the startup process, see "hasErrors" and such in OperationResult */ public OperationResult start(HubResources resources) { this.resources = resources; OperationContext.useHubContext(); // in case resources have not be initialized, do so // OperationResult will be set to the Hub Task Context is all is well OperationResult or = resources.init(); if (or.hasErrors()) { or.exit(113, "Unable to continue, hub rescources not properly initialized"); return or; } XElement config = this.resources.getConfig(); List<LocaleDefinition> fallbacklocales = new ArrayList<LocaleDefinition>(); String hlocale = this.resources.getDefaultLocale(); fallbacklocales.add(this.resources.getLocaleDefinition(hlocale)); // put Logger into default locale and level //Logger.setLocale(this.resources.getDefaultLocale()); // change to overrides if find config XElement logger = config.find("Logger"); // prepare the logger - use files, use custom log writer HubLog.init(logger); // initialize hub context locale and level (depends on logger above) OperationContext.startHubContext(config); or.boundary("Origin", "hub:", "Op", "Start"); // TODO use translation codes for all start up messages after dictionaries are loaded or.info(0, "Using hub id: " + OperationContext.getHubId()); or.info(0, "Java version: " + System.getProperty("java.version")); or.info(0, "Java vendor: " + System.getProperty("java.vendor")); or.info(0, "Java vm: " + System.getProperty("java.vm.name")); Security.addProvider(new BouncyCastleProvider()); or.debug(0, "Starting Clock"); HubDependency bootdep = new HubDependency("Hub Boot"); bootdep.setPassBoot(false); this.addDependency(bootdep); this.clock.init(or, config.find("Clock")); this.clock.start(or); if (or.hasErrors()) { or.exitTr(136); return or; } // this.actman = new ActivityManager(); // work pool prep or.debug(0, "Starting Work Pool"); this.workpool = new WorkPool(); XElement wpxel = config.find("WorkPool"); this.workpool.init(or, wpxel); this.workpool.start(or); if (or.hasErrors()) { or.exitTr(137); return or; } this.workqueue.init(or, config.find("WorkQueue")); if (or.hasErrors()) { or.exitTr(175); return or; } this.workqueue.start(or); if (or.hasErrors()) { or.exitTr(176); return or; } or.debug(0, "Initializing scheduler"); this.scheduler.init(or, config.find("Scheduler")); if (or.hasErrors()) { or.exitTr(138); return or; } or.debug(0, "Initializing Bus"); this.ctp.init(config.find("Ctp")); // setup our bus this.bus.init(or, config.find("Bus")); if (or.hasErrors()) { or.exitTr(139); return or; } // load the sql databases, if any or.debug(0, "Initializing dcDatabase"); XElement dcdb = config.find("dcDatabase"); if (dcdb != null) { String cname = dcdb.getAttribute("Class", "divconq.db.rocks.DatabaseManager"); try { Class<?> dbclass = Class.forName(cname); this.db = (IDatabaseManager) dbclass.newInstance(); this.db.init(dcdb); this.db.start(); } catch (Exception x) { or.error("Unable to load/start database class: " + x); } if (or.hasErrors()) { or.exitTr(146); // TODO fix code to dcdb code return or; } } // load the sql databases, if any or.debug(0, "Initializing SQL Database Manager"); this.sqldbman.init(or, config.find("SQLDatabases")); if (or.hasErrors()) { or.exitTr(146); return or; } or.debug(0, "Initializing package file store"); this.resources.getPackages().init(or, config.find("PackageFileStore")); XElement fstore = config.find("PublicFileStore"); if (fstore != null) { this.publicfilestore = new LocalFileStore(); or.debug(0, "Initializing public file store"); this.publicfilestore.start(or, fstore); } XElement pvfstore = config.find("PrivateFileStore"); if (pvfstore != null) { this.privatefilestore = new LocalFileStore(); or.debug(0, "Initializing private file store"); this.privatefilestore.start(or, pvfstore); } if (or.hasErrors()) { or.exitTr(141); return or; } // sessions or.debug(0, "Initializing local session manager"); this.sessions.init(or, config.find("Sessions")); this.bus.getLocalHub().registerService(this.sessions); if (or.hasErrors()) { or.exitTr(142); return or; } // CountManager - initialize late, may depend on other features or.debug(0, "Initializing count/stats manager"); this.countman.init(or, config.find("CountManager")); if (or.hasErrors()) { or.exitTr(193); return or; } MimeUtil.load(config.find("MimeDefs")); or.debug(0, "Loading modules"); for (XElement el : config.selectAll("Module")) { or.info(0, "Loading module: " + el.getAttribute("Name")); ModuleLoader loader = new ModuleLoader(Hub.class.getClassLoader()); loader.init(el); this.modules.put(loader.getName(), loader); this.orderedModules.add(loader); loader.start(); } if (or.hasErrors()) { or.exitTr(143); return or; } or.debug(0, "Starting scheduler"); this.scheduler.start(or); if (or.hasErrors()) { or.exitTr(147); return or; } if (this.resources.isGateway()) { HubDependency conndep = new HubDependency("Gateway"); conndep.setPassConnected(false); this.subscribeToEvent(HubEvents.BusConnected, e -> { conndep.setPassConnected(this.bus.isConnected()); }); this.subscribeToEvent(HubEvents.BusDisconnected, e -> { conndep.setPassConnected(this.bus.isConnected()); }); this.addDependency(conndep); } // TODO review if this even works... // every five minutes run cleanup to remove expired temp files // also cleanup hub/default operating contexts ISystemWork cleanexpiredtemp = new ISystemWork() { @Override public void run(SysReporter reporter) { reporter.setStatus("Cleaning contexts and temp files"); if (!Hub.instance.isStopping()) { FileUtil.cleanupTemp(); IDatabaseManager db = Hub.instance.getDatabase(); if (db != null) { RecordStruct params = new RecordStruct( new FieldStruct("ExpireThreshold", new DateTime().minusMinutes(5)), new FieldStruct("LongExpireThreshold", new DateTime().minusMinutes(30)) ); db.submit(new DataRequest("dcCleanup").withParams(params), new ObjectResult() { @Override public void process(CompositeStruct result) { if (this.hasErrors()) Logger.errorTr(114); } }); } } reporter.setStatus("After cleaning contexts and temp files"); } @Override public int period() { return 300; } }; this.clock.addSlowSystemWorker(cleanexpiredtemp); // monitor the Hub/Java/Core counters ISystemWork monitorcounters = new ISystemWork() { @Override public void run(SysReporter reporter) { reporter.setStatus("Updating hub counters"); Hub h = Hub.instance; CountManager cm = h.getCountManager(); h.getSessions().recordCounters(); //long st = System.currentTimeMillis(); ClassLoadingMXBean clbean = ManagementFactory.getClassLoadingMXBean(); cm.allocateSetNumberCounter("javaClassCount", clbean.getLoadedClassCount()); cm.allocateSetNumberCounter("javaClassLoads", clbean.getTotalLoadedClassCount()); cm.allocateSetNumberCounter("javaClassUnloads", clbean.getUnloadedClassCount()); CompilationMXBean cpbean = ManagementFactory.getCompilationMXBean(); if (cpbean != null) cm.allocateSetNumberCounter("javaCompileTime", cpbean.getTotalCompilationTime()); MemoryMXBean mebean = ManagementFactory.getMemoryMXBean(); cm.allocateSetNumberCounter("javaMemoryHeapCommitted", mebean.getHeapMemoryUsage().getCommitted()); cm.allocateSetNumberCounter("javaMemoryHeapUsed", mebean.getHeapMemoryUsage().getUsed()); cm.allocateSetNumberCounter("javaMemoryHeapInit", mebean.getHeapMemoryUsage().getInit()); cm.allocateSetNumberCounter("javaMemoryHeapMax", mebean.getHeapMemoryUsage().getMax()); cm.allocateSetNumberCounter("javaMemoryNonHeapCommitted", mebean.getNonHeapMemoryUsage().getCommitted()); cm.allocateSetNumberCounter("javaMemoryNonHeapUsed", mebean.getNonHeapMemoryUsage().getUsed()); cm.allocateSetNumberCounter("javaMemoryNonHeapInit", mebean.getNonHeapMemoryUsage().getInit()); cm.allocateSetNumberCounter("javaMemoryNonHeapMax", mebean.getNonHeapMemoryUsage().getMax()); cm.allocateSetNumberCounter("javaMemoryFinals", mebean.getObjectPendingFinalizationCount()); List<GarbageCollectorMXBean> gcbeans = ManagementFactory.getGarbageCollectorMXBeans(); long collects = 0; long collecttime = 0; for (GarbageCollectorMXBean gcbean : gcbeans) { collects += gcbean.getCollectionCount(); collecttime += gcbean.getCollectionTime(); } cm.allocateSetNumberCounter("javaGarbageCollects", collects); cm.allocateSetNumberCounter("javaGarbageTime", collecttime); OperatingSystemMXBean osbean = ManagementFactory.getOperatingSystemMXBean(); cm.allocateSetNumberCounter("javaSystemLoadAverage", osbean.getSystemLoadAverage()); RuntimeMXBean rtbean = ManagementFactory.getRuntimeMXBean(); cm.allocateSetNumberCounter("javaJvmUptime", rtbean.getUptime()); ThreadMXBean thbean = ManagementFactory.getThreadMXBean(); cm.allocateSetNumberCounter("javaJvmRunningDaemonThreads", thbean.getDaemonThreadCount()); cm.allocateSetNumberCounter("javaJvmRunningPeakThreads", thbean.getPeakThreadCount()); cm.allocateSetNumberCounter("javaJvmRunningThreads", thbean.getThreadCount()); cm.allocateSetNumberCounter("javaJvmStartedThreads", thbean.getTotalStartedThreadCount()); //System.out.println("collect: " + (System.currentTimeMillis() - st)); //System.out.println("reply count: " + Hub.instance.getCountManager().getCounter("dcBusReplyHandlers")); reporter.setStatus("After reviewing hub counters"); } @Override public int period() { return 1; } }; Hub.instance.getClock().addFastSystemWorker(monitorcounters); this.removeDependency(bootdep.source); or.boundary("Origin", "hub:", "Op", "Run"); return or; } /** * Gracefully stop the scheduler, database connections, workpool, logger and such * * @return a log of the shutdown process */ public OperationResult stop() { // tell surface modules and user tasks not to accept new requests - enter a slow and careful come down this.state = HubState.Stopping; Logger.info("Hub entered Stopping state"); this.fireEvent(HubEvents.Stopping, null); OperationContext.useHubContext(); OperationResult or = new OperationResult(); or.boundary("Origin", "hub:", "Op", "Stop"); or.info(0, "Stopping hub"); or.info(0, "Waiting on Primary Tasks"); // wait up to 5 minutes -- TODO configure for (int i = 0; i < 300; i++) { if (this.sessions.countIncompleteTasks() == 0) break; try { Thread.sleep(1000); } catch (Exception x) { } if (i % 30 == 29) or.info(0, "Still Waiting on Primary Tasks"); } or.debug(0, "Stopping bus matrix"); this.ctp.stopMatrix(); // will wait up to 2 seconds for each session to close (should be faster) this.bus.stopMatrix(or); or.debug(0, "Stopping scheduler"); this.scheduler.stop(or); or.debug(0, "Stopping work queue"); this.workqueue.stop(or); or.debug(0, "Stopping modules"); for (int i = this.orderedModules.size() - 1; i >= 0; i--) { ModuleLoader mod = this.orderedModules.get(i); or.info(0, "Stopping module: " + mod.getName()); mod.stop(); } or.debug(0, "Stopping count manager"); this.countman.stop(or); or.debug(0, "Stopping package file store"); this.resources.getPackages().stop(or); if (this.publicfilestore != null) { or.debug(0, "Stopping public file store"); this.publicfilestore.stop(or); } if (this.privatefilestore != null) { or.debug(0, "Stopping private file store"); this.privatefilestore.stop(or); } or.debug(0, "Stopping work pool"); // let everyone know it is time to stop this.workpool.stop(or); // give just a little time for everything to cleanup try { Thread.sleep(500); } catch (InterruptedException x) { } or.debug(0, "Stopping SQL Database Manager"); this.sqldbman.stop(); if (this.db != null) { or.debug(0, "Stopping dcDatabase"); this.db.stop(); } or.debug(0, "Stopping bus"); this.bus.stopFinal(or); try { if (this.eventLoopGroup != null) this.eventLoopGroup.shutdownGracefully().await(); } catch (InterruptedException x) { } or.debug(0, "Stopping clock"); this.clock.stop(or); // find and list any threads from our pools that linger beyond the shut down Set<Thread> threadSet = Thread.getAllStackTraces().keySet(); Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]); for (Thread t : threadArray) { if ((t.getThreadGroup() == null) || !"main".equals(t.getThreadGroup().getName())) continue; boolean fnd = false; if (t.getName().startsWith("WorkPool")) fnd = true; if (fnd) { Logger.info("Lingering Thread: " + t.getName()); StackTraceElement[] g = t.getStackTrace(); if (g.length > 3) { Logger.info(" - " + g[0]); Logger.info(" - " + g[1]); Logger.info(" - " + g[2]); Logger.info(" - " + g[3]); } } } or.debug(0, "Stopping logger"); HubLog.stop(or); or.info(0, "Hub stopped"); return or; } /** * Please don't use this, it goes against design principles of divconq. * * @param name of the module desired * * @return the module if loaded */ public IModule getModule(String name) { ModuleLoader ml = this.modules.get(name); if (ml != null) return ml.getModule(); return null; } public void subscribeToEvent(Integer event, IEventSubscriber sub) { this.subscribers.putIfAbsent(event, new HashSet<IEventSubscriber>()); Set<IEventSubscriber> list = this.subscribers.get(event); list.add(sub); } public void unsubscribeFromEvent(Integer event, IEventSubscriber sub) { Set<IEventSubscriber> list = this.subscribers.get(event); if (list != null) list.remove(sub); } public void fireEvent(Integer event, Object e) { if (Logger.isDebug()) Logger.debug("Hub Event fired: " + event + " with " + e); Set<IEventSubscriber> list = this.subscribers.get(event); if (list == null) return; // to array to be thread safe for (IEventSubscriber sub : list.toArray(new IEventSubscriber[list.size()])) { try { sub.eventFired(e); } catch (Exception x) { Logger.warn("Event subscriber threw an error: " + x); } } } public String getLibraryPath(String libraryName, String alias) { if (this.libpaths == null) { this.libpaths = System.getProperty("java.class.path").split(";"); // if this is UNIX/Linux then split on ':' instead if (this.libpaths.length == 1) this.libpaths = System.getProperty("java.class.path").split(":"); } String retpath = null; for (String path : this.libpaths) { if (path.contains(File.separatorChar + libraryName + ".jar")) { retpath = path; break; } if (path.contains(File.separatorChar + libraryName + File.separatorChar)) { retpath = path + "/"; break; } } if (retpath == null) { try { // try some predictable places File proj = new File("./" + libraryName + "/bin"); if (proj.exists()) retpath = proj.getCanonicalPath() + "/"; else { File jar = new File("./lib/" + libraryName + ".jar"); if (jar.exists()) retpath = jar.getCanonicalPath(); } } catch (Exception x) { } } if (retpath != null) retpath = retpath.replace("\\", "/"); return retpath; } public ApiSession createLocalApiSession(String domain) { IApiSessionFactory man = this.apimans.get("_local"); if (man == null) { man = (IApiSessionFactory) this.getInstance("divconq.api.LocalSessionFactory"); this.apimans.put("_local", man); } return man.create(new XElement("ApiSession", new XAttribute("Domain", domain))); } public ApiSession createApiSession(String name) { IApiSessionFactory man = this.apimans.get(name); if (man == null) { for (XElement mel : this.getConfig().selectAll("ApiSessions/ApiSession")) { if (mel.getAttribute("Name").equals(name)) { String cls = mel.getAttribute("Class"); if (StringUtil.isEmpty(cls)) break; man = (IApiSessionFactory) this.getInstance(cls); if (man != null) { man.init(mel); this.apimans.put(name, man); } break; } } } if (man != null) return man.create(); return null; } // TODO add hub info/detail collector service // System.out.println("Boss Threads: " + this.getBossGroup().isShutdown() + " - " + this.getBossGroup().isShuttingDown() + " - " + this.getBossGroup().isTerminated()); public Object getInstance(String cname) { try { return this.getClass(cname).newInstance(); } catch (Exception x) { } return null; } public Class<?> getClass(String cname) { try { return Class.forName(cname); } catch (Exception x) { } return null; } }