changeset 211:d951bf8be570

Merge CLI and Application frameworks into one. Reviewed-by: omajid, vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-April/000723.html
author Roman Kennke <rkennke@redhat.com>
date Tue, 10 Apr 2012 23:18:38 +0200
parents 088b33902d21
children 9561fdda8fbc
files agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java agent/src/test/java/com/redhat/thermostat/agent/AgentApplicationTest.java common/src/main/java/com/redhat/thermostat/cli/Command.java common/src/main/java/com/redhat/thermostat/cli/CommandContext.java common/src/main/java/com/redhat/thermostat/cli/CommandContextFactory.java common/src/main/java/com/redhat/thermostat/cli/CommandException.java common/src/main/java/com/redhat/thermostat/cli/CommandRegistry.java common/src/main/java/com/redhat/thermostat/cli/HelpCommand.java common/src/main/java/com/redhat/thermostat/cli/Launcher.java common/src/main/java/com/redhat/thermostat/cli/Main.java common/src/main/java/com/redhat/thermostat/common/config/InvalidConfigurationException.java common/src/main/java/com/redhat/thermostat/tools/Application.java common/src/main/java/com/redhat/thermostat/tools/BasicApplication.java common/src/main/java/com/redhat/thermostat/tools/BasicCommand.java common/src/test/java/com/redhat/thermostat/cli/CommandExceptionTest.java common/src/test/java/com/redhat/thermostat/cli/CommandRegistryTest.java common/src/test/java/com/redhat/thermostat/cli/HelpCommandTest.java common/src/test/java/com/redhat/thermostat/cli/LauncherTest.java common/src/test/java/com/redhat/thermostat/cli/MainTest.java common/src/test/java/com/redhat/thermostat/cli/TestCommand.java common/src/test/java/com/redhat/thermostat/cli/TestCommandContextFactory.java common/src/test/java/com/redhat/thermostat/tools/BasicApplicationTest.java common/src/test/java/com/redhat/thermostat/tools/BasicCommandTest.java tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java tools/src/test/java/com/redhat/thermostat/tools/ThermostatServiceTest.java tools/src/test/java/com/redhat/thermostat/tools/db/DBServiceTest.java
diffstat 27 files changed, 884 insertions(+), 522 deletions(-) [+]
line wrap: on
line diff
--- a/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Tue Apr 10 23:18:38 2012 +0200
@@ -47,6 +47,8 @@
 import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
 import com.redhat.thermostat.backend.BackendLoadException;
 import com.redhat.thermostat.backend.BackendRegistry;
+import com.redhat.thermostat.cli.CommandContext;
+import com.redhat.thermostat.cli.CommandException;
 import com.redhat.thermostat.common.Constants;
 import com.redhat.thermostat.common.LaunchException;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
@@ -57,15 +59,24 @@
 import com.redhat.thermostat.common.storage.MongoStorage;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.tools.BasicApplication;
