changeset 826:94c8e4c8a97c

Reduce public API in agent-cli Make all previously public API classes in thermostat-agent-cli bundle private. The classes were all implmenting commands ('agent', 'service' and 'storage') and there is no reason for them to be part of the public API. Also fix the test classes to have names of the form <ClassUnderTest>Test.java and add a test for the activator. Reviewed-by: rkennke Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-December/004471.html
author Omair Majid <omajid@redhat.com>
date Fri, 07 Dec 2012 17:24:23 -0500
parents bb4a52cba23a
children 518cc2809bd2
files agent/cli/pom.xml agent/cli/src/main/java/com/redhat/thermostat/agent/cli/AgentApplication.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/ServiceCommand.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/StorageCommand.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java 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/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/StorageCommand.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/AgentApplicationTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/DBServiceTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/ThermostatServiceTest.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/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/StorageCommandTest.java client/memory-stats-panel/swing/src/test/java/com/redhat/thermostat/client/stats/memory/swing/MemoryStatsPanelActivatorTest.java
diffstat 16 files changed, 1165 insertions(+), 1104 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/pom.xml	Fri Dec 07 14:32:13 2012 -0500
+++ b/agent/cli/pom.xml	Fri Dec 07 17:24:23 2012 -0500
@@ -98,9 +98,6 @@
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
             <Bundle-Activator>com.redhat.thermostat.agent.cli.impl.Activator</Bundle-Activator>
             <Bundle-SymbolicName>com.redhat.thermostat.agent.cli</Bundle-SymbolicName>
