changeset 948:52eac263ab76

Remove DAOFactory Previous patches have moved DAOs into their respective plug-ins. This patch registers the remaining DAOs as part of the storage-core bundle's Activator. This patch also converts Main and AgentApplication to use DbService in order to connect to the database, which handles registering Storage itself. These two final modifications remove the last usefulness of DAOFactory, allowing us to finally remove it. This patch also removes StorageProviderUtil, whose functionality has moved to a private method in DbServiceImpl. This was done since using DbService is the recommended way to register Storage. This patch also adds an improved test case for agent startup in AgentApplicationTest. Reviewed-by: vanaltj, omajid, jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-January/005221.html
author Elliott Baron <ebaron@redhat.com>
date Thu, 31 Jan 2013 14:40:22 -0500
parents 7f2c1c9d4e79
children 792ab05353ae
files agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java agent/core/src/main/java/com/redhat/thermostat/agent/Agent.java agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java agent/core/src/test/java/com/redhat/thermostat/agent/AgentTest.java client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainTest.java common/core/src/main/java/com/redhat/thermostat/common/internal/Activator.java distribution/src/test/java/com/redhat/thermostat/distribution/StorageTest.java storage/core/pom.xml storage/core/src/main/java/com/redhat/thermostat/storage/core/DbService.java storage/core/src/main/java/com/redhat/thermostat/storage/core/HostLatestPojoListGetter.java storage/core/src/main/java/com/redhat/thermostat/storage/core/StorageProviderUtil.java storage/core/src/main/java/com/redhat/thermostat/storage/core/VmLatestPojoListGetter.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/Activator.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/DbServiceImpl.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/ActivatorTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/DbServiceImplTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/DbServiceTest.java system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java
diffstat 27 files changed, 920 insertions(+), 568 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Thu Jan 31 14:40:22 2013 -0500
@@ -48,15 +48,19 @@
 public class Activator implements BundleActivator {
 
     private CommandRegistry reg;
+    private AgentApplication agentApplication;
 
     @Override
-    public void start(BundleContext context) throws Exception {
+    public void start(final BundleContext context) throws Exception {
         reg = new CommandRegistryImpl(context);
-        reg.registerCommands(Arrays.asList(new ServiceCommand(), new StorageCommand(), new AgentApplication(context)));
+        agentApplication = new AgentApplication(context);
+        reg.registerCommands(Arrays.asList(new ServiceCommand(),
+                new StorageCommand(), agentApplication));
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
+        agentApplication.shutdown();
         reg.unregisterCommands();
     }
 }
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Thu Jan 31 14:40:22 2013 -0500
@@ -36,12 +36,14 @@
 
 package com.redhat.thermostat.agent.cli.impl;
 
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
 
 import sun.misc.Signal;
 import sun.misc.SignalHandler;
@@ -55,19 +57,21 @@
 import com.redhat.thermostat.backend.BackendService;
 import com.redhat.thermostat.common.Constants;
 import com.redhat.thermostat.common.LaunchException;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
 import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
 import com.redhat.thermostat.common.cli.Arguments;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
 import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.storage.core.Connection;
-import com.redhat.thermostat.storage.core.StorageProvider;
-import com.redhat.thermostat.storage.core.StorageProviderUtil;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-import com.redhat.thermostat.storage.dao.DAOFactory;
-import com.redhat.thermostat.storage.dao.DAOFactoryImpl;
+import com.redhat.thermostat.storage.core.DbService;
+import com.redhat.thermostat.storage.core.DbServiceFactory;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.BackendInfoDAO;
 
 @SuppressWarnings("restriction")
 public final class AgentApplication extends AbstractStateNotifyingCommand {
@@ -76,19 +80,23 @@
 
     private final BundleContext bundleContext;
     private final ConfigurationCreator configurationCreator;
-    private final DAOFactoryCreator daoFactoryCreator;
 
     private AgentStartupConfiguration configuration;
     private AgentOptionParser parser;
+    private DbServiceFactory dbServiceFactory;
+    private ServiceTracker configServerTracker;
+    private MultipleServiceTracker daoTracker;
+
+    private CountDownLatch shutdownLatch;
 
     public AgentApplication(BundleContext bundleContext) {
-        this(bundleContext, new ConfigurationCreator(), new DAOFactoryCreator());
+        this(bundleContext, new ConfigurationCreator(), new DbServiceFactory());
     }
 
-    AgentApplication(BundleContext bundleContext, ConfigurationCreator configurationCreator, DAOFactoryCreator daoFactoryCreator) {
+    AgentApplication(BundleContext bundleContext, ConfigurationCreator configurationCreator, DbServiceFactory dbServiceFactory) {
         this.bundleContext = bundleContext;
         this.configurationCreator = configurationCreator;
-        this.daoFactoryCreator = daoFactoryCreator;
+        this.dbServiceFactory = dbServiceFactory;
     }
     
     private void parseArguments(Arguments args) throws InvalidConfigurationException {
@@ -105,74 +113,59 @@
         }
         final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
 
-        final DAOFactory daoFactory = daoFactoryCreator.create(configuration);
-
-        Connection connection = daoFactory.getConnection();
-        ConnectionListener connectionListener = new ConnectionListener() {
+        final DbService dbService = dbServiceFactory.createDbService(
+                configuration.getUsername(), configuration.getPassword(),
+                configuration.getDBConnectionString());
+        
+        shutdownLatch = new CountDownLatch(1);
+        
+        configServerTracker = new ServiceTracker(bundleContext, ConfigurationServer.class.getName(), null) {
             @Override
-            public void changed(ConnectionStatus newStatus) {
-                switch (newStatus) {
-                case DISCONNECTED:
-                    logger.warning("Unexpected disconnect event.");
-                    break;
-                case CONNECTING:
-                    logger.fine("Connecting to storage.");
-                    break;
-                case CONNECTED:
-                    logger.fine("Connected to storage, registering storage as service");
-                    daoFactory.registerDAOsAndStorageAsOSGiServices();
-                    break;
-                case FAILED_TO_CONNECT:
-                    logger.warning("Could not connect to storage.");
-                    System.exit(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
-                default:
-                    logger.warning("Unfamiliar ConnectionStatus value");
+            public Object addingService(ServiceReference reference) {
+                final ConfigurationServer configServer = (ConfigurationServer) super.addingService(reference);
+                configServer.startListening(configuration.getConfigListenAddress());
+                
+                ConnectionListener connectionListener = new ConnectionListener() {
+                    @Override
+                    public void changed(ConnectionStatus newStatus) {
+                        switch (newStatus) {
+                        case DISCONNECTED:
+                            logger.warning("Unexpected disconnect event.");
+                            break;
+                        case CONNECTING:
+                            logger.fine("Connecting to storage.");
+                            break;
+                        case CONNECTED:
+                            logger.fine("Connected to storage");
+                            handleConnected(configServer, logger, shutdownLatch);
+                            break;
+                        case FAILED_TO_CONNECT:
+                            logger.warning("Could not connect to storage.");
+                            shutdown();
+                        default:
+                            logger.warning("Unfamiliar ConnectionStatus value");
+                        }
+                    }
+                };
+
+                dbService.addConnectionListener(connectionListener);
+                logger.fine("Connecting to storage...");
+                dbService.connect();
+                
+                return configServer;
+            }
+            
+            @Override
+            public void removedService(ServiceReference reference, Object service) {
+                if (shutdownLatch.getCount() > 0) {
+                    // Lost config server while still running
+                    logger.warning("ConfigurationServer unexpectedly became unavailable");
                 }
+                super.removedService(reference, service);
             }
         };
-
-        connection.addListener(connectionListener);
-        connection.connect();
-        logger.fine("Connecting to storage...");
-
-        @SuppressWarnings("rawtypes")
-        ServiceReference configServiceRef = bundleContext.getServiceReference(ConfigurationServer.class.getName());
-        @SuppressWarnings("unchecked")
-        final ConfigurationServer configServer = (ConfigurationServer) bundleContext.getService(configServiceRef);
-        configServer.startListening(configuration.getConfigListenAddress());
+        configServerTracker.open();
         
-        BackendRegistry backendRegistry = null;
-        try {
-            backendRegistry = new BackendRegistry(bundleContext);
-            
-        } catch (Exception e) {
-            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", e);
-            System.exit(Constants.EXIT_BACKEND_LOAD_ERROR);
-        }
-
-        final Agent agent = new Agent(backendRegistry, configuration, daoFactory);
-        try {
-            logger.fine("Starting agent.");
-            agent.start();
-            
-            bundleContext.registerService(BackendService.class, new BackendService(), null);
-            
-        } catch (LaunchException le) {
-            logger.log(Level.SEVERE,
-                    "Agent could not start, probably because a configured backend could not be activated.",
-                    le);
-            System.exit(Constants.EXIT_BACKEND_START_ERROR);
-        }
-        logger.fine("Agent started.");
-
-        ctx.getConsole().getOutput().println("Agent id: " + agent.getId());
-        ctx.getConsole().getOutput().println("agent started.");
-        logger.fine("Agent id: " + agent.getId());
-
-        final CountDownLatch shutdownLatch = new CountDownLatch(1);
-        SignalHandler handler = new CustomSignalHandler(agent, configServer, logger, shutdownLatch);
-        Signal.handle(new Signal("INT"), handler);
-        Signal.handle(new Signal("TERM"), handler);
         try {
             // Wait for either SIGINT or SIGTERM
             shutdownLatch.await();
@@ -201,6 +194,20 @@
         return NAME;
     }
     
+    public void shutdown() {
+        // Exit application
+        if (shutdownLatch != null) {
+            shutdownLatch.countDown();
+        }
+        
+        if (daoTracker != null) {
+            daoTracker.close();
+        }
+        if (configServerTracker != null) {
+            configServerTracker.close();
+        }
+    }
+    
     // Does not need a reference of the enclosing type so lets declare this class static
     private static class CustomSignalHandler implements SignalHandler {
 
@@ -209,7 +216,8 @@
         private Logger logger;
         private CountDownLatch shutdownLatch;
         
-        CustomSignalHandler(Agent agent, ConfigurationServer configServer, Logger logger, CountDownLatch latch) {
+        CustomSignalHandler(Agent agent, ConfigurationServer configServer,
+                Logger logger, CountDownLatch latch) {
             this.agent = agent;
             this.configServer = configServer;
             this.logger = logger;
@@ -232,23 +240,91 @@
         
     }
 
+    private Agent startAgent(final Logger logger, final Storage storage,
+            AgentInfoDAO agentInfoDAO, BackendInfoDAO backendInfoDAO) {
+        BackendRegistry backendRegistry = null;
+        try {
+            backendRegistry = new BackendRegistry(bundleContext);
+            
+        } catch (Exception e) {
+            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", e);
+            System.exit(Constants.EXIT_BACKEND_LOAD_ERROR);
+        }
+
+        final Agent agent = new Agent(backendRegistry, configuration, storage, agentInfoDAO, backendInfoDAO);
+        try {
+            logger.fine("Starting agent.");
+            agent.start();
+            
+            bundleContext.registerService(BackendService.class, new BackendService(), null);
+            
+        } catch (LaunchException le) {
+            logger.log(Level.SEVERE,
+                    "Agent could not start, probably because a configured backend could not be activated.",
+                    le);
+            System.exit(Constants.EXIT_BACKEND_START_ERROR);
+        }
+        logger.fine("Agent started.");
+
+        logger.info("Agent id: " + agent.getId());
+        logger.info("agent started.");
+        return agent;
+    }
+
+    private void handleConnected(final ConfigurationServer configServer,
+            final Logger logger, final CountDownLatch shutdownLatch) {
+        Class<?>[] deps = new Class<?>[] {
+                Storage.class,
+                AgentInfoDAO.class,
+                BackendInfoDAO.class
+        };
+        daoTracker = new MultipleServiceTracker(bundleContext, deps, new Action() {
+
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                Storage storage = (Storage) services.get(Storage.class.getName());
+                AgentInfoDAO agentInfoDAO = (AgentInfoDAO) services
+                        .get(AgentInfoDAO.class.getName());
+                BackendInfoDAO backendInfoDAO = (BackendInfoDAO) services
+                        .get(BackendInfoDAO.class.getName());
+
+                final Agent agent = startAgent(logger, storage,
+                        agentInfoDAO, backendInfoDAO);
+
+                SignalHandler handler = new CustomSignalHandler(agent,
+                        configServer, logger, shutdownLatch);
+                Signal.handle(new Signal("INT"), handler);
+                Signal.handle(new Signal("TERM"), handler);
+            }
+
+            @Override
+            public void dependenciesUnavailable() {
+                if (shutdownLatch.getCount() > 0) {
+                    // In the rare case we lose one of our deps, gracefully shutdown
+                    logger.severe("Storage unexpectedly became unavailable");
+                    shutdown();
+                }
+            }
+            
+        });
+        daoTracker.open();
+    }
+
     static class ConfigurationCreator {
         public AgentStartupConfiguration create() throws InvalidConfigurationException {
             return AgentConfigsUtils.createAgentConfigs();
         }
     }
 
-    static class DAOFactoryCreator {
-        public DAOFactory create(AgentStartupConfiguration config) {
-            StorageProvider connProv = StorageProviderUtil.getStorageProvider(config);
-            final DAOFactory daoFactory = new DAOFactoryImpl(connProv);
-            return daoFactory;
-        }
-    }
-
     @Override
     public boolean isAvailableInShell() {
-    	return false;
+        return false;
     }
+    
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+    
 }
 
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -51,7 +51,7 @@
     public void verifyActivatorRegistersCommands() throws Exception {
 
         StubBundleContext bundleContext = new StubBundleContext();
-
+        
         Activator activator = new Activator();
 
         activator.start(bundleContext);
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -39,29 +39,37 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.agent.cli.impl.AgentApplication;
 import com.redhat.thermostat.agent.cli.impl.AgentApplication.ConfigurationCreator;
-import com.redhat.thermostat.agent.cli.impl.AgentApplication.DAOFactoryCreator;
+import com.redhat.thermostat.agent.command.ConfigurationServer;
 import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
 import com.redhat.thermostat.common.cli.Arguments;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-import com.redhat.thermostat.storage.dao.DAOFactory;
+import com.redhat.thermostat.storage.core.DbService;
+import com.redhat.thermostat.storage.core.DbServiceFactory;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.BackendInfoDAO;
 import com.redhat.thermostat.testutils.StubBundleContext;
 
 public class AgentApplicationTest {
@@ -69,12 +77,10 @@
     // TODO: Test i18nized versions when they come.
 
     private StubBundleContext context;
-    private DAOFactory daoFactory;
-    private Connection connection;
 
     private AgentApplication agent;
-
-    private ArgumentCaptor<ConnectionListener> listenerCaptor;
+    private ConfigurationServer configServer;
+    private DbService dbService;
     
     @Before
     public void setUp() throws InvalidConfigurationException {
@@ -87,18 +93,19 @@
         ConfigurationCreator configCreator = mock(ConfigurationCreator.class);
         when(configCreator.create()).thenReturn(config);
 
-        listenerCaptor = ArgumentCaptor.forClass(ConnectionListener.class);
-
-        connection = mock(Connection.class);
-        doNothing().when(connection).addListener(listenerCaptor.capture());
+        Storage storage = mock(Storage.class);
+        context.registerService(Storage.class, storage, null);
+        AgentInfoDAO agentInfoDAO = mock(AgentInfoDAO.class);
+        context.registerService(AgentInfoDAO.class.getName(), agentInfoDAO, null);
+        BackendInfoDAO backendInfoDAO = mock(BackendInfoDAO.class);
+        context.registerService(BackendInfoDAO.class.getName(), backendInfoDAO, null);
+        configServer = mock(ConfigurationServer.class);
+        context.registerService(ConfigurationServer.class.getName(), configServer, null);
+        DbServiceFactory dbServiceFactory = mock(DbServiceFactory.class);
+        dbService = mock(DbService.class);
+        when(dbServiceFactory.createDbService(anyString(), anyString(), anyString())).thenReturn(dbService);
 
-        daoFactory = mock(DAOFactory.class);
-        when(daoFactory.getConnection()).thenReturn(connection);
-
-        DAOFactoryCreator daoCreator = mock(DAOFactoryCreator.class);
-        when(daoCreator.create(config)).thenReturn(daoFactory);
-
-        agent = new AgentApplication(context, configCreator, daoCreator);
+        agent = new AgentApplication(context, configCreator, dbServiceFactory);
     }
 
     @After
@@ -119,34 +126,63 @@
     }
 
     @Test
-    public void testAgentStartupRegistersDAOs() throws CommandException {
-
-        doThrow(new ThatsAllThatWeCareAbout()).when(connection).connect();
+    public void testAgentStartup() throws CommandException, InterruptedException {
+        final long timeoutMillis = 5000L;
+        Arguments args = mock(Arguments.class);
+        final CommandContext commandContext = mock(CommandContext.class);
+        when(commandContext.getArguments()).thenReturn(args);
+        
+        // Immediately switch to CONNECTED state on dbService.connect
+        final ArgumentCaptor<ConnectionListener> listenerCaptor = ArgumentCaptor.forClass(ConnectionListener.class);
+        doNothing().when(dbService).addConnectionListener(listenerCaptor.capture());
+        
+        doAnswer(new Answer<Void>() {
 
-        Arguments args = mock(Arguments.class);
-        CommandContext commandContext = mock(CommandContext.class);
-
-        when(commandContext.getArguments()).thenReturn(args);
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                ConnectionListener listener = listenerCaptor.getValue();
+                listener.changed(ConnectionStatus.CONNECTED);
+                return null;
+            }
+            
+        }).when(dbService).connect();
 
-        try {
-            agent.run(commandContext);
-            fail("not supposed to get here");
-        } catch (ThatsAllThatWeCareAbout done) {
-            // agent.run() is so convoluted that we just want to test a part of
-            // it
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        final CommandException[] ce = new CommandException[1];
+        // Run agent in a new thread so we can timeout on failure
+        Thread t = new Thread(new Runnable() {
+            
+            @Override
+            public void run() {
+                // Finish when config server starts listening
+                doAnswer(new Answer<Void>() {
+
+                    @Override
+                    public Void answer(InvocationOnMock invocation) throws Throwable {
+                        latch.countDown();
+                        return null;
+                    }
+                }).when(configServer).startListening(anyString());
+                
+                try {
+                    agent.run(commandContext);
+                } catch (CommandException e) {
+                    ce[0] = e;
+                }
+            }
+        });
+        
+        t.start();
+        boolean ret = latch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+        if (ce[0] != null) {
+            throw ce[0];
         }
-
-        ConnectionListener listener = listenerCaptor.getValue();
-        listener.changed(ConnectionStatus.CONNECTED);
-
-        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
+        if (!ret) {
+            fail("Timeout expired!");
+        }
+        
     }
 
-    // FIXME test the rest of AgentApplication
-
-    @SuppressWarnings("serial")
-    private static class ThatsAllThatWeCareAbout extends RuntimeException {
-
-    }
 }
 
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/Agent.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/Agent.java	Thu Jan 31 14:40:22 2013 -0500
@@ -53,7 +53,6 @@
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.dao.BackendInfoDAO;
-import com.redhat.thermostat.storage.dao.DAOFactory;
 import com.redhat.thermostat.storage.model.AgentInformation;
 import com.redhat.thermostat.storage.model.BackendInformation;
 
@@ -68,8 +67,6 @@
     private final BackendRegistry backendRegistry;
     private final AgentStartupConfiguration config;
 
-    private final DAOFactory daoFactory;
-    
     private AgentInformation agentInfo;
     
     private Map<Backend, BackendInformation> backendInfos;
@@ -94,7 +91,6 @@
 
                     logger.info("Adding backend: " + backend);
                     
-                    backend.setDAOFactory(daoFactory);
                     backend.activate();
 
                     BackendInformation info = createBackendInformation(backend);
@@ -124,23 +120,24 @@
         }
     };
     
-    public Agent(BackendRegistry backendRegistry, AgentStartupConfiguration config, DAOFactory daoFactory)
-    {
-        this(backendRegistry, UUID.randomUUID(), config, daoFactory);
+    public Agent(BackendRegistry backendRegistry,
+            AgentStartupConfiguration config, Storage storage,
+            AgentInfoDAO agentInfoDao, BackendInfoDAO backendInfoDao) {
+        this(backendRegistry, UUID.randomUUID(), config, storage, agentInfoDao,
+                backendInfoDao);
     }
 
-    public Agent(BackendRegistry registry, UUID agentId, AgentStartupConfiguration config, DAOFactory daoFactory)
-    {
+    public Agent(BackendRegistry registry, UUID agentId,
+            AgentStartupConfiguration config, Storage storage,
+            AgentInfoDAO agentInfoDao, BackendInfoDAO backendInfoDao) {
         this.id = agentId;
         this.backendRegistry = registry;
         this.config = config;
-        this.storage = daoFactory.getStorage();
+        this.storage = storage;
         this.storage.setAgentId(agentId);
-        this.agentDao = daoFactory.getAgentInfoDAO();
-        this.backendDao = daoFactory.getBackendInfoDAO();
-        
-        this.daoFactory = daoFactory;
-        
+        this.agentDao = agentInfoDao;
+        this.backendDao = backendInfoDao;
+
         backendInfos = new ConcurrentHashMap<>();
         
         backendRegistry.addActionListener(backendRegistryListener);
--- a/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Thu Jan 31 14:40:22 2013 -0500
@@ -57,7 +57,6 @@
 public abstract class Backend implements Ordered {
 
     private boolean initialConfigurationComplete = false;
-    protected DAOFactory df = null;
     private boolean observeNewJvm = attachToNewProcessByDefault();
 
     private Map<String,String> config = new HashMap<>();
@@ -90,15 +89,6 @@
         initialConfigurationComplete = true;
     }
 
-    public final void setDAOFactory(DAOFactory df) {
-        this.df = df;
-        setDAOFactoryAction();
-    }
-
-    protected void setDAOFactoryAction() {
-        // Default implementation does nothing.
-    }
-
     /**
      * Set the named configuration to the given value.
      * The basic special properties {@code name}, {@code version} and
--- a/agent/core/src/test/java/com/redhat/thermostat/agent/AgentTest.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/AgentTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -37,17 +37,16 @@
 package com.redhat.thermostat.agent;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
 
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.isA;
-import static org.mockito.Mockito.atLeast;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -58,7 +57,6 @@
 
 import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
 import com.redhat.thermostat.backend.Backend;
-import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendRegistry;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
@@ -66,7 +64,6 @@
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.dao.BackendInfoDAO;
-import com.redhat.thermostat.storage.dao.DAOFactory;
 import com.redhat.thermostat.storage.model.AgentInformation;
 import com.redhat.thermostat.storage.model.BackendInformation;
 
@@ -77,8 +74,6 @@
     private BackendRegistry backendRegistry;
     private Backend backend;
 
-    private DAOFactory daoFactory;
-    
     private Storage storage;
     private AgentInfoDAO agentInfoDao;
     private BackendInfoDAO backendInfoDao;
@@ -93,11 +88,6 @@
         agentInfoDao = mock(AgentInfoDAO.class);
         backendInfoDao = mock(BackendInfoDAO.class);
         
-        daoFactory = mock(DAOFactory.class);
-        when(daoFactory.getStorage()).thenReturn(storage);
-        when(daoFactory.getAgentInfoDAO()).thenReturn(agentInfoDao);
-        when(daoFactory.getBackendInfoDAO()).thenReturn(backendInfoDao);
-
         backend = mock(Backend.class);
         when(backend.getName()).thenReturn("testname");
         when(backend.getDescription()).thenReturn("testdesc");
@@ -112,11 +102,7 @@
     @SuppressWarnings("unused")
     @Test
     public void testAgentInit() throws Exception {
-        Agent agent = new Agent(backendRegistry, config, daoFactory);
-
-        verify(daoFactory).getStorage();
-        verify(daoFactory).getAgentInfoDAO();
-        verify(daoFactory).getBackendInfoDAO();
+        Agent agent = new Agent(backendRegistry, config, storage, agentInfoDao, backendInfoDao);
         
         verify(backendRegistry).addActionListener(any(ActionListener.class));
     }
@@ -125,7 +111,7 @@
     public void testStartAgent() throws Exception {
         
         // Start agent.
-        Agent agent = new Agent(backendRegistry, config, daoFactory);
+        Agent agent = new Agent(backendRegistry, config, storage, agentInfoDao, backendInfoDao);
         
         agent.start();
 
@@ -142,7 +128,7 @@
         ArgumentCaptor<ActionListener> backendListener = ArgumentCaptor.forClass(ActionListener.class);
 
         // Start agent.
-        Agent agent = new Agent(backendRegistry, config, daoFactory);
+        Agent agent = new Agent(backendRegistry, config, storage, agentInfoDao, backendInfoDao);
         verify(backendRegistry).addActionListener(backendListener.capture());
         
         agent.start();
@@ -163,7 +149,6 @@
         
         listener.actionPerformed(actionEvent);
         
-        verify(backend).setDAOFactory(daoFactory);
         verify(backend).activate();
         
         assertTrue(agent.getBackendInfos().containsKey(backend));
@@ -188,7 +173,7 @@
     @Test
     public void testStopAgentWithPurging() throws Exception {
                 
-        Agent agent = new Agent(backendRegistry, config, daoFactory);
+        Agent agent = new Agent(backendRegistry, config, storage, agentInfoDao, backendInfoDao);
         agent.start();
         
         // stop agent
@@ -208,7 +193,7 @@
         when(config.getStartTime()).thenReturn(123L);
         when(config.purge()).thenReturn(false);
         
-        Agent agent = new Agent(backendRegistry, config, daoFactory);
+        Agent agent = new Agent(backendRegistry, config, storage, agentInfoDao, backendInfoDao);
         agent.start();
         
         // stop agent
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.java	Thu Jan 31 14:40:22 2013 -0500
@@ -85,6 +85,8 @@
     public void shutdown(int exitCode);
 
     public void awaitShutdown() throws InterruptedException;
+    
+    boolean isShutdown();
 
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java	Thu Jan 31 14:40:22 2013 -0500
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.client.swing.internal;
 
 import java.awt.EventQueue;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -49,6 +50,8 @@
 import javax.swing.UnsupportedLookAndFeelException;
 import javax.swing.plaf.nimbus.NimbusLookAndFeel;
 
+import org.osgi.framework.BundleContext;
+
 import com.redhat.thermostat.client.core.views.ClientConfigurationView;
 import com.redhat.thermostat.client.locale.LocaleResources;
 import com.redhat.thermostat.client.swing.internal.config.ConnectionConfiguration;
@@ -58,20 +61,18 @@
 import com.redhat.thermostat.client.ui.MainWindowController;
 import com.redhat.thermostat.client.ui.UiFacadeFactory;
 import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
 import com.redhat.thermostat.common.config.ClientPreferences;
 import com.redhat.thermostat.common.locale.Translate;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.common.utils.OSGIUtils;
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-import com.redhat.thermostat.storage.core.Connection.ConnectionType;
-import com.redhat.thermostat.storage.core.StorageProvider;
-import com.redhat.thermostat.storage.core.StorageProviderUtil;
-import com.redhat.thermostat.storage.dao.DAOFactory;
-import com.redhat.thermostat.storage.dao.DAOFactoryImpl;
+import com.redhat.thermostat.storage.core.DbService;
+import com.redhat.thermostat.storage.core.DbServiceFactory;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
 public class Main {
@@ -80,28 +81,41 @@
 
     private static final Logger logger = LoggingUtils.getLogger(Main.class);
 
-    private OSGIUtils serviceProvider;
+    private BundleContext context;
+    private ApplicationService appSvc;
     private UiFacadeFactory uiFacadeFactory;
-    private DAOFactory daoFactory;
+    private DbServiceFactory dbServiceFactory;
+    private DbService dbService;
+    private MultipleServiceTracker tracker;
     
-    public Main(Keyring keyring, UiFacadeFactory uiFacadeFactory, String[] args) {
+    public Main(BundleContext context, Keyring keyring,
+            ApplicationService appSvc, UiFacadeFactory uiFacadeFactory,
+            String[] args) {
         ClientPreferences prefs = new ClientPreferences(keyring);
-        StartupConfiguration config = new ConnectionConfiguration(prefs);
-        StorageProvider connProv = StorageProviderUtil.getStorageProvider(config);
+        ConnectionConfiguration config = new ConnectionConfiguration(prefs);
+        DbServiceFactory dbServiceFactory = new DbServiceFactory();
 
-        DAOFactory daoFactory = new DAOFactoryImpl(connProv);
-
-        init(OSGIUtils.getInstance(), uiFacadeFactory, daoFactory);
+        init(context, appSvc, uiFacadeFactory, dbServiceFactory,
+                config.getUsername(), config.getPassword(),
+                config.getDBConnectionString());
     }
 
-    Main(OSGIUtils serviceProvider, UiFacadeFactory uiFacadeFactory, DAOFactory daoFactory) {
-        init(serviceProvider, uiFacadeFactory, daoFactory);
+    Main(BundleContext context, ApplicationService appSvc,
+            UiFacadeFactory uiFacadeFactory, DbServiceFactory dbServiceFactory,
+            String username, String password, String connectionURL) {
+        init(context, appSvc, uiFacadeFactory, dbServiceFactory, username,
+                password, connectionURL);
     }
 
-    private void init(OSGIUtils serviceProvider, UiFacadeFactory uiFacadeFactory, DAOFactory daoFactory) {
-        this.serviceProvider = serviceProvider;
+    private void init(BundleContext context, ApplicationService appSvc,
+            UiFacadeFactory uiFacadeFactory, DbServiceFactory dbServiceFactory,
+            String username, String password, String connectionURL) {
+        this.context = context;
+        this.appSvc = appSvc;
         this.uiFacadeFactory = uiFacadeFactory;
-        this.daoFactory = daoFactory;
+        this.dbServiceFactory = dbServiceFactory;
+        this.dbService = dbServiceFactory.createDbService(username, password,
+                connectionURL);
     }
 
     private void setLAF() {
@@ -172,8 +186,7 @@
     }
 
     private void showGui() {
-        ApplicationService appSrv = serviceProvider.getService(ApplicationService.class);
-        final ExecutorService service = appSrv.getApplicationExecutor();
+        final ExecutorService service = appSvc.getApplicationExecutor();
         service.execute(new ConnectorSetup(service));
     }
     
@@ -186,13 +199,10 @@
         
         @Override
         public void run() {
-            
-            Connection connection = daoFactory.getConnection();
-            connection.setType(ConnectionType.LOCAL);
-            ConnectionListener connectionListener = new ConnectionHandler(connection, service);
-            connection.addListener(connectionListener);
+            ConnectionListener connectionListener = new ConnectionHandler(service);
+            dbService.addConnectionListener(connectionListener);
             try {
-                connection.connect();
+                dbService.connect();
             } catch (Throwable t) {
                 logger.log(Level.WARNING, "connection attempt failed: ", t);
             }
@@ -200,26 +210,20 @@
     }
     
     private class Connector implements Runnable {
-        private Connection connection;
-        Connector(Connection connection) {
-            this.connection = connection;
-        }
         
         @Override
         public void run() {
             try {
-                connection.connect();
+                dbService.connect();
             } catch (Throwable t) {
                 logger.log(Level.WARNING, "connection attempt failed: ", t);
             }
         }
     }
     
-    private class ConnectionAttemp implements Runnable {
-        private Connection connection;
+    private class ConnectionAttempt implements Runnable {
         private ExecutorService service;
-        public ConnectionAttemp(Connection connection, ExecutorService service) {
-            this.connection = connection;
+        public ConnectionAttempt(ExecutorService service) {
             this.service = service;
         }
         
@@ -243,75 +247,92 @@
             case JOptionPane.CANCEL_OPTION:
             case JOptionPane.CLOSED_OPTION:
             case JOptionPane.NO_OPTION:
-                uiFacadeFactory
-                        .shutdown(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
+                shutdown();
                 break;
 
             case JOptionPane.YES_OPTION:
             default:
-                createPreferencesDialog(connection, service);
+                createPreferencesDialog(service);
                 break;
             }
         }
     }
     
-    private void connect(Connection connection, ExecutorService service) {
-        service.execute(new Connector(connection));
+    private void connect(ExecutorService service) {
+        service.execute(new Connector());
     }
     
     private class MainClientConfigReconnector implements ClientConfigReconnector {
-        private Connection connection;
         private ExecutorService service;
-        public MainClientConfigReconnector(Connection connection, ExecutorService service) {
-            this.connection = connection;
+        public MainClientConfigReconnector(ExecutorService service) {
             this.service = service;
         }
         
         @Override
         public void reconnect(ClientPreferences prefs) {
-            connection.setUrl(prefs.getConnectionUrl());
-            connect(connection, service);
+            // Recreate DbService with potentially modified parameters
+            dbService = dbServiceFactory.createDbService(prefs.getUserName(), prefs.getPassword(), prefs.getConnectionUrl());
+            dbService.addConnectionListener(new ConnectionHandler(service));
+            connect(service);
         }
 
         @Override
         public void abort() {
-            uiFacadeFactory.shutdown(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
+            shutdown();
         }
     }
     
-    private void createPreferencesDialog(final Connection connection, final ExecutorService service) {
+    private void createPreferencesDialog(final ExecutorService service) {
 
         ClientPreferences prefs = new ClientPreferences(OSGIUtils.getInstance().getService(Keyring.class));
         ClientConfigurationView configDialog = new ClientConfigurationSwing();
         ClientConfigurationController controller =
-                new ClientConfigurationController(prefs, configDialog, new MainClientConfigReconnector(connection, service));
+                new ClientConfigurationController(prefs, configDialog, new MainClientConfigReconnector(service));
         
         controller.showDialog();
     }
     
     private class ConnectionHandler implements ConnectionListener {
         private boolean retry;
-        private Connection connection;
         private ExecutorService service;
-        public ConnectionHandler(Connection connection, ExecutorService service) {
-            this.connection = connection;
+        public ConnectionHandler(ExecutorService service) {
             this.retry = true;
             this.service = service;
         }
         
         private void showConnectionAttemptWarning() {
-            SwingUtilities.invokeLater(new ConnectionAttemp(connection, service));
+            SwingUtilities.invokeLater(new ConnectionAttempt(service));
         }
         
         @Override
         public void changed(ConnectionStatus newStatus) {
             if (newStatus == ConnectionStatus.CONNECTED) {
-                // register the storage, so other services can request it
-                daoFactory.registerDAOsAndStorageAsOSGiServices();
-                uiFacadeFactory.setHostInfoDao(daoFactory.getHostInfoDAO());
-                uiFacadeFactory.setVmInfoDao(daoFactory.getVmInfoDAO());
+                Class<?>[] deps = new Class<?>[] {
+                        HostInfoDAO.class,
+                        VmInfoDAO.class
+                };
+                tracker = new MultipleServiceTracker(context, deps, new Action() {
+                    
+                    @Override
+                    public void dependenciesAvailable(Map<String, Object> services) {
+                        HostInfoDAO hostInfoDAO = (HostInfoDAO) services.get(HostInfoDAO.class.getName());
+                        uiFacadeFactory.setHostInfoDao(hostInfoDAO);
+                        VmInfoDAO vmInfoDAO = (VmInfoDAO) services.get(VmInfoDAO.class.getName());
+                        uiFacadeFactory.setVmInfoDao(vmInfoDAO);
+                        
+                        showMainWindow();
+                    }
 
-                showMainWindow();
+                    @Override
+                    public void dependenciesUnavailable() {
+                        if (!uiFacadeFactory.isShutdown()) {
+                            // In the rare case we lose one of our deps, gracefully shutdown
+                            logger.severe("Storage unexpectedly became unavailable");
+                            shutdown();
+                        }
+                    }
+                });
+                tracker.open();
             } else if (newStatus == ConnectionStatus.FAILED_TO_CONNECT) {
                 if (retry) {
                     retry = false;
@@ -322,12 +343,19 @@
                             translator.localize(LocaleResources.CONNECTION_FAILED_TO_CONNECT_DESCRIPTION),
                             translator.localize(LocaleResources.CONNECTION_FAILED_TO_CONNECT_TITLE),
                             JOptionPane.ERROR_MESSAGE);
-                    uiFacadeFactory.shutdown(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
+                    shutdown();
                 }
             }
         }
     }
-
+    
+    public void shutdown() {
+        uiFacadeFactory.shutdown();
+        
+        if (tracker != null) {
+            tracker.close();
+        }
+    }
 
     private void showMainWindow() {
         SwingUtilities.invokeLater(new ShowMainWindow());
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java	Thu Jan 31 14:40:22 2013 -0500
@@ -193,5 +193,10 @@
         hostInformationServices.remove(hostInfoService);
     }
 
+    @Override
+    public boolean isShutdown() {
+        return shutdown.getCount() == 0;
+    }
+
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java	Thu Jan 31 14:40:22 2013 -0500
@@ -39,11 +39,10 @@
 import java.util.Arrays;
 import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.Map;
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
 
 import com.redhat.thermostat.client.core.views.AgentInformationViewProvider;
 import com.redhat.thermostat.client.core.views.ClientConfigViewProvider;
@@ -61,7 +60,10 @@
 import com.redhat.thermostat.client.swing.internal.views.SwingSummaryViewProvider;
 import com.redhat.thermostat.client.swing.internal.views.SwingVmInformationViewProvider;
 import com.redhat.thermostat.client.ui.UiFacadeFactory;
+import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 import com.redhat.thermostat.storage.core.HostRef;
@@ -74,8 +76,8 @@
     private VMContextActionServiceTracker vmContextActionTracker;
 
     private CommandRegistry cmdReg;
+    private MultipleServiceTracker dependencyTracker;
 
-    @SuppressWarnings({ "rawtypes", "unchecked" })
     @Override
     public void start(final BundleContext context) throws Exception {
         
@@ -102,11 +104,18 @@
         ClientConfigViewProvider clientConfigViewProvider = new SwingClientConfigurationViewProvider();
         context.registerService(ClientConfigViewProvider.class, clientConfigViewProvider, null);
         
-        ServiceTracker tracker = new ServiceTracker(context, Keyring.class.getName(), null) {
+        Class<?>[] deps = new Class<?>[] {
+                Keyring.class,
+                ApplicationService.class
+        };
+        dependencyTracker = new MultipleServiceTracker(context, deps, new Action() {
+            
+            private Main main;
+
             @Override
-            public Object addingService(ServiceReference reference) {
-              
-                Keyring keyring = (Keyring) context.getService(reference);
+            public void dependenciesAvailable(Map<String, Object> services) {
+                Keyring keyring = (Keyring) services.get(Keyring.class.getName());
+                ApplicationService appSvc = (ApplicationService) services.get(ApplicationService.class.getName());
                 
                 UiFacadeFactory uiFacadeFactory = new UiFacadeFactoryImpl(context);
 
@@ -120,15 +129,20 @@
                 vmContextActionTracker.open();
 
                 cmdReg = new CommandRegistryImpl(context);
-                Main main = new Main(keyring, uiFacadeFactory, new String[0]);
+                main = new Main(context, keyring, appSvc, uiFacadeFactory, new String[0]);
                 
                 GUIClientCommand cmd = new GUIClientCommand(main);
                 cmdReg.registerCommands(Arrays.asList(cmd));
-                
-                return super.addingService(reference);
             }
-        };
-        tracker.open();
+
+            @Override
+            public void dependenciesUnavailable() {
+                if (main != null) {
+                    main.shutdown();
+                }
+            }
+        });
+        dependencyTracker.open();
     }
 
     @Override
@@ -136,6 +150,7 @@
         infoServiceTracker.close();
         hostContextActionTracker.close();
         vmContextActionTracker.close();
+        dependencyTracker.close();
         cmdReg.unregisterCommands();
     }
 }
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainTest.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.swing.internal;
 
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -58,30 +59,35 @@
 import com.redhat.thermostat.client.ui.UiFacadeFactory;
 import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.TimerFactory;
-import com.redhat.thermostat.common.utils.OSGIUtils;
-import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-import com.redhat.thermostat.storage.dao.DAOFactory;
+import com.redhat.thermostat.storage.core.DbService;
+import com.redhat.thermostat.storage.core.DbServiceFactory;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.testutils.StubBundleContext;
 
 public class MainTest {
 
     private ExecutorService executorService;
-    private OSGIUtils serviceProvider;
 
     private MainWindowController mainWindowController;
     private UiFacadeFactory uiFactory;
 
-    private Connection connection;
     private ArgumentCaptor<ConnectionListener> connectionListenerCaptor;
 
-    private DAOFactory daoFactory;
+    private DbServiceFactory dbServiceFactory;
+    private DbService dbService;
 
     private TimerFactory timerFactory;
+    private StubBundleContext context;
+    private ApplicationService appService;
 
     @Before
     public void setUp() {
-        ApplicationService appService = mock(ApplicationService.class);
+        context = new StubBundleContext();
+        appService = mock(ApplicationService.class);
+        context.registerService(ApplicationService.class, appService, null);
 
         executorService = mock(ExecutorService.class);
         doAnswer(new Answer<Void>() {
@@ -95,20 +101,17 @@
 
         when(appService.getApplicationExecutor()).thenReturn(executorService);
 
-        serviceProvider = mock(OSGIUtils.class);
-        when(serviceProvider.getService(ApplicationService.class)).thenReturn(appService);
-
         mainWindowController = mock(MainWindowController.class);
 
         uiFactory = mock(UiFacadeFactory.class);
         when(uiFactory.getMainWindow()).thenReturn(mainWindowController);
 
-        connection = mock(Connection.class);
+        dbServiceFactory = mock(DbServiceFactory.class);
+        dbService = mock(DbService.class);
+        when(dbServiceFactory.createDbService(anyString(), anyString(), anyString())).thenReturn(dbService);
+        
         connectionListenerCaptor = ArgumentCaptor.forClass(ConnectionListener.class);
-        doNothing().when(connection).addListener(connectionListenerCaptor.capture());
-
-        daoFactory = mock(DAOFactory.class);
-        when(daoFactory.getConnection()).thenReturn(connection);
+        doNothing().when(dbService).addConnectionListener(connectionListenerCaptor.capture());
 
         timerFactory = mock(TimerFactory.class);
         when(appService.getTimerFactory()).thenReturn(timerFactory);
@@ -129,7 +132,7 @@
 
     @Test
     public void verifyRunWaitsForShutdown() throws Exception {
-        Main main = new Main(serviceProvider, uiFactory, daoFactory);
+        Main main = new Main(context, appService, uiFactory, dbServiceFactory, null, null, null);
 
         main.run();
 
@@ -140,19 +143,23 @@
 
     @Test
     public void verifyConnectionIsMade() throws Exception {
-        Main main = new Main(serviceProvider, uiFactory, daoFactory);
+        Main main = new Main(context, appService, uiFactory, dbServiceFactory, null, null, null);
 
         main.run();
 
         handleAllEdtEvents();
 
-        verify(connection).connect();
+        verify(dbService).connect();
 
     }
 
     @Test
     public void verifySuccessfulConnectionTriggersMainWindowToBeShown() throws Exception {
-        Main main = new Main(serviceProvider, uiFactory, daoFactory);
+        HostInfoDAO hostInfoDAO = mock(HostInfoDAO.class);
+        context.registerService(HostInfoDAO.class, hostInfoDAO, null);
+        VmInfoDAO vmInfoDAO = mock(VmInfoDAO.class);
+        context.registerService(VmInfoDAO.class, vmInfoDAO, null);
+        Main main = new Main(context, appService, uiFactory, dbServiceFactory, null, null, null);
 
         main.run();
 
@@ -166,28 +173,11 @@
         verify(mainWindowController).showMainMainWindow();
     }
 
-    @Test
-    public void verifySuccessfulConnectionRegistersDAOs() throws Exception {
-
-        Main main = new Main(serviceProvider, uiFactory, daoFactory);
-
-        main.run();
-
-        handleAllEdtEvents();
-
-        ConnectionListener connectionListener = connectionListenerCaptor.getValue();
-        connectionListener.changed(ConnectionStatus.CONNECTED);
-
-        handleAllEdtEvents();
-
-        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
-    }
-
     @Ignore("this prompts the user with some gui")
     @Test
     public void verifyFailedConnectionTriggersShutdown() throws Exception {
 
-        Main main = new Main(serviceProvider, uiFactory, daoFactory);
+        Main main = new Main(context, appService, uiFactory, dbServiceFactory, null, null, null);
 
         main.run();
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/internal/Activator.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/internal/Activator.java	Thu Jan 31 14:40:22 2013 -0500
@@ -42,7 +42,7 @@
 import com.redhat.thermostat.common.ApplicationService;
 
 public class Activator implements BundleActivator {
-
+    
     @Override
     public void start(BundleContext context) throws Exception {
         ApplicationService service = new ApplicationServiceImpl();
@@ -51,7 +51,7 @@
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        // Nothing to do here.
+        // Services unregistered automatically
     }
 }
 
--- a/distribution/src/test/java/com/redhat/thermostat/distribution/StorageTest.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/distribution/src/test/java/com/redhat/thermostat/distribution/StorageTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -77,9 +77,12 @@
             }
         });
 
-        storage.expect("agent started");
-
-        killRecursively(process[0]);
+        try {
+            storage.expectErr("agent started");
+        }
+        finally {
+            killRecursively(process[0]);
+        }
     }
 
 }
--- a/storage/core/pom.xml	Thu Jan 31 14:37:55 2013 -0500
+++ b/storage/core/pom.xml	Thu Jan 31 14:40:22 2013 -0500
@@ -61,6 +61,7 @@
           <instructions>
             <Bundle-SymbolicName>com.redhat.thermostat.storage.core</Bundle-SymbolicName>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-Activator>com.redhat.thermostat.storage.internal.Activator</Bundle-Activator>
             <Export-Package>
               com.redhat.thermostat.storage.core,
               com.redhat.thermostat.storage.config,
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/DbService.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/DbService.java	Thu Jan 31 14:40:22 2013 -0500
@@ -38,6 +38,7 @@
 
 import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.ConnectionException;
+import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 
 @Service
 public interface DbService {
@@ -64,5 +65,20 @@
      *             if not connected to storage.
      */
     String getConnectionUrl();
+    
+    /**
+     * Registers the supplied ConnectionListener to be notified
+     * when the status of the database connection changes.
+     * @param listener - the listener to be registered
+     */
+    void addConnectionListener(ConnectionListener listener);
+    
+    /**
+     * Unregisters the supplied ConnectionListener if it was
+     * previously registered via {@link #addConnectionListener(ConnectionListener)}.
+     * @param listener - the listener to be unregistered
+     */
+    void removeConnectionListener(ConnectionListener listener);
+    
 }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/HostLatestPojoListGetter.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/HostLatestPojoListGetter.java	Thu Jan 31 14:40:22 2013 -0500
@@ -43,8 +43,8 @@
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.TimeStampedPojo;
 
 public class HostLatestPojoListGetter<T extends TimeStampedPojo> {
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/StorageProviderUtil.java	Thu Jan 31 14:37:55 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.storage.core;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-
-public class StorageProviderUtil {
-
-    // FIXME: This should go away once GuiClientCommand and AgentApplication use
-    // DbService via launcher for establishing a connection. I.e. those should just
-    // specify isStorageRequired() == true and the launcher handles the rest
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public static StorageProvider getStorageProvider(StartupConfiguration config) {
-        Bundle bundle = FrameworkUtil.getBundle(StorageProviderUtil.class);
-        BundleContext ctxt = bundle.getBundleContext();
-        try {
-            ServiceReference[] refs = ctxt.getServiceReferences(StorageProvider.class.getName(), null);
-            for (int i = 0; i < refs.length; i++) {
-                StorageProvider prov = (StorageProvider) ctxt.getService(refs[i]);
-                prov.setConfig(config);
-                if (prov.canHandleProtocol()) {
-                    return prov;
-                }
-            }
-        } catch (InvalidSyntaxException e) {
-            // This should not happen since we use a null filter
-            throw new AssertionError();
-        }
-        return null;
-    }
-}
-
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/VmLatestPojoListGetter.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/VmLatestPojoListGetter.java	Thu Jan 31 14:40:22 2013 -0500
@@ -43,8 +43,8 @@
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.TimeStampedPojo;
 
 public class VmLatestPojoListGetter<T extends TimeStampedPojo> {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/Activator.java	Thu Jan 31 14:40:22 2013 -0500
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.BackendInfoDAO;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.internal.dao.AgentInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.BackendInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.HostInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.NetworkInterfaceInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.VmInfoDAOImpl;
+
+public class Activator implements BundleActivator {
+    
+    ServiceTracker tracker;
+    List<ServiceRegistration> regs;
+    
+    public Activator() {
+        regs = new ArrayList<>();
+    }
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        tracker = new ServiceTracker(context, Storage.class, null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                Storage storage = (Storage) super.addingService(reference);
+                AgentInfoDAO agentInfoDao = new AgentInfoDAOImpl(storage);
+                ServiceRegistration reg = context.registerService(AgentInfoDAO.class.getName(), agentInfoDao, null);
+                regs.add(reg);
+                BackendInfoDAO backendInfoDao = new BackendInfoDAOImpl(storage);
+                reg = context.registerService(BackendInfoDAO.class.getName(), backendInfoDao, null);
+                regs.add(reg);
+                HostInfoDAO hostInfoDao = new HostInfoDAOImpl(storage, agentInfoDao);
+                reg = context.registerService(HostInfoDAO.class.getName(), hostInfoDao, null);
+                regs.add(reg);
+                NetworkInterfaceInfoDAO networkInfoDao = new NetworkInterfaceInfoDAOImpl(storage);
+                reg = context.registerService(NetworkInterfaceInfoDAO.class.getName(), networkInfoDao, null);
+                regs.add(reg);
+                VmInfoDAO vmInfoDao = new VmInfoDAOImpl(storage);
+                reg = context.registerService(VmInfoDAO.class.getName(), vmInfoDao, null);
+                regs.add(reg);
+                return storage;
+            }
+            
+            @Override
+            public void removedService(ServiceReference reference,
+                    Object service) {
+                for (ServiceRegistration reg : regs) {
+                    reg.unregister();
+                }
+                super.removedService(reference, service);
+            }
+        };
+        
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        tracker.close();
+    }
+}
+
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/DbServiceImpl.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/DbServiceImpl.java	Thu Jan 31 14:40:22 2013 -0500
@@ -38,43 +38,52 @@
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.storage.config.ConnectionConfiguration;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.ConnectionException;
 import com.redhat.thermostat.storage.core.DbService;
+import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageException;
 import com.redhat.thermostat.storage.core.StorageProvider;
-import com.redhat.thermostat.storage.core.StorageProviderUtil;
-import com.redhat.thermostat.storage.dao.DAOFactory;
-import com.redhat.thermostat.storage.dao.DAOFactoryImpl;
 
 public class DbServiceImpl implements DbService {
     
     @SuppressWarnings("rawtypes")
-    private ServiceRegistration registration;
+    private ServiceRegistration dbServiceReg;
+    @SuppressWarnings("rawtypes")
+    private ServiceRegistration storageReg;
     
-    private DAOFactory daoFactory;
+    private Storage storage;
     private BundleContext context;
     private String dbUrl;
     
     DbServiceImpl(String username, String password, String dbUrl) throws StorageException {
-        this(FrameworkUtil.getBundle(DbService.class).getBundleContext(), getDAOFactory(username, password, dbUrl), dbUrl);
+        BundleContext context = FrameworkUtil.getBundle(DbService.class).getBundleContext();
+        Storage storage = createStorage(context, username, password, dbUrl);
+        init(context, storage, dbUrl);
     }
 
     // for testing
-    DbServiceImpl(BundleContext context, DAOFactory daoFactory, String dbUrl) {
-        this.daoFactory = daoFactory;
+    DbServiceImpl(BundleContext context, Storage storage, String dbUrl) {
+        init(context, storage, dbUrl);
+    }
+    
+    private void init(BundleContext context, Storage storage, String dbUrl) {
+        this.storage = storage;
         this.context = context;
         this.dbUrl = dbUrl;
     }
 
     public void connect() throws ConnectionException {
         try {
-            daoFactory.getConnection().connect();
-            registration = context.registerService(DbService.class, this, null);
-            daoFactory.registerDAOsAndStorageAsOSGiServices();
+            storage.getConnection().connect();
+            dbServiceReg = context.registerService(DbService.class, this, null);
+            storageReg = context.registerService(Storage.class.getName(), storage, null);
         } catch (Exception cause) {
             throw new ConnectionException(cause);
         }
@@ -82,9 +91,9 @@
     
     public void disconnect() throws ConnectionException {
         try {
-            daoFactory.unregisterDAOsAndStorageAsOSGiServices();
-            daoFactory.getConnection().disconnect();
-            registration.unregister();
+            storage.getConnection().disconnect();
+            storageReg.unregister();
+            dbServiceReg.unregister();
         } catch (Exception cause) {
             throw new ConnectionException(cause);
         }
@@ -108,15 +117,43 @@
         return new DbServiceImpl(username, password, dbUrl);
     }
 
-    private static DAOFactory getDAOFactory(String username, String password, String dbUrl) throws StorageException {
+    private static Storage createStorage(BundleContext context, String username, String password, String dbUrl) throws StorageException {
         StartupConfiguration config = new ConnectionConfiguration(dbUrl, username, password);
-        StorageProvider prov = StorageProviderUtil.getStorageProvider(config);
+        StorageProvider prov = getStorageProvider(context, config);
         if (prov == null) {
             // no suitable provider found
             throw new StorageException("No storage found for URL " + dbUrl);
         }
-        return new DAOFactoryImpl(prov);
+        return prov.createStorage();
+    }
+    
+    private static StorageProvider getStorageProvider(BundleContext context, StartupConfiguration config) {
+        try {
+            ServiceReference[] refs = context.getServiceReferences(StorageProvider.class.getName(), null);
+            for (int i = 0; i < refs.length; i++) {
+                StorageProvider prov = (StorageProvider) context.getService(refs[i]);
+                prov.setConfig(config);
+                if (prov.canHandleProtocol()) {
+                    return prov;
+                }
+                else {
+                    context.ungetService(refs[i]);
+                }
+            }
+        } catch (InvalidSyntaxException e) {
+            throw new AssertionError("Bad filter used to get StorageProviders", e);
+        }
+        return null;
+    }
+
+    @Override
+    public void addConnectionListener(ConnectionListener listener) {
+        storage.getConnection().addListener(listener);
+    }
+
+    @Override
+    public void removeConnectionListener(ConnectionListener listener) {
+        storage.getConnection().removeListener(listener);
     }
     
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/ActivatorTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */ 
+
+package com.redhat.thermostat.storage.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.BackendInfoDAO;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.internal.dao.AgentInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.BackendInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.HostInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.NetworkInterfaceInfoDAOImpl;
+import com.redhat.thermostat.storage.internal.dao.VmInfoDAOImpl;
+import com.redhat.thermostat.testutils.StubBundleContext;
+
+public class ActivatorTest {
+    
+    @Test
+    public void verifyActivatorDoesNotRegisterServiceOnMissingDeps() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertEquals(0, context.getAllServices().size());
+        assertEquals(1, context.getServiceListeners().size());
+
+        activator.stop(context);
+    }
+
+    @Test
+    public void verifyActivatorRegistersServices() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+        Storage storage = mock(Storage.class);
+
+        context.registerService(Storage.class, storage, null);
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertTrue(context.isServiceRegistered(HostInfoDAO.class.getName(), HostInfoDAOImpl.class));
+        assertTrue(context.isServiceRegistered(NetworkInterfaceInfoDAO.class.getName(), NetworkInterfaceInfoDAOImpl.class));
+        assertTrue(context.isServiceRegistered(VmInfoDAO.class.getName(), VmInfoDAOImpl.class));
+        assertTrue(context.isServiceRegistered(AgentInfoDAO.class.getName(), AgentInfoDAOImpl.class));
+        assertTrue(context.isServiceRegistered(BackendInfoDAO.class.getName(), BackendInfoDAOImpl.class));
+
+        activator.stop(context);
+
+        assertEquals(0, context.getServiceListeners().size());
+        assertEquals(1, context.getAllServices().size());
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/DbServiceImplTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+import com.redhat.thermostat.storage.core.Connection;
+import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
+import com.redhat.thermostat.storage.core.DbService;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.testutils.StubBundleContext;
+
+public class DbServiceImplTest {
+    
+    private Connection connection;
+    private Storage storage;
+    private DbService dbService;
+    private StubBundleContext context;
+    
+    @Before
+    public void setup() {
+        context = new StubBundleContext();
+        connection = mock(Connection.class);
+
+        storage = mock(Storage.class);
+        when(storage.getConnection()).thenReturn(connection);
+
+        dbService = new DbServiceImpl(context, storage, "http://someUrl.ignored.com");
+    }
+    
+    @After
+    public void teardown() {
+        dbService = null;
+        context = null;
+    }
+
+    @Test
+    public void testConnect() {
+        dbService.connect();
+
+        verify(connection).connect();
+    }
+    
+    @Test
+    public void testConnectRegistersDbService() {
+        dbService.connect();
+
+        verify(connection).connect();
+        @SuppressWarnings("rawtypes")
+        ServiceReference dbServiceRef = context.getServiceReference(DbService.class);
+        // connect registers DbService
+        assertNotNull(dbServiceRef);
+        // make sure we really get the same instance
+        assertTrue(dbService.equals(context.getService(dbServiceRef)));
+    }
+    
+    @Test
+    public void testConnectRegistersStorage() {
+        dbService.connect();
+
+        verify(connection).connect();
+        @SuppressWarnings("rawtypes")
+        ServiceReference storageRef = context.getServiceReference(Storage.class);
+        // connect registers DbService
+        assertNotNull(storageRef);
+        // make sure we really get the same instance
+        assertTrue(storage.equals(context.getService(storageRef)));
+    }
+
+    @Test
+    public void testDisconnect() {
+        dbService.connect();
+        assertNotNull(context.getServiceReference(DbService.class));
+        
+        dbService.disconnect();
+
+        verify(connection).disconnect();
+    }
+
+    @Test
+    public void testDisconnectUnregistersDbService() {
+        dbService.connect();
+        assertNotNull(context.getServiceReference(DbService.class));
+        
+        dbService.disconnect();
+
+        verify(connection).disconnect();
+        // disconnect unregisters DbService
+        assertNull(context.getServiceReference(DbService.class));
+    }
+    
+    @Test
+    public void testDisconnectUnregistersStorage() {
+        dbService.connect();
+        assertNotNull(context.getServiceReference(Storage.class));
+        
+        dbService.disconnect();
+
+        verify(connection).disconnect();
+        // disconnect unregisters Storage
+        assertNull(context.getServiceReference(Storage.class));
+    }
+    
+    @Test
+    public void canGetStorageUrl() {
+        String connectionURL = "http://test.example.com:8082";
+
+        dbService = new DbServiceImpl(context, null, connectionURL);
+        assertEquals(connectionURL, dbService.getConnectionUrl());
+    }
+    
+    @Test
+    public void testAddListener() {
+        ConnectionListener listener = mock(ConnectionListener.class);
+        dbService.addConnectionListener(listener);
+        verify(connection).addListener(listener);
+    }
+    
+    @Test
+    public void testRemoveListener() {
+        // Remove called regardless of listener actually being added
+        ConnectionListener listener = mock(ConnectionListener.class);
+        dbService.removeConnectionListener(listener);
+        verify(connection).removeListener(listener);
+    }
+}
+
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/DbServiceTest.java	Thu Jan 31 14:37:55 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.storage.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.osgi.framework.ServiceReference;
-
-import com.redhat.thermostat.storage.core.Connection;
-import com.redhat.thermostat.storage.core.DbService;
-import com.redhat.thermostat.storage.dao.DAOFactory;
-import com.redhat.thermostat.testutils.StubBundleContext;
-
-public class DbServiceTest {
-    
-    private Connection connection;
-    private DAOFactory daoFactory;
-    private DbService dbService;
-    private StubBundleContext context;
-    
-    @Before
-    public void setup() {
-        context = new StubBundleContext();
-        connection = mock(Connection.class);
-
-        daoFactory = mock(DAOFactory.class);
-        when(daoFactory.getConnection()).thenReturn(connection);
-
-        dbService = new DbServiceImpl(context, daoFactory, "http://someUrl.ignored.com");
-    }
-    
-    @After
-    public void teardown() {
-        dbService = null;
-        context = null;
-    }
-
-    @Test
-    public void testConnect() {
-        dbService.connect();
-
-        verify(connection).connect();
-        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
-    }
-    
-    @Test
-    public void testConnectRegistersDbService() {
-        dbService.connect();
-
-        verify(connection).connect();
-        @SuppressWarnings("rawtypes")
-        ServiceReference dbServiceRef = context.getServiceReference(DbService.class);
-        // connect registers DbService
-        assertNotNull(dbServiceRef);
-        // make sure we really get the same instance
-        assertTrue(dbService.equals(context.getService(dbServiceRef)));
-        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
-    }
-
-    @Test
-    public void testDisconnect() {
-        dbService.connect();
-        assertNotNull(context.getServiceReference(DbService.class));
-        
-        dbService.disconnect();
-
-        verify(daoFactory).unregisterDAOsAndStorageAsOSGiServices();
-        verify(connection).disconnect();
-    }
-
-    @Test
-    public void testDisconnectUnregistersService() {
-        dbService.connect();
-        assertNotNull(context.getServiceReference(DbService.class));
-        
-        dbService.disconnect();
-
-        verify(daoFactory).unregisterDAOsAndStorageAsOSGiServices();
-        verify(connection).disconnect();
-        // disconnect unregisters DbService
-        assertNull(context.getServiceReference(DbService.class));
-    }
-    
-    @Test
-    public void canGetStorageUrl() {
-        String connectionURL = "http://test.example.com:8082";
-
-        dbService = new DbServiceImpl(context, null, connectionURL);
-        assertEquals(connectionURL, dbService.getConnectionUrl());
-    }
-}
-
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Thu Jan 31 14:40:22 2013 -0500
@@ -37,10 +37,8 @@
 package com.redhat.thermostat.backend.system;
 
 import java.net.URISyntaxException;
-import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
-import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -54,6 +52,7 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.dao.HostInfoDAO;
 import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
 import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
 import com.redhat.thermostat.utils.ProcDataSource;
 
@@ -64,10 +63,6 @@
     private HostInfoDAO hostInfos;
     private NetworkInterfaceInfoDAO networkInterfaces;
 
-    private final VmStatusChangeNotifier notifier;
-
-    private final Set<Integer> pidsToMonitor = new CopyOnWriteArraySet<>();
-
     private long procCheckInterval = 1000; // TODO make this configurable.
 
     private Timer timer = null;
@@ -79,9 +74,10 @@
     private final HostInfoBuilder hostInfoBuilder;
 
 
-    public SystemBackend(VmStatusChangeNotifier notifier) {
+    public SystemBackend(HostInfoDAO hostInfoDAO, NetworkInterfaceInfoDAO netInfoDAO, VmInfoDAO vmInfoDAO, VmStatusChangeNotifier notifier) {
         super(new BackendID("System Backend", SystemBackend.class.getName()));
-        this.notifier = notifier;
+        this.hostInfos = hostInfoDAO;
+        this.networkInterfaces = netInfoDAO;
 
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers basic information from the system");
@@ -89,13 +85,7 @@
         
         ProcDataSource source = new ProcDataSource();
         hostInfoBuilder = new HostInfoBuilder(source);
-    }
-
-    @Override
-    protected void setDAOFactoryAction() {
-        hostInfos = df.getHostInfoDAO();
-        networkInterfaces = df.getNetworkInterfaceInfoDAO();
-        hostListener = new JvmStatHostListener(df.getVmInfoDAO(), notifier);
+        hostListener = new JvmStatHostListener(vmInfoDAO, notifier);
     }
 
     @Override
@@ -103,9 +93,6 @@
         if (timer != null) {
             return true;
         }
-        if (df == null) {
-            throw new IllegalStateException("Cannot activate backend without DAOFactory.");
-        }
 
         if (!getObserveNewJvm()) {
             logger.fine("not monitoring new vms");
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java	Thu Jan 31 14:40:22 2013 -0500
@@ -36,57 +36,69 @@
 
 package com.redhat.thermostat.backend.system.osgi;
 
+import java.util.Map;
+
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendService;
 import com.redhat.thermostat.backend.system.SystemBackend;
 import com.redhat.thermostat.backend.system.VmStatusChangeNotifier;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
 
 @SuppressWarnings("rawtypes")
 public class SystemBackendActivator implements BundleActivator {
 
-    private ServiceTracker tracker;
+    private MultipleServiceTracker tracker;
     private SystemBackend backend;
-
+    private ServiceRegistration reg;
     private VmStatusChangeNotifier notifier;
     
-    @SuppressWarnings("unchecked")
     @Override
-    public void start(BundleContext context) throws Exception {
+    public void start(final BundleContext context) throws Exception {
         
         notifier = new VmStatusChangeNotifier(context);
         notifier.start();
-
-        backend = new SystemBackend(notifier);
         
-        tracker = new ServiceTracker(context, BackendService.class, null) {
+        Class<?>[] deps = new Class<?>[] {
+                BackendService.class,
+                HostInfoDAO.class,
+                NetworkInterfaceInfoDAO.class,
+                VmInfoDAO.class
+        };
+        tracker = new MultipleServiceTracker(context, deps, new Action() {
             @Override
-            public Object addingService(ServiceReference reference) {
-                context.registerService(Backend.class, backend, null);
-                return super.addingService(reference);
+            public void dependenciesAvailable(Map<String, Object> services) {
+                HostInfoDAO hostInfoDAO = (HostInfoDAO) services.get(HostInfoDAO.class.getName());
+                NetworkInterfaceInfoDAO netInfoDAO = (NetworkInterfaceInfoDAO) services
+                        .get(NetworkInterfaceInfoDAO.class.getName());
+                VmInfoDAO vmInfoDAO = (VmInfoDAO) services.get(VmInfoDAO.class.getName());
+                backend = new SystemBackend(hostInfoDAO, netInfoDAO, vmInfoDAO, notifier);
+                reg = context.registerService(Backend.class, backend, null);
             }
             
             @Override
-            public void removedService(ServiceReference reference, Object service) {
-                
+            public void dependenciesUnavailable() {
                 if (backend.isActive()) {
                     backend.deactivate();
                 }
-                context.ungetService(reference);
-                super.removedService(reference, service);
+                reg.unregister();
             }
-        };
-        
+            
+        });
+                
         tracker.open();
     }
     
     @Override
     public void stop(BundleContext context) throws Exception {
-        if (backend.isActive()) {
+        if (backend != null && backend.isActive()) {
             backend.deactivate();
         }
         tracker.close();
--- a/system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java	Thu Jan 31 14:37:55 2013 -0500
+++ b/system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java	Thu Jan 31 14:40:22 2013 -0500
@@ -39,15 +39,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 import org.junit.Before;
 import org.junit.Test;
 
-import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.dao.DAOFactory;
 import com.redhat.thermostat.storage.dao.HostInfoDAO;
 import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
 
 public class SystemBackendTest {
 
@@ -55,18 +53,13 @@
 
     @Before
     public void setUp() {
-        Storage s = mock(Storage.class);
         HostInfoDAO hDAO = mock(HostInfoDAO.class);
         NetworkInterfaceInfoDAO nDAO = mock(NetworkInterfaceInfoDAO.class);
-        DAOFactory df = mock(DAOFactory.class);
-        when(df.getStorage()).thenReturn(s);
-        when(df.getHostInfoDAO()).thenReturn(hDAO);
-        when(df.getNetworkInterfaceInfoDAO()).thenReturn(nDAO);
+        VmInfoDAO vmInfoDAO = mock(VmInfoDAO.class);
 
         VmStatusChangeNotifier notifier = mock(VmStatusChangeNotifier.class);
 
-        b = new SystemBackend(notifier);
-        b.setDAOFactory(df);
+        b = new SystemBackend(hDAO, nDAO, vmInfoDAO, notifier);
     }
 
     @Test