This is an automated email from the ASF dual-hosted git repository.

jkoshakow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/fluo.git


The following commit(s) were added to refs/heads/master by this push:
     new f441623  Centralize command logic to be run from one class
f441623 is described below

commit f441623f03bda88cb9d514822d349b0abcd21d9d
Author: Joseph Koshakow <[email protected]>
AuthorDate: Mon Jan 13 22:21:22 2020 -0500

    Centralize command logic to be run from one class
    
    Using the Command Pattern as inspiration, all the logic for running a
    command has been moved to the FluoProgram class. The purpose of this
    was to prevent calling System.exit() from within the command classes.
    This will allow the commands to be more easily reused outside of a CLI.
    This also makes it easier to print standard error messages for expected
    exceptions by throwing a FluoCommandException, and to print a stack
    trace for all other exceptions.
    
    Fixes #983
---
 .../command/{CommonOpts.java => AppCommand.java}   |  18 ++-
 .../command/{BaseOpts.java => BaseCommand.java}    |  29 +---
 .../java/org/apache/fluo/command/CommandUtil.java  |  13 +-
 .../{ConfigOpts.java => ConfigCommand.java}        |  30 ++--
 .../command/{CommonOpts.java => FluoCommand.java}  |  17 +--
 .../{CommonOpts.java => FluoCommandException.java} |  26 ++--
 .../java/org/apache/fluo/command/FluoConfig.java   |  13 +-
 .../java/org/apache/fluo/command/FluoExec.java     |  46 ++++--
 .../java/org/apache/fluo/command/FluoGetJars.java  |  68 ++++-----
 .../java/org/apache/fluo/command/FluoInit.java     | 137 ++++++++---------
 .../java/org/apache/fluo/command/FluoList.java     |  63 +++++---
 .../java/org/apache/fluo/command/FluoOracle.java   |  26 ++--
 .../java/org/apache/fluo/command/FluoProgram.java  |  75 +++++++++
 .../java/org/apache/fluo/command/FluoRemove.java   |  21 +--
 .../java/org/apache/fluo/command/FluoScan.java     | 167 ++++++++++-----------
 .../java/org/apache/fluo/command/FluoStatus.java   |  13 +-
 .../java/org/apache/fluo/command/FluoWait.java     |  21 +--
 .../java/org/apache/fluo/command/FluoWorker.java   |  27 ++--
 .../org/apache/fluo/command/AppCommandTest.java    |  51 +++++++
 .../org/apache/fluo/command/CommandUtilTest.java}  |  29 ++--
 .../org/apache/fluo/command/ConfigCommandTest.java | 104 +++++++++++++
 .../org/apache/fluo/command/FluoProgramTest.java   | 112 ++++++++++++++
 .../java/org/apache/fluo/command/ScanTest.java     |   6 +-
 .../src/test/resources/test-fluo-conn.properties   |  15 ++
 modules/distribution/src/main/lib/fetch.sh         |   2 +-
 modules/distribution/src/main/scripts/fluo         |  44 +++---
 pom.xml                                            |   2 +-
 27 files changed, 760 insertions(+), 415 deletions(-)

diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java 
b/modules/command/src/main/java/org/apache/fluo/command/AppCommand.java
similarity index 70%
copy from modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
copy to modules/command/src/main/java/org/apache/fluo/command/AppCommand.java
index 004c2ae..3409d51 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/AppCommand.java
@@ -16,19 +16,27 @@
 package org.apache.fluo.command;
 
 import com.beust.jcommander.Parameter;
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.fluo.api.config.FluoConfiguration;
 