-            <Export-Package>
-              com.redhat.thermostat.agent.cli,
-            </Export-Package>
             <Private-Package>
               com.redhat.thermostat.agent.cli.impl,
               com.redhat.thermostat.agent.cli.impl.locale,
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/AgentApplication.java	Fri Dec 07 14:32:13 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-/*
- * Copyright 2012 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.agent.cli;
-
-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 sun.misc.Signal;
-import sun.misc.SignalHandler;
-
-import com.redhat.thermostat.agent.Agent;
-import com.redhat.thermostat.agent.command.ConfigurationServer;
-import com.redhat.thermostat.agent.config.AgentConfigsUtils;
-import com.redhat.thermostat.agent.config.AgentOptionParser;
-import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
-import com.redhat.thermostat.backend.BackendRegistry;
-import com.redhat.thermostat.backend.BackendService;
-import com.redhat.thermostat.common.Constants;
-import com.redhat.thermostat.common.LaunchException;
-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.dao.DAOFactory;
-import com.redhat.thermostat.common.dao.DAOFactoryImpl;
-import com.redhat.thermostat.common.tools.BasicCommand;
-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;
-
-@SuppressWarnings("restriction")
-public final class AgentApplication extends BasicCommand {
-
-    private static final String NAME = "agent";
-
-    private final BundleContext bundleContext;
-    private final ConfigurationCreator configurationCreator;
-    private final DAOFactoryCreator daoFactoryCreator;
-
-    private AgentStartupConfiguration configuration;
-    private AgentOptionParser parser;
-
-    public AgentApplication(BundleContext bundleContext) {
-        this(bundleContext, new ConfigurationCreator(), new DAOFactoryCreator());
-    }
-
-    AgentApplication(BundleContext bundleContext, ConfigurationCreator configurationCreator, DAOFactoryCreator daoFactoryCreator) {
-        this.bundleContext = bundleContext;
-        this.configurationCreator = configurationCreator;
-        this.daoFactoryCreator = daoFactoryCreator;
-    }
-    
-    private void parseArguments(Arguments args) throws InvalidConfigurationException {
-        parser = new AgentOptionParser(configuration, args);
-        parser.parse();
-    }
-
-    @Override
-    public AgentStartupConfiguration getConfiguration() {
-        return configuration;
-    }
-    
-    private void runAgent(CommandContext ctx) {
-        long startTime = System.currentTimeMillis();
-        configuration.setStartTime(startTime);
-        
-        if (configuration.isDebugConsole()) {
-            LoggingUtils.useDevelConsole();
-        }
-        final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
-
-        final DAOFactory daoFactory = daoFactoryCreator.create(configuration);
-
-        Connection connection = daoFactory.getConnection();
-        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, 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");
-                }
-            }
-        };
-
-        connection.addListener(connectionListener);
-        connection.connect();
-        logger.fine("Connecting to storage...");
-
-        ServiceReference configServiceRef = bundleContext.getServiceReference(ConfigurationServer.class.getName());
-        final ConfigurationServer configServer = (ConfigurationServer) bundleContext.getService(configServiceRef);
-        configServer.startListening(configuration.getConfigListenAddress());
-        
-        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();
-            logger.fine("terminating agent cmd");
-        } catch (InterruptedException e) {
-            return;
-        }
-    }
-
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-        try {
-            configuration = configurationCreator.create();
-
-            parseArguments(ctx.getArguments());
-            if (!parser.isHelp()) {
-                runAgent(ctx);
-            }
-        } catch (InvalidConfigurationException ex) {
-            throw new CommandException(ex);
-        }
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-    
-    // Does not need a reference of the enclosing type so lets declare this class static
-    private static class CustomSignalHandler implements SignalHandler {
-
-        private Agent agent;
-        private ConfigurationServer configServer;
-        private Logger logger;
-        private CountDownLatch shutdownLatch;
-        
-        CustomSignalHandler(Agent agent, ConfigurationServer configServer, Logger logger, CountDownLatch latch) {
-            this.agent = agent;
-            this.configServer = configServer;
-            this.logger = logger;
-            this.shutdownLatch = latch;
-        }
-        
-        @Override
-        public void handle(Signal arg0) {
-            configServer.stopListening();
-            try {
-                agent.stop();
-            } catch (Exception ex) {
-                // We don't want any exception to hold back the signal handler, otherwise
-                // there will be no way to actually stop Thermostat.
-                ex.printStackTrace();
-            }
-            logger.fine("Agent stopped.");       
-            shutdownLatch.countDown();
-        }
-        
-    }
-
-    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;
-        }
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/ServiceCommand.java	Fri Dec 07 14:32:13 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-/*
- * Copyright 2012 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.agent.cli;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Semaphore;
-
-import com.redhat.thermostat.agent.cli.db.StorageAlreadyRunningException;
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.cli.SimpleCommand;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.common.utils.OSGIUtils;
-import com.redhat.thermostat.launcher.Launcher;
-
-/**
- * Simple service that allows starting Agent and DB Backend
- * in a single step.
- */
-public class ServiceCommand extends SimpleCommand implements ActionListener<ApplicationState> {
-    
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private static final String NAME = "service";
-
-    private List<ActionListener<ApplicationState>> listeners;
-
-    private Semaphore agentBarrier = new Semaphore(0);
-
-    public ServiceCommand() {
-        listeners = new ArrayList<>();
-        listeners.add(this);
-    }
-
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-        Launcher launcher = getLauncher();
-        String[] storageStartArgs = new String[] { "storage", "--start" };
-        launcher.setArgs(storageStartArgs);
-        launcher.run(listeners);
-        agentBarrier.acquireUninterruptibly();
-        String[] storageStopArgs = new String[] { "storage", "--stop" };
-        launcher.setArgs(storageStopArgs);
-        launcher.run();
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-        if (actionEvent.getSource() instanceof StorageCommand) {
-            StorageCommand storage = (StorageCommand) actionEvent.getSource();
-            // Implementation detail: there is a single StorageCommand instance registered
-            // as an OSGi service.  We remove ourselves as listener so that we don't get
-            // notified in the case that the command is invoked by some other means later.
-            storage.getNotifier().removeActionListener(this);
-            switch (actionEvent.getActionId()) {
-            case START:
-                String dbUrl = storage.getConfiguration().getDBConnectionString();
-                Launcher launcher = getLauncher();
-                String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
-                System.err.println(translator.localize(LocaleResources.STARTING_AGENT));
-                launcher.setArgs(agentArgs);
-                launcher.run();
-                agentBarrier.release();
-                break;
-            case FAIL:
-                System.err.println(translator.localize(LocaleResources.ERROR_STARTING_DB));
-                Object payload = actionEvent.getPayload();
-                if (payload instanceof StorageAlreadyRunningException) {
-                    System.err.println(translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING));
-                }
-                break;
-            }
-        }
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public boolean isAvailableInShell() {
-        return false;
-    }
-
-    @Override
-    public boolean isStorageRequired() {
-        return false;
-    }
-
-    private Launcher getLauncher() {
-        OSGIUtils osgi = OSGIUtils.getInstance();
-        return osgi.getService(Launcher.class);
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/StorageCommand.java	Fri Dec 07 14:32:13 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +0,0 @@
-/*
- * Copyright 2012 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.agent.cli;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Properties;
-
-import com.redhat.thermostat.agent.cli.db.DBConfig;
-import com.redhat.thermostat.agent.cli.db.DBOptionParser;
-import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
-import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
-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.ConfigUtils;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.common.tools.BasicCommand;
-
-public class StorageCommand extends BasicCommand {
-
-    private static final String NAME = "storage";
-
-    private DBStartupConfiguration configuration;
-    private DBOptionParser parser;
-    
-    private MongoProcessRunner runner;
-    
-    private void parseArguments(Arguments args) throws InvalidConfigurationException {
-    
-        this.configuration = new DBStartupConfiguration();
-        // configs, read everything that is in the configs
-        File propertyFile = ConfigUtils.getStorageConfigurationFile();
-        if (!propertyFile.exists()) {
-            throw new InvalidConfigurationException("can't access database configuration file " +
-                                                    propertyFile);
-        }
-        readAndSetProperties(propertyFile);
-        
-        parser = new DBOptionParser(configuration, args);
-        parser.parse();
-    }
-    
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-
-        try {
-            parseArgsAndRun(ctx);
-        } catch (InvalidConfigurationException e) {
-            throw new CommandException(e);
-        }
-    }
-
-    private void parseArgsAndRun(CommandContext ctx)
-            throws InvalidConfigurationException {
-        parseArguments(ctx.getArguments());
-
-        // dry run means we don't do anything at all
-        if (parser.isDryRun()) return;
-        
-        runner = createRunner();
-        try {
-            switch (parser.getAction()) {
-            case START:
-                startService();
-                break;
-            case STOP:
-                stopService();
-                break;
-             default:
-                break;
-            }
-            getNotifier().fireAction(ApplicationState.SUCCESS);
-        } catch (Exception e) {
-            getNotifier().fireAction(ApplicationState.FAIL, e);
-        }
-    }
-    
-    private void readAndSetProperties(File propertyFile) throws InvalidConfigurationException {
-    
-        Properties properties = new Properties();
-        try {
-            properties.load(new FileInputStream(propertyFile));
-            
-        } catch (IOException e) {
-            throw new InvalidConfigurationException(e);
-        }
-        
-        if (properties.containsKey(DBConfig.PORT.name())) {
-            String port = (String) properties.get(DBConfig.PORT.name());
-            int localPort = Integer.parseInt(port);
-            configuration.setPort(localPort);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.PORT + " property missing");
-        }
-        
-        if (properties.containsKey(DBConfig.PROTOCOL.name())) {
-            String url = (String) properties.get(DBConfig.PROTOCOL.name());
-            configuration.setProtocol(url);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.PROTOCOL + " property missing");
-        }
-        
-        if (properties.containsKey(DBConfig.BIND.name())) {
-            String ip = (String) properties.get(DBConfig.BIND.name());
-            configuration.setBindIP(ip);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.BIND + " property missing");
-        }
-    }
-    
-    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        runner.startService();
-        getNotifier().fireAction(ApplicationState.START);
-    }
-    
-    
-    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        check();
-        runner.stopService();
-        getNotifier().fireAction(ApplicationState.STOP);
-    }
-    
-    MongoProcessRunner createRunner() {
-        return new MongoProcessRunner(configuration, parser.isQuiet());
-    }
-
-    private void check() throws InvalidConfigurationException {
-        if (!configuration.getDBPath().exists() ||
-            !configuration.getLogFile().getParentFile().exists() || 
-            !configuration.getPidFile().getParentFile().exists())
-        {
-            throw new InvalidConfigurationException("database directories do not exist...");
-        }
-    }
-
-    @Override
-    public DBStartupConfiguration getConfiguration() {
-        return configuration;
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java	Fri Dec 07 14:32:13 2012 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java	Fri Dec 07 17:24:23 2012 -0500
@@ -36,7 +36,7 @@
 
 package com.redhat.thermostat.agent.cli.db;
 
-import com.redhat.thermostat.agent.cli.StorageCommand;
+import com.redhat.thermostat.agent.cli.impl.StorageCommand;
 
 /**
  * Set of configuration option that the {@link StorageCommand} understands.
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Fri Dec 07 14:32:13 2012 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Fri Dec 07 17:24:23 2012 -0500
@@ -41,9 +41,6 @@
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
-import com.redhat.thermostat.agent.cli.AgentApplication;
-import com.redhat.thermostat.agent.cli.StorageCommand;
-import com.redhat.thermostat.agent.cli.ServiceCommand;
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Fri Dec 07 17:24:23 2012 -0500
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2012 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.agent.cli.impl;
+
+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 sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import com.redhat.thermostat.agent.Agent;
+import com.redhat.thermostat.agent.command.ConfigurationServer;
+import com.redhat.thermostat.agent.config.AgentConfigsUtils;
+import com.redhat.thermostat.agent.config.AgentOptionParser;
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.backend.BackendRegistry;
+import com.redhat.thermostat.backend.BackendService;
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.LaunchException;
+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.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.DAOFactoryImpl;
+import com.redhat.thermostat.common.tools.BasicCommand;
+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;
+
+@SuppressWarnings("restriction")
+public final class AgentApplication extends BasicCommand {
+
+    private static final String NAME = "agent";
+
+    private final BundleContext bundleContext;
+    private final ConfigurationCreator configurationCreator;
+    private final DAOFactoryCreator daoFactoryCreator;
+
+    private AgentStartupConfiguration configuration;
+    private AgentOptionParser parser;
+
+    public AgentApplication(BundleContext bundleContext) {
+        this(bundleContext, new ConfigurationCreator(), new DAOFactoryCreator());
+    }
+
+    AgentApplication(BundleContext bundleContext, ConfigurationCreator configurationCreator, DAOFactoryCreator daoFactoryCreator) {
+        this.bundleContext = bundleContext;
+        this.configurationCreator = configurationCreator;
+        this.daoFactoryCreator = daoFactoryCreator;
+    }
+    
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
+        parser = new AgentOptionParser(configuration, args);
+        parser.parse();
+    }
+
+    @Override
+    public AgentStartupConfiguration getConfiguration() {
+        return configuration;
+    }
+    
+    private void runAgent(CommandContext ctx) {
+        long startTime = System.currentTimeMillis();
+        configuration.setStartTime(startTime);
+        
+        if (configuration.isDebugConsole()) {
+            LoggingUtils.useDevelConsole();
+        }
+        final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
+
+        final DAOFactory daoFactory = daoFactoryCreator.create(configuration);
+
+        Connection connection = daoFactory.getConnection();
+        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, 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");
+                }
+            }
+        };
+
+        connection.addListener(connectionListener);
+        connection.connect();
+        logger.fine("Connecting to storage...");
+
+        ServiceReference configServiceRef = bundleContext.getServiceReference(ConfigurationServer.class.getName());
+        final ConfigurationServer configServer = (ConfigurationServer) bundleContext.getService(configServiceRef);
+        configServer.startListening(configuration.getConfigListenAddress());
+        
+        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();
+            logger.fine("terminating agent cmd");
+        } catch (InterruptedException e) {
+            return;
+        }
+    }
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        try {
+            configuration = configurationCreator.create();
+
+            parseArguments(ctx.getArguments());
+            if (!parser.isHelp()) {
+                runAgent(ctx);
+            }
+        } catch (InvalidConfigurationException ex) {
+            throw new CommandException(ex);
+        }
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+    
+    // Does not need a reference of the enclosing type so lets declare this class static
+    private static class CustomSignalHandler implements SignalHandler {
+
+        private Agent agent;
+        private ConfigurationServer configServer;
+        private Logger logger;
+        private CountDownLatch shutdownLatch;
+        
+        CustomSignalHandler(Agent agent, ConfigurationServer configServer, Logger logger, CountDownLatch latch) {
+            this.agent = agent;
+            this.configServer = configServer;
+            this.logger = logger;
+            this.shutdownLatch = latch;
+        }
+        
+        @Override
+        public void handle(Signal arg0) {
+            configServer.stopListening();
+            try {
+                agent.stop();
+            } catch (Exception ex) {
+                // We don't want any exception to hold back the signal handler, otherwise
+                // there will be no way to actually stop Thermostat.
+                ex.printStackTrace();
+            }
+            logger.fine("Agent stopped.");       
+            shutdownLatch.countDown();
+        }
+        
+    }
+
+    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;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Fri Dec 07 17:24:23 2012 -0500
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2012 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.agent.cli.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+
+import com.redhat.thermostat.agent.cli.db.StorageAlreadyRunningException;
+import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.SimpleCommand;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.Launcher;
+
+/**
+ * Simple service that allows starting Agent and DB Backend
+ * in a single step.
+ */
+public class ServiceCommand extends SimpleCommand implements ActionListener<ApplicationState> {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private static final String NAME = "service";
+
+    private List<ActionListener<ApplicationState>> listeners;
+
+    private Semaphore agentBarrier = new Semaphore(0);
+
+    public ServiceCommand() {
+        listeners = new ArrayList<>();
+        listeners.add(this);
+    }
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        Launcher launcher = getLauncher();
+        String[] storageStartArgs = new String[] { "storage", "--start" };
+        launcher.setArgs(storageStartArgs);
+        launcher.run(listeners);
+        agentBarrier.acquireUninterruptibly();
+        String[] storageStopArgs = new String[] { "storage", "--stop" };
+        launcher.setArgs(storageStopArgs);
+        launcher.run();
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+        if (actionEvent.getSource() instanceof StorageCommand) {
+            StorageCommand storage = (StorageCommand) actionEvent.getSource();
+            // Implementation detail: there is a single StorageCommand instance registered
+            // as an OSGi service.  We remove ourselves as listener so that we don't get
+            // notified in the case that the command is invoked by some other means later.
+            storage.getNotifier().removeActionListener(this);
+            switch (actionEvent.getActionId()) {
+            case START:
+                String dbUrl = storage.getConfiguration().getDBConnectionString();
+                Launcher launcher = getLauncher();
+                String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
+                System.err.println(translator.localize(LocaleResources.STARTING_AGENT));
+                launcher.setArgs(agentArgs);
+                launcher.run();
+                agentBarrier.release();
+                break;
+            case FAIL:
+                System.err.println(translator.localize(LocaleResources.ERROR_STARTING_DB));
+                Object payload = actionEvent.getPayload();
+                if (payload instanceof StorageAlreadyRunningException) {
+                    System.err.println(translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING));
+                }
+                break;
+            }
+        }
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public boolean isAvailableInShell() {
+        return false;
+    }
+
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+
+    private Launcher getLauncher() {
+        OSGIUtils osgi = OSGIUtils.getInstance();
+        return osgi.getService(Launcher.class);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/StorageCommand.java	Fri Dec 07 17:24:23 2012 -0500
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 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.agent.cli.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import com.redhat.thermostat.agent.cli.db.DBConfig;
+import com.redhat.thermostat.agent.cli.db.DBOptionParser;
+import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
+import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
+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.ConfigUtils;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.tools.BasicCommand;
+
+public class StorageCommand extends BasicCommand {
+
+    private static final String NAME = "storage";
+
+    private DBStartupConfiguration configuration;
+    private DBOptionParser parser;
+    
+    private MongoProcessRunner runner;
+    
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
+    
+        this.configuration = new DBStartupConfiguration();
+        // configs, read everything that is in the configs
+        File propertyFile = ConfigUtils.getStorageConfigurationFile();
+        if (!propertyFile.exists()) {
+            throw new InvalidConfigurationException("can't access database configuration file " +
+                                                    propertyFile);
+        }
+        readAndSetProperties(propertyFile);
+        
+        parser = new DBOptionParser(configuration, args);
+        parser.parse();
+    }
+    
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+
+        try {
+            parseArgsAndRun(ctx);
+        } catch (InvalidConfigurationException e) {
+            throw new CommandException(e);
+        }
+    }
+
+    private void parseArgsAndRun(CommandContext ctx)
+            throws InvalidConfigurationException {
+        parseArguments(ctx.getArguments());
+
+        // dry run means we don't do anything at all
+        if (parser.isDryRun()) return;
+        
+        runner = createRunner();
+        try {
+            switch (parser.getAction()) {
+            case START:
+                startService();
+                break;
+            case STOP:
+                stopService();
+                break;
+             default:
+                break;
+            }
+            getNotifier().fireAction(ApplicationState.SUCCESS);
+        } catch (Exception e) {
+            getNotifier().fireAction(ApplicationState.FAIL, e);
+        }
+    }
+    
+    private void readAndSetProperties(File propertyFile) throws InvalidConfigurationException {
+    
+        Properties properties = new Properties();
+        try {
+            properties.load(new FileInputStream(propertyFile));
+            
+        } catch (IOException e) {
+            throw new InvalidConfigurationException(e);
+        }
+        
+        if (properties.containsKey(DBConfig.PORT.name())) {
+            String port = (String) properties.get(DBConfig.PORT.name());
+            int localPort = Integer.parseInt(port);
+            configuration.setPort(localPort);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.PORT + " property missing");
+        }
+        
+        if (properties.containsKey(DBConfig.PROTOCOL.name())) {
+            String url = (String) properties.get(DBConfig.PROTOCOL.name());
+            configuration.setProtocol(url);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.PROTOCOL + " property missing");
+        }
+        
+        if (properties.containsKey(DBConfig.BIND.name())) {
+            String ip = (String) properties.get(DBConfig.BIND.name());
+            configuration.setBindIP(ip);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.BIND + " property missing");
+        }
+    }
+    
+    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        runner.startService();
+        getNotifier().fireAction(ApplicationState.START);
+    }
+    
+    
+    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        check();
+        runner.stopService();
+        getNotifier().fireAction(ApplicationState.STOP);
+    }
+    
+    MongoProcessRunner createRunner() {
+        return new MongoProcessRunner(configuration, parser.isQuiet());
+    }
+
+    private void check() throws InvalidConfigurationException {
+        if (!configuration.getDBPath().exists() ||
+            !configuration.getLogFile().getParentFile().exists() || 
+            !configuration.getPidFile().getParentFile().exists())
+        {
+            throw new InvalidConfigurationException("database directories do not exist...");
+        }
+    }
+
+    @Override
+    public DBStartupConfiguration getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/AgentApplicationTest.java	Fri Dec 07 14:32:13 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * Copyright 2012 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.agent.cli;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-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 org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import com.redhat.thermostat.agent.cli.AgentApplication.ConfigurationCreator;
-import com.redhat.thermostat.agent.cli.AgentApplication.DAOFactoryCreator;
-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.common.dao.DAOFactory;
-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.test.StubBundleContext;
-
-public class AgentApplicationTest {
-
-    // TODO: Test i18nized versions when they come.
-
-    private StubBundleContext context;
-    private DAOFactory daoFactory;
-    private Connection connection;
-
-    private AgentApplication agent;
-
-    private ArgumentCaptor<ConnectionListener> listenerCaptor;
-    
-    @Before
-    public void setUp() throws InvalidConfigurationException {
-        
-        context = new StubBundleContext();
-        
-        AgentStartupConfiguration config = mock(AgentStartupConfiguration.class);
-        when(config.getDBConnectionString()).thenReturn("test string; please ignore");
-
-        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());
-
-        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);
-    }
-
-    @After
-    public void tearDown() {
-        agent = null;
-    }
-
-    @Test
-    public void testName() {
-        String name = agent.getName();
-        assertEquals("agent", name);
-    }
-
-    @Test
-    public void testDescAndUsage() {
-        assertNotNull(agent.getDescription());
-        assertNotNull(agent.getUsage());
-    }
-
-    @Test
-    public void testAgentStartupRegistersDAOs() throws CommandException {
-
-        doThrow(new ThatsAllThatWeCareAbout()).when(connection).connect();
-
-        Arguments args = mock(Arguments.class);
-        CommandContext commandContext = mock(CommandContext.class);
-
-        when(commandContext.getArguments()).thenReturn(args);
-
-        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
-        }
-
-        ConnectionListener listener = listenerCaptor.getValue();
-        listener.changed(ConnectionStatus.CONNECTED);
-
-        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
-    }
-
-    // FIXME test the rest of AgentApplication
-
-    private static class ThatsAllThatWeCareAbout extends RuntimeException {
-
-    }
-}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/DBServiceTest.java	Fri Dec 07 14:32:13 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,296 +0,0 @@
-/*
- * Copyright 2012 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.agent.cli;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Properties;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-
-import junit.framework.Assert;
-
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.OptionGroup;
-import org.apache.commons.cli.Options;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import com.redhat.thermostat.agent.cli.StorageCommand;
-import com.redhat.thermostat.agent.cli.db.DBConfig;
-import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
-import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.cli.SimpleArguments;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-
-public class DBServiceTest {
-    
-    private static final String PORT = "27518";
-    private static final String BIND = "127.0.0.1";
-    private static final String PROTOCOL = "mongodb";
-    private static final String DB = "storage/db";
-
-    private String tmpDir;
-    
-    @Before
-    public void setup() {
-        // need to create a dummy config file for the test
-        try {
-            Random random = new Random();
-            
-            tmpDir = System.getProperty("java.io.tmpdir") + File.separatorChar +
-                     Math.abs(random.nextInt()) + File.separatorChar;
-            
-            System.setProperty("THERMOSTAT_HOME", tmpDir);
-            File base = new File(tmpDir + "storage");
-            base.mkdirs();
-                        
-            File tmpConfigs = new File(base, "db.properties");
-            
-            new File(base, "run").mkdirs();
-            new File(base, "logs").mkdirs();
-            new File(base, "db").mkdirs();
-            
-            Properties props = new Properties();
-            
-            props.setProperty(DBConfig.BIND.name(), BIND);
-            props.setProperty(DBConfig.PORT.name(), PORT);
-            props.setProperty(DBConfig.PROTOCOL.name(), PROTOCOL);
-
-            props.store(new FileOutputStream(tmpConfigs), "thermostat test properties");
-            
-        } catch (IOException e) {
-            Assert.fail("cannot setup tests: " + e);
-        }
-    }
-    
-    @Test
-    public void testConfig() throws CommandException {
-        SimpleArguments args = new SimpleArguments();
-        args.addArgument("quiet", null);
-        args.addArgument("start", null);
-        args.addArgument("dryRun", null);
-        CommandContext ctx = mock(CommandContext.class);
-        when(ctx.getArguments()).thenReturn(args);
-
-        StorageCommand service = new StorageCommand() {
-            @Override
-            MongoProcessRunner createRunner() {
-                throw new AssertionError("dry run should never create an actual runner");
-            }
-        };
-
-        service.run(ctx);
-        
-        DBStartupConfiguration conf = service.getConfiguration();
-        
-        Assert.assertEquals(tmpDir + DB, conf.getDBPath().getPath());
-        Assert.assertEquals(Integer.parseInt(PORT), conf.getPort());
-        Assert.assertEquals(PROTOCOL, conf.getProtocol());
-    }
-    
-    private StorageCommand prepareService(boolean startSuccess) throws IOException,
-            InterruptedException, InvalidConfigurationException, ApplicationException
-    {
-        final MongoProcessRunner runner = mock(MongoProcessRunner.class);
-        if (!startSuccess) {
-           doThrow(new ApplicationException("mock exception")).when(runner).startService();
-        }
-        
-        // TODO: stop not tested yet, but be sure it's not called from the code
-        doThrow(new ApplicationException("mock exception")).when(runner).stopService();
-        
-        StorageCommand service = new StorageCommand() {
-            @Override
-            MongoProcessRunner createRunner() {
-                return runner;
-            }
-        };
-        
-        return service;
-    }
-    
-    @Test
-    public void testListeners() throws InterruptedException, IOException, ApplicationException, InvalidConfigurationException, CommandException
-    {
-        StorageCommand service = prepareService(true);
-        
-        final CountDownLatch latch = new CountDownLatch(2);
-        
-        final boolean[] result = new boolean[2];
-        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = false;
-                    latch.countDown();
-                    latch.countDown();
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = true;
-                    latch.countDown();
-                    break;
-
-                case START:
-                    result[1] = true;
-                    latch.countDown();
-                    break;
-                }
-            }
-        });
-        
-        service.run(prepareContext());
-        latch.await();
-        
-        Assert.assertTrue(result[0]);
-        Assert.assertTrue(result[1]);
-    }
-    
-    @Test
-    public void testListenersFail() throws InterruptedException, IOException, ApplicationException, CommandException, InvalidConfigurationException
-    {
-        StorageCommand service = prepareService(false);
-        
-        final CountDownLatch latch = new CountDownLatch(1);
-        final boolean[] result = new boolean[1];
-        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = true;
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = false;
-                    break;
-                }
-                latch.countDown();
-            }
-        });
-        
-        service.run(prepareContext());
-        latch.await();
-        
-        Assert.assertTrue(result[0]);
-    }
-
-    private CommandContext prepareContext() {
-        SimpleArguments args = new SimpleArguments();
-        args.addArgument("quiet", "--quiet");
-        args.addArgument("start", "--start");
-        CommandContext ctx = mock(CommandContext.class);
-        when(ctx.getArguments()).thenReturn(args);
-        return ctx;
-    }
-
-    @Test
-    public void testName() {
-        StorageCommand dbService = new StorageCommand();
-        String name = dbService.getName();
-        assertEquals("storage", name);
-    }
-
-    @Test
-    public void testDescAndUsage() {
-        StorageCommand dbService = new StorageCommand();
-        assertNotNull(dbService.getDescription());
-        assertNotNull(dbService.getUsage());
-    }
-
-    @Ignore
-    @Test
-    public void testOptions() {
-        StorageCommand dbService = new StorageCommand();
-        Options options = dbService.getOptions();
-        assertNotNull(options);
-        assertEquals(4, options.getOptions().size());
-
-        assertTrue(options.hasOption("dryRun"));
-        Option dry = options.getOption("dryRun");
-        assertEquals("d", dry.getOpt());
-        assertEquals("run the service in dry run mode", dry.getDescription());
-        assertFalse(dry.isRequired());
-        assertFalse(dry.hasArg());
-
-        assertTrue(options.hasOption("start"));
-        Option start = options.getOption("start");
-        assertEquals("start the database", start.getDescription());
-        assertFalse(start.isRequired());
-        assertFalse(start.hasArg());
-
-        assertTrue(options.hasOption("stop"));
-        Option stop = options.getOption("stop");
-        assertEquals("stop the database", stop.getDescription());
-        assertFalse(stop.isRequired());
-        assertFalse(stop.hasArg());
-
-        assertTrue(options.hasOption("quiet"));
-        Option quiet = options.getOption("quiet");
-        assertEquals("q", quiet.getOpt());
-        assertEquals("don't produce any output", quiet.getDescription());
-        assertFalse(quiet.isRequired());
-        assertFalse(quiet.hasArg());
-
-        OptionGroup startStop = options.getOptionGroup(start);
-        assertTrue(startStop.isRequired());
-        @SuppressWarnings("unchecked")
-        Collection<Option> groupOpts = startStop.getOptions();
-        assertEquals(2, groupOpts.size());
-        assertTrue(groupOpts.contains(start));
-        assertTrue(groupOpts.contains(stop));
-    }
-}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/ThermostatServiceTest.java	Fri Dec 07 14:32:13 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- * Copyright 2012 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.agent.cli;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import org.apache.commons.cli.Options;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.agent.cli.ServiceCommand;
-
-public class ThermostatServiceTest {
-
-    private ServiceCommand thermostatService;
-
-    @Before
-    public void setUp() {
-        thermostatService = new ServiceCommand();
-    }
-
-    @After
-    public void tearDown() {
-        thermostatService = null;
-    }
-
-    @Test
-    public void testName() {
-        String name = thermostatService.getName();
-        assertEquals("service", name);
-    }
-
-    @Test
-    public void testDescAndUsage() {
-        assertNotNull(thermostatService.getDescription());
-        assertNotNull(thermostatService.getUsage());
-    }
-
-    @Test
-    public void testOptions() {
-        Options options = thermostatService.getOptions();
-        assertNotNull(options);
-        assertEquals(0, options.getOptions().size());
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Fri Dec 07 17:24:23 2012 -0500
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 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.agent.cli.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class ActivatorTest {
+
+    @Test
+    public void verifyActivatorRegistersCommands() throws Exception {
+
+        StubBundleContext bundleContext = new StubBundleContext();
+
+        Activator activator = new Activator();
+
+        activator.start(bundleContext);
+
+        assertTrue(bundleContext.isServiceRegistered(Command.class.getName(), AgentApplication.class));
+        assertTrue(bundleContext.isServiceRegistered(Command.class.getName(), ServiceCommand.class));
+        assertTrue(bundleContext.isServiceRegistered(Command.class.getName(), StorageCommand.class));
+
+        activator.stop(bundleContext);
+
+        assertEquals(0, bundleContext.getAllServices().size());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Fri Dec 07 17:24:23 2012 -0500
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2012 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.agent.cli.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+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.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.common.dao.DAOFactory;
+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.test.StubBundleContext;
+
+public class AgentApplicationTest {
+
+    // TODO: Test i18nized versions when they come.
+
+    private StubBundleContext context;
+    private DAOFactory daoFactory;
+    private Connection connection;
+
+    private AgentApplication agent;
+
+    private ArgumentCaptor<ConnectionListener> listenerCaptor;
+    
+    @Before
+    public void setUp() throws InvalidConfigurationException {
+        
+        context = new StubBundleContext();
+        
+        AgentStartupConfiguration config = mock(AgentStartupConfiguration.class);
+        when(config.getDBConnectionString()).thenReturn("test string; please ignore");
+
+        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());
+
+        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);
+    }
+
+    @After
+    public void tearDown() {
+        agent = null;
+    }
+
+    @Test
+    public void testName() {
+        String name = agent.getName();
+        assertEquals("agent", name);
+    }
+
+    @Test
+    public void testDescAndUsage() {
+        assertNotNull(agent.getDescription());
+        assertNotNull(agent.getUsage());
+    }
+
+    @Test
+    public void testAgentStartupRegistersDAOs() throws CommandException {
+
+        doThrow(new ThatsAllThatWeCareAbout()).when(connection).connect();
+
+        Arguments args = mock(Arguments.class);
+        CommandContext commandContext = mock(CommandContext.class);
+
+        when(commandContext.getArguments()).thenReturn(args);
+
+        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
+        }
+
+        ConnectionListener listener = listenerCaptor.getValue();
+        listener.changed(ConnectionStatus.CONNECTED);
+
+        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
+    }
+
+    // FIXME test the rest of AgentApplication
+
+    private static class ThatsAllThatWeCareAbout extends RuntimeException {
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java	Fri Dec 07 17:24:23 2012 -0500
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 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.agent.cli.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.commons.cli.Options;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.cli.impl.ServiceCommand;
+
+public class ServiceCommandTest {
+
+    private ServiceCommand thermostatService;
+
+    @Before
+    public void setUp() {
+        thermostatService = new ServiceCommand();
+    }
+
+    @After
+    public void tearDown() {
+        thermostatService = null;
+    }
+
+    @Test
+    public void testName() {
+        String name = thermostatService.getName();
+        assertEquals("service", name);
+    }
+
+    @Test
+    public void testDescAndUsage() {
+        assertNotNull(thermostatService.getDescription());
+        assertNotNull(thermostatService.getUsage());
+    }
+
+    @Test
+    public void testOptions() {
+        Options options = thermostatService.getOptions();
+        assertNotNull(options);
+        assertEquals(0, options.getOptions().size());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/StorageCommandTest.java	Fri Dec 07 17:24:23 2012 -0500
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2012 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.agent.cli.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Properties;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+
+import junit.framework.Assert;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.cli.db.DBConfig;
+import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
+import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
+import com.redhat.thermostat.agent.cli.impl.StorageCommand;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.SimpleArguments;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+
+public class StorageCommandTest {
+    
+    private static final String PORT = "27518";
+    private static final String BIND = "127.0.0.1";
+    private static final String PROTOCOL = "mongodb";
+    private static final String DB = "storage/db";
+
+    private String tmpDir;
+    
+    @Before
+    public void setup() {
+        // need to create a dummy config file for the test
+        try {
+            Random random = new Random();
+            
+            tmpDir = System.getProperty("java.io.tmpdir") + File.separatorChar +
+                     Math.abs(random.nextInt()) + File.separatorChar;
+            
+            System.setProperty("THERMOSTAT_HOME", tmpDir);
+            File base = new File(tmpDir + "storage");
+            base.mkdirs();
+                        
+            File tmpConfigs = new File(base, "db.properties");
+            
+            new File(base, "run").mkdirs();
+            new File(base, "logs").mkdirs();
+            new File(base, "db").mkdirs();
+            
+            Properties props = new Properties();
+            
+            props.setProperty(DBConfig.BIND.name(), BIND);
+            props.setProperty(DBConfig.PORT.name(), PORT);
+            props.setProperty(DBConfig.PROTOCOL.name(), PROTOCOL);
+
+            props.store(new FileOutputStream(tmpConfigs), "thermostat test properties");
+            
+        } catch (IOException e) {
+            Assert.fail("cannot setup tests: " + e);
+        }
+    }
+    
+    @Test
+    public void testConfig() throws CommandException {
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", null);
+        args.addArgument("start", null);
+        args.addArgument("dryRun", null);
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+
+        StorageCommand service = new StorageCommand() {
+            @Override
+            MongoProcessRunner createRunner() {
+                throw new AssertionError("dry run should never create an actual runner");
+            }
+        };
+
+        service.run(ctx);
+        
+        DBStartupConfiguration conf = service.getConfiguration();
+        
+        Assert.assertEquals(tmpDir + DB, conf.getDBPath().getPath());
+        Assert.assertEquals(Integer.parseInt(PORT), conf.getPort());
+        Assert.assertEquals(PROTOCOL, conf.getProtocol());
+    }
+    
+    private StorageCommand prepareService(boolean startSuccess) throws IOException,
+            InterruptedException, InvalidConfigurationException, ApplicationException
+    {
+        final MongoProcessRunner runner = mock(MongoProcessRunner.class);
+        if (!startSuccess) {
+           doThrow(new ApplicationException("mock exception")).when(runner).startService();
+        }
+        
+        // TODO: stop not tested yet, but be sure it's not called from the code
+        doThrow(new ApplicationException("mock exception")).when(runner).stopService();
+        
+        StorageCommand service = new StorageCommand() {
+            @Override
+            MongoProcessRunner createRunner() {
+                return runner;
+            }
+        };
+        
+        return service;
+    }
+    
+    @Test
+    public void testListeners() throws InterruptedException, IOException, ApplicationException, InvalidConfigurationException, CommandException
+    {
+        StorageCommand service = prepareService(true);
+        
+        final CountDownLatch latch = new CountDownLatch(2);
+        
+        final boolean[] result = new boolean[2];
+        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = false;
+                    latch.countDown();
+                    latch.countDown();
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = true;
+                    latch.countDown();
+                    break;
+
+                case START:
+                    result[1] = true;
+                    latch.countDown();
+                    break;
+                }
+            }
+        });
+        
+        service.run(prepareContext());
+        latch.await();
+        
+        Assert.assertTrue(result[0]);
+        Assert.assertTrue(result[1]);
+    }
+    
+    @Test
+    public void testListenersFail() throws InterruptedException, IOException, ApplicationException, CommandException, InvalidConfigurationException
+    {
+        StorageCommand service = prepareService(false);
+        
+        final CountDownLatch latch = new CountDownLatch(1);
+        final boolean[] result = new boolean[1];
+        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = true;
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = false;
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        
+        service.run(prepareContext());
+        latch.await();
+        
+        Assert.assertTrue(result[0]);
+    }
+
+    private CommandContext prepareContext() {
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", "--quiet");
+        args.addArgument("start", "--start");
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+        return ctx;
+    }
+
+    @Test
+    public void testName() {
+        StorageCommand dbService = new StorageCommand();
+        String name = dbService.getName();
+        assertEquals("storage", name);
+    }
+
+    @Test
+    public void testDescAndUsage() {
+        StorageCommand dbService = new StorageCommand();
+        assertNotNull(dbService.getDescription());
+        assertNotNull(dbService.getUsage());
+    }
+
+    @Ignore
+    @Test
+    public void testOptions() {
+        StorageCommand dbService = new StorageCommand();
+        Options options = dbService.getOptions();
+        assertNotNull(options);
+        assertEquals(4, options.getOptions().size());
+
+        assertTrue(options.hasOption("dryRun"));
+        Option dry = options.getOption("dryRun");
+        assertEquals("d", dry.getOpt());
+        assertEquals("run the service in dry run mode", dry.getDescription());
+        assertFalse(dry.isRequired());
+        assertFalse(dry.hasArg());
+
+        assertTrue(options.hasOption("start"));
+        Option start = options.getOption("start");
+        assertEquals("start the database", start.getDescription());
+        assertFalse(start.isRequired());
+        assertFalse(start.hasArg());
+
+        assertTrue(options.hasOption("stop"));
+        Option stop = options.getOption("stop");
+        assertEquals("stop the database", stop.getDescription());
+        assertFalse(stop.isRequired());
+        assertFalse(stop.hasArg());
+
+        assertTrue(options.hasOption("quiet"));
+        Option quiet = options.getOption("quiet");
+        assertEquals("q", quiet.getOpt());
+        assertEquals("don't produce any output", quiet.getDescription());
+        assertFalse(quiet.isRequired());
+        assertFalse(quiet.hasArg());
+
+        OptionGroup startStop = options.getOptionGroup(start);
+        assertTrue(startStop.isRequired());
+        @SuppressWarnings("unchecked")
+        Collection<Option> groupOpts = startStop.getOptions();
+        assertEquals(2, groupOpts.size());
+        assertTrue(groupOpts.contains(start));
+        assertTrue(groupOpts.contains(stop));
+    }
+}