changeset 246:c1dbd9616820

Integrate arguments parsing in CLI framework. Reviewed-by: vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-April/000939.html
author Roman Kennke <rkennke@redhat.com>
date Thu, 19 Apr 2012 21:15:47 +0200
parents 7711b0e95139
children 8c612ce65625
files agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java agent/src/main/java/com/redhat/thermostat/agent/config/AgentOptionParser.java agent/src/test/java/com/redhat/thermostat/agent/AgentApplicationTest.java agent/src/test/java/com/redhat/thermostat/agent/config/AgentOptionParserTest.java common/pom.xml common/src/main/java/com/redhat/thermostat/cli/ArgumentSpec.java common/src/main/java/com/redhat/thermostat/cli/Arguments.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/CommandContextImpl.java common/src/main/java/com/redhat/thermostat/cli/CommandLineArgumentParseException.java common/src/main/java/com/redhat/thermostat/cli/CommandLineArguments.java common/src/main/java/com/redhat/thermostat/cli/CommandLineArgumentsParser.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/SimpleArgumentSpec.java common/src/main/java/com/redhat/thermostat/cli/SimpleArguments.java common/src/main/java/com/redhat/thermostat/common/config/ThermostatOptionParser.java common/src/main/java/com/redhat/thermostat/test/TestCommandContextFactory.java common/src/test/java/com/redhat/thermostat/cli/CommandContextFactoryTest.java common/src/test/java/com/redhat/thermostat/cli/CommandLineArgumentsParserTest.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/SimpleArgumentSpecTest.java common/src/test/java/com/redhat/thermostat/cli/SimpleArgumentsTest.java common/src/test/java/com/redhat/thermostat/cli/TestCommand.java common/src/test/java/com/redhat/thermostat/tools/BasicCommandTest.java pom.xml tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java tools/src/main/java/com/redhat/thermostat/tools/db/DBOptionParser.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/cli/ListVMsCommandTest.java tools/src/test/java/com/redhat/thermostat/tools/db/DBServiceTest.java
diffstat 37 files changed, 1147 insertions(+), 219 deletions(-) [+]
line wrap: on
line diff
--- a/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Thu Apr 19 21:15:47 2012 +0200
@@ -37,8 +37,7 @@
 package com.redhat.thermostat.agent;
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Collection;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -47,6 +46,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.ArgumentSpec;
+import com.redhat.thermostat.cli.Arguments;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandException;
 import com.redhat.thermostat.common.Constants;
@@ -58,16 +59,15 @@
 import com.redhat.thermostat.common.dao.DAOFactory;
 import com.redhat.thermostat.common.dao.MongoDAOFactory;
 import com.redhat.thermostat.common.storage.Connection;
-import com.redhat.thermostat.common.storage.StorageProvider;
-import com.redhat.thermostat.common.storage.MongoStorageProvider;
 import com.redhat.thermostat.common.storage.Connection.ConnectionListener;
 import com.redhat.thermostat.common.storage.Connection.ConnectionStatus;