+import com.redhat.thermostat.tools.BasicCommand;
+
+public final class AgentApplication extends BasicCommand {
+
+    private static final String NAME = "agent";
 
-public final class AgentApplication extends BasicApplication {
+    // TODO: Use LocaleResources for i18n-ized strings.
+    private static final String DESCRIPTION = "starts and stops the thermostat agent";
+
+    private static final String USAGE = "agent start|stop\n\n"
+                                + DESCRIPTION + "\n\n\t"
+                                + "With argument 'start', start the agent.\n\t"
+                                + "With argument 'stop', stop the agent.";
 
     private AgentStartupConfiguration configuration;
     private AgentOptionParser parser;
     
-    @Override
-    public void parseArguments(List<String> args) throws InvalidConfigurationException {
+    private void parseArguments(List<String> args) throws InvalidConfigurationException {
         configuration = AgentConfigsUtils.createAgentConfigs();
         parser = new AgentOptionParser(configuration, args);
         parser.parse();
@@ -131,20 +142,30 @@
     }
     
     @Override
-    public void run() {
-         if (!parser.isHelp()) {
-             runAgent();
-         }
-    }
-
-    public static void main(String[] args) throws InvalidConfigurationException {        
-        AgentApplication service = new AgentApplication();
-        service.parseArguments(Arrays.asList(args));
-        service.run();
+    public void run(CommandContext ctx) throws CommandException {
+        try {
+            parseArguments(Arrays.asList(ctx.getArguments()));
+            if (!parser.isHelp()) {
+                runAgent();
+            }
+        } catch (InvalidConfigurationException ex) {
+            throw new CommandException(ex);
+        }
     }
 
     @Override
-    public void printHelp() {
-        // TODO: add help
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESCRIPTION;
     }
+
+    @Override
+    public String getUsage() {
+        return USAGE;
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/test/java/com/redhat/thermostat/agent/AgentApplicationTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -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;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AgentApplicationTest {
+
+    // TODO: Test i18nized versions when they come.
+
+    private AgentApplication agent;
+
+    @Before
+    public void setUp() {
+        agent = new AgentApplication();
+    }
+
+    @After
+    public void tearDown() {
+        agent = null;
+    }
+
+    @Test
+    public void testName() {
+        String name = agent.getName();
+        assertEquals("agent", name);
+    }
+
+    @Test
+    public void testDescription() {
+        String description = agent.getDescription();
+        assertEquals("starts and stops the thermostat agent", description);
+    }
+
+    @Test
+    public void testUsage() {
+        String usage = agent.getUsage();
+        assertEquals("agent start|stop\n\n"
+                + "starts and stops the thermostat agent" + "\n\n\t"
+                + "With argument 'start', start the agent.\n\t"
+                + "With argument 'stop', stop the agent.", usage);
+    }
+}
--- a/common/src/main/java/com/redhat/thermostat/cli/Command.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/Command.java	Tue Apr 10 23:18:38 2012 +0200
@@ -36,9 +36,9 @@
 
 package com.redhat.thermostat.cli;
 
-interface Command {
+public interface Command {
 
-    void run(CommandContext ctx);
+    void run(CommandContext ctx) throws CommandException;
 
     String getName();
 
--- a/common/src/main/java/com/redhat/thermostat/cli/CommandContext.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandContext.java	Tue Apr 10 23:18:38 2012 +0200
@@ -36,7 +36,7 @@
 
 package com.redhat.thermostat.cli;
 
-interface CommandContext {
+public interface CommandContext {
 
     Console getConsole();
 
--- a/common/src/main/java/com/redhat/thermostat/cli/CommandContextFactory.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandContextFactory.java	Tue Apr 10 23:18:38 2012 +0200
@@ -36,11 +36,11 @@
 
 package com.redhat.thermostat.cli;
 
-class CommandContextFactory {
+public class CommandContextFactory {
 
     private static CommandContextFactory instance = new CommandContextFactory();
 
-    static CommandContextFactory getInstance() {
+    public static CommandContextFactory getInstance() {
         return instance;
     }
 
@@ -50,7 +50,7 @@
 
     private CommandRegistry commandRegistry = new CommandRegistry();
 
-    CommandContext createContext(final String[] args) {
+    public CommandContext createContext(final String[] args) {
         return new CommandContextImpl(args, commandRegistry);
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandException.java	Tue Apr 10 23:18:38 2012 +0200
@@ -0,0 +1,57 @@
+/*
+ * 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.cli;
+
+public class CommandException extends Exception {
+
+    public CommandException() {
+        super();
+    }
+
+    public CommandException(String message) {
+        super(message);
+    }
+
+    public CommandException(Throwable cause) {
+        super(cause);
+    }
+
+    public CommandException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
--- a/common/src/main/java/com/redhat/thermostat/cli/CommandRegistry.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandRegistry.java	Tue Apr 10 23:18:38 2012 +0200
@@ -41,7 +41,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-class CommandRegistry {
+public class CommandRegistry {
 
     private Map<String,Command> commands;
 
@@ -59,11 +59,11 @@
         }
     }
 
-    Command getCommand(String name) {
+    public Command getCommand(String name) {
         return commands.get(name);
     }
 
-    Collection<Command> getRegisteredCommands() {
+    public Collection<Command> getRegisteredCommands() {
         return Collections.unmodifiableCollection(commands.values());
     }
 
--- a/common/src/main/java/com/redhat/thermostat/cli/HelpCommand.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/HelpCommand.java	Tue Apr 10 23:18:38 2012 +0200
@@ -43,9 +43,9 @@
     private static final String NAME = "help";
     private static final String DESCRIPTION = "show help for a given command or help overview";
     private static final String USAGE = "help [COMMAND]\n\n"
-            + DESCRIPTION + "\n\n"
-            + "    With no arguments, print a list of commands with short help messages.\n\n"
-            + "    Given a command, print help for that command.";
+            + DESCRIPTION + "\n\n\t"
+            + "With no arguments, print a list of commands with short help messages.\n\n\t"
+            + "Given a command, print help for that command.";
 
     @Override
     public void run(CommandContext ctx) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/Launcher.java	Tue Apr 10 23:18:38 2012 +0200
@@ -0,0 +1,97 @@
+/*
+ * 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.cli;
+
+import java.util.Arrays;
+import java.util.ServiceLoader;
+
+public class Launcher {
+
+    public static void main(String[] args) {
+        new Launcher(args).run();
+    }
+
+    private Launcher(String[] args) {
+        this.args = args;
+    }
+
+    private String[] args;
+
+    private void run() {
+        registerDefaultCommands();
+        if (hasNoArguments()) {
+            runHelpCommand();
+        } else {
+            runCommandFromArguments();
+        }
+    }
+
+    private boolean hasNoArguments() {
+        return args.length == 0;
+    }
+
+    private void runHelpCommand() {
+        runCommand("help", new String[0]);
+    }
+
+    private void runCommandFromArguments() {
+        runCommand(args[0], Arrays.copyOfRange(args, 1, args.length));
+    }
+
+    private void runCommand(String cmdName, String[] cmdArgs) {
+        CommandContextFactory cmdCtxFactory = CommandContextFactory.getInstance();
+        CommandRegistry registry = cmdCtxFactory.getCommandRegistry();
+        Command cmd = registry.getCommand(cmdName);
+        CommandContext ctx = cmdCtxFactory.createContext(cmdArgs);
+        runCommandWithContext(cmd, ctx);
+    }
+
+    private void runCommandWithContext(Command cmd, CommandContext ctx) {
+        try {
+            cmd.run(ctx);
+        } catch (CommandException e) {
+            ctx.getConsole().getError().println(e.getMessage());
+        }
+    }
+
+    private void registerDefaultCommands() {
+        CommandContextFactory cmdCtxFactory = CommandContextFactory.getInstance();
+        CommandRegistry registry = cmdCtxFactory.getCommandRegistry();
+        ServiceLoader<Command> cmds = ServiceLoader.load(Command.class);
+        registry.registerCommands(cmds);
+    }
+}
--- a/common/src/main/java/com/redhat/thermostat/cli/Main.java	Tue Apr 10 21:27:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +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.cli;
-
-import java.util.Arrays;
-import java.util.ServiceLoader;
-
-public class Main {
-
-    public static void main(String[] args) {
-        new Main(args).run();
-    }
-
-    private Main(String[] args) {
-        this.args = args;
-    }
-
-    private String[] args;
-
-    private void run() {
-        registerDefaultCommands();
-        if (hasNoArguments()) {
-            runHelpCommand();
-        } else {
-            runCommandFromArguments();
-        }
-    }
-
-    private boolean hasNoArguments() {
-        return args.length == 0;
-    }
-
-    private void runHelpCommand() {
-        runCommand("help", new String[0]);
-    }
-
-    private void runCommandFromArguments() {
-        runCommand(args[0], Arrays.copyOfRange(args, 1, args.length));
-    }
-
-    private void runCommand(String cmdName, String[] cmdArgs) {
-        CommandContextFactory cmdCtxFactory = CommandContextFactory.getInstance();
-        CommandRegistry registry = cmdCtxFactory.getCommandRegistry();
-        Command cmd = registry.getCommand(cmdName);
-        CommandContext ctx = cmdCtxFactory.createContext(cmdArgs);
-        cmd.run(ctx);
-    }
-
-    private void registerDefaultCommands() {
-        CommandContextFactory cmdCtxFactory = CommandContextFactory.getInstance();
-        CommandRegistry registry = cmdCtxFactory.getCommandRegistry();
-        ServiceLoader<Command> cmds = ServiceLoader.load(Command.class);
-        registry.registerCommands(cmds);
-    }
-}
--- a/common/src/main/java/com/redhat/thermostat/common/config/InvalidConfigurationException.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/config/InvalidConfigurationException.java	Tue Apr 10 23:18:38 2012 +0200
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.common.config;
 
+
 public class InvalidConfigurationException extends Exception {
 
     public InvalidConfigurationException() {
--- a/common/src/main/java/com/redhat/thermostat/tools/Application.java	Tue Apr 10 21:27:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +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.tools;
-
-import java.util.List;
-
-import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.config.StartupConfiguration;
-
-public interface Application {
-    
-    void parseArguments(List<String> args) throws InvalidConfigurationException;
-    void run();
-    void printHelp();
-    ActionNotifier<ApplicationState> getNotifier();
-    StartupConfiguration getConfiguration();
-}
--- a/common/src/main/java/com/redhat/thermostat/tools/BasicApplication.java	Tue Apr 10 21:27:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +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.tools;
-
-import com.redhat.thermostat.common.ActionNotifier;
-
-/**
- * Common base class for all daemon and application
- */
-public abstract class BasicApplication implements Application {
-    
-    private ActionNotifier<ApplicationState> notifier;
-    
-    public BasicApplication() {
-        this.notifier = new ActionNotifier<>(this);
-    }
-
-    @Override
-    public ActionNotifier<ApplicationState> getNotifier() {
-        return notifier;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/tools/BasicCommand.java	Tue Apr 10 23:18:38 2012 +0200
@@ -0,0 +1,59 @@
+/*
+ * 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.tools;
+
+import com.redhat.thermostat.cli.Command;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.config.StartupConfiguration;
+
+/**
+ * Common base class for all daemon and application
+ */
+public abstract class BasicCommand implements Command {
+    
+    private ActionNotifier<ApplicationState> notifier;
+    
+    public BasicCommand() {
+        this.notifier = new ActionNotifier<>(this);
+    }
+
+    public ActionNotifier<ApplicationState> getNotifier() {
+        return notifier;
+    }
+
+    public abstract StartupConfiguration getConfiguration();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/cli/CommandExceptionTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -0,0 +1,75 @@
+/*
+ * 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.cli;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class CommandExceptionTest {
+
+    @Test
+    public void testDefaultConstructor() {
+        CommandException ce = new CommandException();
+        verifyMessageAndCause(ce, null, null);
+    }
+
+    @Test
+    public void testMessageConstructor() {
+        CommandException ce = new CommandException("test");
+        verifyMessageAndCause(ce, "test", null);
+    }
+
+    @Test
+    public void testCauseConstructor() {
+        Exception cause = new Exception("test fluff");
+        CommandException ce = new CommandException(cause);
+        verifyMessageAndCause(ce, "java.lang.Exception: test fluff", cause);
+    }
+
+    @Test
+    public void testCombinedConstructor() {
+        Exception cause = new Exception("test fluff");
+        CommandException ce = new CommandException("test", cause);
+        verifyMessageAndCause(ce, "test", cause);
+    }
+
+    private void verifyMessageAndCause(CommandException ce, String message, Exception cause) {
+        assertEquals(message, ce.getMessage());
+        assertSame(cause, ce.getCause());
+    }
+}
--- a/common/src/test/java/com/redhat/thermostat/cli/CommandRegistryTest.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/CommandRegistryTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -77,12 +77,12 @@
     }
 
     @Test
-    public void testRegisterCommands() {
+    public void testRegisterCommands() throws CommandException {
         runAndVerifyCommand("test1", cmd1);
         runAndVerifyCommand("test2", cmd2);
     }
 
-    private void runAndVerifyCommand(String name, Command cmd) {
+    private void runAndVerifyCommand(String name, Command cmd) throws CommandException {
         Command actualCmd = registry.getCommand(name);
         TestCommandContextFactory cf = new TestCommandContextFactory();
         CommandContext ctx = cf.createContext(new String[0]);
--- a/common/src/test/java/com/redhat/thermostat/cli/HelpCommandTest.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/HelpCommandTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -137,9 +137,9 @@
     public void testUsage() {
         HelpCommand cmd = new HelpCommand();
         String expected = "help [COMMAND]\n\n"
-                          + "show help for a given command or help overview\n\n"
-                          + "    With no arguments, print a list of commands with short help messages.\n\n"
-                          + "    Given a command, print help for that command.";
+                          + "show help for a given command or help overview\n\n\t"
+                          + "With no arguments, print a list of commands with short help messages.\n\n\t"
+                          + "Given a command, print help for that command.";
 
         assertEquals(expected, cmd.getUsage());
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/cli/LauncherTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -0,0 +1,125 @@
+/*
+ * 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.cli;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LauncherTest {
+
+    private static class TestCmd1 implements TestCommand.Handle {
+
+        @Override
+        public void run(CommandContext ctx) {
+            ctx.getConsole().getOutput().print(ctx.getArguments()[0] + ", " + ctx.getArguments()[1]);
+        }
+
+    }
+
+    private static class TestCmd2 implements TestCommand.Handle {
+        @Override
+        public void run(CommandContext ctx) {
+            ctx.getConsole().getOutput().print(ctx.getArguments()[1] + ": " + ctx.getArguments()[0]);
+        }
+    }
+
+    private TestCommandContextFactory  ctxFactory;
+
+    @Before
+    public void setUp() {
+
+        CLITestEnvironment.setUp();
+        ctxFactory = new TestCommandContextFactory();
+        CommandContextFactory.setInstance(ctxFactory);
+
+        TestCommand cmd1 = new TestCommand("test1", new TestCmd1());
+        cmd1.setDescription("description 1");
+        TestCommand cmd2 = new TestCommand("test2", new TestCmd2());
+        cmd2.setDescription("description 2");
+        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(cmd1, cmd2, new HelpCommand()));
+
+    }
+
+    @After
+    public void tearDown() {
+        CLITestEnvironment.tearDown();
+    }
+
+    @Test
+    public void testMain() {
+        runAndVerifyCommand(new String[] {"test1", "Hello", "World"}, "Hello, World");
+
+        ctxFactory.reset();
+
+        runAndVerifyCommand(new String[] {"test2", "Hello", "World"}, "World: Hello");
+    }
+
+    @Test
+    public void testMainNoArgs() {
+        String expected = "list of commands:\n\n"
+                          + " help\t\tshow help for a given command or help overview\n"
+                          + " test1\t\tdescription 1\n"
+                          + " test2\t\tdescription 2\n";
+        runAndVerifyCommand(new String[0], expected);
+    }
+
+    @Test
+    public void testMainExceptionInCommand() {
+        TestCommand errorCmd = new TestCommand("error", new TestCommand.Handle() {
+            
+            @Override
+            public void run(CommandContext ctx) throws CommandException {
+                throw new CommandException("test error");
+            }
+        });
+        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(errorCmd));
+
+        Launcher.main(new String[] { "error" });
+        assertEquals("test error\n", ctxFactory.getError());
+
+    }
+
+    private void runAndVerifyCommand(String[] args, String expected) {
+        Launcher.main(args);
+        assertEquals(expected, ctxFactory.getOutput());
+    }
+}
--- a/common/src/test/java/com/redhat/thermostat/cli/MainTest.java	Tue Apr 10 21:27:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +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.cli;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.Arrays;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class MainTest {
-
-    private static class TestCmd1 implements TestCommand.Handle {
-
-        @Override
-        public void run(CommandContext ctx) {
-            ctx.getConsole().getOutput().print(ctx.getArguments()[0] + ", " + ctx.getArguments()[1]);
-        }
-
-    }
-
-    private static class TestCmd2 implements TestCommand.Handle {
-        @Override
-        public void run(CommandContext ctx) {
-            ctx.getConsole().getOutput().print(ctx.getArguments()[1] + ": " + ctx.getArguments()[0]);
-        }
-    }
-
-    private TestCommandContextFactory  ctxFactory;
-
-    @Before
-    public void setUp() {
-
-        CLITestEnvironment.setUp();
-        ctxFactory = new TestCommandContextFactory();
-        CommandContextFactory.setInstance(ctxFactory);
-
-        TestCommand cmd1 = new TestCommand("test1", new TestCmd1());
-        cmd1.setDescription("description 1");
-        TestCommand cmd2 = new TestCommand("test2", new TestCmd2());
-        cmd2.setDescription("description 2");
-        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(cmd1, cmd2, new HelpCommand()));
-
-    }
-
-    @After
-    public void tearDown() {
-        CLITestEnvironment.tearDown();
-    }
-
-    @Test
-    public void testMain() {
-        runAndVerifyCommand(new String[] {"test1", "Hello", "World"}, "Hello, World");
-
-        ctxFactory.reset();
-
-        runAndVerifyCommand(new String[] {"test2", "Hello", "World"}, "World: Hello");
-    }
-
-    @Test
-    public void testMainNoArgs() {
-        String expected = "list of commands:\n\n"
-                          + " help\t\tshow help for a given command or help overview\n"
-                          + " test1\t\tdescription 1\n"
-                          + " test2\t\tdescription 2\n";
-        runAndVerifyCommand(new String[0], expected);
-    }
-
-    private void runAndVerifyCommand(String[] args, String expected) {
-        Main.main(args);
-        assertEquals(expected, ctxFactory.getOutput());
-    }
-}
--- a/common/src/test/java/com/redhat/thermostat/cli/TestCommand.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/TestCommand.java	Tue Apr 10 23:18:38 2012 +0200
@@ -45,7 +45,7 @@
     private String usage;
 
     static interface Handle {
-        public void run(CommandContext ctx);
+        public void run(CommandContext ctx) throws CommandException;
     }
 
     TestCommand(String name) {
@@ -58,7 +58,7 @@
     }
 
     @Override
-    public void run(CommandContext ctx) {
+    public void run(CommandContext ctx) throws CommandException {
         handle.run(ctx);
     }
 
--- a/common/src/test/java/com/redhat/thermostat/cli/TestCommandContextFactory.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/TestCommandContextFactory.java	Tue Apr 10 23:18:38 2012 +0200
@@ -73,7 +73,7 @@
     }
 
     @Override
-    CommandContext createContext(final String[] args) {
+    public CommandContext createContext(final String[] args) {
         return new CommandContext() {
 
             @Override
--- a/common/src/test/java/com/redhat/thermostat/tools/BasicApplicationTest.java	Tue Apr 10 21:27:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +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.tools;
-
-import static org.junit.Assert.*;
-
-import java.util.List;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.config.StartupConfiguration;
-
-public class BasicApplicationTest {
-
-    private BasicApplication application;
-
-    @Before
-    public void setUp() {
-        application = new BasicApplication() {
-            @Override
-            public void parseArguments(List<String> args)
-                    throws InvalidConfigurationException { }
-
-            @Override
-            public void run() {}
-
-            @Override
-            public StartupConfiguration getConfiguration() {
-                return null;
-            }
-
-            @Override
-            public void printHelp() {}
-        };
-    }
-
-    @After
-    public void tearDown() {
-        application = null;
-    }
-
-    @Test
-    public void testNotfier() {
-        assertNotNull(application.getNotifier());
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/tools/BasicCommandTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -0,0 +1,96 @@
+/*
+ * 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.tools;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.cli.CommandContext;
+import com.redhat.thermostat.cli.CommandException;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.config.StartupConfiguration;
+
+public class BasicCommandTest {
+
+    private BasicCommand application;
+
+    @Before
+    public void setUp() {
+        application = new BasicCommand() {
+
+            @Override
+            public void run(CommandContext ctx) throws CommandException {
+                // Nothing to do here.
+            }
+
+            @Override
+            public String getName() {
+                return null;
+            }
+
+            @Override
+            public String getDescription() {
+                return null;
+            }
+
+            @Override
+            public String getUsage() {
+                return null;
+            }
+
+            @Override
+            public StartupConfiguration getConfiguration() {
+                return null;
+            }
+        };
+    }
+
+    @After
+    public void tearDown() {
+        application = null;
+    }
+
+    @Test
+    public void testNotfier() {
+        assertNotNull(application.getNotifier());
+    }
+}
--- a/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Tue Apr 10 23:18:38 2012 +0200
@@ -36,12 +36,10 @@
 
 package com.redhat.thermostat.tools;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 import com.redhat.thermostat.agent.AgentApplication;
+import com.redhat.thermostat.cli.CommandContext;
+import com.redhat.thermostat.cli.CommandContextFactory;
+import com.redhat.thermostat.cli.CommandException;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
@@ -54,39 +52,47 @@
  * Simple service that allows starting Agent and DB Backend
  * in a single step.
  */
-public class ThermostatService implements Application, ActionListener<ApplicationState> {
+public class ThermostatService extends BasicCommand implements ActionListener<ApplicationState> {
     
-    private Application database;
+    private static final String NAME = "service";
+
+    // TODO: Use LocaleResources for i18n-ized strings.
+    private static final String DESCRIPTION = "starts and stops the thermostat storage and agent";
+
+    private static final String USAGE = "service start|stop\n\n"
+                + DESCRIPTION + "\n\n\t"
+                + "With argument 'start', start the storage amd agent\n\t"
+                + "With argument 'stop', stop the storage and agent.";
+
+    private BasicCommand database;
     private AgentApplication agent;
     
     private ActionNotifier<ApplicationState> notifier;
-    
+
     public ThermostatService() {
         database = new DBService();
         agent = new AgentApplication();
         notifier = new ActionNotifier<>(this);
     }
     
-    @Override
-    public void parseArguments(List<String> args) throws InvalidConfigurationException {
+    private void addListeners() throws InvalidConfigurationException {
         // Currently, all the arguments are for the db. We only configure the
         // agent accordingly to the database settings.
         // so nothing else is done here at this stage
-        database.parseArguments(args);
         database.getNotifier().addActionListener(this);
         agent.getNotifier().addActionListener(this);
     }
 
     @Override
-    public void run() {
-        // just run the database, if the database is successful, let the
-        // listeners start the agent for us.
-        database.run();
-    }
-
-    @Override
-    public void printHelp() {
-        // TODO, no, really, seriously
+    public void run(CommandContext ctx) throws CommandException {
+        try {
+            addListeners();
+            // just run the database, if the database is successful, let the
+            // listeners start the agent for us.
+            database.run(ctx);
+        } catch (InvalidConfigurationException e) {
+            throw new CommandException(e);
+        }
     }
 
     @Override
@@ -98,15 +104,6 @@
     public StartupConfiguration getConfiguration() {
         throw new NotImplementedException("NYI");
     }
-    
-    public static void main(String[] args) throws IOException, InvalidConfigurationException {
-        ThermostatService service = new ThermostatService();
-        // TODO: other than failing, this should really print the help
-        // from the appropriate application instead, see the printHelp comment
-        // too.
-        service.parseArguments(Arrays.asList(args));
-        service.run();
-    }
 
     @Override
     public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
@@ -116,14 +113,11 @@
             // we started the database ourselves
             case START:
                 String dbUrl = database.getConfiguration().getDBConnectionString();
-                List<String> args = new ArrayList<>();
-                args.add("--dbUrl");
-                args.add(dbUrl);
+                String[] args = new String[] { "--dbUrl", dbUrl };
                 try {
-                    agent.parseArguments(args);
                     System.err.println("starting agent now...");
-                    agent.run();
-                } catch (InvalidConfigurationException e) {
+                    agent.run(CommandContextFactory.getInstance().createContext(args));
+                } catch (CommandException e) {
                     notifier.fireAction(ApplicationState.FAIL);
                 }
                 break;
@@ -137,4 +131,19 @@
             }
         }
     }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESCRIPTION;
+    }
+
+    @Override
+    public String getUsage() {
+        return USAGE;
+    }
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Tue Apr 10 23:18:38 2012 +0200
@@ -43,21 +43,32 @@
 import java.util.List;
 import java.util.Properties;
 
+import com.redhat.thermostat.cli.CommandContext;
+import com.redhat.thermostat.cli.CommandException;
 import com.redhat.thermostat.common.config.ConfigUtils;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
 import com.redhat.thermostat.tools.ApplicationException;
 import com.redhat.thermostat.tools.ApplicationState;
-import com.redhat.thermostat.tools.BasicApplication;
+import com.redhat.thermostat.tools.BasicCommand;
+
+public class DBService extends BasicCommand {
+
+    private static final String NAME = "storage";
 
-public class DBService extends BasicApplication {
+    // TODO: Use LocaleResources for i18n-ized strings.
+    private static final String DESCRIPTION = "starts and stops the thermostat storage";
+
+    private static final String USAGE = "storage start|stop\n\n"
+                + DESCRIPTION + "\n\n\t"
+                + "With argument 'start', start the storage.\n\t"
+                + "With argument 'stop', stop the storage.";
 
     private DBStartupConfiguration configuration;
     private DBOptionParser parser;
     
     private MongoProcessRunner runner;
     
-    @Override
-    public void parseArguments(List<String> args) throws InvalidConfigurationException {
+    private void parseArguments(List<String> args) throws InvalidConfigurationException {
     
         this.configuration = new DBStartupConfiguration();
         // configs, read everything that is in the configs
@@ -72,6 +83,43 @@
         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(Arrays.asList(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);
+        }
+    }
+    
     private void readAndSetProperties(File propertyFile) throws InvalidConfigurationException {
     
         Properties properties = new Properties();
@@ -125,32 +173,6 @@
         getNotifier().fireAction(ApplicationState.STOP);
     }
     
-    @Override
-    public void run() {
-        
-        // 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);
-        }
-    }
-    
     MongoProcessRunner createRunner() {
         return new MongoProcessRunner(configuration, parser.isQuiet());
     }
@@ -165,18 +187,23 @@
     }
     
     @Override
-    public void printHelp() {
-        parser.displayHelp();
-    }
-    
-    @Override
     public DBStartupConfiguration getConfiguration() {
         return configuration;
     }
-    
-    public static void main(String[] args) throws InvalidConfigurationException {
-        DBService service = new DBService();
-        service.parseArguments(Arrays.asList(args));
-        service.run();
+
+    @Override
+    public String getName() {
+        return NAME;
     }
+
+    @Override
+    public String getDescription() {
+        return DESCRIPTION;
+    }
+
+    @Override
+    public String getUsage() {
+        return USAGE;
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/src/test/java/com/redhat/thermostat/tools/ThermostatServiceTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -0,0 +1,79 @@
+/*
+ * 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.tools;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ThermostatServiceTest {
+
+    private ThermostatService thermostatService;
+
+    @Before
+    public void setUp() {
+        thermostatService = new ThermostatService();
+    }
+
+    @After
+    public void tearDown() {
+        thermostatService = null;
+    }
+
+    @Test
+    public void testName() {
+        String name = thermostatService.getName();
+        assertEquals("service", name);
+    }
+
+    @Test
+    public void testDescription() {
+        String desc = thermostatService.getDescription();
+        assertEquals("starts and stops the thermostat storage and agent", desc);
+    }
+
+    @Test
+    public void testUsage() {
+        String usage = thermostatService.getUsage();
+        assertEquals("service start|stop\n\n"
+                + "starts and stops the thermostat storage and agent" + "\n\n\t"
+                + "With argument 'start', start the storage amd agent\n\t"
+                + "With argument 'stop', stop the storage and agent.", usage);
+    }
+}
--- a/tools/src/test/java/com/redhat/thermostat/tools/db/DBServiceTest.java	Tue Apr 10 21:27:33 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/db/DBServiceTest.java	Tue Apr 10 23:18:38 2012 +0200
@@ -36,14 +36,14 @@
 
 package com.redhat.thermostat.tools.db;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Properties;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
@@ -53,6 +53,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.cli.CommandContext;
+import com.redhat.thermostat.cli.CommandException;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
@@ -105,17 +107,15 @@
     }
     
     @Test
-    public void testConfig() throws InvalidConfigurationException {
+    public void testConfig() throws CommandException {
         
-        List<String> args = new ArrayList<>();
-        args.add("--quiet");
-        args.add("--start");
-        args.add("--dry-run");
-        
+        String[] args = new String[] {"--quiet", "--start", "--dry-run" };
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+
         DBService service = new DBService();
-        service.parseArguments(args);
 
-        service.run();
+        service.run(ctx);
         
         DBStartupConfiguration conf = service.getConfiguration();
         
@@ -136,24 +136,18 @@
         // TODO: stop not tested yet, but be sure it's not called from the code
         doThrow(new ApplicationException("mock exception")).when(runner).stopService();
         
-        List<String> args = new ArrayList<>();
-        args.add("--quiet");
-        args.add("--start");
-
         DBService service = new DBService() {
             @Override
             MongoProcessRunner createRunner() {
                 return runner;
             }
         };
-        service.parseArguments(args);
         
         return service;
     }
     
     @Test
-    public void testListeners() throws InvalidConfigurationException,
-            InterruptedException, IOException, ApplicationException
+    public void testListeners() throws InterruptedException, IOException, ApplicationException, InvalidConfigurationException, CommandException
     {
         DBService service = prepareService(true);
         
@@ -183,7 +177,7 @@
             }
         });
         
-        service.run();
+        service.run(prepareContext());
         latch.await();
         
         Assert.assertTrue(result[0]);
@@ -191,8 +185,7 @@
     }
     
     @Test
-    public void testListenersFail() throws InvalidConfigurationException,
-            InterruptedException, IOException, ApplicationException
+    public void testListenersFail() throws InterruptedException, IOException, ApplicationException, CommandException, InvalidConfigurationException
     {
         DBService service = prepareService(false);
         
@@ -214,9 +207,40 @@
             }
         });
         
-        service.run();
+        service.run(prepareContext());
         latch.await();
         
         Assert.assertTrue(result[0]);
     }
+
+    private CommandContext prepareContext() {
+        String[] args = new String[] { "--quiet", "--start" };
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+        return ctx;
+    }
+
+    @Test
+    public void testName() {
+        DBService dbService = new DBService();
+        String name = dbService.getName();
+        assertEquals("storage", name);
+    }
+
+    @Test
+    public void testDescription() {
+        DBService dbService = new DBService();
+        String desc = dbService.getDescription();
+        assertEquals("starts and stops the thermostat storage", desc);
+    }
+
+    @Test
+    public void testUsage() {
+        DBService dbService = new DBService();
+        String usage = dbService.getUsage();
+        assertEquals("storage start|stop\n\n"
+                + "starts and stops the thermostat storage" + "\n\n\t"
+                + "With argument 'start', start the storage.\n\t"
+                + "With argument 'stop', stop the storage.", usage);
+    }
 }