You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by el...@apache.org on 2014/04/08 05:03:24 UTC

[28/53] [abbrv] Revert "ACCUMULO-1897 Move shell into new package and module"

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetIterCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetIterCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetIterCommand.java
new file mode 100644
index 0000000..3d8a5a7
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetIterCommand.java
@@ -0,0 +1,453 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.IteratorSetting;
+import org.apache.accumulo.core.client.NamespaceNotFoundException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.OptionDescriber;
+import org.apache.accumulo.core.iterators.OptionDescriber.IteratorOptions;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iterators.user.AgeOffFilter;
+import org.apache.accumulo.core.iterators.user.RegExFilter;
+import org.apache.accumulo.core.iterators.user.ReqVisFilter;
+import org.apache.accumulo.core.iterators.user.VersioningIterator;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.accumulo.core.util.shell.ShellCommandException;
+import org.apache.accumulo.core.util.shell.ShellCommandException.ErrorCode;
+import org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader;
+import org.apache.accumulo.start.classloader.vfs.ContextManager;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.vfs2.FileSystemException;
+
+public class SetIterCommand extends Command {
+
+  private Option allScopeOpt, mincScopeOpt, majcScopeOpt, scanScopeOpt, nameOpt, priorityOpt;
+  private Option aggTypeOpt, ageoffTypeOpt, regexTypeOpt, versionTypeOpt, reqvisTypeOpt, classnameTypeOpt;
+
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException,
+      TableNotFoundException, IOException, ShellCommandException {
+
+    boolean tables = cl.hasOption(OptUtil.tableOpt().getOpt()) || !shellState.getTableName().isEmpty();
+    boolean namespaces = cl.hasOption(OptUtil.namespaceOpt().getOpt());
+
+    final int priority = Integer.parseInt(cl.getOptionValue(priorityOpt.getOpt()));
+
+    final Map<String,String> options = new HashMap<String,String>();
+    String classname = cl.getOptionValue(classnameTypeOpt.getOpt());
+    if (cl.hasOption(aggTypeOpt.getOpt())) {
+      Shell.log.warn("aggregators are deprecated");
+      @SuppressWarnings("deprecation")
+      String deprecatedClassName = org.apache.accumulo.core.iterators.AggregatingIterator.class.getName();
+      classname = deprecatedClassName;
+    } else if (cl.hasOption(regexTypeOpt.getOpt())) {
+      classname = RegExFilter.class.getName();
+    } else if (cl.hasOption(ageoffTypeOpt.getOpt())) {
+      classname = AgeOffFilter.class.getName();
+    } else if (cl.hasOption(versionTypeOpt.getOpt())) {
+      classname = VersioningIterator.class.getName();
+    } else if (cl.hasOption(reqvisTypeOpt.getOpt())) {
+      classname = ReqVisFilter.class.getName();
+    }
+
+    ClassLoader classloader = getClassLoader(cl, shellState);
+
+    // Get the iterator options, with potentially a name provided by the OptionDescriber impl or through user input
+    String configuredName = setUpOptions(classloader, shellState.getReader(), classname, options);
+
+    // Try to get the name provided by the setiter command
+    String name = cl.getOptionValue(nameOpt.getOpt(), null);
+    
+    // Cannot continue if no name is provided
+    if (null == name && null == configuredName) {
+      throw new IllegalArgumentException("No provided or default name for iterator");
+    } else if (null == name) {
+      // Fall back to the name from OptionDescriber or user input if none is provided on setiter option
+      name = configuredName;
+    }
+
+    if (namespaces) {
+      try {
+        setNamespaceProperties(cl, shellState, priority, options, classname, name);
+      } catch (NamespaceNotFoundException e) {
+        throw new IllegalArgumentException(e);
+      }
+    } else if (tables) {
+      setTableProperties(cl, shellState, priority, options, classname, name);
+    } else {
+      throw new IllegalArgumentException("No table or namespace specified");
+    }
+    return 0;
+  }
+
+  private ClassLoader getClassLoader(final CommandLine cl, final Shell shellState) throws AccumuloException, TableNotFoundException, AccumuloSecurityException,
+      IOException, FileSystemException {
+
+    boolean tables = cl.hasOption(OptUtil.tableOpt().getOpt()) || !shellState.getTableName().isEmpty();
+    boolean namespaces = cl.hasOption(OptUtil.namespaceOpt().getOpt());
+
+    String classpath = null;
+    Iterable<Entry<String,String>> tableProps;
+
+    if (namespaces) {
+      try {
+        tableProps = shellState.getConnector().namespaceOperations().getProperties(OptUtil.getNamespaceOpt(cl, shellState));
+      } catch (NamespaceNotFoundException e) {
+        throw new IllegalArgumentException(e);
+      }
+    } else if (tables) {
+      tableProps = shellState.getConnector().tableOperations().getProperties(OptUtil.getTableOpt(cl, shellState));
+    } else {
+      throw new IllegalArgumentException("No table or namespace specified");
+    }
+    for (Entry<String,String> entry : tableProps) {
+      if (entry.getKey().equals(Property.TABLE_CLASSPATH.getKey())) {
+        classpath = entry.getValue();
+      }
+    }
+
+    ClassLoader classloader;
+
+    if (classpath != null && !classpath.equals("")) {
+      shellState.getConnector().instanceOperations().getSystemConfiguration().get(Property.VFS_CONTEXT_CLASSPATH_PROPERTY.getKey() + classpath);
+
+      try {
+        AccumuloVFSClassLoader.getContextManager().setContextConfig(new ContextManager.DefaultContextsConfig(new Iterable<Map.Entry<String,String>>() {
+          @Override
+          public Iterator<Entry<String,String>> iterator() {
+            try {
+              return shellState.getConnector().instanceOperations().getSystemConfiguration().entrySet().iterator();
+            } catch (AccumuloException e) {
+              throw new RuntimeException(e);
+            } catch (AccumuloSecurityException e) {
+              throw new RuntimeException(e);
+            }
+          }
+        }));
+      } catch (IllegalStateException ise) {}
+
+      classloader = AccumuloVFSClassLoader.getContextManager().getClassLoader(classpath);
+    } else {
+      classloader = AccumuloVFSClassLoader.getClassLoader();
+    }
+    return classloader;
+  }
+
+  protected void setTableProperties(final CommandLine cl, final Shell shellState, final int priority, final Map<String,String> options, final String classname,
+      final String name) throws AccumuloException, AccumuloSecurityException, ShellCommandException, TableNotFoundException {
+    // remove empty values
+
+    final String tableName = OptUtil.getTableOpt(cl, shellState);
+
+    if (!shellState.getConnector().tableOperations().testClassLoad(tableName, classname, SortedKeyValueIterator.class.getName())) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "Servers are unable to load " + classname + " as type "
+          + SortedKeyValueIterator.class.getName());
+    }
+
+    final String aggregatorClass = options.get("aggregatorClass");
+    @SuppressWarnings("deprecation")
+    String deprecatedAggregatorClassName = org.apache.accumulo.core.iterators.aggregation.Aggregator.class.getName();
+    if (aggregatorClass != null && !shellState.getConnector().tableOperations().testClassLoad(tableName, aggregatorClass, deprecatedAggregatorClassName)) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "Servers are unable to load " + aggregatorClass + " as type "
+          + deprecatedAggregatorClassName);
+    }
+
+    for (Iterator<Entry<String,String>> i = options.entrySet().iterator(); i.hasNext();) {
+      final Entry<String,String> entry = i.next();
+      if (entry.getValue() == null || entry.getValue().isEmpty()) {
+        i.remove();
+      }
+    }
+    final EnumSet<IteratorScope> scopes = EnumSet.noneOf(IteratorScope.class);
+    if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(mincScopeOpt.getOpt())) {
+      scopes.add(IteratorScope.minc);
+    }
+    if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(majcScopeOpt.getOpt())) {
+      scopes.add(IteratorScope.majc);
+    }
+    if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(scanScopeOpt.getOpt())) {
+      scopes.add(IteratorScope.scan);
+    }
+    if (scopes.isEmpty()) {
+      throw new IllegalArgumentException("You must select at least one scope to configure");
+    }
+    final IteratorSetting setting = new IteratorSetting(priority, name, classname, options);
+    shellState.getConnector().tableOperations().attachIterator(tableName, setting, scopes);
+  }
+
+  protected void setNamespaceProperties(final CommandLine cl, final Shell shellState, final int priority, final Map<String,String> options,
+      final String classname, final String name) throws AccumuloException, AccumuloSecurityException, ShellCommandException, NamespaceNotFoundException {
+    // remove empty values
+
+    final String namespace = OptUtil.getNamespaceOpt(cl, shellState);
+
+    if (!shellState.getConnector().namespaceOperations().testClassLoad(namespace, classname, SortedKeyValueIterator.class.getName())) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "Servers are unable to load " + classname + " as type "
+          + SortedKeyValueIterator.class.getName());
+    }
+
+    final String aggregatorClass = options.get("aggregatorClass");
+    @SuppressWarnings("deprecation")
+    String deprecatedAggregatorClassName = org.apache.accumulo.core.iterators.aggregation.Aggregator.class.getName();
+    if (aggregatorClass != null && !shellState.getConnector().namespaceOperations().testClassLoad(namespace, aggregatorClass, deprecatedAggregatorClassName)) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "Servers are unable to load " + aggregatorClass + " as type "
+          + deprecatedAggregatorClassName);
+    }
+
+    for (Iterator<Entry<String,String>> i = options.entrySet().iterator(); i.hasNext();) {
+      final Entry<String,String> entry = i.next();
+      if (entry.getValue() == null || entry.getValue().isEmpty()) {
+        i.remove();
+      }
+    }
+    final EnumSet<IteratorScope> scopes = EnumSet.noneOf(IteratorScope.class);
+    if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(mincScopeOpt.getOpt())) {
+      scopes.add(IteratorScope.minc);
+    }
+    if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(majcScopeOpt.getOpt())) {
+      scopes.add(IteratorScope.majc);
+    }
+    if (cl.hasOption(allScopeOpt.getOpt()) || cl.hasOption(scanScopeOpt.getOpt())) {
+      scopes.add(IteratorScope.scan);
+    }
+    if (scopes.isEmpty()) {
+      throw new IllegalArgumentException("You must select at least one scope to configure");
+    }
+    final IteratorSetting setting = new IteratorSetting(priority, name, classname, options);
+    shellState.getConnector().namespaceOperations().attachIterator(namespace, setting, scopes);
+  }
+
+  private static String setUpOptions(ClassLoader classloader, final ConsoleReader reader, final String className, final Map<String,String> options)
+      throws IOException, ShellCommandException {
+    String input;
+    @SuppressWarnings("rawtypes")
+    SortedKeyValueIterator untypedInstance;
+    @SuppressWarnings("rawtypes")
+    Class<? extends SortedKeyValueIterator> clazz;
+    try {
+      clazz = classloader.loadClass(className).asSubclass(SortedKeyValueIterator.class);
+      untypedInstance = clazz.newInstance();
+    } catch (ClassNotFoundException e) {
+      StringBuilder msg = new StringBuilder("Unable to load ").append(className);
+      if (className.indexOf('.') < 0) {
+        msg.append("; did you use a fully qualified package name?");
+      } else {
+        msg.append("; class not found.");
+      }
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, msg.toString());
+    } catch (InstantiationException e) {
+      throw new IllegalArgumentException(e.getMessage());
+    } catch (IllegalAccessException e) {
+      throw new IllegalArgumentException(e.getMessage());
+    } catch (ClassCastException e) {
+      StringBuilder msg = new StringBuilder(50);
+      msg.append(className).append(" loaded successfully but does not implement SortedKeyValueIterator.");
+      msg.append(" This class cannot be used with this command.");
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, msg.toString());
+    }
+
+    @SuppressWarnings("unchecked")
+    SortedKeyValueIterator<Key,Value> skvi = (SortedKeyValueIterator<Key,Value>) untypedInstance;
+    OptionDescriber iterOptions = null;
+    if (OptionDescriber.class.isAssignableFrom(skvi.getClass())) {
+      iterOptions = (OptionDescriber) skvi;
+    }
+
+    String iteratorName;
+    if (null != iterOptions) {
+      final IteratorOptions itopts = iterOptions.describeOptions();
+      iteratorName = itopts.getName();
+      
+      if (iteratorName == null) {
+        throw new IllegalArgumentException(className + " described its default distinguishing name as null");
+      }
+      String shortClassName = className;
+      if (className.contains(".")) {
+        shortClassName = className.substring(className.lastIndexOf('.') + 1);
+      }
+      final Map<String,String> localOptions = new HashMap<String,String>();
+      do {
+        // clean up the overall options that caused things to fail
+        for (String key : localOptions.keySet()) {
+          options.remove(key);
+        }
+        localOptions.clear();
+  
+        reader.println(itopts.getDescription());
+  
+        String prompt;
+        if (itopts.getNamedOptions() != null) {
+          for (Entry<String,String> e : itopts.getNamedOptions().entrySet()) {
+            prompt = Shell.repeat("-", 10) + "> set " + shortClassName + " parameter " + e.getKey() + ", " + e.getValue() + ": ";
+            reader.flush();
+            input = reader.readLine(prompt);
+            if (input == null) {
+              reader.println();
+              throw new IOException("Input stream closed");
+            }
+            // Places all Parameters and Values into the LocalOptions, even if the value is "".
+            // This allows us to check for "" values when setting the iterators and allows us to remove
+            // the parameter and value from the table property.
+            localOptions.put(e.getKey(), input);
+          }
+        }
+  
+        if (itopts.getUnnamedOptionDescriptions() != null) {
+          for (String desc : itopts.getUnnamedOptionDescriptions()) {
+            reader.println(Shell.repeat("-", 10) + "> entering options: " + desc);
+            input = "start";
+            prompt = Shell.repeat("-", 10) + "> set " + shortClassName + " option (<name> <value>, hit enter to skip): ";
+            while (true) {
+              reader.flush();
+              input = reader.readLine(prompt);
+              if (input == null) {
+                reader.println();
+                throw new IOException("Input stream closed");
+              } else {
+                input = new String(input);
+              }
+  
+              if (input.length() == 0)
+                break;
+  
+              String[] sa = input.split(" ", 2);
+              localOptions.put(sa[0], sa[1]);
+            }
+          }
+        }
+  
+        options.putAll(localOptions);
+        if (!iterOptions.validateOptions(options))
+          reader.println("invalid options for " + clazz.getName());
+  
+      } while (!iterOptions.validateOptions(options));
+    } else {
+      reader.flush();
+      reader.println("The iterator class does not implement OptionDescriber. Consider this for better iterator configuration using this setiter command.");
+      iteratorName = reader.readLine("Name for iterator (enter to skip): ");
+      if (null == iteratorName) {
+        reader.println();
+        throw new IOException("Input stream closed");
+      } else if (StringUtils.isWhitespace(iteratorName)) {
+        // Treat whitespace or empty string as no name provided
+        iteratorName = null;
+      }
+      
+      reader.flush();
+      reader.println("Optional, configure name-value options for iterator:");
+      String prompt = Shell.repeat("-", 10) + "> set option (<name> <value>, hit enter to skip): ";
+      final HashMap<String,String> localOptions = new HashMap<String,String>();
+      
+      while (true) {
+        reader.flush();
+        input = reader.readLine(prompt);
+        if (input == null) {
+          reader.println();
+          throw new IOException("Input stream closed");
+        } else if (StringUtils.isWhitespace(input)) {
+          break;
+        } 
+
+        String[] sa = input.split(" ", 2);
+        localOptions.put(sa[0], sa[1]);
+      }
+      
+      options.putAll(localOptions);
+    }
+    
+    return iteratorName;
+  }
+
+  @Override
+  public String description() {
+    return "sets a table-specific or namespace-specific iterator";
+  }
+
+  @Override
+  public Options getOptions() {
+    final Options o = new Options();
+
+    priorityOpt = new Option("p", "priority", true, "the order in which the iterator is applied");
+    priorityOpt.setArgName("pri");
+    priorityOpt.setRequired(true);
+
+    nameOpt = new Option("n", "name", true, "iterator to set");
+    nameOpt.setArgName("itername");
+
+    allScopeOpt = new Option("all", "all-scopes", false, "applied at scan time, minor and major compactions");
+    mincScopeOpt = new Option(IteratorScope.minc.name(), "minor-compaction", false, "applied at minor compaction");
+    majcScopeOpt = new Option(IteratorScope.majc.name(), "major-compaction", false, "applied at major compaction");
+    scanScopeOpt = new Option(IteratorScope.scan.name(), "scan-time", false, "applied at scan time");
+
+    final OptionGroup typeGroup = new OptionGroup();
+    classnameTypeOpt = new Option("class", "class-name", true, "a java class that implements SortedKeyValueIterator");
+    classnameTypeOpt.setArgName("name");
+    aggTypeOpt = new Option("agg", "aggregator", false, "an aggregating type");
+    regexTypeOpt = new Option("regex", "regular-expression", false, "a regex matching iterator");
+    versionTypeOpt = new Option("vers", "version", false, "a versioning iterator");
+    reqvisTypeOpt = new Option("reqvis", "require-visibility", false, "an iterator that omits entries with empty visibilities");
+    ageoffTypeOpt = new Option("ageoff", "ageoff", false, "an aging off iterator");
+
+    typeGroup.addOption(classnameTypeOpt);
+    typeGroup.addOption(aggTypeOpt);
+    typeGroup.addOption(regexTypeOpt);
+    typeGroup.addOption(versionTypeOpt);
+    typeGroup.addOption(reqvisTypeOpt);
+    typeGroup.addOption(ageoffTypeOpt);
+    typeGroup.setRequired(true);
+
+    final OptionGroup tableGroup = new OptionGroup();
+    tableGroup.addOption(OptUtil.tableOpt("table to configure iterators on"));
+    tableGroup.addOption(OptUtil.namespaceOpt("namespace to configure iterators on"));
+
+    o.addOption(priorityOpt);
+    o.addOption(nameOpt);
+    o.addOption(allScopeOpt);
+    o.addOption(mincScopeOpt);
+    o.addOption(majcScopeOpt);
+    o.addOption(scanScopeOpt);
+    o.addOptionGroup(typeGroup);
+    o.addOptionGroup(tableGroup);
+    return o;
+  }
+
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetScanIterCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetScanIterCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetScanIterCommand.java
new file mode 100644
index 0000000..513975c
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetScanIterCommand.java
@@ -0,0 +1,127 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.IteratorSetting;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.ShellCommandException;
+import org.apache.accumulo.core.util.shell.ShellCommandException.ErrorCode;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+
+public class SetScanIterCommand extends SetIterCommand {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
+      IOException, ShellCommandException {
+    Shell.log.warn("Deprecated, use " + new SetShellIterCommand().getName());
+    return super.execute(fullCommand, cl, shellState);
+  }
+  
+  @Override
+  protected void setTableProperties(final CommandLine cl, final Shell shellState, final int priority, final Map<String,String> options, final String classname,
+      final String name) throws AccumuloException, AccumuloSecurityException, ShellCommandException, TableNotFoundException {
+    
+    final String tableName = OptUtil.getTableOpt(cl, shellState);
+
+    // instead of setting table properties, just put the options in a list to use at scan time
+    Class<?> loadClass;
+    try {
+      loadClass = getClass().getClassLoader().loadClass(classname);
+    } catch (ClassNotFoundException e) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "Unable to load " + classname);
+    }
+    try {
+      loadClass.asSubclass(SortedKeyValueIterator.class);
+    } catch (ClassCastException ex) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "Unable to load " + classname  + " as type "
+          + SortedKeyValueIterator.class.getName());
+    }
+    
+    for (Iterator<Entry<String,String>> i = options.entrySet().iterator(); i.hasNext();) {
+      final Entry<String,String> entry = i.next();
+      if (entry.getValue() == null || entry.getValue().isEmpty()) {
+        i.remove();
+      }
+    }
+
+    List<IteratorSetting> tableScanIterators = shellState.scanIteratorOptions.get(tableName);
+    if (tableScanIterators == null) {
+      tableScanIterators = new ArrayList<IteratorSetting>();
+      shellState.scanIteratorOptions.put(tableName, tableScanIterators);
+    }
+    final IteratorSetting setting = new IteratorSetting(priority, name, classname);
+    setting.addOptions(options);
+    
+    // initialize a scanner to ensure the new setting does not conflict with existing settings
+    final String user = shellState.getConnector().whoami();
+    final Authorizations auths = shellState.getConnector().securityOperations().getUserAuthorizations(user);
+    final Scanner scanner = shellState.getConnector().createScanner(tableName, auths);
+    for (IteratorSetting s : tableScanIterators) {
+      scanner.addScanIterator(s);
+    }
+    scanner.addScanIterator(setting);
+    
+    // if no exception has been thrown, it's safe to add it to the list
+    tableScanIterators.add(setting);
+    Shell.log.debug("Scan iterators :" + shellState.scanIteratorOptions.get(tableName));
+  }
+  
+  @Override
+  public String description() {
+    return "sets a table-specific scan iterator for this shell session";
+  }
+  
+  @Override
+  public Options getOptions() {
+    // Remove the options that specify which type of iterator this is, since
+    // they are all scan iterators with this command.
+    final HashSet<OptionGroup> groups = new HashSet<OptionGroup>();
+    final Options parentOptions = super.getOptions();
+    final Options modifiedOptions = new Options();
+    for (Iterator<?> it = parentOptions.getOptions().iterator(); it.hasNext();) {
+      Option o = (Option) it.next();
+      if (!IteratorScope.majc.name().equals(o.getOpt()) && !IteratorScope.minc.name().equals(o.getOpt()) && !IteratorScope.scan.name().equals(o.getOpt())) {
+        modifiedOptions.addOption(o);
+        OptionGroup group = parentOptions.getOptionGroup(o);
+        if (group != null)
+          groups.add(group);
+      }
+    }
+    for (OptionGroup group : groups) {
+      modifiedOptions.addOptionGroup(group);
+    }
+    return modifiedOptions;
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetShellIterCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetShellIterCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetShellIterCommand.java
new file mode 100644
index 0000000..b95725a
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SetShellIterCommand.java
@@ -0,0 +1,131 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.IteratorSetting;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.ShellCommandException;
+import org.apache.accumulo.core.util.shell.ShellCommandException.ErrorCode;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+
+public class SetShellIterCommand extends SetIterCommand {
+  private Option profileOpt;
+
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException,
+      TableNotFoundException, IOException, ShellCommandException {
+    return super.execute(fullCommand, cl, shellState);
+  }
+  
+  @Override
+  protected void setTableProperties(final CommandLine cl, final Shell shellState, final int priority, final Map<String,String> options, final String classname,
+      final String name) throws AccumuloException, AccumuloSecurityException, ShellCommandException, TableNotFoundException {
+    // instead of setting table properties, just put the options in a list to use at scan time
+    
+    String profile = cl.getOptionValue(profileOpt.getOpt());
+
+    // instead of setting table properties, just put the options in a list to use at scan time
+    Class<?> loadClass;
+    try {
+      loadClass = getClass().getClassLoader().loadClass(classname);
+    } catch (ClassNotFoundException e) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "Unable to load " + classname);
+    }
+    try {
+      loadClass.asSubclass(SortedKeyValueIterator.class);
+    } catch (ClassCastException ex) {
+      throw new ShellCommandException(ErrorCode.INITIALIZATION_FAILURE, "xUnable to load " + classname  + " as type "
+          + SortedKeyValueIterator.class.getName());
+    }
+    
+    for (Iterator<Entry<String,String>> i = options.entrySet().iterator(); i.hasNext();) {
+      final Entry<String,String> entry = i.next();
+      if (entry.getValue() == null || entry.getValue().isEmpty()) {
+        i.remove();
+      }
+    }
+
+    List<IteratorSetting> tableScanIterators = shellState.iteratorProfiles.get(profile);
+    if (tableScanIterators == null) {
+      tableScanIterators = new ArrayList<IteratorSetting>();
+      shellState.iteratorProfiles.put(profile, tableScanIterators);
+    }
+    final IteratorSetting setting = new IteratorSetting(priority, name, classname);
+    setting.addOptions(options);
+
+    Iterator<IteratorSetting> iter = tableScanIterators.iterator();
+    while (iter.hasNext()) {
+      if (iter.next().getName().equals(name)) {
+        iter.remove();
+      }
+    }
+
+    tableScanIterators.add(setting);
+  }
+  
+  @Override
+  public String description() {
+    return "adds an iterator to a profile for this shell session";
+  }
+  
+  @Override
+  public Options getOptions() {
+    // Remove the options that specify which type of iterator this is, since
+    // they are all scan iterators with this command.
+    final HashSet<OptionGroup> groups = new HashSet<OptionGroup>();
+    final Options parentOptions = super.getOptions();
+    final Options modifiedOptions = new Options();
+    for (Iterator<?> it = parentOptions.getOptions().iterator(); it.hasNext();) {
+      Option o = (Option) it.next();
+      if (!IteratorScope.majc.name().equals(o.getOpt()) && !IteratorScope.minc.name().equals(o.getOpt()) && !IteratorScope.scan.name().equals(o.getOpt())
+          && !"table".equals(o.getLongOpt())) {
+        modifiedOptions.addOption(o);
+        OptionGroup group = parentOptions.getOptionGroup(o);
+        if (group != null)
+          groups.add(group);
+      }
+    }
+    for (OptionGroup group : groups) {
+      modifiedOptions.addOptionGroup(group);
+    }
+    
+    profileOpt = new Option("pn", "profile", true, "iterator profile name");
+    profileOpt.setRequired(true);
+    profileOpt.setArgName("profile");
+    
+    modifiedOptions.addOption(profileOpt);
+
+    return modifiedOptions;
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/ShellPluginConfigurationCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/ShellPluginConfigurationCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/ShellPluginConfigurationCommand.java
new file mode 100644
index 0000000..d4c9739
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/ShellPluginConfigurationCommand.java
@@ -0,0 +1,146 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.log4j.Logger;
+
+public abstract class ShellPluginConfigurationCommand extends Command {
+  private Option removePluginOption, pluginClassOption, listPluginOption;
+  
+  private String pluginType;
+  
+  private Property tableProp;
+  
+  private String classOpt;
+  
+  ShellPluginConfigurationCommand(final String typeName, final Property tableProp, final String classOpt) {
+    this.pluginType = typeName;
+    this.tableProp = tableProp;
+    this.classOpt = classOpt;
+  }
+
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws Exception {
+    final String tableName = OptUtil.getTableOpt(cl, shellState);
+    
+    if (cl.hasOption(removePluginOption.getOpt())) {
+      // Remove the property
+      removePlugin(cl, shellState, tableName);
+      
+      shellState.getReader().println("Removed "+pluginType+" on " + tableName);
+    } else if (cl.hasOption(listPluginOption.getOpt())) {
+      // Get the options for this table
+      final Iterator<Entry<String,String>> iter = shellState.getConnector().tableOperations().getProperties(tableName).iterator();
+      
+      while (iter.hasNext()) {
+        Entry<String,String> ent = iter.next();
+        
+        // List all parameters with the property name
+        if (ent.getKey().startsWith(tableProp.toString())) {
+          shellState.getReader().println(ent.getKey() + ": " + ent.getValue());
+        }
+      }
+    } else {
+      // Set the plugin with the provided options
+      String className = cl.getOptionValue(pluginClassOption.getOpt());
+      
+      // Set the plugin property on the table
+      setPlugin(cl, shellState, tableName, className);
+    }
+    
+    return 0;
+  }
+
+  protected void setPlugin(final CommandLine cl, final Shell shellState, final String tableName, final String className) throws AccumuloException, AccumuloSecurityException {
+    shellState.getConnector().tableOperations().setProperty(tableName, tableProp.toString(), className);
+  }
+  
+  protected void removePlugin(final CommandLine cl, final Shell shellState, final String tableName) throws AccumuloException, AccumuloSecurityException {
+    shellState.getConnector().tableOperations().removeProperty(tableName, tableProp.toString());
+  }
+  
+  public static <T> Class<? extends T> getPluginClass(final String tableName, final Shell shellState, final Class<T> clazz, final Property pluginProp) {
+    Iterator<Entry<String,String>> props;
+    try {
+      props = shellState.getConnector().tableOperations().getProperties(tableName).iterator();
+    } catch (AccumuloException e) {
+      return null;
+    } catch (TableNotFoundException e) {
+      return null;
+    }
+    
+    while (props.hasNext()) {
+      final Entry<String,String> ent = props.next();
+      if (ent.getKey().equals(pluginProp.toString())) {
+        Class<? extends T> pluginClazz;
+        try {
+          pluginClazz = AccumuloVFSClassLoader.loadClass(ent.getValue(), clazz);
+        } catch (ClassNotFoundException e) {
+          Logger.getLogger(ShellPluginConfigurationCommand.class).warn("Class not found" + e.getMessage());
+          return null;
+        }
+        
+        return pluginClazz;
+      }
+    }
+    
+    return null;
+  }
+  
+  @Override
+  public Options getOptions() {
+    final Options o = new Options();
+    final OptionGroup actionGroup = new OptionGroup();
+    
+    pluginClassOption = new Option(classOpt, pluginType, true, "fully qualified name of the " + pluginType + " class to use");
+    pluginClassOption.setArgName("className");
+    
+    // Action to take: apply (default), remove, list
+    removePluginOption = new Option("r", "remove", false, "remove the current "+pluginType+"");
+    listPluginOption = new Option("l", "list", false, "display the current "+pluginType+"");
+    
+    actionGroup.addOption(pluginClassOption);
+    actionGroup.addOption(removePluginOption);
+    actionGroup.addOption(listPluginOption);
+    actionGroup.setRequired(true);
+    
+    o.addOptionGroup(actionGroup);
+    o.addOption(OptUtil.tableOpt("table to set the "+pluginType+" on"));
+    
+    return o;
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SleepCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SleepCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SleepCommand.java
new file mode 100644
index 0000000..ab69456
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SleepCommand.java
@@ -0,0 +1,46 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class SleepCommand extends Command {
+  
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws Exception {
+    final double secs = Double.parseDouble(cl.getArgs()[0]);
+    Thread.sleep((long) (secs * 1000));
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "sleeps for the given number of seconds";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 1;
+  }
+  
+  @Override
+  public String usage() {
+    return getName() + " <seconds>";
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SystemPermissionsCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SystemPermissionsCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SystemPermissionsCommand.java
new file mode 100644
index 0000000..3dec6fc
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/SystemPermissionsCommand.java
@@ -0,0 +1,44 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.security.SystemPermission;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class SystemPermissionsCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws IOException {
+    for (String p : SystemPermission.printableValues()) {
+      shellState.getReader().println(p);
+    }
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "displays a list of valid system permissions";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableCommand.java
new file mode 100644
index 0000000..d77f6fb
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableCommand.java
@@ -0,0 +1,60 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.accumulo.core.util.shell.Token;
+import org.apache.commons.cli.CommandLine;
+
+public class TableCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException {
+    final String tableName = cl.getArgs()[0];
+    if (!shellState.getConnector().tableOperations().exists(tableName)) {
+      throw new TableNotFoundException(null, tableName, null);
+    }
+    shellState.setTableName(tableName);
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "switches to the specified table";
+  }
+  
+  @Override
+  public void registerCompletion(final Token root, final Map<Command.CompletionSet,Set<String>> special) {
+    registerCompletionForTables(root, special);
+  }
+  
+  @Override
+  public String usage() {
+    return getName() + " <tableName>";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 1;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableOperation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableOperation.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableOperation.java
new file mode 100644
index 0000000..27946b8
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TableOperation.java
@@ -0,0 +1,153 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.client.impl.Namespaces;
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.accumulo.core.util.shell.Token;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+
+public abstract class TableOperation extends Command {
+
+  protected Option optTablePattern, optTableName, optNamespace;
+  private boolean force = true;
+  private boolean useCommandLine = true;
+
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws Exception {
+    // populate the tableSet set with the tables you want to operate on
+    final SortedSet<String> tableSet = new TreeSet<String>();
+    if (cl.hasOption(optTablePattern.getOpt())) {
+      for (String table : shellState.getConnector().tableOperations().list())
+        if (table.matches(cl.getOptionValue(optTablePattern.getOpt()))) {
+          tableSet.add(table);
+        }
+    } else if (cl.hasOption(optTableName.getOpt())) {
+      tableSet.add(cl.getOptionValue(optTableName.getOpt()));
+    } else if (cl.hasOption(optNamespace.getOpt())) {
+      Instance instance = shellState.getInstance();
+      String namespaceId = Namespaces.getNamespaceId(instance, cl.getOptionValue(optNamespace.getOpt()));
+      for (String tableId : Namespaces.getTableIds(instance, namespaceId)) {
+        tableSet.add(Tables.getTableName(instance, tableId));
+      }
+    } else if (useCommandLine && cl.getArgs().length > 0) {
+      for (String tableName : cl.getArgs()) {
+        tableSet.add(tableName);
+      }
+    } else {
+      shellState.checkTableState();
+      tableSet.add(shellState.getTableName());
+    }
+
+    if (tableSet.isEmpty())
+      Shell.log.warn("No tables found that match your criteria");
+
+    boolean more = true;
+    // flush the tables
+    for (String tableName : tableSet) {
+      if (!more) {
+        break;
+      }
+      if (!shellState.getConnector().tableOperations().exists(tableName)) {
+        throw new TableNotFoundException(null, tableName, null);
+      }
+      boolean operate = true;
+      if (!force) {
+        shellState.getReader().flush();
+        String line = shellState.getReader().readLine(getName() + " { " + tableName + " } (yes|no)? ");
+        more = line != null;
+        operate = line != null && (line.equalsIgnoreCase("y") || line.equalsIgnoreCase("yes"));
+      }
+      if (operate) {
+        doTableOp(shellState, tableName);
+      }
+    }
+
+    return 0;
+  }
+
+  protected abstract void doTableOp(Shell shellState, String tableName) throws Exception;
+
+  @Override
+  public String description() {
+    return "makes a best effort to flush tables from memory to disk";
+  }
+
+  @Override
+  public Options getOptions() {
+    final Options o = new Options();
+
+    optTablePattern = new Option("p", "pattern", true, "regex pattern of table names to operate on");
+    optTablePattern.setArgName("pattern");
+
+    optTableName = new Option(Shell.tableOption, "table", true, "name of a table to operate on");
+    optTableName.setArgName("tableName");
+
+    optNamespace = new Option(Shell.namespaceOption, "namespace", true, "name of a namespace to operate on");
+    optNamespace.setArgName("namespace");
+
+    final OptionGroup opg = new OptionGroup();
+
+    opg.addOption(optTablePattern);
+    opg.addOption(optTableName);
+    opg.addOption(optNamespace);
+
+    o.addOptionGroup(opg);
+
+    return o;
+  }
+
+  @Override
+  public int numArgs() {
+    return useCommandLine ? Shell.NO_FIXED_ARG_LENGTH_CHECK : 0;
+  }
+
+  protected void force() {
+    force = true;
+  }
+
+  protected void noForce() {
+    force = false;
+  }
+
+  protected void disableUnflaggedTableOptions() {
+    useCommandLine = false;
+  }
+
+  @Override
+  public String usage() {
+    return getName() + " [<table>{ <table>}]";
+  }
+
+  @Override
+  public void registerCompletion(final Token root, final Map<Command.CompletionSet,Set<String>> special) {
+    if (useCommandLine)
+      registerCompletionForTables(root, special);
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablePermissionsCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablePermissionsCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablePermissionsCommand.java
new file mode 100644
index 0000000..1f92f21
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablePermissionsCommand.java
@@ -0,0 +1,44 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.security.TablePermission;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class TablePermissionsCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws IOException {
+    for (String p : TablePermission.printableValues()) {
+      shellState.getReader().println(p);
+    }
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "displays a list of valid table permissions";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablesCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablesCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablesCommand.java
new file mode 100644
index 0000000..a03a986
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TablesCommand.java
@@ -0,0 +1,107 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.NamespaceNotFoundException;
+import org.apache.accumulo.core.client.impl.Tables;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Maps;
+
+public class TablesCommand extends Command {
+  static final String NAME_AND_ID_FORMAT = "%-20s => %9s%n";
+
+  private Option tableIdOption;
+  private Option sortByTableIdOption;
+  private Option disablePaginationOpt;
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException,
+      NamespaceNotFoundException {
+
+    final String namespace = cl.hasOption(OptUtil.namespaceOpt().getOpt()) ? OptUtil.getNamespaceOpt(cl, shellState) : null;
+    Map<String,String> tables = shellState.getConnector().tableOperations().tableIdMap();
+
+    // filter only specified namespace
+    tables = Maps.filterKeys(tables, new Predicate<String>() {
+      @Override
+      public boolean apply(String tableName) {
+        return namespace == null || Tables.qualify(tableName).getFirst().equals(namespace);
+      }
+    });
+
+    final boolean sortByTableId = cl.hasOption(sortByTableIdOption.getOpt());
+    tables = new TreeMap<String,String>((sortByTableId ? MapUtils.invertMap(tables) : tables));
+
+    Iterator<String> it = Iterators.transform(tables.entrySet().iterator(), new Function<Entry<String,String>,String>() {
+      @Override
+      public String apply(Map.Entry<String,String> entry) {
+        String tableName = String.valueOf(sortByTableId ? entry.getValue() : entry.getKey());
+        String tableId = String.valueOf(sortByTableId ? entry.getKey() : entry.getValue());
+        if (namespace != null)
+          tableName = Tables.qualify(tableName).getSecond();
+        if (cl.hasOption(tableIdOption.getOpt()))
+          return String.format(NAME_AND_ID_FORMAT, tableName, tableId);
+        else
+          return tableName;
+      };
+    });
+
+    shellState.printLines(it, !cl.hasOption(disablePaginationOpt.getOpt()));
+    return 0;
+  }
+
+  @Override
+  public String description() {
+    return "displays a list of all existing tables";
+  }
+
+  @Override
+  public Options getOptions() {
+    final Options o = new Options();
+    tableIdOption = new Option("l", "list-ids", false, "display internal table ids along with the table name");
+    o.addOption(tableIdOption);
+    sortByTableIdOption = new Option("s", "sort-ids", false, "with -l: sort output by table ids");
+    o.addOption(sortByTableIdOption);
+    disablePaginationOpt = new Option("np", "no-pagination", false, "disable pagination of output");
+    o.addOption(disablePaginationOpt);
+    o.addOption(OptUtil.namespaceOpt("name of namespace to list only its tables"));
+    return o;
+  }
+
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TraceCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TraceCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TraceCommand.java
new file mode 100644
index 0000000..281a33a
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/TraceCommand.java
@@ -0,0 +1,101 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.accumulo.trace.instrument.Trace;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.trace.TraceDump;
+import org.apache.accumulo.core.trace.TraceDump.Printer;
+import org.apache.accumulo.core.util.BadArgumentException;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.commons.cli.CommandLine;
+import org.apache.hadoop.io.Text;
+
+public class TraceCommand extends DebugCommand {
+  
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws IOException {
+    if (cl.getArgs().length == 1) {
+      if (cl.getArgs()[0].equalsIgnoreCase("on")) {
+        Trace.on("shell:" + shellState.getPrincipal());
+      } else if (cl.getArgs()[0].equalsIgnoreCase("off")) {
+        if (Trace.isTracing()) {
+          final long trace = Trace.currentTrace().traceId();
+          Trace.off();
+          StringBuffer sb = new StringBuffer();
+          int traceCount = 0;
+          for (int i = 0; i < 30; i++) {
+            sb = new StringBuffer();
+            try {
+              final Map<String,String> properties = shellState.getConnector().instanceOperations().getSystemConfiguration();
+              final String table = properties.get(Property.TRACE_TABLE.getKey());
+              final String user = shellState.getConnector().whoami();
+              final Authorizations auths = shellState.getConnector().securityOperations().getUserAuthorizations(user);
+              final Scanner scanner = shellState.getConnector().createScanner(table, auths);
+              scanner.setRange(new Range(new Text(Long.toHexString(trace))));
+              final StringBuffer finalSB = sb;
+              traceCount = TraceDump.printTrace(scanner, new Printer() {
+                @Override
+                public void print(final String line) {
+                  try {
+                    finalSB.append(line + "\n");
+                  } catch (Exception ex) {
+                    throw new RuntimeException(ex);
+                  }
+                }
+              });
+              if (traceCount > 0) {
+                shellState.getReader().print(sb.toString());
+                break;
+              }
+            } catch (Exception ex) {
+              shellState.printException(ex);
+            }
+            shellState.getReader().println("Waiting for trace information");
+            shellState.getReader().flush();
+            UtilWaitThread.sleep(500);
+          }
+          if (traceCount < 0) {
+            // display the trace even though there are unrooted spans
+            shellState.getReader().print(sb.toString());
+          }
+        } else {
+          shellState.getReader().println("Not tracing");
+        }
+      } else
+        throw new BadArgumentException("Argument must be 'on' or 'off'", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
+    } else if (cl.getArgs().length == 0) {
+      shellState.getReader().println(Trace.isTracing() ? "on" : "off");
+    } else {
+      shellState.printException(new IllegalArgumentException("Expected 0 or 1 argument. There were " + cl.getArgs().length + "."));
+      printHelp(shellState);
+      return 1;
+    }
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "turns trace logging on or off";
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserCommand.java
new file mode 100644
index 0000000..07da450
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserCommand.java
@@ -0,0 +1,71 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.accumulo.core.util.shell.Token;
+import org.apache.commons.cli.CommandLine;
+
+public class UserCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
+    // save old credentials and connection in case of failure
+    String user = cl.getArgs()[0];
+    byte[] pass;
+    
+    // We can't let the wrapping try around the execute method deal
+    // with the exceptions because we have to do something if one
+    // of these methods fails
+    final String p = shellState.readMaskedLine("Enter password for user " + user + ": ", '*');
+    if (p == null) {
+      shellState.getReader().println();
+      return 0;
+    } // user canceled
+    pass = p.getBytes(StandardCharsets.UTF_8);
+    shellState.updateUser(user, new PasswordToken(pass));
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "switches to the specified user";
+  }
+  
+  @Override
+  public void registerCompletion(final Token root, final Map<Command.CompletionSet,Set<String>> special) {
+    registerCompletionForUsers(root, special);
+  }
+  
+  @Override
+  public String usage() {
+    return getName() + " <username>";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 1;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserPermissionsCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserPermissionsCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserPermissionsCommand.java
new file mode 100644
index 0000000..a126159
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UserPermissionsCommand.java
@@ -0,0 +1,106 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.security.NamespacePermission;
+import org.apache.accumulo.core.security.SystemPermission;
+import org.apache.accumulo.core.security.TablePermission;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+public class UserPermissionsCommand extends Command {
+  private Option userOpt;
+  
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
+    final String user = cl.getOptionValue(userOpt.getOpt(), shellState.getConnector().whoami());
+
+    String delim = "";
+    shellState.getReader().print("System permissions: ");
+    for (SystemPermission p : SystemPermission.values()) {
+      if (p != null && shellState.getConnector().securityOperations().hasSystemPermission(user, p)) {
+        shellState.getReader().print(delim + "System." + p.name());
+        delim = ", ";
+      }
+    }
+    shellState.getReader().println();
+
+    boolean runOnce = true;
+    for (String n : shellState.getConnector().namespaceOperations().list()) {
+      delim = "";
+      for (NamespacePermission p : NamespacePermission.values()) {
+        if (p != null && shellState.getConnector().securityOperations().hasNamespacePermission(user, n, p)) {
+          if (runOnce) {
+            shellState.getReader().print("\nNamespace permissions (" + n + "): ");
+            runOnce = false;
+          }
+          shellState.getReader().print(delim + "Namespace." + p.name());
+          delim = ", ";
+        }
+      }
+      runOnce = true;
+    }
+    shellState.getReader().println();
+
+    
+    runOnce = true;
+    for (String t : shellState.getConnector().tableOperations().list()) {
+      delim = "";
+      for (TablePermission p : TablePermission.values()) {
+        if (shellState.getConnector().securityOperations().hasTablePermission(user, t, p) && p != null) {
+          if (runOnce) {
+            shellState.getReader().print("\nTable permissions (" + t + "): ");
+            runOnce = false;
+          }
+          shellState.getReader().print(delim + "Table." + p.name());
+          delim = ", ";
+        }
+
+      }
+      runOnce = true;
+    }
+    shellState.getReader().println();
+
+    return 0;
+  }
+
+  @Override
+  public String description() {
+    return "displays a user's system, table, and namespace permissions";
+  }
+
+  @Override
+  public Options getOptions() {
+    Options o = new Options();
+    userOpt = new Option(Shell.userOption, "user", true, "user to operate on");
+    userOpt.setArgName("user");
+    o.addOption(userOpt);
+    return o;
+  }
+
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UsersCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UsersCommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UsersCommand.java
new file mode 100644
index 0000000..023c38b
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/UsersCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class UsersCommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
+    for (String user : shellState.getConnector().securityOperations().listLocalUsers()) {
+      shellState.getReader().println(user);
+    }
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "displays a list of existing users";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/main/java/org/apache/accumulo/core/util/shell/commands/WhoAmICommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/util/shell/commands/WhoAmICommand.java b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/WhoAmICommand.java
new file mode 100644
index 0000000..e7bb29d
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/shell/commands/WhoAmICommand.java
@@ -0,0 +1,41 @@
+/*
+ * 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.accumulo.core.util.shell.commands;
+
+import java.io.IOException;
+
+import org.apache.accumulo.core.util.shell.Shell;
+import org.apache.accumulo.core.util.shell.Shell.Command;
+import org.apache.commons.cli.CommandLine;
+
+public class WhoAmICommand extends Command {
+  @Override
+  public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws IOException {
+    shellState.getReader().println(shellState.getConnector().whoami());
+    return 0;
+  }
+  
+  @Override
+  public String description() {
+    return "reports the current user name";
+  }
+  
+  @Override
+  public int numArgs() {
+    return 0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/test/java/org/apache/accumulo/core/util/format/DeleterFormatterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/accumulo/core/util/format/DeleterFormatterTest.java b/core/src/test/java/org/apache/accumulo/core/util/format/DeleterFormatterTest.java
new file mode 100644
index 0000000..9223166
--- /dev/null
+++ b/core/src/test/java/org/apache/accumulo/core/util/format/DeleterFormatterTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.accumulo.core.util.format;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import jline.UnsupportedTerminal;
+import jline.console.ConsoleReader;
+
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.MutationsRejectedException;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.util.shell.Shell;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DeleterFormatterTest {
+  DeleterFormatter formatter;
+  Map<Key,Value> data;
+  BatchWriter writer;
+  BatchWriter exceptionWriter;
+  Shell shellState;
+
+  ByteArrayOutputStream baos;
+  ConsoleReader reader;
+
+  SettableInputStream input;
+
+  class SettableInputStream extends InputStream {
+    ByteArrayInputStream bais;
+
+    @Override
+    public int read() throws IOException {
+      return bais.read();
+    }
+
+    public void set(String in) {
+      bais = new ByteArrayInputStream(in.getBytes(StandardCharsets.UTF_8));
+    }
+  };
+
+  @Before
+  public void setUp() throws IOException, MutationsRejectedException {
+    input = new SettableInputStream();
+    baos = new ByteArrayOutputStream();
+
+    MutationsRejectedException mre = createMock(MutationsRejectedException.class);
+
+    writer = createNiceMock(BatchWriter.class);
+    exceptionWriter = createNiceMock(BatchWriter.class);
+    exceptionWriter.close();
+    expectLastCall().andThrow(mre);
+    exceptionWriter.addMutation(anyObject(Mutation.class));
+    expectLastCall().andThrow(mre);
+
+    shellState = createNiceMock(Shell.class);
+
+    reader = new ConsoleReader(input, baos, new UnsupportedTerminal());
+    expect(shellState.getReader()).andReturn(reader).anyTimes();
+
+    replay(writer, exceptionWriter, shellState);
+
+    data = new TreeMap<Key,Value>();
+    data.put(new Key("r", "cf", "cq"), new Value("value".getBytes(StandardCharsets.UTF_8)));
+  }
+
+  @Test
+  public void testEmpty() {
+    formatter = new DeleterFormatter(writer, Collections.<Key,Value> emptyMap().entrySet(), true, shellState, true);
+    assertFalse(formatter.hasNext());
+  }
+
+  @Test
+  public void testSingle() throws IOException {
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, true);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+
+    verify("[DELETED]", " r ", "cf", "cq", "value");
+  }
+
+  @Test
+  public void testNo() throws IOException {
+    input.set("no\n");
+    data.put(new Key("z"), new Value("v2".getBytes(StandardCharsets.UTF_8)));
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, false);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+
+    verify("[SKIPPED]", " r ", "cf", "cq", "value");
+
+    assertTrue(formatter.hasNext());
+  }
+
+  @Test
+  public void testNoConfirmation() throws IOException {
+    input.set("");
+    data.put(new Key("z"), new Value("v2".getBytes(StandardCharsets.UTF_8)));
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, false);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+
+    verify("[SKIPPED]", " r ", "cf", "cq", "value");
+
+    assertFalse(formatter.hasNext());
+  }
+
+  @Test
+  public void testYes() throws IOException {
+    input.set("y\nyes\n");
+    data.put(new Key("z"), new Value("v2".getBytes(StandardCharsets.UTF_8)));
+    formatter = new DeleterFormatter(writer, data.entrySet(), true, shellState, false);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+    verify("[DELETED]", " r ", "cf", "cq", "value");
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+    verify("[DELETED]", " z ", "v2");
+  }
+
+  @Test
+  public void testMutationException() {
+    formatter = new DeleterFormatter(exceptionWriter, data.entrySet(), true, shellState, true);
+
+    assertTrue(formatter.hasNext());
+    assertNull(formatter.next());
+    assertFalse(formatter.hasNext());
+  }
+
+  private void verify(String... chunks) throws IOException {
+    reader.flush();
+
+    String output = baos.toString();
+    for (String chunk : chunks) {
+      assertTrue(output.contains(chunk));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/b2b985e2/core/src/test/java/org/apache/accumulo/core/util/shell/PasswordConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/accumulo/core/util/shell/PasswordConverterTest.java b/core/src/test/java/org/apache/accumulo/core/util/shell/PasswordConverterTest.java
new file mode 100644
index 0000000..d1d24a6
--- /dev/null
+++ b/core/src/test/java/org/apache/accumulo/core/util/shell/PasswordConverterTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.accumulo.core.util.shell;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Scanner;
+
+import org.apache.accumulo.core.util.shell.ShellOptionsJC.PasswordConverter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+
+public class PasswordConverterTest {
+  
+  private class Password {
+    @Parameter(names = "--password", converter = PasswordConverter.class)
+    String password;
+  }
+  
+  private String[] argv;
+  private Password password;
+  private static InputStream realIn;
+  
+  @BeforeClass
+  public static void saveIn() {
+    realIn = System.in;
+  }
+  
+  @Before
+  public void setup() throws IOException {
+    argv = new String[] {"--password", ""};
+    password = new Password();
+    
+    PipedInputStream in = new PipedInputStream();
+    PipedOutputStream out = new PipedOutputStream(in);
+    OutputStreamWriter osw = new OutputStreamWriter(out);
+    osw.write("secret");
+    osw.close();
+    
+    System.setIn(in);
+  }
+  
+  @After
+  public void teardown() {
+    System.setIn(realIn);
+  }
+  
+  @Test
+  public void testPass() {
+    String expected = String.valueOf(Math.random());
+    argv[1] = "pass:" + expected;
+    new JCommander(password, argv);
+    assertEquals(expected, password.password);
+  }
+  
+  @Test
+  public void testEnv() {
+    String name = System.getenv().keySet().iterator().next();
+    argv[1] = "env:" + name;
+    new JCommander(password, argv);
+    assertEquals(System.getenv(name), password.password);
+  }
+  
+  @Test
+  public void testFile() throws FileNotFoundException {
+    argv[1] = "file:pom.xml";
+    Scanner scan = new Scanner(new File("pom.xml"));
+    String expected = scan.nextLine();
+    scan.close();
+    new JCommander(password, argv);
+    assertEquals(expected, password.password);
+  }
+  
+  @Test(expected=ParameterException.class)
+  public void testNoFile() throws FileNotFoundException {
+    argv[1] = "file:doesnotexist";
+    new JCommander(password, argv);
+  }
+
+  @Test
+  public void testStdin() {
+    argv[1] = "stdin";
+    new JCommander(password, argv);
+    assertEquals("stdin", password.password);
+  }
+}