-class CommonOpts extends ConfigOpts {
+abstract class AppCommand extends ConfigCommand {
 
   @Parameter(names = "-a", required = true, description = "Fluo application 
name")
   private String applicationName;
 
+  @Override
+  FluoConfiguration getConfig() {
+    FluoConfiguration config = super.getConfig();
+    config.setApplicationName(applicationName);
+    return config;
+  }
+
   String getApplicationName() {
     return applicationName;
   }
 
-  public static CommonOpts parse(String programName, String[] args) {
-    CommonOpts opts = new CommonOpts();
-    parse(programName, opts, args);
-    return opts;
+  @VisibleForTesting
+  void setApplicationName(String applicationName) {
+    this.applicationName = applicationName;
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/BaseOpts.java 
b/modules/command/src/main/java/org/apache/fluo/command/BaseCommand.java
similarity index 63%
rename from modules/command/src/main/java/org/apache/fluo/command/BaseOpts.java
rename to modules/command/src/main/java/org/apache/fluo/command/BaseCommand.java
index c700afe..7e2c734 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/BaseOpts.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/BaseCommand.java
@@ -4,9 +4,9 @@
  * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
  * "License"); you may not use this file except in compliance with the 
License. You may obtain a
  * copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software 
distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
  * or implied. See the License for the specific language governing permissions 
and limitations under
@@ -15,29 +15,14 @@
 
 package org.apache.fluo.command;
 
-import com.beust.jcommander.JCommander;
 import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-
-public class BaseOpts {
 
+public abstract class BaseCommand implements FluoCommand {
   @Parameter(names = {"-h", "-help", "--help"}, help = true, description = 
"Prints help")
-  boolean help;
-
-  public static void parse(String programName, BaseOpts opts, String[] args) {
-    JCommander jcommand = new JCommander(opts);
-    jcommand.setProgramName(programName);
-    try {
-      jcommand.parse(args);
-    } catch (ParameterException e) {
-      System.err.println(e.getMessage());
-      jcommand.usage();
-      System.exit(-1);
-    }
+  private boolean help;
 
-    if (opts.help) {
-      jcommand.usage();
-      System.exit(1);
-    }
+  @Override
+  public boolean isHelp() {
+    return help;
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/CommandUtil.java 
b/modules/command/src/main/java/org/apache/fluo/command/CommandUtil.java
index ee839f4..762f17d 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/CommandUtil.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/CommandUtil.java
@@ -23,11 +23,13 @@ import org.apache.fluo.core.client.FluoAdminImpl;
 
 public class CommandUtil {
 
+  public static final String FLUO_CONN_PROPS = "fluo.conn.props";
+
   public static void verifyAppInitialized(FluoConfiguration config) {
     if (!FluoAdminImpl.isInitialized(config)) {
-      System.out.println("A Fluo '" + config.getApplicationName() + "' 
application has not "
-          + "been initialized yet in Zookeeper at " + 
config.getAppZookeepers());
-      System.exit(-1);
+      throw new FluoCommandException(
+          "A Fluo '" + config.getApplicationName() + "' application has not "
+              + "been initialized yet in Zookeeper at " + 
config.getAppZookeepers());
     }
   }
 
@@ -35,15 +37,14 @@ public class CommandUtil {
     verifyAppInitialized(config);
     try (FluoAdminImpl admin = new FluoAdminImpl(config)) {
       if (!admin.applicationRunning()) {
-        System.out.println("A Fluo '" + config.getApplicationName()
+        throw new FluoCommandException("A Fluo '" + config.getApplicationName()
             + "' application is initialized but is not running!");
-        System.exit(-1);
       }
     }
   }
 
   public static FluoConfiguration resolveFluoConfig() {
-    String connPropsPath = System.getProperty("fluo.conn.props");
+    String connPropsPath = System.getProperty(FLUO_CONN_PROPS);
     if (connPropsPath == null) {
       return new FluoConfiguration();
     } else {
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/ConfigOpts.java 
b/modules/command/src/main/java/org/apache/fluo/command/ConfigCommand.java
similarity index 74%
rename from 
modules/command/src/main/java/org/apache/fluo/command/ConfigOpts.java
rename to 
modules/command/src/main/java/org/apache/fluo/command/ConfigCommand.java
index 09c9dea..35b5c36 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/ConfigOpts.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/ConfigCommand.java
@@ -21,9 +21,10 @@ import java.util.List;
 
 import com.beust.jcommander.Parameter;
 import com.beust.jcommander.converters.IParameterSplitter;
+import com.google.common.annotations.VisibleForTesting;
 import org.apache.fluo.api.config.FluoConfiguration;
 
-public class ConfigOpts extends BaseOpts {
+public abstract class ConfigCommand extends BaseCommand {
 
   public static class NullSplitter implements IParameterSplitter {
     @Override
@@ -36,30 +37,35 @@ public class ConfigOpts extends BaseOpts {
       description = "Override configuration set in properties file. Expected 
format: -o <key>=<value>")
   private List<String> properties = new ArrayList<>();
 
-  List<String> getProperties() {
-    return properties;
-  }
-
-  void overrideFluoConfig(FluoConfiguration config) {
+  private void overrideFluoConfig(FluoConfiguration config) {
     for (String prop : getProperties()) {
       String[] propArgs = prop.split("=", 2);
       if (propArgs.length == 2) {
         String key = propArgs[0].trim();
         String value = propArgs[1].trim();
         if (key.isEmpty() || value.isEmpty()) {
-          throw new IllegalArgumentException("Invalid command line -o option: 
" + prop);
+          throw new FluoCommandException("Invalid command line -o option: " + 
prop);
         } else {
           config.setProperty(key, value);
         }
       } else {
-        throw new IllegalArgumentException("Invalid command line -o option: " 
+ prop);
+        throw new FluoCommandException("Invalid command line -o option: " + 
prop);
       }
     }
   }
 
-  public static ConfigOpts parse(String programName, String[] args) {
-    ConfigOpts opts = new ConfigOpts();
-    parse(programName, opts, args);
-    return opts;
+  FluoConfiguration getConfig() {
+    FluoConfiguration config = CommandUtil.resolveFluoConfig();
+    overrideFluoConfig(config);
+    return config;
+  }
+
+  public List<String> getProperties() {
+    return properties;
+  }
+
+  @VisibleForTesting
+  void setProperties(List<String> properties) {
+    this.properties = properties;
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoCommand.java
similarity index 66%
copy from modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
copy to modules/command/src/main/java/org/apache/fluo/command/FluoCommand.java
index 004c2ae..e2f6bb3 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoCommand.java
@@ -15,20 +15,9 @@
 
 package org.apache.fluo.command;
 
-import com.beust.jcommander.Parameter;
+public interface FluoCommand {
 
-class CommonOpts extends ConfigOpts {
+  void execute() throws FluoCommandException;
 
-  @Parameter(names = "-a", required = true, description = "Fluo application 
name")
-  private String applicationName;
-
-  String getApplicationName() {
-    return applicationName;
-  }
-
-  public static CommonOpts parse(String programName, String[] args) {
-    CommonOpts opts = new CommonOpts();
-    parse(programName, opts, args);
-    return opts;
-  }
+  boolean isHelp();
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoCommandException.java
similarity index 66%
copy from modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
copy to 
modules/command/src/main/java/org/apache/fluo/command/FluoCommandException.java
index 004c2ae..60d25ed 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
+++ 
b/modules/command/src/main/java/org/apache/fluo/command/FluoCommandException.java
@@ -4,9 +4,9 @@
  * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
  * "License"); you may not use this file except in compliance with the 
License. You may obtain a
  * copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software 
distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
  * or implied. See the License for the specific language governing permissions 
and limitations under
@@ -15,20 +15,22 @@
 
 package org.apache.fluo.command;
 
-import com.beust.jcommander.Parameter;
+public class FluoCommandException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
 
-class CommonOpts extends ConfigOpts {
+  public FluoCommandException() {
+    super();
+  }
 
-  @Parameter(names = "-a", required = true, description = "Fluo application 
name")
-  private String applicationName;
+  public FluoCommandException(String msg) {
+    super(msg);
+  }
 
-  String getApplicationName() {
-    return applicationName;
+  public FluoCommandException(String msg, Throwable cause) {
+    super(msg, cause);
   }
 
-  public static CommonOpts parse(String programName, String[] args) {
-    CommonOpts opts = new CommonOpts();
-    parse(programName, opts, args);
-    return opts;
+  public FluoCommandException(Throwable cause) {
+    super(cause);
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoConfig.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoConfig.java
index 467b928..605ec74 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoConfig.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoConfig.java
@@ -17,17 +17,18 @@ package org.apache.fluo.command;
 
 import java.util.Map;
 
+import com.beust.jcommander.Parameters;
 import org.apache.fluo.api.client.FluoAdmin;
 import org.apache.fluo.api.client.FluoFactory;
 import org.apache.fluo.api.config.FluoConfiguration;
 
-public class FluoConfig {
+@Parameters(commandNames = "config",
+    commandDescription = "Prints application configuration stored in Zookeeper 
for <app>")
+public class FluoConfig extends AppCommand {
 
-  public static void main(String[] args) {
-    CommonOpts opts = CommonOpts.parse("fluo config", args);
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
     CommandUtil.verifyAppInitialized(config);
     try (FluoAdmin admin = FluoFactory.newAdmin(config)) {
       for (Map.Entry<String, String> entry : 
admin.getApplicationConfig().toMap().entrySet()) {
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoExec.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoExec.java
index bffc448..1a993f5 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoExec.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoExec.java
@@ -15,17 +15,22 @@
 
 package org.apache.fluo.command;
 
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.Arrays;
+import java.util.List;
 
 import javax.inject.Provider;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.client.FluoAdminImpl;
 
-public class FluoExec {
+@Parameters(commandNames = "exec",
+    commandDescription = "Executes <class> with <args> using classpath for 
<app>")
+public class FluoExec extends BaseCommand implements FluoCommand {
 
   private static class FluoConfigModule extends AbstractModule {
 
@@ -44,25 +49,40 @@ public class FluoExec {
     }
   }
 
-  public static void main(String[] args) throws Exception {
-    if (args.length < 2) {
-      System.err.println("Usage: fluo exec <app> <class> args...");
-      System.exit(-1);
+  @Parameter(description = "<app> <class> args...", variableArity = true)
+  private List<String> args;
+
+  @Override
+  public void execute() throws FluoCommandException {
+    if (args.size() < 2) {
+      throw new FluoCommandException("Usage: fluo exec <app> <class> args...");
     }
-    final String applicationName = args[0];
-    final String className = args[1];
+    final String applicationName = args.get(0);
+    final String className = args.get(1);
 
     FluoConfiguration fluoConfig = CommandUtil.resolveFluoConfig();
     fluoConfig.setApplicationName(applicationName);
     CommandUtil.verifyAppInitialized(fluoConfig);
     fluoConfig = FluoAdminImpl.mergeZookeeperConfig(fluoConfig);
 
-    Class<?> clazz = Class.forName(className);
+    try {
+      Class<?> clazz = Class.forName(className);
+
+      // inject fluo configuration
+      Guice.createInjector(new FluoConfigModule(clazz, fluoConfig));
 
-    // inject fluo configuration
-    Guice.createInjector(new FluoConfigModule(clazz, fluoConfig));
+      Method method = clazz.getMethod("main", String[].class);
+      List<String> execArgs = args.subList(2, args.size());
+      method.invoke(null, (Object) execArgs.toArray(new 
String[execArgs.size()]));
+    } catch (NoSuchMethodException | IllegalAccessException | 
InvocationTargetException e) {
+      throw new FluoCommandException(String.format("Class %s must have a main 
method", className),
+          e);
+    } catch (ClassNotFoundException e) {
+      throw new FluoCommandException(String.format("Class %s not found", 
className), e);
+    }
+  }
 
-    Method method = clazz.getMethod("main", String[].class);
-    method.invoke(null, (Object) Arrays.copyOfRange(args, 2, args.length));
+  public List<String> getArgs() {
+    return args;
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoGetJars.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoGetJars.java
index 2ac32fe..4f55bb2 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoGetJars.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoGetJars.java
@@ -4,9 +4,9 @@
  * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
  * "License"); you may not use this file except in compliance with the 
License. You may obtain a
  * copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software 
distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
  * or implied. See the License for the specific language governing permissions 
and limitations under
@@ -16,9 +16,12 @@
 package org.apache.fluo.command;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.URI;
+import java.net.URISyntaxException;
 
 import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import org.apache.commons.io.FileUtils;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.client.FluoAdminImpl;
@@ -28,57 +31,46 @@ import org.apache.hadoop.fs.Path;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class FluoGetJars {
+@Parameters(commandNames = "get-jars",
+    commandDescription = "Copies <app> jars from DFS to local <dir>")
+public class FluoGetJars extends AppCommand {
 
   private static final Logger log = LoggerFactory.getLogger(FluoGetJars.class);
 
-  public static class GetJarsOpts extends CommonOpts {
+  @Parameter(names = "-d", required = true, description = "Download directory 
path")
+  private String downloadPath;
 
-    @Parameter(names = "-d", required = true, description = "Download 
directory path")
-    private String downloadPath;
-
-    String getDownloadPath() {
-      return downloadPath;
-    }
-
-    public static GetJarsOpts parse(String[] args) {
-      GetJarsOpts opts = new GetJarsOpts();
-      parse("fluo get-jars", opts, args);
-      return opts;
-    }
+  String getDownloadPath() {
+    return downloadPath;
   }
 
-  public static void main(String[] args) {
-
-    GetJarsOpts opts = GetJarsOpts.parse(args);
-
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
 
     CommandUtil.verifyAppInitialized(config);
     config = FluoAdminImpl.mergeZookeeperConfig(config);
     if (config.getObserverJarsUrl().isEmpty()) {
-      log.info("No observer jars found for the '{}' Fluo application!", 
opts.getApplicationName());
+      log.info("No observer jars found for the '{}' Fluo application!", 
getApplicationName());
       return;
     }
 
-    try {
-      if (config.getObserverJarsUrl().startsWith("hdfs://")) {
-        try (FileSystem fs = FileSystem.get(new URI(config.getDfsRoot()), new 
Configuration())) {
-          File downloadPathFile = new File(opts.getDownloadPath());
-          if (downloadPathFile.exists()) {
-            FileUtils.deleteDirectory(downloadPathFile);
-          }
-          fs.copyToLocalFile(new Path(config.getObserverJarsUrl()),
-              new Path(opts.getDownloadPath()));
+    if (config.getObserverJarsUrl().startsWith("hdfs://")) {
+      try (FileSystem fs = FileSystem.get(new URI(config.getDfsRoot()), new 
Configuration())) {
+        File downloadPathFile = new File(getDownloadPath());
+        if (downloadPathFile.exists()) {
+          FileUtils.deleteDirectory(downloadPathFile);
         }
-      } else {
-        log.error("Unsupported url prefix for {}={}", 
FluoConfiguration.OBSERVER_JARS_URL_PROP,
-            config.getObserverJarsUrl());
+        fs.copyToLocalFile(new Path(config.getObserverJarsUrl()), new 
Path(getDownloadPath()));
+      } catch (URISyntaxException e) {
+        throw new FluoCommandException(
+            String.format("Error parsing DFS ROOT URI: %s", e.getMessage()), 
e);
+      } catch (IOException e) {
+        throw new FluoCommandException(e);
       }
-    } catch (Exception e) {
-      log.error("", e);
+    } else {
+      throw new FluoCommandException(String.format("Unsupported url prefix for 
%s=%s",
+          FluoConfiguration.OBSERVER_JARS_URL_PROP, 
config.getObserverJarsUrl()));
     }
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoInit.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoInit.java
index 9eeb5b9..e1c9bb2 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoInit.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoInit.java
@@ -4,9 +4,9 @@
  * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
  * "License"); you may not use this file except in compliance with the 
License. You may obtain a
  * copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software 
distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
  * or implied. See the License for the specific language governing permissions 
and limitations under
@@ -22,65 +22,59 @@ import java.io.InputStreamReader;
 import java.util.Optional;
 
 import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.base.Preconditions;
 import org.apache.fluo.api.client.FluoAdmin;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.client.FluoAdminImpl;
 
-public class FluoInit {
+@Parameters(commandNames = "init",
+    commandDescription = "Initializes Fluo application for <app> using 
<appProps>")
+public class FluoInit extends AppCommand {
 
-  public static class InitOptions extends CommonOpts {
+  @Parameter(names = "-p", required = true, description = "Path to application 
properties file")
+  private String appPropsPath;
 
-    @Parameter(names = "-p", required = true, description = "Path to 
application properties file")
-    private String appPropsPath;
+  @Parameter(names = {"-f", "--force"},
+      description = "Skip all prompts and clears Zookeeper and Accumulo table. 
 Equivalent to "
+          + "setting both --clearTable --clearZookeeper")
+  private boolean force;
 
-    @Parameter(names = {"-f", "--force"},
-        description = "Skip all prompts and clears Zookeeper and Accumulo 
table.  Equivalent to "
-            + "setting both --clearTable --clearZookeeper")
-    private boolean force;
+  @Parameter(names = {"--clearTable"}, description = "Skips prompt and clears 
Accumulo table")
+  private boolean clearTable;
 
-    @Parameter(names = {"--clearTable"}, description = "Skips prompt and 
clears Accumulo table")
-    private boolean clearTable;
+  @Parameter(names = {"--clearZookeeper"}, description = "Skips prompt and 
clears Zookeeper")
+  private boolean clearZookeeper;
 
-    @Parameter(names = {"--clearZookeeper"}, description = "Skips prompt and 
clears Zookeeper")
-    private boolean clearZookeeper;
+  @Parameter(names = {"-u", "--update"}, description = "Update Fluo 
configuration in Zookeeper")
+  private boolean update;
 
-    @Parameter(names = {"-u", "--update"}, description = "Update Fluo 
configuration in Zookeeper")
-    private boolean update;
+  @Parameter(names = "--retrieveProperty",
+      description = "Gets specified property without initializing")
+  private String retrieveProperty;
 
-    @Parameter(names = "--retrieveProperty",
-        description = "Gets specified property without initializing")
-    private String retrieveProperty;
-
-    String getAppPropsPath() {
-      return appPropsPath;
-    }
-
-    boolean getForce() {
-      return force;
-    }
+  String getAppPropsPath() {
+    return appPropsPath;
+  }
 
-    boolean getClearTable() {
-      return clearTable;
-    }
+  boolean getForce() {
+    return force;
+  }
 
-    boolean getClearZookeeper() {
-      return clearZookeeper;
-    }
+  boolean getClearTable() {
+    return clearTable;
+  }
 
-    boolean getUpdate() {
-      return update;
-    }
+  boolean getClearZookeeper() {
+    return clearZookeeper;
+  }
 
-    String getRetrieveProperty() {
-      return retrieveProperty;
-    }
+  boolean getUpdate() {
+    return update;
+  }
 
-    public static InitOptions parse(String[] args) {
-      InitOptions opts = new InitOptions();
-      parse("fluo init", opts, args);
-      return opts;
-    }
+  String getRetrieveProperty() {
+    return retrieveProperty;
   }
 
   private static boolean readYes() {
@@ -90,7 +84,7 @@ public class FluoInit {
       try {
         input = 
Optional.ofNullable(bufferedReader.readLine()).orElse("").trim();
       } catch (IOException e) {
-        throw new IllegalStateException(e);
+        throw new FluoCommandException(e);
       }
       if (input.equalsIgnoreCase("y")) {
         return true;
@@ -102,53 +96,49 @@ public class FluoInit {
     }
   }
 
-  public static void main(String[] args) {
-
-    InitOptions opts = InitOptions.parse(args);
-    File applicationPropsFile = new File(opts.getAppPropsPath());
+  @Override
+  public void execute() throws FluoCommandException {
+    File applicationPropsFile = new File(getAppPropsPath());
     Preconditions.checkArgument(applicationPropsFile.exists(),
-        opts.getAppPropsPath() + " does not exist");
+        getAppPropsPath() + " does not exist");
 
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
+    FluoConfiguration config = getConfig();
     config.load(applicationPropsFile);
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
 
-    String propKey = opts.getRetrieveProperty();
+    String propKey = getRetrieveProperty();
     if (propKey != null && !propKey.isEmpty()) {
       if (config.containsKey(propKey)) {
         System.out.println(config.getString(propKey));
       }
-      System.exit(0);
+      return;
     }
 
     if (!config.hasRequiredAdminProps()) {
-      System.err.println("Error - Required properties are not set in " + 
opts.getAppPropsPath());
-      System.exit(-1);
+      throw new FluoCommandException(
+          "Error - Required properties are not set in " + getAppPropsPath());
     }
     try {
       config.validate();
     } catch (Exception e) {
-      System.err.println("Error - Invalid configuration due to " + 
e.getMessage());
-      System.exit(-1);
+      throw new FluoCommandException("Error - Invalid configuration due to " + 
e.getMessage(), e);
     }
 
     try (FluoAdminImpl admin = new FluoAdminImpl(config)) {
 
       FluoAdmin.InitializationOptions initOpts = new 
FluoAdmin.InitializationOptions();
 
-      if (opts.getUpdate()) {
+      if (getUpdate()) {
         System.out.println("Updating configuration for the Fluo '" + 
config.getApplicationName()
-            + "' application in Zookeeper using " + opts.getAppPropsPath());
+            + "' application in Zookeeper using " + getAppPropsPath());
         admin.updateSharedConfig();
         System.out.println("Update is complete.");
-        System.exit(0);
+        return;
       }
 
-      if (opts.getForce()) {
+      if (getForce()) {
         initOpts.setClearZookeeper(true).setClearTable(true);
       } else {
-        if (opts.getClearZookeeper()) {
+        if (getClearZookeeper()) {
           initOpts.setClearZookeeper(true);
         } else if (admin.zookeeperInitialized()) {
           System.out.print("A Fluo '" + config.getApplicationName()
@@ -158,12 +148,11 @@ public class FluoInit {
           if (readYes()) {
             initOpts.setClearZookeeper(true);
           } else {
-            System.out.println("Aborted initialization.");
-            System.exit(-1);
+            throw new FluoCommandException("Aborted initialization.");
           }
         }
 
-        if (opts.getClearTable()) {
+        if (getClearTable()) {
           initOpts.setClearTable(true);
         } else if (admin.accumuloTableExists()) {
           System.out.print("The Accumulo table '" + config.getAccumuloTable()
@@ -171,24 +160,18 @@ public class FluoInit {
           if (readYes()) {
             initOpts.setClearTable(true);
           } else {
-            System.out.println("Aborted initialization.");
-            System.exit(-1);
+            throw new FluoCommandException("Aborted initialization.");
           }
         }
       }
 
       System.out.println("Initializing Fluo '" + config.getApplicationName()
-          + "' application using " + opts.getAppPropsPath());
+          + "' application using " + getAppPropsPath());
 
       admin.initialize(initOpts);
       System.out.println("Initialization is complete.");
-    } catch (FluoAdmin.AlreadyInitializedException e) {
-      System.err.println(e.getMessage());
-      System.exit(-1);
-    } catch (Exception e) {
-      System.out.println("Initialization failed due to the following 
exception:");
-      e.printStackTrace();
-      System.exit(-1);
+    } catch (FluoAdmin.AlreadyInitializedException | 
FluoAdmin.TableExistsException e) {
+      throw new FluoCommandException(e.getMessage(), e);
     }
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoList.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoList.java
index 02f9768..a430d0c 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoList.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoList.java
@@ -18,29 +18,30 @@ package org.apache.fluo.command;
 import java.util.Collections;
 import java.util.List;
 
+import com.beust.jcommander.Parameters;
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.client.FluoAdminImpl;
 import org.apache.fluo.core.util.CuratorUtil;
 
-public class FluoList {
+@Parameters(commandNames = "list",
+    commandDescription = "Lists all Fluo applications in Fluo instance")
+public class FluoList extends ConfigCommand {
 
-  public static void main(String[] args) throws Exception {
-
-    ConfigOpts commandOpts = ConfigOpts.parse("fluo list", args);
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    commandOpts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
 
     try (CuratorFramework curator = CuratorUtil.newFluoCurator(config)) {
       curator.start();
 
-      if (curator.checkExists().forPath("/") == null) {
+      if (!checkCuratorExists(curator)) {
         System.out.println("Fluo instance (" + config.getInstanceZookeepers() 
+ ") has not been "
             + "created yet in Zookeeper.  It will be created when the first 
Fluo application is "
             + "initialized for this instance.");
         return;
       }
-      List<String> children = curator.getChildren().forPath("/");
+      List<String> children = getCuratorChildren(curator);
       if (children.isEmpty()) {
         System.out.println("Fluo instance (" + config.getInstanceZookeepers() 
+ ") does not "
             + "contain any Fluo applications.");
@@ -54,17 +55,43 @@ public class FluoList {
       System.out.println("-----------     ------     ---------");
 
       for (String path : children) {
-        FluoConfiguration appConfig = new FluoConfiguration(config);
-        appConfig.setApplicationName(path);
-        try (FluoAdminImpl admin = new FluoAdminImpl(appConfig)) {
-          String state = "STOPPED";
-          if (admin.applicationRunning()) {
-            state = "RUNNING";
-          }
-          int numWorkers = admin.numWorkers();
-          System.out.format("%-15s %-11s %4d\n", path, state, numWorkers);
-        }
+        listApp(config, path);
+      }
+    }
+  }
+
+  private boolean checkCuratorExists(CuratorFramework curator) {
+    try {
+      return curator.checkExists().forPath("/") != null;
+    } catch (RuntimeException e) {
+      throw e;
+    } catch (Exception e) {
+      // throwing RuntimeException so stack trace is printed on command line
+      throw new RuntimeException("Error getting curator children", e);
+    }
+  }
+
+  private List<String> getCuratorChildren(CuratorFramework curator) {
+    try {
+      return curator.getChildren().forPath("/");
+    } catch (RuntimeException e) {
+      throw e;
+    } catch (Exception e) {
+      // throwing RuntimeException so stack trace is printed on command line
+      throw new RuntimeException("Error getting curator children", e);
+    }
+  }
+
+  private void listApp(FluoConfiguration config, String path) {
+    FluoConfiguration appConfig = new FluoConfiguration(config);
+    appConfig.setApplicationName(path);
+    try (FluoAdminImpl admin = new FluoAdminImpl(appConfig)) {
+      String state = "STOPPED";
+      if (admin.applicationRunning()) {
+        state = "RUNNING";
       }
+      int numWorkers = admin.numWorkers();
+      System.out.format("%-15s %-11s %4d\n", path, state, numWorkers);
     }
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoOracle.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoOracle.java
index 3cea36a..0f2f896 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoOracle.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoOracle.java
@@ -15,30 +15,24 @@
 
 package org.apache.fluo.command;
 
+import com.beust.jcommander.Parameters;
 import org.apache.fluo.api.client.FluoFactory;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.util.UtilWaitThread;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class FluoOracle {
+@Parameters(commandNames = "oracle", commandDescription = "Starts Fluo Oracle 
process for <app>")
+public class FluoOracle extends AppCommand {
 
-  private static final Logger log = LoggerFactory.getLogger(FluoOracle.class);
-
-  public static void main(String[] args) {
-    CommonOpts opts = CommonOpts.parse("fluo oracle", args);
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
     CommandUtil.verifyAppInitialized(config);
-    try {
-      org.apache.fluo.api.service.FluoOracle oracle = 
FluoFactory.newOracle(config);
-      oracle.start();
-      while (true) {
-        UtilWaitThread.sleep(10000);
-      }
-    } catch (Exception e) {
-      log.error("Exception running FluoOracle: ", e);
+    org.apache.fluo.api.service.FluoOracle oracle = 
FluoFactory.newOracle(config);
+    oracle.start();
+    while (true) {
+      UtilWaitThread.sleep(10000);
     }
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoProgram.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoProgram.java
new file mode 100644
index 0000000..dfa9bed
--- /dev/null
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoProgram.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional 
information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the 
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+ * or implied. See the License for the specific language governing permissions 
and limitations under
+ * the License.
+ */
+
+package org.apache.fluo.command;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import com.google.common.collect.Iterables;
+
+public class FluoProgram {
+
+  public static void main(String[] args) {
+    List<FluoCommand> fluoCommands = Arrays.asList(new FluoConfig(), new 
FluoExec(),
+        new FluoGetJars(), new FluoInit(), new FluoList(), new FluoOracle(), 
new FluoRemove(),
+        new FluoScan(), new FluoStatus(), new FluoWait(), new FluoWorker());
+    try {
+      runFluoCommand(fluoCommands, args);
+    } catch (FluoCommandException | ParameterException e) {
+      System.exit(1);
+    }
+  }
+
+  public static void runFluoCommand(List<FluoCommand> fluoCommands, String[] 
args) {
+    JCommander.Builder jCommanderBuilder = JCommander.newBuilder();
+    fluoCommands.forEach(jCommanderBuilder::addCommand);
+    JCommander jcommand = jCommanderBuilder.build();
+
+    try {
+      jcommand.parse(args);
+    } catch (ParameterException e) {
+      System.err.println(e.getMessage());
+      String commandName = 
Optional.ofNullable(jcommand.getParsedCommand()).orElse("");
+      JCommander parsedJCommandOrProgram =
+          
Optional.ofNullable(jcommand.findCommandByAlias(commandName)).orElse(jcommand);
+      parsedJCommandOrProgram.setProgramName(String.format("fluo %s", 
commandName));
+      parsedJCommandOrProgram.usage();
+      throw e;
+    }
+
+    String parsedCommandType = jcommand.getParsedCommand();
+    JCommander parsedJCommand = jcommand.findCommandByAlias(parsedCommandType);
+    String programName = String.format("fluo %s", parsedCommandType);
+    parsedJCommand.setProgramName(programName);
+    FluoCommand parsedFluoCommand =
+        (FluoCommand) Iterables.getOnlyElement(parsedJCommand.getObjects());
+
+    if (parsedFluoCommand.isHelp()) {
+      parsedJCommand.usage();
+      return;
+    }
+
+    try {
+      parsedFluoCommand.execute();
+    } catch (FluoCommandException e) {
+      System.err.println(String.format("%s failed - %s", programName, 
e.getMessage()));
+      throw e;
+    }
+  }
+}
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoRemove.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoRemove.java
index a1b37e2..a12fced 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoRemove.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoRemove.java
@@ -15,19 +15,17 @@
 
 package org.apache.fluo.command;
 
+import com.beust.jcommander.Parameters;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.api.exceptions.FluoException;
 import org.apache.fluo.core.client.FluoAdminImpl;
 
-public class FluoRemove {
+@Parameters(commandNames = "remove", commandDescription = "Removes Fluo 
application for <app>")
+public class FluoRemove extends AppCommand {
 
-  public static void main(String[] args) {
-
-    CommonOpts opts = CommonOpts.parse("fluo remove", args);
-
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
     config = FluoAdminImpl.mergeZookeeperConfig(config);
 
     try (FluoAdminImpl admin = new FluoAdminImpl(config)) {
@@ -35,12 +33,7 @@ public class FluoRemove {
       admin.remove();
       System.out.println("Remove is complete.");
     } catch (FluoException e) {
-      System.err.println(e.getMessage());
-      System.exit(-1);
-    } catch (Exception e) {
-      System.out.println("Remove failed due to the following exception:");
-      e.printStackTrace();
-      System.exit(-1);
+      throw new FluoCommandException(e);
     }
   }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoScan.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoScan.java
index 3844e4d..25a2d87 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoScan.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoScan.java
@@ -21,6 +21,7 @@ import java.util.EnumSet;
 import java.util.List;
 
 import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.client.FluoAdminImpl;
 import org.apache.fluo.core.util.ScanUtil;
@@ -28,126 +29,110 @@ import org.apache.fluo.core.util.ScanUtil.ScanFlags;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 
-public class FluoScan {
+@Parameters(commandNames = "scan", commandDescription = "Prints snapshot of 
data in Fluo <app>")
+public class FluoScan extends AppCommand {
 
-  public static class ScanOptions extends CommonOpts {
+  @Parameter(names = "-s", description = "Start row (inclusive) of scan")
+  private String startRow;
 
-    @Parameter(names = "-s", description = "Start row (inclusive) of scan")
-    private String startRow;
+  @Parameter(names = "-e", description = "End row (inclusive) of scan")
+  private String endRow;
 
-    @Parameter(names = "-e", description = "End row (inclusive) of scan")
-    private String endRow;
+  @Parameter(names = "-c", description = "Columns of scan in comma separated 
format: "
+      + 
"<<columnfamily>[:<columnqualifier>]{,<columnfamily>[:<columnqualifier>]}> ")
+  private List<String> columns;
 
-    @Parameter(names = "-c", description = "Columns of scan in comma separated 
format: "
-        + 
"<<columnfamily>[:<columnqualifier>]{,<columnfamily>[:<columnqualifier>]}> ")
-    private List<String> columns;
+  @Parameter(names = "-r", description = "Exact row to scan")
+  private String exactRow;
 
-    @Parameter(names = "-r", description = "Exact row to scan")
-    private String exactRow;
+  @Parameter(names = "-p", description = "Row prefix to scan")
+  private String rowPrefix;
 
-    @Parameter(names = "-p", description = "Row prefix to scan")
-    private String rowPrefix;
+  @Parameter(names = {"-esc", "--escape-non-ascii"}, help = true,
+      description = "Hex encode non ascii bytes", arity = 1)
+  public boolean hexEncNonAscii = true;
 
-    @Parameter(names = {"-esc", "--escape-non-ascii"}, help = true,
-        description = "Hex encode non ascii bytes", arity = 1)
-    public boolean hexEncNonAscii = true;
+  @Parameter(names = "--raw", help = true,
+      description = "Show underlying key/values stored in Accumulo. Interprets 
the data using Fluo "
+          + "internal schema, making it easier to comprehend.")
+  public boolean scanAccumuloTable = false;
 
-    @Parameter(names = "--raw", help = true,
-        description = "Show underlying key/values stored in Accumulo. 
Interprets the data using Fluo "
-            + "internal schema, making it easier to comprehend.")
-    public boolean scanAccumuloTable = false;
+  @Parameter(names = "--json", help = true,
+      description = "Export key/values stored in Accumulo as JSON file.")
+  public boolean exportAsJson = false;
 
-    @Parameter(names = "--json", help = true,
-        description = "Export key/values stored in Accumulo as JSON file.")
-    public boolean exportAsJson = false;
+  @Parameter(names = "--ntfy", help = true, description = "Scan active 
notifications")
+  public boolean scanNtfy = false;
 
-    @Parameter(names = "--ntfy", help = true, description = "Scan active 
notifications")
-    public boolean scanNtfy = false;
-
-    public String getStartRow() {
-      return startRow;
-    }
-
-    public String getEndRow() {
-      return endRow;
-    }
-
-    public String getExactRow() {
-      return exactRow;
-    }
-
-    public String getRowPrefix() {
-      return rowPrefix;
+  /**
+   * Check if the parameters informed can be used together.
+   */
+  private void checkScanOptions() {
+    if (this.scanAccumuloTable && this.exportAsJson) {
+      throw new FluoCommandException("Both \"--raw\" and \"--json\" can not be 
set together.");
     }
 
-    public List<String> getColumns() {
-      if (columns == null) {
-        return Collections.emptyList();
-      }
-      return columns;
+    if (this.scanAccumuloTable && this.scanNtfy) {
+      throw new FluoCommandException("Both \"--raw\" and \"--ntfy\" can not be 
set together.");
     }
+  }
 
-    /**
-     * Check if the parameters informed can be used together.
-     */
-    private void checkScanOptions() {
-      if (this.scanAccumuloTable && this.exportAsJson) {
-        throw new IllegalArgumentException(
-            "Both \"--raw\" and \"--json\" can not be set together.");
-      }
-
-      if (this.scanAccumuloTable && this.scanNtfy) {
-        throw new IllegalArgumentException(
-            "Both \"--raw\" and \"--ntfy\" can not be set together.");
-      }
-    }
-
-    public ScanUtil.ScanOpts getScanOpts() {
-      EnumSet<ScanFlags> flags = EnumSet.noneOf(ScanFlags.class);
-
-      ScanUtil.setFlag(flags, help, ScanFlags.HELP);
-      ScanUtil.setFlag(flags, hexEncNonAscii, ScanFlags.HEX);
-      ScanUtil.setFlag(flags, scanAccumuloTable, ScanFlags.ACCUMULO);
-      ScanUtil.setFlag(flags, exportAsJson, ScanFlags.JSON);
-      ScanUtil.setFlag(flags, scanNtfy, ScanFlags.NTFY);
+  public ScanUtil.ScanOpts getScanOpts() {
+    EnumSet<ScanFlags> flags = EnumSet.noneOf(ScanFlags.class);
 
-      return new ScanUtil.ScanOpts(startRow, endRow, columns, exactRow, 
rowPrefix, flags);
-    }
+    ScanUtil.setFlag(flags, isHelp(), ScanFlags.HELP);
+    ScanUtil.setFlag(flags, hexEncNonAscii, ScanFlags.HEX);
+    ScanUtil.setFlag(flags, scanAccumuloTable, ScanFlags.ACCUMULO);
+    ScanUtil.setFlag(flags, exportAsJson, ScanFlags.JSON);
+    ScanUtil.setFlag(flags, scanNtfy, ScanFlags.NTFY);
 
-    public static ScanOptions parse(String[] args) {
-      ScanOptions opts = new ScanOptions();
-      parse("fluo scan", opts, args);
-      return opts;
-    }
+    return new ScanUtil.ScanOpts(startRow, endRow, columns, exactRow, 
rowPrefix, flags);
   }
 
-  public static void main(String[] args) {
-
+  @Override
+  public void execute() throws FluoCommandException {
     Logger.getRootLogger().setLevel(Level.ERROR);
     Logger.getLogger("org.apache.fluo").setLevel(Level.ERROR);
 
-    ScanOptions options = ScanOptions.parse(args);
-    options.checkScanOptions();
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(options.getApplicationName());
-    options.overrideFluoConfig(config);
+    checkScanOptions();
+    FluoConfiguration config = getConfig();
 
     try {
-      options.overrideFluoConfig(config);
-      if (options.scanAccumuloTable) {
+      if (scanAccumuloTable) {
         config = FluoAdminImpl.mergeZookeeperConfig(config);
-        ScanUtil.scanAccumulo(options.getScanOpts(), config, System.out);
-      } else if (options.scanNtfy) {
+        ScanUtil.scanAccumulo(getScanOpts(), config, System.out);
+      } else if (scanNtfy) {
         config = FluoAdminImpl.mergeZookeeperConfig(config);
-        ScanUtil.scanNotifications(options.getScanOpts(), config, System.out);
+        ScanUtil.scanNotifications(getScanOpts(), config, System.out);
       } else {
         CommandUtil.verifyAppRunning(config);
-        ScanUtil.scanFluo(options.getScanOpts(), config, System.out);
+        ScanUtil.scanFluo(getScanOpts(), config, System.out);
       }
-    } catch (RuntimeException | IOException e) {
-      System.err.println("Scan failed - " + e.getMessage());
-      System.exit(-1);
+    } catch (IOException e) {
+      throw new FluoCommandException(e);
     }
   }
 
+  public String getStartRow() {
+    return startRow;
+  }
+
+  public String getEndRow() {
+    return endRow;
+  }
+
+  public String getExactRow() {
+    return exactRow;
+  }
+
+  public String getRowPrefix() {
+    return rowPrefix;
+  }
+
+  public List<String> getColumns() {
+    if (columns == null) {
+      return Collections.emptyList();
+    }
+    return columns;
+  }
 }
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoStatus.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoStatus.java
index 4cd227f..27ecd5d 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoStatus.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoStatus.java
@@ -15,16 +15,17 @@
 
 package org.apache.fluo.command;
 
+import com.beust.jcommander.Parameters;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.client.FluoAdminImpl;
 
-public class FluoStatus {
+@Parameters(commandNames = "status",
+    commandDescription = "Prints status of Fluo application for <app>")
+public class FluoStatus extends AppCommand {
 
-  public static void main(String[] args) throws Exception {
-    CommonOpts opts = CommonOpts.parse("fluo status", args);
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
     try (FluoAdminImpl admin = new FluoAdminImpl(config)) {
       if (!admin.zookeeperInitialized()) {
         System.out.println("NOT_FOUND");
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoWait.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoWait.java
index 464a904..5b0fa55 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoWait.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoWait.java
@@ -18,6 +18,7 @@ package org.apache.fluo.command;
 import java.util.Collections;
 import java.util.List;
 
+import com.beust.jcommander.Parameters;
 import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.AccumuloSecurityException;
 import org.apache.accumulo.core.client.Scanner;
@@ -33,7 +34,9 @@ import org.slf4j.LoggerFactory;
 
 import static java.util.concurrent.TimeUnit.MINUTES;
 
-public class FluoWait {
+@Parameters(commandNames = "wait",
+    commandDescription = "Waits until all notifications are processed for 
<app>")
+public class FluoWait extends AppCommand {
 
   private static final Logger log = LoggerFactory.getLogger(FluoWait.class);
   private static final long MIN_SLEEP_MS = 250;
@@ -109,17 +112,17 @@ public class FluoWait {
           break;
         }
       }
-    } catch (Exception e) {
-      log.error("An exception was thrown -", e);
-      System.exit(-1);
+    } catch (AccumuloSecurityException | AccumuloException e) {
+      throw new FluoCommandException(String.format("Error getting table 
ranges: ", e.getMessage()),
+          e);
+    } catch (TableNotFoundException e) {
+      throw new FluoCommandException(String.format("Table %s not found", 
e.getTableName()), e);
     }
   }
 
-  public static void main(String[] args) throws Exception {
-    CommonOpts opts = CommonOpts.parse("fluo wait", args);
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
     CommandUtil.verifyAppRunning(config);
     config = FluoAdminImpl.mergeZookeeperConfig(config);
     waitUntilFinished(config);
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/FluoWorker.java 
b/modules/command/src/main/java/org/apache/fluo/command/FluoWorker.java
index 6a0f982..3fd02a7 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/FluoWorker.java
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoWorker.java
@@ -15,32 +15,25 @@
 
 package org.apache.fluo.command;
 
+import com.beust.jcommander.Parameters;
 import org.apache.fluo.api.client.FluoFactory;
 import org.apache.fluo.api.config.FluoConfiguration;
 import org.apache.fluo.core.util.UtilWaitThread;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class FluoWorker {
+@Parameters(commandNames = "worker", commandDescription = "Starts Fluo Worker 
process for <app>")
+public class FluoWorker extends AppCommand {
 
-  private static final Logger log = LoggerFactory.getLogger(FluoWorker.class);
-
-  public static void main(String[] args) {
-
-    CommonOpts opts = CommonOpts.parse("fluo worker", args);
-    FluoConfiguration config = CommandUtil.resolveFluoConfig();
-    config.setApplicationName(opts.getApplicationName());
-    opts.overrideFluoConfig(config);
+  @Override
+  public void execute() throws FluoCommandException {
+    FluoConfiguration config = getConfig();
     CommandUtil.verifyAppInitialized(config);
 
-    try {
-      org.apache.fluo.api.service.FluoWorker worker = 
FluoFactory.newWorker(config);
-      worker.start();
-      while (true) {
-        UtilWaitThread.sleep(10000);
-      }
-    } catch (Exception e) {
-      log.error("Exception running FluoWorker: ", e);
+    org.apache.fluo.api.service.FluoWorker worker = 
FluoFactory.newWorker(config);
+    worker.start();
+    while (true) {
+      UtilWaitThread.sleep(10000);
     }
   }
 }
diff --git 
a/modules/command/src/test/java/org/apache/fluo/command/AppCommandTest.java 
b/modules/command/src/test/java/org/apache/fluo/command/AppCommandTest.java
new file mode 100644
index 0000000..c9e079b
--- /dev/null
+++ b/modules/command/src/test/java/org/apache/fluo/command/AppCommandTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional 
information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the 
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+ * or implied. See the License for the specific language governing permissions 
and limitations under
+ * the License.
+ */
+
+package org.apache.fluo.command;
+
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.fluo.api.config.FluoConfiguration;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class AppCommandTest {
+
+  @Test
+  public void testGetConfig() {
+    AppCommand appCommand = new AppCommand() {
+      @Override
+      public void execute() throws FluoCommandException {}
+    };
+
+    URL testConfig = 
getClass().getClassLoader().getResource("test-fluo-conn.properties");
+    System.setProperty(CommandUtil.FLUO_CONN_PROPS, testConfig.getPath());
+
+    String newAppName = "new-app-name";
+    int newZookeeperTimeout = 100;
+    List<String> overrideConfig = Collections.singletonList(
+        FluoConfiguration.CONNECTION_ZOOKEEPER_TIMEOUT_PROP + "=" + 
newZookeeperTimeout);
+    appCommand.setApplicationName(newAppName);
+    appCommand.setProperties(overrideConfig);
+
+    FluoConfiguration fluoConfiguration = appCommand.getConfig();
+
+    assertEquals(newAppName, fluoConfiguration.getApplicationName());
+    assertEquals(newZookeeperTimeout, fluoConfiguration.getZookeeperTimeout());
+  }
+}
diff --git 
a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java 
b/modules/command/src/test/java/org/apache/fluo/command/CommandUtilTest.java
similarity index 57%
rename from 
modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
rename to 
modules/command/src/test/java/org/apache/fluo/command/CommandUtilTest.java
index 004c2ae..802f71a 100644
--- a/modules/command/src/main/java/org/apache/fluo/command/CommonOpts.java
+++ b/modules/command/src/test/java/org/apache/fluo/command/CommandUtilTest.java
@@ -4,9 +4,9 @@
  * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
  * "License"); you may not use this file except in compliance with the 
License. You may obtain a
  * copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software 
distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
  * or implied. See the License for the specific language governing permissions 
and limitations under
@@ -15,20 +15,23 @@
 
 package org.apache.fluo.command;
 
-import com.beust.jcommander.Parameter;
+import java.net.URL;
 
-class CommonOpts extends ConfigOpts {
+import org.apache.fluo.api.config.FluoConfiguration;
+import org.junit.Test;
 
-  @Parameter(names = "-a", required = true, description = "Fluo application 
name")
-  private String applicationName;
+import static org.junit.Assert.assertEquals;
 
-  String getApplicationName() {
-    return applicationName;
-  }
+public class CommandUtilTest {
+
+  @Test
+  public void testResolveFluoConfig() {
+    URL testConfig = 
getClass().getClassLoader().getResource("test-fluo-conn.properties");
+    System.setProperty(CommandUtil.FLUO_CONN_PROPS, testConfig.getPath());
+
+    FluoConfiguration fluoConfiguration = CommandUtil.resolveFluoConfig();
 
-  public static CommonOpts parse(String programName, String[] args) {
-    CommonOpts opts = new CommonOpts();
-    parse(programName, opts, args);
-    return opts;
+    assertEquals("app-name", fluoConfiguration.getApplicationName());
+    assertEquals(999, fluoConfiguration.getZookeeperTimeout());
   }
 }
diff --git 
a/modules/command/src/test/java/org/apache/fluo/command/ConfigCommandTest.java 
b/modules/command/src/test/java/org/apache/fluo/command/ConfigCommandTest.java
new file mode 100644
index 0000000..494362f
--- /dev/null
+++ 
b/modules/command/src/test/java/org/apache/fluo/command/ConfigCommandTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional 
information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the 
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+ * or implied. See the License for the specific language governing permissions 
and limitations under
+ * the License.
+ */
+
+package org.apache.fluo.command;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.fluo.api.config.FluoConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class ConfigCommandTest {
+
+  private ConfigCommand configCommand;
+
+  @Before
+  public void setUp() {
+    configCommand = new ConfigCommand() {
+      @Override
+      public void execute() throws FluoCommandException {}
+    };
+
+    URL testConfig = 
getClass().getClassLoader().getResource("test-fluo-conn.properties");
+    System.setProperty(CommandUtil.FLUO_CONN_PROPS, testConfig.getPath());
+  }
+
+  @Test
+  public void testNullSplitter() {
+    String testStr = "asdf, safggr = adfjc :";
+    ConfigCommand.NullSplitter nullSplitter = new ConfigCommand.NullSplitter();
+
+    List<String> nullSplitList = nullSplitter.split(testStr);
+
+    assertEquals(1, nullSplitList.size());
+    assertEquals(testStr, nullSplitList.get(0));
+  }
+
+  @Test
+  public void testGetConfigWithOneOverriddenProp() {
+    int newZookeeperTimeout = 100;
+    List<String> overrideConfig = Collections.singletonList(
+        FluoConfiguration.CONNECTION_ZOOKEEPER_TIMEOUT_PROP + "=" + 
newZookeeperTimeout);
+    configCommand.setProperties(overrideConfig);
+
+    FluoConfiguration fluoConfiguration = configCommand.getConfig();
+
+    assertEquals(newZookeeperTimeout, fluoConfiguration.getZookeeperTimeout());
+    assertEquals("app-name", fluoConfiguration.getApplicationName());
+  }
+
+  @Test
+  public void testGetConfigWithTwoOverriddenProp() {
+    int newZookeeperTimeout = 100;
+    int loaderQueueSize = 256;
+    List<String> overrideConfig = Arrays.asList(
+        FluoConfiguration.CONNECTION_ZOOKEEPER_TIMEOUT_PROP + "=" + 
newZookeeperTimeout,
+        FluoConfiguration.LOADER_QUEUE_SIZE_PROP + "=" + "256");
+    configCommand.setProperties(overrideConfig);
+
+    FluoConfiguration fluoConfiguration = configCommand.getConfig();
+
+    assertEquals(newZookeeperTimeout, fluoConfiguration.getZookeeperTimeout());
+    assertEquals(loaderQueueSize, fluoConfiguration.getLoaderQueueSize());
+    assertEquals("app-name", fluoConfiguration.getApplicationName());
+  }
+
+  @Test(expected = FluoCommandException.class)
+  public void testGetConfigInvalidOption() {
+    configCommand.setProperties(Collections.singletonList("Invalid-Option"));
+
+    configCommand.getConfig();
+  }
+
+  @Test(expected = FluoCommandException.class)
+  public void testGetConfigMissingKey() {
+    configCommand.setProperties(Collections.singletonList(" =value"));
+
+    configCommand.getConfig();
+  }
+
+  @Test(expected = FluoCommandException.class)
+  public void testGetConfigMissingValue() {
+    configCommand.setProperties(Collections.singletonList("key= "));
+
+    configCommand.getConfig();
+  }
+}
diff --git 
a/modules/command/src/test/java/org/apache/fluo/command/FluoProgramTest.java 
b/modules/command/src/test/java/org/apache/fluo/command/FluoProgramTest.java
new file mode 100644
index 0000000..e3586a4
--- /dev/null
+++ b/modules/command/src/test/java/org/apache/fluo/command/FluoProgramTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional 
information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache 
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the 
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+ * or implied. See the License for the specific language governing permissions 
and limitations under
+ * the License.
+ */
+
+package org.apache.fluo.command;
+
+import java.io.PrintStream;
+import java.util.Collections;
+
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.Parameters;
+import org.apache.commons.io.output.NullOutputStream;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class FluoProgramTest {
+  @Parameters(commandNames = "test")
+  class MockFluoCommand implements FluoCommand {
+
+    boolean help = false;
+    boolean throwException = false;
+    private boolean executed = false;
+
+    @Override
+    public void execute() throws FluoCommandException {
+      if (throwException) {
+        throw new FluoCommandException();
+      } else {
+        executed = true;
+      }
+    }
+
+    @Override
+    public boolean isHelp() {
+      return help;
+    }
+
+    public boolean isExecuted() {
+      return executed;
+    }
+  }
+
+  private MockFluoCommand mockFluoCommand;
+  private static PrintStream outPS;
+  private static PrintStream errPS;
+
+  @BeforeClass
+  public static void disablePrinting() {
+    outPS = System.out;
+    errPS = System.err;
+    // This will hide usage and error logs when running tests
+    try (PrintStream ps = new PrintStream(new NullOutputStream())) {
+      System.setOut(ps);
+      System.setErr(ps);
+    }
+  }
+
+  @AfterClass
+  public static void restorePrinting() {
+    System.setOut(outPS);
+    System.setErr(errPS);
+  }
+
+  @Before
+  public void setUp() {
+    mockFluoCommand = new MockFluoCommand();
+  }
+
+  @Test(expected = ParameterException.class)
+  public void testUnparsableCommand() {
+    FluoProgram.runFluoCommand(Collections.singletonList(new 
MockFluoCommand()),
+        new String[] {"invalid", "command"});
+  }
+
+  @Test
+  public void testHelpCommand() {
+    mockFluoCommand.help = true;
+
+    FluoProgram.runFluoCommand(Collections.singletonList(mockFluoCommand), new 
String[] {"test"});
+
+    assertFalse(mockFluoCommand.isExecuted());
+  }
+
+  @Test
+  public void testExecutedCommand() {
+    FluoProgram.runFluoCommand(Collections.singletonList(mockFluoCommand), new 
String[] {"test"});
+
+    assertTrue(mockFluoCommand.isExecuted());
+  }
+
+  @Test(expected = FluoCommandException.class)
+  public void testExecutionError() {
+    mockFluoCommand.throwException = true;
+
+    FluoProgram.runFluoCommand(Collections.singletonList(mockFluoCommand), new 
String[] {"test"});
+  }
+}
diff --git 
a/modules/command/src/test/java/org/apache/fluo/command/ScanTest.java 
b/modules/command/src/test/java/org/apache/fluo/command/ScanTest.java
index 20377a9..fabc3aa 100644
--- a/modules/command/src/test/java/org/apache/fluo/command/ScanTest.java
+++ b/modules/command/src/test/java/org/apache/fluo/command/ScanTest.java
@@ -30,10 +30,10 @@ import org.junit.Test;
 public class ScanTest {
 
   private SnapshotScanner.Opts parseArgs(String args) {
-    FluoScan.ScanOptions options = new FluoScan.ScanOptions();
-    JCommander jcommand = new JCommander(options);
+    FluoScan scan = new FluoScan();
+    JCommander jcommand = new JCommander(scan);
     jcommand.parse(args.split(" "));
-    ScanUtil.ScanOpts opts = options.getScanOpts();
+    ScanUtil.ScanOpts opts = scan.getScanOpts();
     return new SnapshotScanner.Opts(ScanUtil.getSpan(opts), 
ScanUtil.getColumns(opts), false);
   }
 
diff --git a/modules/command/src/test/resources/test-fluo-conn.properties 
b/modules/command/src/test/resources/test-fluo-conn.properties
new file mode 100644
index 0000000..113f9f3
--- /dev/null
+++ b/modules/command/src/test/resources/test-fluo-conn.properties
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+# agreements.  See the NOTICE file distributed with this work for additional 
information regarding
+# copyright ownership.  The ASF licenses this file to you under the Apache 
License, Version 2.0
+# (the "License"); you may not use this file except in compliance with the 
License.  You may
+# obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software 
distributed under the License
+# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+# or implied. See the License for the specific language governing permissions 
and limitations under
+# the License.
+
+fluo.connection.application.name=app-name
+fluo.connection.zookeeper.timeout=999
diff --git a/modules/distribution/src/main/lib/fetch.sh 
b/modules/distribution/src/main/lib/fetch.sh
index 5f9c854..4cedc82 100755
--- a/modules/distribution/src/main/lib/fetch.sh
+++ b/modules/distribution/src/main/lib/fetch.sh
@@ -55,7 +55,7 @@ ahz)
 extra)
   echo "Fetching extra Fluo dependencies"
   download aopalliance:aopalliance:jar:1.0
-  download com.beust:jcommander:jar:1.72
+  download com.beust:jcommander:jar:1.78
   download com.google.code.gson:gson:jar:2.8.5
   download com.google.guava:guava:jar:27.0-jre
   download com.google.inject:guice:jar:4.2.2
diff --git a/modules/distribution/src/main/scripts/fluo 
b/modules/distribution/src/main/scripts/fluo
index 9a9426b..7372ddc 100755
--- a/modules/distribution/src/main/scripts/fluo
+++ b/modules/distribution/src/main/scripts/fluo
@@ -94,9 +94,6 @@ function verify_app {
     echo -e "The application name (set by <app>) cannot be an empty string!\n"
     print_usage
   fi
-  if [[ $1 = *"-h"* ]]; then
-    print_usage
-  fi
 }
 
 function check_hadoop {
@@ -113,13 +110,14 @@ function check_hadoop {
 function setup_service {
   if [[ "$@" =~ ^.*-a\ *([^\ ]*).*$ ]]; then
     app=${BASH_REMATCH[1]}
+
     verify_app "$app"
     check_conn_props
     # create a temp dir to fetch application jars to
     app_lib=$(mktemp -d "$FLUO_TMP"/fluo-"$app"-XXXXXXXXX) || die "fatal: 
unable to allocate a temporary directory"
     # schedule removal of app_lib tmp dir when this script exits
     trap "rm -rf '""$app_lib""'" EXIT HUP INT QUIT TERM
-    $JAVA org.apache.fluo.command.FluoGetJars -d "$app_lib" "$@"
+    $JAVA org.apache.fluo.command.FluoProgram get-jars -d "$app_lib" "$@"
     export CLASSPATH="$conf:$app_lib/*:$CLASSPATH"
   else
     echo "Application name must be set!"
@@ -131,50 +129,50 @@ function setup_service {
 case "$1" in
 config)
   check_conn_props
-  $JAVA org.apache.fluo.command.FluoConfig "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 get-jars)
   check_conn_props
-  $JAVA org.apache.fluo.command.FluoGetJars "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 init)
   if [[ $2 = *"-h"* ]]; then
-    $JAVA org.apache.fluo.command.FluoInit -h
+    $JAVA org.apache.fluo.command.FluoProgram $1 -h
     exit 0
   fi
-  init_dir=$($JAVA org.apache.fluo.command.FluoInit "${@:2}" 
--retrieveProperty fluo.observer.init.dir)
+  init_dir=$($JAVA org.apache.fluo.command.FluoProgram "$@" --retrieveProperty 
fluo.observer.init.dir)
   if [ -d "$init_dir" ]; then
     echo "Adding $init_dir/* to CLASSPATH"
     export CLASSPATH="$init_dir/*:$CLASSPATH"
   fi
-  $JAVA org.apache.fluo.command.FluoInit "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 remove)
   if [[ $2 = *"-h"* ]]; then
-    $JAVA org.apache.fluo.command.FluoRemove -h
+    $JAVA org.apache.fluo.command.FluoProgram $1 -h
     exit 0
   fi
-  $JAVA org.apache.fluo.command.FluoRemove "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 oracle)
   if [[ $2 = *"-h"* ]]; then
-    $JAVA org.apache.fluo.command.FluoOracle -h
+    $JAVA org.apache.fluo.command.FluoProgram $1 -h
     exit 0
   fi
   setup_service "${@:2}"
-  $JAVA org.apache.fluo.command.FluoOracle "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 worker)
   if [[ $2 = *"-h"* ]]; then
-    $JAVA org.apache.fluo.command.FluoWorker -h
+    $JAVA org.apache.fluo.command.FluoProgram $1 -h
     exit 0
   fi
   setup_service "${@:2}"
-  $JAVA org.apache.fluo.command.FluoWorker "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 scan)
   if [ -f "$FLUO_CONN_PROPS" ]; then
-    $JAVA org.apache.fluo.command.FluoScan "${@:2}"
+    $JAVA org.apache.fluo.command.FluoProgram "$@"
   else
     check_hadoop
     java org.apache.fluo.cluster.command.FluoCommand "$basedir" 
"$HADOOP_PREFIX" "$@"
@@ -185,7 +183,7 @@ ps)
   ;;
 list)
   if [ -f "$FLUO_CONN_PROPS" ]; then
-    $JAVA org.apache.fluo.command.FluoList "${@:2}"
+    $JAVA org.apache.fluo.command.FluoProgram "$@"
   else
     check_hadoop
     java org.apache.fluo.cluster.command.FluoCommand "$basedir" 
"$HADOOP_PREFIX" list app "${@:2}"
@@ -195,6 +193,10 @@ classpath)
   echo "$CLASSPATH"
   ;;
 exec)
+  if [[ $2 = *"-h"* ]]; then
+    $JAVA org.apache.fluo.command.FluoProgram $1 -h
+    exit 0
+  fi
   app=$2
   verify_app "$app"
   check_conn_props
@@ -202,18 +204,18 @@ exec)
   app_lib=$(mktemp -d "$FLUO_TMP"/fluo-"$app"-XXXXXXXXX) || die "fatal: unable 
to allocate a temporary directory"
   # schedule removal of app_lib tmp dir when this script exits
   trap "rm -rf '""$app_lib""'" EXIT HUP INT QUIT TERM
-  $JAVA org.apache.fluo.command.FluoGetJars -d "$app_lib" -a "$app"
+  $JAVA org.apache.fluo.command.FluoProgram get-jars -d "$app_lib" -a "$app"
   export CLASSPATH="$conf:$app_lib/*:$CLASSPATH"
-  $JAVA org.apache.fluo.command.FluoExec "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 status)
-  $JAVA org.apache.fluo.command.FluoStatus "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 version)
   echo "$FLUO_VERSION"
   ;;
 wait)
-  $JAVA org.apache.fluo.command.FluoWait "${@:2}"
+  $JAVA org.apache.fluo.command.FluoProgram "$@"
   ;;
 *)
   print_usage
diff --git a/pom.xml b/pom.xml
index eebd34c..8715a45 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,7 +71,7 @@
       <dependency>
         <groupId>com.beust</groupId>
         <artifactId>jcommander</artifactId>
-        <version>1.72</version>
+        <version>1.78</version>
       </dependency>
       <dependency>
         <groupId>com.github.spotbugs</groupId>

Reply via email to