+import com.redhat.thermostat.common.storage.MongoStorageProvider;
+import com.redhat.thermostat.common.storage.StorageProvider;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.tools.BasicCommand;
 
 public final class AgentApplication extends BasicCommand {
 
-    private CommandContext contex;
     private static final String NAME = "agent";
 
     // TODO: Use LocaleResources for i18n-ized strings.
@@ -81,7 +81,7 @@
     private AgentStartupConfiguration configuration;
     private AgentOptionParser parser;
     
-    private void parseArguments(List<String> args) throws InvalidConfigurationException {
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
         configuration = AgentConfigsUtils.createAgentConfigs();
         parser = new AgentOptionParser(configuration, args);
         parser.parse();
@@ -92,7 +92,7 @@
         return configuration;
     }
     
-    private void runAgent() {
+    private void runAgent(CommandContext ctx) {
         long startTime = System.currentTimeMillis();
         configuration.setStartTime(startTime);
         
@@ -156,7 +156,7 @@
         }
         logger.fine("Agent started.");
 
-        contex.getConsole().getOutput().println("Agent id: " + agent.getId());
+        ctx.getConsole().getOutput().println("Agent id: " + agent.getId());
         logger.fine("Agent id: " + agent.getId());
         
         try {
@@ -172,10 +172,9 @@
     @Override
     public void run(CommandContext ctx) throws CommandException {
         try {
-            contex = ctx;
-            parseArguments(Arrays.asList(ctx.getArguments()));
+            parseArguments(ctx.getArguments());
             if (!parser.isHelp()) {
-                runAgent();
+                runAgent(ctx);
             }
         } catch (InvalidConfigurationException ex) {
             throw new CommandException(ex);
@@ -197,4 +196,9 @@
         return USAGE;
     }
 
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        return AgentOptionParser.getAcceptedArguments();
+    }
+
 }
--- a/agent/src/main/java/com/redhat/thermostat/agent/config/AgentOptionParser.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/agent/config/AgentOptionParser.java	Thu Apr 19 21:15:47 2012 +0200
@@ -36,74 +36,52 @@
 
 package com.redhat.thermostat.agent.config;
 
-import java.io.IOException;
-import java.util.List;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.logging.Level;
 
-import joptsimple.OptionParser;
-import joptsimple.OptionSet;
-import joptsimple.OptionSpec;
-
+import com.redhat.thermostat.cli.ArgumentSpec;
+import com.redhat.thermostat.cli.Arguments;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
 import com.redhat.thermostat.common.config.ThermostatOptionParser;
 
 public class AgentOptionParser implements ThermostatOptionParser {
 
     private AgentStartupConfiguration configuration;
-    private OptionParser parser;
-    private List<String> args;
+    private Arguments args;
     
     private boolean isHelp;
     
-    public AgentOptionParser(AgentStartupConfiguration configuration, List<String> args) {
+    public AgentOptionParser(AgentStartupConfiguration configuration, Arguments args) {
         this.configuration = configuration;
         this.args = args;
-        parser = new OptionParser();
         isHelp = false;
     }
     
     @Override
     public void parse() throws InvalidConfigurationException {
 
-        parser.accepts(Args.DEBUG.option, Args.DEBUG.description);
-        parser.accepts(Args.HELP.option, Args.HELP.description);
-        parser.accepts(Args.SAVE_ON_EXIT.option, Args.SAVE_ON_EXIT.description);
-        
-        OptionSpec<String> logLevel =
-                parser.accepts(Args.LEVEL.option, Args.LEVEL.description).
-                      withRequiredArg();
-        OptionSpec<String> dbUrl =
-                parser.accepts(Args.DB.option, Args.DB.description).
-                      withRequiredArg();
-        
-        OptionSet options = parser.parse(args.toArray(new String[0]));
-        if (options.has(Args.HELP.option)) {
-            displayHelp();
-            isHelp = true;
-            return;
-        }
-        
-        if (options.has(Args.SAVE_ON_EXIT.option)) {
+        if (args.hasArgument(Args.SAVE_ON_EXIT.option)) {
             configuration.setPurge(false);
         }
         
-        if (options.has(Args.LEVEL.option)) {
-            String levelString = logLevel.value(options);
+        if (args.hasArgument(Args.LEVEL.option)) {
+            String levelString = args.getArgument(Args.LEVEL.option);
             Level level = AgentConfigsUtils.getLogLevel(levelString);
             configuration.setLogLevel(level);
         }
 
-        configuration.setDebugConsole(options.has(Args.DEBUG.option));
+        configuration.setDebugConsole(args.hasArgument(Args.DEBUG.option));
         
-        if (options.has(Args.DB.option)) {
-            String url = dbUrl.value(options);
+        if (args.hasArgument(Args.DB.option)) {
+            String url = args.getArgument(Args.DB.option);
             configuration.setDatabaseURL(url);
         } else {
             if (configuration.getDBConnectionString() == null) {
                 System.err.println("database url not specified... must be " +
                                    "either set in config or passed on " +
                                    "the command line");
-                displayHelp();
                 isHelp = true;
             }
         }
@@ -113,13 +91,6 @@
         return isHelp;
     }
     
-    @Override
-    public void displayHelp() {
-        try {
-            parser.printHelpOn(System.out);
-        } catch (IOException ignore) {}
-    }
-    
     private static enum Args {
         
         // TODO: localize
@@ -137,4 +108,12 @@
             this.description = description;
         }
     }
+
+    public static Collection<ArgumentSpec> getAcceptedArguments() {
+        ArgumentSpec level = new SimpleArgumentSpec(Args.LEVEL.option, Args.LEVEL.description, false, true);
+        ArgumentSpec saveOnExit = new SimpleArgumentSpec(Args.SAVE_ON_EXIT.option, Args.SAVE_ON_EXIT.description);
+        ArgumentSpec db = new SimpleArgumentSpec(Args.DB.option, Args.DB.description, true, true);
+        ArgumentSpec debug = new SimpleArgumentSpec(Args.DEBUG.option, Args.DEBUG.description);
+        return Arrays.asList(level, saveOnExit, db, debug);
+    }
 }
--- a/agent/src/test/java/com/redhat/thermostat/agent/AgentApplicationTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/agent/src/test/java/com/redhat/thermostat/agent/AgentApplicationTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -37,11 +37,18 @@
 package com.redhat.thermostat.agent;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.cli.ArgumentSpec;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
+
 public class AgentApplicationTest {
 
     // TODO: Test i18nized versions when they come.
@@ -78,4 +85,15 @@
                 + "With argument 'start', start the agent.\n\t"
                 + "With argument 'stop', stop the agent.\n", usage);
     }
+
+    @Test
+    public void testAcceptedArguments() {
+        Collection<ArgumentSpec> args = agent.getAcceptedArguments();
+        assertNotNull(args);
+        assertEquals(4, args.size());
+        assertTrue(args.contains(new SimpleArgumentSpec("saveOnExit", "save the data on exit")));
+        assertTrue(args.contains(new SimpleArgumentSpec("debug", "launch with debug console enabled")));
+        assertTrue(args.contains(new SimpleArgumentSpec("dbUrl", "connect to the given url", true, true)));
+        assertTrue(args.contains(new SimpleArgumentSpec("logLevel", "log level", false, true)));
+    }
 }
--- a/agent/src/test/java/com/redhat/thermostat/agent/config/AgentOptionParserTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/agent/src/test/java/com/redhat/thermostat/agent/config/AgentOptionParserTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -38,8 +38,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.logging.Level;
 
 import junit.framework.Assert;
@@ -49,6 +47,7 @@
 import org.junit.Test;
 
 import com.redhat.thermostat.TestUtils;
+import com.redhat.thermostat.cli.SimpleArguments;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
 
 public class AgentOptionParserTest {
@@ -68,12 +67,10 @@
     @Test
     public void testConfigs1() throws IOException, InvalidConfigurationException {
         
-        List<String> args = new ArrayList<>();
-        args.add("--logLevel");
-        args.add("ALL");
-        args.add("--dbUrl");
-        args.add("testURL");
-        args.add("--debug");
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("logLevel", "ALL");
+        args.addArgument("dbUrl", "testURL");
+        args.addArgument("debug", "--debug");
         
         AgentStartupConfiguration configs = AgentConfigsUtils.createAgentConfigs();
         AgentOptionParser parser = new AgentOptionParser(configs, args);
@@ -88,12 +85,10 @@
     @Test
     public void testConfigs2() throws IOException, InvalidConfigurationException {
         
-        List<String> args = new ArrayList<>();
-        args.add("--logLevel");
-        args.add("FINE");
-        args.add("--dbUrl");
-        args.add("testURL2");
-        args.add("--saveOnExit");
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("logLevel", "FINE");
+        args.addArgument("dbUrl", "testURL2");
+        args.addArgument("saveOnExit", "--saveOnExit");
         
         AgentStartupConfiguration configs = new AgentStartupConfiguration();
         AgentOptionParser parser = new AgentOptionParser(configs, args);
--- a/common/pom.xml	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/pom.xml	Thu Apr 19 21:15:47 2012 +0200
@@ -88,8 +88,8 @@
       <artifactId>mongo-java-driver</artifactId>
     </dependency>
     <dependency>
-      <groupId>net.sf.jopt-simple</groupId>
-      <artifactId>jopt-simple</artifactId>
+      <groupId>commons-cli</groupId>
+      <artifactId>commons-cli</artifactId>
     </dependency>
   </dependencies>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/ArgumentSpec.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,49 @@
+/*
+ * 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 interface ArgumentSpec {
+
+    String getName();
+
+    boolean isRequired();
+
+    boolean isUsingAdditionalArgument();
+
+    String getDescription();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/Arguments.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,45 @@
+/*
+ * 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.List;
+
+public interface Arguments {
+    List<String> getNonOptionArguments();
+    boolean hasArgument(String name);
+    String getArgument(String name);
+}
--- a/common/src/main/java/com/redhat/thermostat/cli/Command.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/Command.java	Thu Apr 19 21:15:47 2012 +0200
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.cli;
 
+import java.util.Collection;
+
+
 public interface Command {
 
     void run(CommandContext ctx) throws CommandException;
@@ -45,4 +48,6 @@
     String getDescription();
 
     String getUsage();
+
+    Collection<ArgumentSpec> getAcceptedArguments();
 }
--- a/common/src/main/java/com/redhat/thermostat/cli/CommandContext.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandContext.java	Thu Apr 19 21:15:47 2012 +0200
@@ -40,7 +40,7 @@
 
     Console getConsole();
 
-    String[] getArguments();
+    Arguments getArguments();
 
     CommandRegistry getCommandRegistry();
 
--- a/common/src/main/java/com/redhat/thermostat/cli/CommandContextFactory.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandContextFactory.java	Thu Apr 19 21:15:47 2012 +0200
@@ -49,17 +49,21 @@
     }
 
     private CommandRegistry commandRegistry = new CommandRegistry();
+    private Console console = new SystemConsole();
 
-    public CommandContext createContext(final String[] args) {
-        return new CommandContextImpl(args, commandRegistry, getAppContextSetup());
+    public CommandContext createContext(Arguments args) {
+        return new CommandContextImpl(args, commandRegistry, getAppContextSetup(), getConsole());
     }
 
     protected AppContextSetup getAppContextSetup() {
         return new AppContextSetupImpl();
     }
 
-    protected CommandRegistry getCommandRegistry() {
+    public CommandRegistry getCommandRegistry() {
         return commandRegistry;
     }
 
+    public Console getConsole() {
+        return console ;
+    }
 }
--- a/common/src/main/java/com/redhat/thermostat/cli/CommandContextImpl.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandContextImpl.java	Thu Apr 19 21:15:47 2012 +0200
@@ -38,23 +38,25 @@
 
 class CommandContextImpl implements CommandContext {
 
-    private String[] arguments;
+    private Arguments arguments;
     private CommandRegistry commandRegistry;
     private AppContextSetup appContextSetup;
+    private Console console;
 
-    CommandContextImpl(String[] args, CommandRegistry cmdReg, AppContextSetup appContextSetup) {
+    CommandContextImpl(Arguments args, CommandRegistry cmdReg, AppContextSetup appContextSetup, Console console) {
         arguments = args;
         commandRegistry = cmdReg;
         this.appContextSetup = appContextSetup;
+        this.console = console;
     }
 
     @Override
     public Console getConsole() {
-        return new SystemConsole();
+        return console;
     }
 
     @Override
-    public String[] getArguments() {
+    public Arguments getArguments() {
         return arguments;
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandLineArgumentParseException.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,58 @@
+/*
+ * 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 CommandLineArgumentParseException extends CommandException {
+
+    public CommandLineArgumentParseException() {
+        super();
+    }
+
+    public CommandLineArgumentParseException(String message) {
+        super(message);
+    }
+
+    public CommandLineArgumentParseException(Throwable cause) {
+        super(cause);
+    }
+
+    public CommandLineArgumentParseException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandLineArguments.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,67 @@
+/*
+ * 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.List;
+
+import org.apache.commons.cli.CommandLine;
+
+class CommandLineArguments implements Arguments {
+
+    private CommandLine cmdLine;
+
+    public CommandLineArguments(CommandLine commandLine) {
+        cmdLine = commandLine;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<String> getNonOptionArguments() {
+        return cmdLine.getArgList();
+    }
+
+    @Override
+    public boolean hasArgument(String name) {
+        return cmdLine.hasOption(name);
+    }
+
+    @Override
+    public String getArgument(String name) {
+        return cmdLine.getOptionValue(name);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/CommandLineArgumentsParser.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,84 @@
+/*
+ * 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.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+public class CommandLineArgumentsParser {
+
+    private List<ArgumentSpec> arguments = new LinkedList<>();
+
+    void addArguments(Collection<ArgumentSpec> args) {
+        arguments.addAll(args);
+    }
+
+    Arguments parse(String[] args) throws CommandLineArgumentParseException {
+        try {
+            Options options = convertToCommonsCLIOptions(arguments);
+            CommandLineParser parser = new GnuParser();
+            CommandLine commandLine;
+            commandLine = parser.parse(options, args);
+            return new CommandLineArguments(commandLine);
+        } catch (ParseException e) {
+            throw new CommandLineArgumentParseException(e);
+        }
+    }
+
+    private Options convertToCommonsCLIOptions(List<ArgumentSpec> args) {
+        Options options = new Options();
+        for (ArgumentSpec spec : args) {
+            options.addOption(convertSpecToOption(spec));
+        }
+        return options;
+    }
+
+    private Option convertSpecToOption(ArgumentSpec spec) {
+        Option option = new Option(spec.getName(), spec.getDescription());
+        option.setRequired(spec.isRequired());
+        option.setArgs(spec.isUsingAdditionalArgument() ? 1 : 0);
+        return option;
+    }
+}
--- a/common/src/main/java/com/redhat/thermostat/cli/HelpCommand.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/HelpCommand.java	Thu Apr 19 21:15:47 2012 +0200
@@ -37,12 +37,11 @@
 package com.redhat.thermostat.cli;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 public class HelpCommand implements Command {
 
-    /**
-     * 
-     */
     private static final int COMMANDS_COLUMNS_WIDTH = 15;
     private static final String NAME = "help";
     private static final String DESCRIPTION = "show help for a given command or help overview";
@@ -53,11 +52,12 @@
 
     @Override
     public void run(CommandContext ctx) {
-        String[] args = ctx.getArguments();
-        if (args.length == 0) {
+        Arguments args = ctx.getArguments();
+        List<String> nonParsed = args.getNonOptionArguments();
+        if (nonParsed.isEmpty()) {
             printCommandSummaries(ctx);
         } else {
-            printCommandUsage(ctx, args[0]);
+            printCommandUsage(ctx, nonParsed.get(0));
         }
     }
 
@@ -108,4 +108,9 @@
         return USAGE;
     }
 
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        return Collections.emptyList();
+    }
+
 }
--- a/common/src/main/java/com/redhat/thermostat/cli/Launcher.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/cli/Launcher.java	Thu Apr 19 21:15:47 2012 +0200
@@ -67,20 +67,26 @@
 
     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);
+        try {
+            parseArgsAndRunCommand(cmdName, cmdArgs, cmdCtxFactory);
+        } catch (CommandException e) {
+            cmdCtxFactory.getConsole().getError().println(e.getMessage());
+        }
     }
 
-    private void runCommandWithContext(Command cmd, CommandContext ctx) {
-        try {
-            cmd.run(ctx);
-        } catch (CommandException e) {
-            ctx.getConsole().getError().println(e.getMessage());
-        }
+    private void parseArgsAndRunCommand(String cmdName, String[] cmdArgs, CommandContextFactory cmdCtxFactory)
+            throws CommandLineArgumentParseException, CommandException {
+
+        CommandRegistry registry = cmdCtxFactory.getCommandRegistry();
+        Command cmd = registry.getCommand(cmdName);
+        CommandLineArgumentsParser cliArgsParser = new CommandLineArgumentsParser();
+        cliArgsParser.addArguments(cmd.getAcceptedArguments());
+        Arguments args = cliArgsParser.parse(cmdArgs);
+        CommandContext ctx = cmdCtxFactory.createContext(args);
+        cmd.run(ctx);
     }
 
+
     private void registerDefaultCommands() {
         CommandContextFactory cmdCtxFactory = CommandContextFactory.getInstance();
         CommandRegistry registry = cmdCtxFactory.getCommandRegistry();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/SimpleArgumentSpec.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,115 @@
+/*
+ * 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.Objects;
+
+public class SimpleArgumentSpec implements ArgumentSpec {
+
+    private String name;
+    private String description;
+    private boolean required;
+    private boolean usingAddionalArgument;
+
+    public SimpleArgumentSpec() {
+        this(null, null, false, false);
+    }
+
+    public SimpleArgumentSpec(String name, String description) {
+        this(name, description, false, false);
+    }
+
+    public SimpleArgumentSpec(String name, String description, boolean required, boolean usingAdditionalArgument) {
+        this.name = name;
+        this.description = description;
+        this.required = required;
+        this.usingAddionalArgument = usingAdditionalArgument;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public boolean isRequired() {
+        return required;
+    }
+
+    public void setRequired(boolean required) {
+        this.required = required;
+    }
+
+    @Override
+    public boolean isUsingAdditionalArgument() {
+        return usingAddionalArgument;
+    }
+
+    public void setUsingAdditionalArgument(boolean usingAddionalArgument) {
+        this.usingAddionalArgument = usingAddionalArgument;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public boolean equals(Object o) {
+        if (! (o instanceof SimpleArgumentSpec)) {
+            return false;
+        }
+        SimpleArgumentSpec other = (SimpleArgumentSpec) o;
+        return Objects.equals(name, other.name)
+                && Objects.equals(description, other.description)
+                && usingAddionalArgument == other.usingAddionalArgument
+                && required == other.required;
+    }
+
+    public int hashCode() {
+        return Objects.hashCode(name) ^ Objects.hashCode(description)
+                ^ Boolean.valueOf(usingAddionalArgument).hashCode()
+                ^ Boolean.valueOf(required).hashCode();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/cli/SimpleArguments.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,68 @@
+/*
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SimpleArguments implements Arguments {
+
+    private Map<String,String> arguments = new HashMap<>();
+
+    @Override
+    public boolean hasArgument(String name) {
+        return arguments.containsKey(name);
+    }
+
+    @Override
+    public String getArgument(String name) {
+        return arguments.get(name);
+    }
+
+    public void addArgument(String name, String value) {
+        arguments.put(name, value);
+        
+    }
+
+    @Override
+    public List<String> getNonOptionArguments() {
+        return null;
+    }
+
+}
--- a/common/src/main/java/com/redhat/thermostat/common/config/ThermostatOptionParser.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/config/ThermostatOptionParser.java	Thu Apr 19 21:15:47 2012 +0200
@@ -38,5 +38,4 @@
 
 public interface ThermostatOptionParser {
     void parse() throws InvalidConfigurationException;
-    void displayHelp();
 }
--- a/common/src/main/java/com/redhat/thermostat/test/TestCommandContextFactory.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/test/TestCommandContextFactory.java	Thu Apr 19 21:15:47 2012 +0200
@@ -42,6 +42,7 @@
 import java.io.PrintStream;
 
 import com.redhat.thermostat.cli.AppContextSetup;
+import com.redhat.thermostat.cli.Arguments;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandContextFactory;
 import com.redhat.thermostat.cli.CommandRegistry;
@@ -67,6 +68,8 @@
         }
     };
 
+    private TestConsole console;
+
     private class TestConsole implements Console {
 
         @Override
@@ -87,7 +90,7 @@
     }
 
     @Override
-    public CommandContext createContext(final String[] args) {
+    public CommandContext createContext(final Arguments args) {
         return new CommandContext() {
 
             @Override
@@ -96,7 +99,7 @@
             }
 
             @Override
-            public String[] getArguments() {
+            public Arguments getArguments() {
                 return args;
             }
 
@@ -139,5 +142,10 @@
         out = new ByteArrayOutputStream();
         err = new ByteArrayOutputStream();
         in = new ByteArrayInputStream(new byte[0]);
+        console = new TestConsole();
+    }
+
+    public Console getConsole() {
+        return console;
     }
 }
--- a/common/src/test/java/com/redhat/thermostat/cli/CommandContextFactoryTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/CommandContextFactoryTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -39,17 +39,19 @@
 import static org.junit.Assert.*;
 
 import org.junit.Test;
+import static org.mockito.Mockito.*;
 
 public class CommandContextFactoryTest {
 
     @Test
     public void testDefaultInstance() {
         CommandContextFactory cmdCtxFactory = CommandContextFactory.getInstance();
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] {"arg1", "arg2"});
+        Arguments args = mock(Arguments.class);
+        CommandContext ctx = cmdCtxFactory.createContext(args);
         assertSame(System.out, ctx.getConsole().getOutput());
         assertSame(System.err, ctx.getConsole().getError());
         assertSame(System.in, ctx.getConsole().getInput());
-        assertArrayEquals(new String[] {"arg1", "arg2"}, ctx.getArguments());
+        assertSame(args, ctx.getArguments());
         assertNotNull(cmdCtxFactory.getCommandRegistry());
         assertNotNull(ctx.getCommandRegistry());
         assertSame(cmdCtxFactory.getCommandRegistry(), ctx.getCommandRegistry());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/cli/CommandLineArgumentsParserTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,114 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CommandLineArgumentsParserTest {
+
+    private CommandLineArgumentsParser parser;
+
+    private ArgumentSpec arg3;
+
+    @Before
+    public void setUp() {
+        parser = new CommandLineArgumentsParser();
+
+        ArgumentSpec arg1 = mock(ArgumentSpec.class);
+        when(arg1.getName()).thenReturn("test1");
+        when(arg1.isRequired()).thenReturn(true);
+
+        ArgumentSpec arg2 = mock(ArgumentSpec.class);
+        when(arg2.getName()).thenReturn("test2");
+        when(arg2.isRequired()).thenReturn(false);
+
+        arg3 = mock(ArgumentSpec.class);
+        when(arg3.getName()).thenReturn("test3");
+        when(arg3.isRequired()).thenReturn(false);
+        when(arg3.isUsingAdditionalArgument()).thenReturn(true);
+
+        parser.addArguments(Arrays.asList(arg1, arg2, arg3));
+    }
+
+    @After
+    public void tearDown() {
+       parser = null; 
+       arg3 = null;
+    }
+
+    @Test
+    public void testSimpleArgs() throws CommandLineArgumentParseException {
+        Arguments args = parser.parse(new String[] { "--test1", "--test2" });
+
+        assertTrue(args.hasArgument("test1"));
+        assertEquals(null, args.getArgument("test1"));
+        assertTrue(args.hasArgument("test2"));
+        assertEquals(null, args.getArgument("test2"));
+    }
+
+    @Test(expected=CommandLineArgumentParseException.class)
+    public void testNoMatchingArgs() throws CommandLineArgumentParseException {
+        parser.parse(new String[] { "--test1", "--no-match" });
+    }
+
+    @Test(expected=CommandLineArgumentParseException.class)
+    public void testMissingRequiredArgument() throws CommandLineArgumentParseException {
+        parser.parse(new String[] { "--test2" });
+    }
+
+    @Test
+    public void testArgumentWithAdditionalArgument() throws CommandLineArgumentParseException {
+        Arguments args = parser.parse(new String[] { "--test3", "parameter", "--test1" } );
+        assertTrue(args.hasArgument("test3"));
+        assertEquals("parameter", args.getArgument("test3"));
+        assertTrue(args.hasArgument("test1"));
+    }
+
+    @Test(expected=CommandLineArgumentParseException.class)
+    public void testMissingAdditionalArgument() throws CommandLineArgumentParseException {
+        parser.parse(new String[] { "--test3" });
+    }
+}
--- a/common/src/test/java/com/redhat/thermostat/cli/CommandRegistryTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/CommandRegistryTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -87,7 +87,8 @@
     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]);
+        Arguments args = mock(Arguments.class);
+        CommandContext ctx = cf.createContext(args);
         actualCmd.run(ctx);
         verify(cmd).run(ctx);
     }
--- a/common/src/test/java/com/redhat/thermostat/cli/HelpCommandTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/HelpCommandTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -37,6 +37,8 @@
 package com.redhat.thermostat.cli;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
 
@@ -75,7 +77,8 @@
     public void verifyHelpNoArgPrintsListOfCommandsNoCommands() {
 
         HelpCommand cmd = new HelpCommand();
-        cmd.run(ctxFactory.createContext(new String[0]));
+        Arguments args = mock(Arguments.class);
+        cmd.run(ctxFactory.createContext(args));
         String expected = "list of commands:\n\n";
         String actual = ctxFactory.getOutput();
         assertEquals(expected, actual);
@@ -91,7 +94,8 @@
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(cmd1, cmd2));
 
         HelpCommand cmd = new HelpCommand();
-        cmd.run(ctxFactory.createContext(new String[0]));
+        Arguments args = mock(Arguments.class);
+        cmd.run(ctxFactory.createContext(args));
         String expected = "list of commands:\n\n"
                         + " test1         test command 1\n"
                         + " test2longname test command 2\n";
@@ -107,7 +111,9 @@
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(cmd1));
 
         HelpCommand cmd = new HelpCommand();
-        cmd.run(ctxFactory.createContext(new String[] { "test1" }));
+        Arguments args = mock(Arguments.class);
+        when(args.getNonOptionArguments()).thenReturn(Arrays.asList("test1"));
+        cmd.run(ctxFactory.createContext(args));
 
         String actual = ctxFactory.getOutput();
         assertEquals(usage, actual);
@@ -121,7 +127,8 @@
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(cmd1));
 
         HelpCommand cmd = new HelpCommand();
-        cmd.run(ctxFactory.createContext(new String[] { "test12" }));
+        Arguments args = mock(Arguments.class);
+        cmd.run(ctxFactory.createContext(args));
 
         String expected = "list of commands:\n\n"
                         + " test1         test command 1\n";
--- a/common/src/test/java/com/redhat/thermostat/cli/LauncherTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/LauncherTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -52,7 +52,8 @@
 
         @Override
         public void run(CommandContext ctx) {
-            ctx.getConsole().getOutput().print(ctx.getArguments()[0] + ", " + ctx.getArguments()[1]);
+            Arguments args = ctx.getArguments();
+            ctx.getConsole().getOutput().print(args.getArgument("arg1") + ", " + args.getArgument("arg2"));
         }
 
     }
@@ -60,7 +61,8 @@
     private static class TestCmd2 implements TestCommand.Handle {
         @Override
         public void run(CommandContext ctx) {
-            ctx.getConsole().getOutput().print(ctx.getArguments()[1] + ": " + ctx.getArguments()[0]);
+            Arguments args = ctx.getArguments();
+            ctx.getConsole().getOutput().print(args.getArgument("arg4") + ": " + args.getArgument("arg3"));
         }
     }
 
@@ -74,8 +76,22 @@
         CommandContextFactory.setInstance(ctxFactory);
 
         TestCommand cmd1 = new TestCommand("test1", new TestCmd1());
+        SimpleArgumentSpec arg1 = new SimpleArgumentSpec();
+        arg1.setName("arg1");
+        arg1.setUsingAdditionalArgument(true);
+        SimpleArgumentSpec arg2 = new SimpleArgumentSpec();
+        arg2.setName("arg2");
+        arg2.setUsingAdditionalArgument(true);
+        cmd1.addArguments(arg1, arg2);
         cmd1.setDescription("description 1");
         TestCommand cmd2 = new TestCommand("test2", new TestCmd2());
+        SimpleArgumentSpec arg3 = new SimpleArgumentSpec();
+        arg3.setName("arg3");
+        arg3.setUsingAdditionalArgument(true);
+        SimpleArgumentSpec arg4 = new SimpleArgumentSpec();
+        arg4.setName("arg4");
+        arg4.setUsingAdditionalArgument(true);
+        cmd2.addArguments(arg3, arg4);
         cmd2.setDescription("description 2");
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(cmd1, cmd2, new HelpCommand()));
 
@@ -88,11 +104,11 @@
 
     @Test
     public void testMain() {
-        runAndVerifyCommand(new String[] {"test1", "Hello", "World"}, "Hello, World");
+        runAndVerifyCommand(new String[] {"test1", "--arg1", "Hello", "--arg2", "World"}, "Hello, World");
 
         ctxFactory.reset();
 
-        runAndVerifyCommand(new String[] {"test2", "Hello", "World"}, "World: Hello");
+        runAndVerifyCommand(new String[] {"test2", "--arg3", "Hello", "--arg4", "World"}, "World: Hello");
     }
 
     @Test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/cli/SimpleArgumentSpecTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,163 @@
+/*
+ * 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.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SimpleArgumentSpecTest {
+
+    private SimpleArgumentSpec spec;
+    private SimpleArgumentSpec other;
+
+    @Before
+    public void setUp() {
+        spec = new SimpleArgumentSpec();
+        other = new SimpleArgumentSpec();
+    }
+
+    @After
+    public void tearDown() {
+        spec = null;
+        other = null;
+    }
+
+    @Test
+    public void testName() {
+        spec.setName("test1");
+        assertEquals("test1", spec.getName());
+        spec.setName("test2");
+        assertEquals("test2", spec.getName());
+    }
+
+    @Test
+    public void testDescription() {
+        spec.setDescription("test1");
+        assertEquals("test1", spec.getDescription());
+        spec.setDescription("test2");
+        assertEquals("test2", spec.getDescription());
+    }
+
+    @Test
+    public void testRequired() {
+        spec.setRequired(true);
+        assertTrue(spec.isRequired());
+        spec.setRequired(false);
+        assertFalse(spec.isRequired());
+    }
+
+    @Test
+    public void testUsesAdditionalArgument() {
+        spec.setUsingAdditionalArgument(true);
+        assertTrue(spec.isUsingAdditionalArgument());
+        spec.setUsingAdditionalArgument(false);
+        assertFalse(spec.isUsingAdditionalArgument());
+    }
+
+    @Test
+    public void testEquals() {
+        prepareSpecForEqualsTest(spec);
+        prepareSpecForEqualsTest(other);
+
+        assertTrue(spec.equals(other));
+    }
+
+    @Test
+    public void testEqualsUnequalName() {
+        prepareSpecForEqualsTest(spec);
+        prepareSpecForEqualsTest(other);
+
+        other.setName("fluff");
+
+        assertFalse(spec.equals(other));
+    }
+
+    @Test
+    public void testEqualsUnequalDescription() {
+        prepareSpecForEqualsTest(spec);
+        prepareSpecForEqualsTest(other);
+
+        other.setDescription("fluff");
+
+        assertFalse(spec.equals(other));
+    }
+
+    @Test
+    public void testEqualsUnequalUsingAdditionalArgument() {
+        prepareSpecForEqualsTest(spec);
+        prepareSpecForEqualsTest(other);
+
+        other.setUsingAdditionalArgument(false);
+
+        assertFalse(spec.equals(other));
+    }
+
+    @Test
+    public void testEqualsUnequalRequired() {
+        prepareSpecForEqualsTest(spec);
+        prepareSpecForEqualsTest(other);
+
+        other.setRequired(false);
+
+        assertFalse(spec.equals(other));
+    }
+
+    @Test
+    public void testEqualsNull() {
+        prepareSpecForEqualsTest(spec);
+
+        assertFalse(spec.equals(null));
+    }
+
+    @Test
+    public void testHashCode() {
+        prepareSpecForEqualsTest(spec);
+        prepareSpecForEqualsTest(other);
+
+        assertEquals(spec.hashCode(), other.hashCode());
+    }
+
+    private void prepareSpecForEqualsTest(SimpleArgumentSpec spec) {
+        spec.setName("test");
+        spec.setDescription("description");
+        spec.setUsingAdditionalArgument(true);
+        spec.setRequired(true);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/cli/SimpleArgumentsTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -0,0 +1,71 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SimpleArgumentsTest {
+
+    private SimpleArguments args;
+
+    @Before
+    public void setUp() {
+        args = new SimpleArguments();
+    }
+
+    @After
+    public void tearDown() {
+        args = null;
+    }
+
+    @Test
+    public void testSimpleArgs() {
+        args.addArgument("fluff", "fluffor");
+        assertTrue(args.hasArgument("fluff"));
+        assertEquals("fluffor", args.getArgument("fluff"));
+        assertFalse(args.hasArgument("foo"));
+        assertNull(args.getArgument("bar"));
+    }
+
+}
--- a/common/src/test/java/com/redhat/thermostat/cli/TestCommand.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/cli/TestCommand.java	Thu Apr 19 21:15:47 2012 +0200
@@ -36,6 +36,11 @@
 
 package com.redhat.thermostat.cli;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
 
 class TestCommand implements Command {
 
@@ -44,6 +49,8 @@
     private String description;
     private String usage;
 
+    private List<ArgumentSpec> arguments = new LinkedList<ArgumentSpec>();
+
     static interface Handle {
         public void run(CommandContext ctx) throws CommandException;
     }
@@ -84,4 +91,13 @@
     void setUsage(String usage) {
         this.usage = usage;
     }
+
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        return arguments;
+    }
+
+    void addArguments(ArgumentSpec... arguments) {
+        this.arguments.addAll(Arrays.asList(arguments));
+    }
 }
--- a/common/src/test/java/com/redhat/thermostat/tools/BasicCommandTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/tools/BasicCommandTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -38,10 +38,13 @@
 
 import static org.junit.Assert.assertNotNull;
 
+import java.util.Collection;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.cli.ArgumentSpec;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandException;
 import com.redhat.thermostat.common.config.StartupConfiguration;
@@ -78,6 +81,11 @@
             public StartupConfiguration getConfiguration() {
                 return null;
             }
+
+            @Override
+            public Collection<ArgumentSpec> getAcceptedArguments() {
+                return null;
+            }
         };
     }
 
--- a/pom.xml	Mon Apr 16 23:21:57 2012 +0200
+++ b/pom.xml	Thu Apr 19 21:15:47 2012 +0200
@@ -66,7 +66,7 @@
     <jdktools.version>1.7.0</jdktools.version>
     <jfreechart.version>1.0.14</jfreechart.version>
     <mongo-driver.version>2.7.3</mongo-driver.version>
-    <jopt.version>4.3</jopt.version>
+    <commons-cli.version>1.2</commons-cli.version>
 
   </properties>
 
@@ -171,9 +171,9 @@
         <version>${mongo-driver.version}</version>
       </dependency>
       <dependency>
-    	<groupId>net.sf.jopt-simple</groupId>
-    	<artifactId>jopt-simple</artifactId>
-    	<version>${jopt.version}</version>
+        <groupId>commons-cli</groupId>
+        <artifactId>commons-cli</artifactId>
+        <version>${commons-cli.version}</version>
       </dependency>
 
       <dependency>
--- a/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Thu Apr 19 21:15:47 2012 +0200
@@ -36,10 +36,16 @@
 
 package com.redhat.thermostat.tools;
 
+import java.util.Arrays;
+import java.util.Collection;
+
 import com.redhat.thermostat.agent.AgentApplication;
+import com.redhat.thermostat.cli.ArgumentSpec;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandContextFactory;
 import com.redhat.thermostat.cli.CommandException;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
+import com.redhat.thermostat.cli.SimpleArguments;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
@@ -123,7 +129,8 @@
                 });
                 
                 String dbUrl = database.getConfiguration().getDBConnectionString();
-                String[] args = new String[] { "--dbUrl", dbUrl };
+                SimpleArguments args = new SimpleArguments();
+                args.addArgument("dbUrl", dbUrl);
                 try {
                     System.err.println("starting agent now...");
                     agent.run(CommandContextFactory.getInstance().createContext(args));
@@ -156,4 +163,11 @@
     public String getUsage() {
         return USAGE;
     }
+
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        ArgumentSpec start = new SimpleArgumentSpec("start", "start the database and agent");
+        ArgumentSpec stop = new SimpleArgumentSpec("stop", "stop the database and agent");
+        return Arrays.asList(start, stop);
+    }
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java	Thu Apr 19 21:15:47 2012 +0200
@@ -36,15 +36,14 @@
 
 package com.redhat.thermostat.tools.cli;
 
+import java.util.Arrays;
 import java.util.Collection;
 
-import joptsimple.OptionException;
-import joptsimple.OptionParser;
-import joptsimple.OptionSet;
-
+import com.redhat.thermostat.cli.ArgumentSpec;
 import com.redhat.thermostat.cli.Command;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandException;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.dao.DAOFactory;
 import com.redhat.thermostat.common.dao.HostInfoDAO;
@@ -64,11 +63,14 @@
                                         + "Options:\n\n"
                                         + "--dbUrl URL  the URL of the storage to connect to.\n";
 
+    private static final String DB_URL_ARG = "dbUrl";
+
+    private static final String DB_URL_DESC = "the URL of the storage to connect to";
+
     @Override
     public void run(CommandContext ctx) throws CommandException {
 
-        OptionSet options = parseArguments(ctx);
-        String dbUrl = getDBURL(options);
+        String dbUrl = ctx.getArguments().getArgument(DB_URL_ARG);
 
         ctx.getAppContextSetup().setupAppContext(dbUrl);
 
@@ -86,25 +88,6 @@
         formatter.format(ctx.getConsole().getOutput());
     }
 
-    private OptionSet parseArguments(CommandContext ctx) throws CommandException {
-        OptionParser parser = new OptionParser();
-        parser.accepts("dbUrl").withRequiredArg();
-        try {
-            OptionSet options = parser.parse(ctx.getArguments());
-            if (! options.nonOptionArguments().isEmpty()) {
-                throw new CommandException("Unknown arguments: " + options.nonOptionArguments());
-            }
-            return options;
-        } catch (OptionException ex) {
-            throw new CommandException(ex);
-        }
-    }
-
-    private String getDBURL(OptionSet options) {
-        String dbUrl = (String) options.valueOf("dbUrl");
-        return dbUrl;
-    }
-
     @Override
     public String getName() {
         return NAME;
@@ -120,4 +103,10 @@
         return USAGE;
     }
 
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        ArgumentSpec dbUrl = new SimpleArgumentSpec(DB_URL_ARG, DB_URL_DESC, true, true);
+        return Arrays.asList(dbUrl);
+    }
+
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/db/DBOptionParser.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/db/DBOptionParser.java	Thu Apr 19 21:15:47 2012 +0200
@@ -36,12 +36,12 @@
 
 package com.redhat.thermostat.tools.db;
 
-import java.io.IOException;
-import java.util.List;
+import java.util.Arrays;
+import java.util.Collection;
 
-import joptsimple.OptionParser;
-import joptsimple.OptionSet;
-
+import com.redhat.thermostat.cli.ArgumentSpec;
+import com.redhat.thermostat.cli.Arguments;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
 import com.redhat.thermostat.common.config.ThermostatOptionParser;
 import com.redhat.thermostat.tools.ApplicationState;
@@ -52,50 +52,33 @@
     
     private DBStartupConfiguration configuration;
     
-    private List<String> args;
-    private OptionParser parser;
+    private Arguments args;
 
     private DBArgs serviceAction;
     
     private boolean dryRun;
     
-    DBOptionParser(DBStartupConfiguration configuration, List<String> args) {
+    DBOptionParser(DBStartupConfiguration configuration, Arguments args) {
         this.args = args;
         this.configuration = configuration;
-        parser = new OptionParser();
     }
     
     @Override
     public void parse() throws InvalidConfigurationException {
         
-        parser.accepts(DBArgs.START.option, DBArgs.START.description);
-        parser.accepts(DBArgs.STOP.option, DBArgs.STOP.description);
-        
-        parser.accepts(DBArgs.DRY.option, DBArgs.DRY.description);
-        
-        parser.accepts(DBArgs.HELP.option, DBArgs.HELP.description);
-        parser.accepts(DBArgs.QUIET.option, DBArgs.QUIET.description);
-             
-        OptionSet options = parser.parse(args.toArray(new String[0]));
-        if (!options.hasOptions() || options.has(DBArgs.HELP.option)) {
-            displayHelp();
-            return;
-        }
-
-        if (options.has(DBArgs.START.option)) {
+        if (args.hasArgument(DBArgs.START.option)) {
             serviceAction = DBArgs.START;
-        } else if (options.has(DBArgs.STOP.option)) {
+        } else if (args.hasArgument(DBArgs.STOP.option)) {
             serviceAction = DBArgs.STOP;
-            
         } else {
             throw new InvalidConfigurationException("either --start or --stop must be given");
         }
 
-        if (options.has(DBArgs.DRY.option)) {
+        if (args.hasArgument(DBArgs.DRY.option)) {
             dryRun = true;
         }
         
-        if (options.has(DBArgs.QUIET.option)) {
+        if (args.hasArgument(DBArgs.QUIET.option)) {
             quiet = true;
         }
         
@@ -103,7 +86,7 @@
         String url = configuration.getUrl();
         long port = configuration.getLocalPort();
         configuration.setLocal(true);
-        if (options.has(DBArgs.CLUSTER.option)) {
+        if (args.hasArgument(DBArgs.CLUSTER.option)) {
             port = configuration.getClusterPort();
             configuration.setLocal(false);
         }
@@ -114,27 +97,16 @@
         return dryRun;
     }
     
-    @Override
-    public void displayHelp() {
-        
-        if (quiet) return;
-        
-        try {
-            System.out.println("Module [DBService]");
-            parser.printHelpOn(System.out);
-        } catch (IOException ignore) {}
-    }
-    
     ApplicationState getAction() {
         return serviceAction.state;
     }
-    
+
     static enum DBArgs {
                         
         CLUSTER("cluster", "launch the db in cluster mode, if not specified, " +
                 "local mode is the default", ApplicationState.NONE),
                 
-        DRY("dry-run", "run the service in dry run mode", ApplicationState.NONE),
+        DRY("dryRun", "run the service in dry run mode", ApplicationState.NONE),
         
         HELP("help", "print this usage help", ApplicationState.HELP),
         
@@ -157,4 +129,13 @@
     boolean isQuiet() {
         return quiet;
     }
+
+    static Collection<ArgumentSpec> getAcceptedArguments() {
+        ArgumentSpec cluster = new SimpleArgumentSpec(DBArgs.CLUSTER.option, DBArgs.CLUSTER.description);
+        ArgumentSpec dryRun = new SimpleArgumentSpec(DBArgs.DRY.option, DBArgs.DRY.description);
+        ArgumentSpec start = new SimpleArgumentSpec(DBArgs.START.option, DBArgs.START.description);
+        ArgumentSpec stop = new SimpleArgumentSpec(DBArgs.STOP.option, DBArgs.STOP.description);
+        ArgumentSpec quiet = new SimpleArgumentSpec(DBArgs.QUIET.option, DBArgs.QUIET.description);
+        return Arrays.asList(cluster, dryRun, start, stop, quiet);
+    }
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Thu Apr 19 21:15:47 2012 +0200
@@ -39,10 +39,11 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Collection;
 import java.util.Properties;
 
+import com.redhat.thermostat.cli.ArgumentSpec;
+import com.redhat.thermostat.cli.Arguments;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandException;
 import com.redhat.thermostat.common.config.ConfigUtils;
@@ -68,7 +69,7 @@
     
     private MongoProcessRunner runner;
     
-    private void parseArguments(List<String> args) throws InvalidConfigurationException {
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
     
         this.configuration = new DBStartupConfiguration();
         // configs, read everything that is in the configs
@@ -95,7 +96,7 @@
 
     private void parseArgsAndRun(CommandContext ctx)
             throws InvalidConfigurationException {
-        parseArguments(Arrays.asList(ctx.getArguments()));
+        parseArguments(ctx.getArguments());
 
         // dry run means we don't do anything at all
         if (parser.isDryRun()) return;
@@ -206,4 +207,9 @@
         return USAGE;
     }
 
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        return DBOptionParser.getAcceptedArguments();
+    }
+
 }
--- a/tools/src/test/java/com/redhat/thermostat/tools/ThermostatServiceTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/ThermostatServiceTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -36,12 +36,19 @@
 
 package com.redhat.thermostat.tools;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.cli.ArgumentSpec;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
+
 public class ThermostatServiceTest {
 
     private ThermostatService thermostatService;
@@ -76,4 +83,12 @@
                 + "With argument 'start', start the storage amd agent\n\t"
                 + "With argument 'stop', stop the storage and agent.\n", usage);
     }
+
+    @Test
+    public void testArgumentSpecs() {
+        Collection<ArgumentSpec> args = thermostatService.getAcceptedArguments();
+        assertNotNull(args);
+        assertTrue(args.contains(new SimpleArgumentSpec("start", "start the database and agent")));
+        assertTrue(args.contains(new SimpleArgumentSpec("stop", "stop the database and agent")));
+    }
 }
--- a/tools/src/test/java/com/redhat/thermostat/tools/cli/ListVMsCommandTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/cli/ListVMsCommandTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -37,20 +37,26 @@
 package com.redhat.thermostat.tools.cli;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
+import java.util.Collection;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import com.redhat.thermostat.cli.AppContextSetup;
+import com.redhat.thermostat.cli.ArgumentSpec;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandContextFactory;
 import com.redhat.thermostat.cli.CommandException;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
+import com.redhat.thermostat.cli.SimpleArguments;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
 import com.redhat.thermostat.common.dao.DAOFactory;
@@ -113,7 +119,9 @@
     @Test
     public void testRunCreatesConnectionFromArgumentURL() throws CommandException {
 
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] { "--dbUrl", "mongo://fluff:12345" });
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("dbUrl", "mongo://fluff:12345");
+        CommandContext ctx = cmdCtxFactory.createContext(args);
 
         cmd.run(ctx);
 
@@ -121,35 +129,6 @@
 
     }
 
-    @Test(expected=CommandException.class)
-    public void testUnknownOptionThrowsCommandException() throws CommandException {
-
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] { "--fluff", "mongo://fluff:12345" });
-
-        cmd.run(ctx);
-    }
-
-    @Test(expected=CommandException.class)
-    public void testMissingURLThrowsCommandException() throws CommandException {
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] { "--dbUrl" });
-
-        cmd.run(ctx);
-    }
-
-    @Test(expected=CommandException.class)
-    public void testUnknownOptions() throws CommandException {
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] { "--dbUrl", "fluff", "fluff"});
-
-        cmd.run(ctx);
-    }
-
-    @Test(expected=CommandException.class)
-    public void testUnknownOptions2() throws CommandException {
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] { "--dbUrl"});
-
-        cmd.run(ctx);
-    }
-
     @Test
     public void verifyOutputFormatOneLine() throws CommandException {
 
@@ -157,7 +136,9 @@
         when(hostsDAO.getHosts()).thenReturn(Arrays.asList(host1));
         when(vmsDAO.getVMs(host1)).thenReturn(Arrays.asList(new VmRef(host1, 1, "n")));
 
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] { "--dbUrl", "fluff" });
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("--dbUrl", "fluff");
+        CommandContext ctx = cmdCtxFactory.createContext(args);
 
         cmd.run(ctx);
 
@@ -176,7 +157,9 @@
         when(vmsDAO.getVMs(host1)).thenReturn(Arrays.asList(new VmRef(host1, 1, "n"), new VmRef(host1, 2, "n1")));
         when(vmsDAO.getVMs(host2)).thenReturn(Arrays.asList(new VmRef(host2, 123456, "longvmname")));
 
-        CommandContext ctx = cmdCtxFactory.createContext(new String[] { "--dbUrl", "fluff" });
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("--dbUrl", "fluff");
+        CommandContext ctx = cmdCtxFactory.createContext(args);
 
         cmd.run(ctx);
 
@@ -206,4 +189,12 @@
 
         assertEquals(expected, cmd.getUsage());
     }
+
+    @Test
+    public void testAcceptedArguments() {
+        Collection<ArgumentSpec> args = cmd.getAcceptedArguments();
+        assertNotNull(args);
+        assertEquals(1, args.size());
+        assertTrue(args.contains(new SimpleArgumentSpec("dbUrl", "the URL of the storage to connect to", true, true)));
+    }
 }
--- a/tools/src/test/java/com/redhat/thermostat/tools/db/DBServiceTest.java	Mon Apr 16 23:21:57 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/db/DBServiceTest.java	Thu Apr 19 21:15:47 2012 +0200
@@ -37,6 +37,8 @@
 package com.redhat.thermostat.tools.db;
 
 import static org.junit.Assert.assertEquals;
+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;
@@ -44,6 +46,7 @@
 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;
@@ -53,8 +56,11 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.cli.ArgumentSpec;
 import com.redhat.thermostat.cli.CommandContext;
 import com.redhat.thermostat.cli.CommandException;
+import com.redhat.thermostat.cli.SimpleArgumentSpec;
+import com.redhat.thermostat.cli.SimpleArguments;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
@@ -108,8 +114,10 @@
     
     @Test
     public void testConfig() throws CommandException {
-        
-        String[] args = new String[] {"--quiet", "--start", "--dry-run" };
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", "--quiet");
+        args.addArgument("start", "--start");
+        args.addArgument("dry-run", "--dry-run");
         CommandContext ctx = mock(CommandContext.class);
         when(ctx.getArguments()).thenReturn(args);
 
@@ -214,7 +222,9 @@
     }
 
     private CommandContext prepareContext() {
-        String[] args = new String[] { "--quiet", "--start" };
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", "--quiet");
+        args.addArgument("start", "--start");
         CommandContext ctx = mock(CommandContext.class);
         when(ctx.getArguments()).thenReturn(args);
         return ctx;
@@ -243,4 +253,17 @@
                 + "With argument 'start', start the storage.\n\t"
                 + "With argument 'stop', stop the storage.\n", usage);
     }
+
+    @Test
+    public void testArguments() {
+        DBService dbService = new DBService();
+        Collection<ArgumentSpec> args = dbService.getAcceptedArguments();
+        assertNotNull(args);
+        assertEquals(5, args.size());
+        assertTrue(args.contains(new SimpleArgumentSpec("cluster", "launch the db in cluster mode, if not specified, local mode is the default")));
+        assertTrue(args.contains(new SimpleArgumentSpec("dryRun", "run the service in dry run mode")));
+        assertTrue(args.contains(new SimpleArgumentSpec("start", "start the database")));
+        assertTrue(args.contains(new SimpleArgumentSpec("stop", "stop the database")));
+        assertTrue(args.contains(new SimpleArgumentSpec("quiet", "don't produce any output")));
+    }
 }