Merge branch '1.6' into 1.7 Conflicts: core/src/main/java/org/apache/accumulo/core/util/shell/commands/CompactCommand.java shell/src/main/java/org/apache/accumulo/shell/commands/ScanCommand.java shell/src/main/java/org/apache/accumulo/shell/commands/SetScanIterCommand.java shell/src/main/java/org/apache/accumulo/shell/commands/SetShellIterCommand.java
Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/6df97b89 Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/6df97b89 Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/6df97b89 Branch: refs/heads/1.7 Commit: 6df97b8905d07f65d617ec59a2ad4a34afac81f1 Parents: 2033005 e4ffc77 Author: Keith Turner <ke...@deenlo.com> Authored: Thu Sep 3 17:05:41 2015 -0400 Committer: Keith Turner <ke...@deenlo.com> Committed: Thu Sep 3 17:05:41 2015 -0400 ---------------------------------------------------------------------- .../java/org/apache/accumulo/shell/Shell.java | 8 ++++---- .../accumulo/shell/commands/CompactCommand.java | 4 ++++ .../accumulo/shell/commands/ScanCommand.java | 18 +++++++++++++++++- .../accumulo/shell/commands/SetIterCommand.java | 11 ++++------- .../shell/commands/SetScanIterCommand.java | 15 +-------------- .../shell/commands/SetShellIterCommand.java | 7 ------- 6 files changed, 30 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/accumulo/blob/6df97b89/shell/src/main/java/org/apache/accumulo/shell/Shell.java ---------------------------------------------------------------------- diff --cc shell/src/main/java/org/apache/accumulo/shell/Shell.java index 37856ad,0000000..483edb0 mode 100644,000000..100644 --- a/shell/src/main/java/org/apache/accumulo/shell/Shell.java +++ b/shell/src/main/java/org/apache/accumulo/shell/Shell.java @@@ -1,1267 -1,0 +1,1267 @@@ +/* + * 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.shell; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + - import jline.console.ConsoleReader; - import jline.console.UserInterruptException; - import jline.console.history.FileHistory; - +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.ClientConfiguration; +import org.apache.accumulo.core.client.ClientConfiguration.ClientProperty; +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.Instance; +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.client.ZooKeeperInstance; +import org.apache.accumulo.core.client.impl.ClientContext; +import org.apache.accumulo.core.client.impl.Tables; +import org.apache.accumulo.core.client.mock.MockInstance; +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.conf.AccumuloConfiguration; +import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.conf.SiteConfiguration; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.data.thrift.TConstraintViolationSummary; +import org.apache.accumulo.core.tabletserver.thrift.ConstraintViolationException; +import org.apache.accumulo.core.trace.DistributedTrace; +import org.apache.accumulo.core.util.BadArgumentException; +import org.apache.accumulo.core.util.format.BinaryFormatter; +import org.apache.accumulo.core.util.format.DefaultFormatter; +import org.apache.accumulo.core.util.format.Formatter; +import org.apache.accumulo.core.util.format.FormatterFactory; +import org.apache.accumulo.core.volume.VolumeConfiguration; +import org.apache.accumulo.core.zookeeper.ZooUtil; +import org.apache.accumulo.shell.commands.AboutCommand; +import org.apache.accumulo.shell.commands.AddAuthsCommand; +import org.apache.accumulo.shell.commands.AddSplitsCommand; +import org.apache.accumulo.shell.commands.AuthenticateCommand; +import org.apache.accumulo.shell.commands.ByeCommand; +import org.apache.accumulo.shell.commands.ClasspathCommand; +import org.apache.accumulo.shell.commands.ClearCommand; +import org.apache.accumulo.shell.commands.CloneTableCommand; +import org.apache.accumulo.shell.commands.ClsCommand; +import org.apache.accumulo.shell.commands.CompactCommand; +import org.apache.accumulo.shell.commands.ConfigCommand; +import org.apache.accumulo.shell.commands.ConstraintCommand; +import org.apache.accumulo.shell.commands.CreateNamespaceCommand; +import org.apache.accumulo.shell.commands.CreateTableCommand; +import org.apache.accumulo.shell.commands.CreateUserCommand; +import org.apache.accumulo.shell.commands.DUCommand; +import org.apache.accumulo.shell.commands.DebugCommand; +import org.apache.accumulo.shell.commands.DeleteCommand; +import org.apache.accumulo.shell.commands.DeleteIterCommand; +import org.apache.accumulo.shell.commands.DeleteManyCommand; +import org.apache.accumulo.shell.commands.DeleteNamespaceCommand; +import org.apache.accumulo.shell.commands.DeleteRowsCommand; +import org.apache.accumulo.shell.commands.DeleteScanIterCommand; +import org.apache.accumulo.shell.commands.DeleteShellIterCommand; +import org.apache.accumulo.shell.commands.DeleteTableCommand; +import org.apache.accumulo.shell.commands.DeleteUserCommand; +import org.apache.accumulo.shell.commands.DropTableCommand; +import org.apache.accumulo.shell.commands.DropUserCommand; +import org.apache.accumulo.shell.commands.EGrepCommand; +import org.apache.accumulo.shell.commands.ExecfileCommand; +import org.apache.accumulo.shell.commands.ExitCommand; +import org.apache.accumulo.shell.commands.ExportTableCommand; +import org.apache.accumulo.shell.commands.ExtensionCommand; +import org.apache.accumulo.shell.commands.FateCommand; +import org.apache.accumulo.shell.commands.FlushCommand; +import org.apache.accumulo.shell.commands.FormatterCommand; +import org.apache.accumulo.shell.commands.GetAuthsCommand; +import org.apache.accumulo.shell.commands.GetGroupsCommand; +import org.apache.accumulo.shell.commands.GetSplitsCommand; +import org.apache.accumulo.shell.commands.GrantCommand; +import org.apache.accumulo.shell.commands.GrepCommand; +import org.apache.accumulo.shell.commands.HelpCommand; +import org.apache.accumulo.shell.commands.HiddenCommand; +import org.apache.accumulo.shell.commands.HistoryCommand; +import org.apache.accumulo.shell.commands.ImportDirectoryCommand; +import org.apache.accumulo.shell.commands.ImportTableCommand; +import org.apache.accumulo.shell.commands.InfoCommand; +import org.apache.accumulo.shell.commands.InsertCommand; +import org.apache.accumulo.shell.commands.InterpreterCommand; +import org.apache.accumulo.shell.commands.ListCompactionsCommand; +import org.apache.accumulo.shell.commands.ListIterCommand; +import org.apache.accumulo.shell.commands.ListScansCommand; +import org.apache.accumulo.shell.commands.ListShellIterCommand; +import org.apache.accumulo.shell.commands.MaxRowCommand; +import org.apache.accumulo.shell.commands.MergeCommand; +import org.apache.accumulo.shell.commands.NamespacePermissionsCommand; +import org.apache.accumulo.shell.commands.NamespacesCommand; +import org.apache.accumulo.shell.commands.NoTableCommand; +import org.apache.accumulo.shell.commands.OfflineCommand; +import org.apache.accumulo.shell.commands.OnlineCommand; +import org.apache.accumulo.shell.commands.OptUtil; +import org.apache.accumulo.shell.commands.PasswdCommand; +import org.apache.accumulo.shell.commands.PingCommand; +import org.apache.accumulo.shell.commands.QuestionCommand; +import org.apache.accumulo.shell.commands.QuitCommand; +import org.apache.accumulo.shell.commands.QuotedStringTokenizer; +import org.apache.accumulo.shell.commands.RenameNamespaceCommand; +import org.apache.accumulo.shell.commands.RenameTableCommand; +import org.apache.accumulo.shell.commands.RevokeCommand; +import org.apache.accumulo.shell.commands.ScanCommand; +import org.apache.accumulo.shell.commands.ScriptCommand; +import org.apache.accumulo.shell.commands.SetAuthsCommand; +import org.apache.accumulo.shell.commands.SetGroupsCommand; +import org.apache.accumulo.shell.commands.SetIterCommand; +import org.apache.accumulo.shell.commands.SetScanIterCommand; +import org.apache.accumulo.shell.commands.SetShellIterCommand; +import org.apache.accumulo.shell.commands.SleepCommand; +import org.apache.accumulo.shell.commands.SystemPermissionsCommand; +import org.apache.accumulo.shell.commands.TableCommand; +import org.apache.accumulo.shell.commands.TablePermissionsCommand; +import org.apache.accumulo.shell.commands.TablesCommand; +import org.apache.accumulo.shell.commands.TraceCommand; +import org.apache.accumulo.shell.commands.UserCommand; +import org.apache.accumulo.shell.commands.UserPermissionsCommand; +import org.apache.accumulo.shell.commands.UsersCommand; +import org.apache.accumulo.shell.commands.WhoAmICommand; +import org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader; +import org.apache.accumulo.start.classloader.vfs.ContextManager; +import org.apache.accumulo.start.spi.KeywordExecutable; +import org.apache.commons.cli.BasicParser; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.MissingOptionException; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.hadoop.fs.Path; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; +import com.google.auto.service.AutoService; + ++import jline.console.ConsoleReader; ++import jline.console.UserInterruptException; ++import jline.console.history.FileHistory; ++ +/** + * A convenient console interface to perform basic accumulo functions Includes auto-complete, help, and quoted strings with escape sequences + */ +@AutoService(KeywordExecutable.class) +public class Shell extends ShellOptions implements KeywordExecutable { + public static final Logger log = Logger.getLogger(Shell.class); + private static final Logger audit = Logger.getLogger(Shell.class.getName() + ".audit"); + + public static final Charset CHARSET = ISO_8859_1; + public static final int NO_FIXED_ARG_LENGTH_CHECK = -1; + public static final String COMMENT_PREFIX = "#"; + public static final String HISTORY_DIR_NAME = ".accumulo"; + public static final String HISTORY_FILE_NAME = "shell_history.txt"; + private static final String SHELL_DESCRIPTION = "Shell - Apache Accumulo Interactive Shell"; + + protected int exitCode = 0; + private String tableName; + protected Instance instance; + private Connector connector; + protected ConsoleReader reader; + private AuthenticationToken token; + private final Class<? extends Formatter> defaultFormatterClass = DefaultFormatter.class; + private final Class<? extends Formatter> binaryFormatterClass = BinaryFormatter.class; + public Map<String,List<IteratorSetting>> scanIteratorOptions = new HashMap<String,List<IteratorSetting>>(); + public Map<String,List<IteratorSetting>> iteratorProfiles = new HashMap<String,List<IteratorSetting>>(); + + private Token rootToken; + public final Map<String,Command> commandFactory = new TreeMap<String,Command>(); + public final Map<String,Command[]> commandGrouping = new TreeMap<String,Command[]>(); + + // exit if true + private boolean exit = false; + + // file to execute commands from + protected File execFile = null; + // single command to execute from the command line + protected String execCommand = null; + protected boolean verbose = true; + + private boolean tabCompletion; + private boolean disableAuthTimeout; + private long authTimeout; + private long lastUserActivity = System.nanoTime(); + private boolean logErrorsToConsole = false; + private PrintWriter writer = null; + private boolean masking = false; + + public Shell() throws IOException { + this(new ConsoleReader(), new PrintWriter(new OutputStreamWriter(System.out, System.getProperty("jline.WindowsTerminal.output.encoding", + System.getProperty("file.encoding"))))); + } + + public Shell(ConsoleReader reader, PrintWriter writer) { + super(); + this.reader = reader; + this.writer = writer; + } + + /** + * Configures the shell using the provided options. Not for client use. + * + * @return true if the shell was successfully configured, false otherwise. + */ + public boolean config(String... args) { + ShellOptionsJC options = new ShellOptionsJC(); + JCommander jc = new JCommander(); + + jc.setProgramName("accumulo shell"); + jc.addObject(options); + try { + jc.parse(args); + } catch (ParameterException e) { + jc.usage(); + exitCode = 1; + return false; + } + + if (options.isHelpEnabled()) { + jc.usage(); + // Not an error + exitCode = 0; + return false; + } + + if (options.getUnrecognizedOptions() != null) { + logError("Unrecognized Options: " + options.getUnrecognizedOptions().toString()); + jc.usage(); + exitCode = 1; + return false; + } + + setDebugging(options.isDebugEnabled()); + authTimeout = TimeUnit.MINUTES.toNanos(options.getAuthTimeout()); + disableAuthTimeout = options.isAuthTimeoutDisabled(); + + ClientConfiguration clientConf; + try { + clientConf = options.getClientConfiguration(); + } catch (Exception e) { + printException(e); + return true; + } + + if (Boolean.parseBoolean(clientConf.get(ClientProperty.INSTANCE_RPC_SASL_ENABLED))) { + log.debug("SASL is enabled, disabling authorization timeout"); + disableAuthTimeout = true; + } + + // get the options that were parsed + final String user; + try { + user = options.getUsername(); + } catch (Exception e) { + printException(e); + return true; + } + String password = options.getPassword(); + + tabCompletion = !options.isTabCompletionDisabled(); + + // Use a fake (Mock), ZK, or HdfsZK Accumulo instance + setInstance(options); + + // AuthenticationToken options + try { + token = options.getAuthenticationToken(); + } catch (Exception e) { + printException(e); + return true; + } + + Map<String,String> loginOptions = options.getTokenProperties(); + + // process default parameters if unspecified + try { + final boolean hasToken = (token != null); + + if (hasToken && password != null) { + throw new ParameterException("Can not supply '--pass' option with '--tokenClass' option"); + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + reader.getTerminal().setEchoEnabled(true); + } + }); + + if (hasToken) { // implied hasTokenOptions + // Fully qualified name so we don't shadow java.util.Properties + org.apache.accumulo.core.client.security.tokens.AuthenticationToken.Properties props; + // and line wrap it because the package name is so long + props = new org.apache.accumulo.core.client.security.tokens.AuthenticationToken.Properties(); + + if (!loginOptions.isEmpty()) { + props.putAllStrings(loginOptions); + } + token.init(props); + } else { + // Read password if the user explicitly asked for it, or didn't specify anything at all + if ("stdin".equals(password) || password == null) { + password = reader.readLine("Password: ", '*'); + } + + if (password == null) { + // User cancel, e.g. Ctrl-D pressed + throw new ParameterException("No password or token option supplied"); + } else { + this.token = new PasswordToken(password); + } + } + + if (!options.isFake()) { + DistributedTrace.enable(InetAddress.getLocalHost().getHostName(), "shell", clientConf); + } + + this.setTableName(""); + connector = instance.getConnector(user, token); + + } catch (Exception e) { + printException(e); + exitCode = 1; + return false; + } + + // decide whether to execute commands from a file and quit + if (options.getExecFile() != null) { + execFile = options.getExecFile(); + verbose = false; + } else if (options.getExecFileVerbose() != null) { + execFile = options.getExecFileVerbose(); + verbose = true; + } + execCommand = options.getExecCommand(); + if (execCommand != null) { + verbose = false; + } + + rootToken = new Token(); + + Command[] dataCommands = {new DeleteCommand(), new DeleteManyCommand(), new DeleteRowsCommand(), new EGrepCommand(), new FormatterCommand(), + new InterpreterCommand(), new GrepCommand(), new ImportDirectoryCommand(), new InsertCommand(), new MaxRowCommand(), new ScanCommand()}; + Command[] debuggingCommands = {new ClasspathCommand(), new DebugCommand(), new ListScansCommand(), new ListCompactionsCommand(), new TraceCommand(), + new PingCommand()}; + Command[] execCommands = {new ExecfileCommand(), new HistoryCommand(), new ExtensionCommand(), new ScriptCommand()}; + Command[] exitCommands = {new ByeCommand(), new ExitCommand(), new QuitCommand()}; + Command[] helpCommands = {new AboutCommand(), new HelpCommand(), new InfoCommand(), new QuestionCommand()}; + Command[] iteratorCommands = {new DeleteIterCommand(), new DeleteScanIterCommand(), new ListIterCommand(), new SetIterCommand(), new SetScanIterCommand(), + new SetShellIterCommand(), new ListShellIterCommand(), new DeleteShellIterCommand()}; + Command[] otherCommands = {new HiddenCommand()}; + Command[] permissionsCommands = {new GrantCommand(), new RevokeCommand(), new SystemPermissionsCommand(), new TablePermissionsCommand(), + new UserPermissionsCommand(), new NamespacePermissionsCommand()}; + Command[] stateCommands = {new AuthenticateCommand(), new ClsCommand(), new ClearCommand(), new FateCommand(), new NoTableCommand(), new SleepCommand(), + new TableCommand(), new UserCommand(), new WhoAmICommand()}; + Command[] tableCommands = {new CloneTableCommand(), new ConfigCommand(), new CreateTableCommand(), new DeleteTableCommand(), new DropTableCommand(), + new DUCommand(), new ExportTableCommand(), new ImportTableCommand(), new OfflineCommand(), new OnlineCommand(), new RenameTableCommand(), + new TablesCommand(), new NamespacesCommand(), new CreateNamespaceCommand(), new DeleteNamespaceCommand(), new RenameNamespaceCommand()}; + Command[] tableControlCommands = {new AddSplitsCommand(), new CompactCommand(), new ConstraintCommand(), new FlushCommand(), new GetGroupsCommand(), + new GetSplitsCommand(), new MergeCommand(), new SetGroupsCommand()}; + Command[] userCommands = {new AddAuthsCommand(), new CreateUserCommand(), new DeleteUserCommand(), new DropUserCommand(), new GetAuthsCommand(), + new PasswdCommand(), new SetAuthsCommand(), new UsersCommand()}; + commandGrouping.put("-- Writing, Reading, and Removing Data --", dataCommands); + commandGrouping.put("-- Debugging Commands -------------------", debuggingCommands); + commandGrouping.put("-- Shell Execution Commands -------------", execCommands); + commandGrouping.put("-- Exiting Commands ---------------------", exitCommands); + commandGrouping.put("-- Help Commands ------------------------", helpCommands); + commandGrouping.put("-- Iterator Configuration ---------------", iteratorCommands); + commandGrouping.put("-- Permissions Administration Commands --", permissionsCommands); + commandGrouping.put("-- Shell State Commands -----------------", stateCommands); + commandGrouping.put("-- Table Administration Commands --------", tableCommands); + commandGrouping.put("-- Table Control Commands ---------------", tableControlCommands); + commandGrouping.put("-- User Administration Commands ---------", userCommands); + + for (Command[] cmds : commandGrouping.values()) { + for (Command cmd : cmds) + commandFactory.put(cmd.getName(), cmd); + } + for (Command cmd : otherCommands) { + commandFactory.put(cmd.getName(), cmd); + } + return true; + } + + /** + * Sets the instance used by the shell based on the given options. + * + * @param options + * shell options + */ + protected void setInstance(ShellOptionsJC options) { + // should only be one set of instance options set + instance = null; + if (options.isFake()) { + instance = new MockInstance("fake"); + } else { + String instanceName, hosts; + if (options.isHdfsZooInstance()) { + instanceName = hosts = null; + } else if (options.getZooKeeperInstance().size() > 0) { + List<String> zkOpts = options.getZooKeeperInstance(); + instanceName = zkOpts.get(0); + hosts = zkOpts.get(1); + } else { + instanceName = options.getZooKeeperInstanceName(); + hosts = options.getZooKeeperHosts(); + } + try { + instance = getZooInstance(instanceName, hosts, options.getClientConfiguration()); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to load client config from " + options.getClientConfigFile(), e); + } + } + } + + /** + * Get the ZooKeepers. Use the value passed in (if there was one), then fall back to the ClientConf, finally trying the accumulo-site.xml. + * + * @param keepers + * ZooKeepers passed to the shell + * @param clientConfig + * ClientConfiguration instance + * @return The ZooKeepers to connect to + */ + static String getZooKeepers(String keepers, ClientConfiguration clientConfig, AccumuloConfiguration conf) { + if (null != keepers) { + return keepers; + } + + if (clientConfig.containsKey(ClientProperty.INSTANCE_ZK_HOST.getKey())) { + return clientConfig.get(ClientProperty.INSTANCE_ZK_HOST); + } + + return conf.get(Property.INSTANCE_ZK_HOST); + } + + /* + * Takes instanceName and keepers as separate arguments, rather than just packaged into the clientConfig, so that we can fail over to accumulo-site.xml or + * HDFS config if they're unspecified. + */ + private static Instance getZooInstance(String instanceName, String keepersOption, ClientConfiguration clientConfig) { + UUID instanceId = null; + if (instanceName == null) { + instanceName = clientConfig.get(ClientProperty.INSTANCE_NAME); + } + AccumuloConfiguration conf = SiteConfiguration.getInstance(ClientContext.convertClientConfig(clientConfig)); + String keepers = getZooKeepers(keepersOption, clientConfig, conf); + if (instanceName == null) { + Path instanceDir = new Path(VolumeConfiguration.getVolumeUris(conf)[0], "instance_id"); + instanceId = UUID.fromString(ZooUtil.getInstanceIDFromHdfs(instanceDir, conf)); + } + if (instanceId != null) { + return new ZooKeeperInstance(clientConfig.withInstance(instanceId).withZkHosts(keepers)); + } else { + return new ZooKeeperInstance(clientConfig.withInstance(instanceName).withZkHosts(keepers)); + } + } + + public Connector getConnector() { + return connector; + } + + public Instance getInstance() { + return instance; + } + + public 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; + } + + @Override + public String keyword() { + return "shell"; + } + + @Override + public void execute(final String[] args) throws IOException { + try { + if (!config(args)) { + System.exit(getExitCode()); + } + + System.exit(start()); + } finally { + shutdown(); + DistributedTrace.disable(); + } + } + + public static void main(String args[]) throws IOException { + new Shell().execute(args); + } + + public int start() throws IOException { + String input; + if (isVerbose()) + printInfo(); + + String home = System.getProperty("HOME"); + if (home == null) + home = System.getenv("HOME"); + String configDir = home + "/" + HISTORY_DIR_NAME; + String historyPath = configDir + "/" + HISTORY_FILE_NAME; + File accumuloDir = new File(configDir); + if (!accumuloDir.exists() && !accumuloDir.mkdirs()) + log.warn("Unable to make directory for history at " + accumuloDir); + try { + final FileHistory history = new FileHistory(new File(historyPath)); + reader.setHistory(history); + // Add shutdown hook to flush file history, per jline javadocs + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + history.flush(); + } catch (IOException e) { + log.warn("Could not flush history to file."); + } + } + }); + } catch (IOException e) { + log.warn("Unable to load history file at " + historyPath); + } + + // Turn Ctrl+C into Exception instead of JVM exit + reader.setHandleUserInterrupt(true); + + ShellCompletor userCompletor = null; + + if (execFile != null) { + java.util.Scanner scanner = new java.util.Scanner(execFile, UTF_8.name()); + try { + while (scanner.hasNextLine() && !hasExited()) { + execCommand(scanner.nextLine(), true, isVerbose()); + } + } finally { + scanner.close(); + } + } else if (execCommand != null) { + for (String command : execCommand.split("\n")) { + execCommand(command, true, isVerbose()); + } + return exitCode; + } + + while (true) { + try { + if (hasExited()) + return exitCode; + + // If tab completion is true we need to reset + if (tabCompletion) { + if (userCompletor != null) + reader.removeCompleter(userCompletor); + + userCompletor = setupCompletion(); + reader.addCompleter(userCompletor); + } + + reader.setPrompt(getDefaultPrompt()); + input = reader.readLine(); + if (input == null) { + reader.println(); + return exitCode; + } // User Canceled (Ctrl+D) + + execCommand(input, disableAuthTimeout, false); + } catch (UserInterruptException uie) { + // User Cancelled (Ctrl+C) + reader.println(); + + String partialLine = uie.getPartialLine(); + if (partialLine == null || "".equals(uie.getPartialLine().trim())) { + // No content, actually exit + return exitCode; + } + } finally { + reader.flush(); + } + } + } + + public void shutdown() { + if (reader != null) { + reader.shutdown(); + } + } + + public void printInfo() throws IOException { + reader.print("\n" + SHELL_DESCRIPTION + "\n" + "- \n" + "- version: " + Constants.VERSION + "\n" + "- instance name: " + + connector.getInstance().getInstanceName() + "\n" + "- instance id: " + connector.getInstance().getInstanceID() + "\n" + "- \n" + + "- type 'help' for a list of available commands\n" + "- \n"); + reader.flush(); + } + + public void printVerboseInfo() throws IOException { + StringBuilder sb = new StringBuilder("-\n"); + sb.append("- Current user: ").append(connector.whoami()).append("\n"); + if (execFile != null) + sb.append("- Executing commands from: ").append(execFile).append("\n"); + if (disableAuthTimeout) + sb.append("- Authorization timeout: disabled\n"); + else + sb.append("- Authorization timeout: ").append(String.format("%ds%n", TimeUnit.NANOSECONDS.toSeconds(authTimeout))); + sb.append("- Debug: ").append(isDebuggingEnabled() ? "on" : "off").append("\n"); + if (!scanIteratorOptions.isEmpty()) { + for (Entry<String,List<IteratorSetting>> entry : scanIteratorOptions.entrySet()) { + sb.append("- Session scan iterators for table ").append(entry.getKey()).append(":\n"); + for (IteratorSetting setting : entry.getValue()) { + sb.append("- Iterator ").append(setting.getName()).append(" options:\n"); + sb.append("- ").append("iteratorPriority").append(" = ").append(setting.getPriority()).append("\n"); + sb.append("- ").append("iteratorClassName").append(" = ").append(setting.getIteratorClass()).append("\n"); + for (Entry<String,String> optEntry : setting.getOptions().entrySet()) { + sb.append("- ").append(optEntry.getKey()).append(" = ").append(optEntry.getValue()).append("\n"); + } + } + } + } + sb.append("-\n"); + reader.print(sb.toString()); + } + + public String getDefaultPrompt() { + return connector.whoami() + "@" + connector.getInstance().getInstanceName() + (getTableName().isEmpty() ? "" : " ") + getTableName() + "> "; + } + + public void execCommand(String input, boolean ignoreAuthTimeout, boolean echoPrompt) throws IOException { + audit.log(Level.INFO, getDefaultPrompt() + input); + if (echoPrompt) { + reader.print(getDefaultPrompt()); + reader.println(input); + } + + if (input.startsWith(COMMENT_PREFIX)) { + return; + } + + String fields[]; + try { + fields = new QuotedStringTokenizer(input).getTokens(); + } catch (BadArgumentException e) { + printException(e); + ++exitCode; + return; + } + if (fields.length == 0) + return; + + String command = fields[0]; + fields = fields.length > 1 ? Arrays.copyOfRange(fields, 1, fields.length) : new String[] {}; + + Command sc = null; + if (command.length() > 0) { + try { + // Obtain the command from the command table + sc = commandFactory.get(command); + if (sc == null) { + reader.println(String.format("Unknown command \"%s\". Enter \"help\" for a list possible commands.", command)); + reader.flush(); + return; + } + + long duration = System.nanoTime() - lastUserActivity; + if (!(sc instanceof ExitCommand) && !ignoreAuthTimeout && (duration < 0 || duration > authTimeout)) { + reader.println("Shell has been idle for too long. Please re-authenticate."); + boolean authFailed = true; + do { + String pwd = readMaskedLine("Enter current password for '" + connector.whoami() + "': ", '*'); + if (pwd == null) { + reader.println(); + return; + } // user canceled + + try { + authFailed = !connector.securityOperations().authenticateUser(connector.whoami(), new PasswordToken(pwd)); + } catch (Exception e) { + ++exitCode; + printException(e); + } + + if (authFailed) + reader.print("Invalid password. "); + } while (authFailed); + lastUserActivity = System.nanoTime(); + } + + // Get the options from the command on how to parse the string + Options parseOpts = sc.getOptionsWithHelp(); + + // Parse the string using the given options + CommandLine cl = new BasicParser().parse(parseOpts, fields); + + int actualArgLen = cl.getArgs().length; + int expectedArgLen = sc.numArgs(); + if (cl.hasOption(helpOption)) { + // Display help if asked to; otherwise execute the command + sc.printHelp(this); + } else if (expectedArgLen != NO_FIXED_ARG_LENGTH_CHECK && actualArgLen != expectedArgLen) { + ++exitCode; + // Check for valid number of fixed arguments (if not + // negative; negative means it is not checked, for + // vararg-like commands) + printException(new IllegalArgumentException(String.format("Expected %d argument%s. There %s %d.", expectedArgLen, expectedArgLen == 1 ? "" : "s", + actualArgLen == 1 ? "was" : "were", actualArgLen))); + sc.printHelp(this); + } else { + int tmpCode = sc.execute(input, cl, this); + exitCode += tmpCode; + reader.flush(); + } + + } catch (ConstraintViolationException e) { + ++exitCode; + printConstraintViolationException(e); + } catch (TableNotFoundException e) { + ++exitCode; + if (getTableName().equals(e.getTableName())) + setTableName(""); + printException(e); + } catch (ParseException e) { + // not really an error if the exception is a missing required + // option when the user is asking for help + if (!(e instanceof MissingOptionException && (Arrays.asList(fields).contains("-" + helpOption) || Arrays.asList(fields).contains("--" + helpLongOption)))) { + ++exitCode; + printException(e); + } + if (sc != null) + sc.printHelp(this); + } catch (UserInterruptException e) { + ++exitCode; + } catch (Exception e) { + ++exitCode; + printException(e); + } + } else { + ++exitCode; + printException(new BadArgumentException("Unrecognized empty command", command, -1)); + } + reader.flush(); + } + + /** + * The command tree is built in reverse so that the references are more easily linked up. There is some code in token to allow forward building of the command + * tree. + */ + private ShellCompletor setupCompletion() { + rootToken = new Token(); + + Set<String> tableNames = null; + try { + tableNames = connector.tableOperations().list(); + } catch (Exception e) { + log.debug("Unable to obtain list of tables", e); + tableNames = Collections.emptySet(); + } + + Set<String> userlist = null; + try { + userlist = connector.securityOperations().listLocalUsers(); + } catch (Exception e) { + log.debug("Unable to obtain list of users", e); + userlist = Collections.emptySet(); + } + + Set<String> namespaces = null; + try { + namespaces = connector.namespaceOperations().list(); + } catch (Exception e) { + log.debug("Unable to obtain list of namespaces", e); + namespaces = Collections.emptySet(); + } + + Map<Command.CompletionSet,Set<String>> options = new HashMap<Command.CompletionSet,Set<String>>(); + + Set<String> commands = new HashSet<String>(); + for (String a : commandFactory.keySet()) + commands.add(a); + + Set<String> modifiedUserlist = new HashSet<String>(); + Set<String> modifiedTablenames = new HashSet<String>(); + Set<String> modifiedNamespaces = new HashSet<String>(); + + for (String a : tableNames) + modifiedTablenames.add(a.replaceAll("([\\s'\"])", "\\\\$1")); + for (String a : userlist) + modifiedUserlist.add(a.replaceAll("([\\s'\"])", "\\\\$1")); + for (String a : namespaces) { + String b = a.replaceAll("([\\s'\"])", "\\\\$1"); + modifiedNamespaces.add(b.isEmpty() ? "\"\"" : b); + } + + options.put(Command.CompletionSet.USERNAMES, modifiedUserlist); + options.put(Command.CompletionSet.TABLENAMES, modifiedTablenames); + options.put(Command.CompletionSet.NAMESPACES, modifiedNamespaces); + options.put(Command.CompletionSet.COMMANDS, commands); + + for (Command[] cmdGroup : commandGrouping.values()) { + for (Command c : cmdGroup) { + c.getOptionsWithHelp(); // prep the options for the command + // so that the completion can + // include them + c.registerCompletion(rootToken, options); + } + } + return new ShellCompletor(rootToken, options); + } + + /** + * The Command class represents a command to be run in the shell. It contains the methods to execute along with some methods to help tab completion, and + * return the command name, help, and usage. + */ + public static abstract class Command { + // Helper methods for completion + public enum CompletionSet { + TABLENAMES, USERNAMES, COMMANDS, NAMESPACES + } + + public void registerCompletionGeneral(Token root, Set<String> args, boolean caseSens) { + Token t = new Token(args); + t.setCaseSensitive(caseSens); + + Token command = new Token(getName()); + command.addSubcommand(t); + + root.addSubcommand(command); + } + + public void registerCompletionForTables(Token root, Map<CompletionSet,Set<String>> completionSet) { + registerCompletionGeneral(root, completionSet.get(CompletionSet.TABLENAMES), true); + } + + public void registerCompletionForUsers(Token root, Map<CompletionSet,Set<String>> completionSet) { + registerCompletionGeneral(root, completionSet.get(CompletionSet.USERNAMES), true); + } + + public void registerCompletionForCommands(Token root, Map<CompletionSet,Set<String>> completionSet) { + registerCompletionGeneral(root, completionSet.get(CompletionSet.COMMANDS), false); + } + + public void registerCompletionForNamespaces(Token root, Map<CompletionSet,Set<String>> completionSet) { + registerCompletionGeneral(root, completionSet.get(CompletionSet.NAMESPACES), true); + } + + // abstract methods to override + public abstract int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception; + + public abstract String description(); + + /** + * If the number of arguments is not always zero (not including those arguments handled through Options), make sure to override the {@link #usage()} method. + * Otherwise, {@link #usage()} does need to be overridden. + */ + public abstract int numArgs(); + + // OPTIONAL methods to override: + + // the general version of getname uses reflection to get the class name + // and then cuts off the suffix -Command to get the name of the command + public String getName() { + String s = this.getClass().getName(); + int st = Math.max(s.lastIndexOf('$'), s.lastIndexOf('.')); + int i = s.indexOf("Command"); + return i > 0 ? s.substring(st + 1, i).toLowerCase(Locale.ENGLISH) : null; + } + + // The general version of this method adds the name + // of the command to the completion tree + public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completion_set) { + root.addSubcommand(new Token(getName())); + } + + // The general version of this method uses the HelpFormatter + // that comes with the apache Options package to print out the help + public final void printHelp(Shell shellState) { + shellState.printHelp(usage(), "description: " + this.description(), getOptionsWithHelp()); + } + + public final void printHelp(Shell shellState, int width) { + shellState.printHelp(usage(), "description: " + this.description(), getOptionsWithHelp(), width); + } + + // Get options with help + public final Options getOptionsWithHelp() { + Options opts = getOptions(); + opts.addOption(new Option(helpOption, helpLongOption, false, "display this help")); + return opts; + } + + // General usage is just the command + public String usage() { + return getName(); + } + + // General Options are empty + public Options getOptions() { + return new Options(); + } + } + + public interface PrintLine { + void print(String s); + + void close(); + } + + public static class PrintShell implements PrintLine { + ConsoleReader reader; + + public PrintShell(ConsoleReader reader) { + this.reader = reader; + } + + @Override + public void print(String s) { + try { + reader.println(s); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void close() {} + }; + + public static class PrintFile implements PrintLine { + PrintWriter writer; + + public PrintFile(String filename) throws FileNotFoundException { + writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), UTF_8))); + } + + @Override + public void print(String s) { + writer.println(s); + } + + @Override + public void close() { + writer.close(); + } + }; + + public final void printLines(Iterator<String> lines, boolean paginate) throws IOException { + printLines(lines, paginate, null); + } + + public final void printLines(Iterator<String> lines, boolean paginate, PrintLine out) throws IOException { + int linesPrinted = 0; + String prompt = "-- hit any key to continue or 'q' to quit --"; + int lastPromptLength = prompt.length(); + int termWidth = reader.getTerminal().getWidth(); + int maxLines = reader.getTerminal().getHeight(); + + String peek = null; + while (lines.hasNext()) { + String nextLine = lines.next(); + if (nextLine == null) + continue; + for (String line : nextLine.split("\\n")) { + if (out == null) { + if (peek != null) { + reader.println(peek); + if (paginate) { + linesPrinted += peek.length() == 0 ? 0 : Math.ceil(peek.length() * 1.0 / termWidth); + + // check if displaying the next line would result in + // scrolling off the screen + if (linesPrinted + Math.ceil(lastPromptLength * 1.0 / termWidth) + Math.ceil(prompt.length() * 1.0 / termWidth) + + Math.ceil(line.length() * 1.0 / termWidth) > maxLines) { + linesPrinted = 0; + int numdashes = (termWidth - prompt.length()) / 2; + String nextPrompt = repeat("-", numdashes) + prompt + repeat("-", numdashes); + lastPromptLength = nextPrompt.length(); + reader.print(nextPrompt); + reader.flush(); + + if (Character.toUpperCase((char) reader.readCharacter()) == 'Q') { + reader.println(); + return; + } + reader.println(); + termWidth = reader.getTerminal().getWidth(); + maxLines = reader.getTerminal().getHeight(); + } + } + } + peek = line; + } else { + out.print(line); + } + } + } + if (out == null && peek != null) { + reader.println(peek); + } + } + + public final void printRecords(Iterable<Entry<Key,Value>> scanner, boolean printTimestamps, boolean paginate, Class<? extends Formatter> formatterClass, + PrintLine outFile) throws IOException { + printLines(FormatterFactory.getFormatter(formatterClass, scanner, printTimestamps), paginate, outFile); + } + + public final void printRecords(Iterable<Entry<Key,Value>> scanner, boolean printTimestamps, boolean paginate, Class<? extends Formatter> formatterClass) + throws IOException { + printLines(FormatterFactory.getFormatter(formatterClass, scanner, printTimestamps), paginate); + } + + public final void printBinaryRecords(Iterable<Entry<Key,Value>> scanner, boolean printTimestamps, boolean paginate, PrintLine outFile) throws IOException { + printLines(FormatterFactory.getFormatter(binaryFormatterClass, scanner, printTimestamps), paginate, outFile); + } + + public final void printBinaryRecords(Iterable<Entry<Key,Value>> scanner, boolean printTimestamps, boolean paginate) throws IOException { + printLines(FormatterFactory.getFormatter(binaryFormatterClass, scanner, printTimestamps), paginate); + } + + public static String repeat(String s, int c) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < c; i++) + sb.append(s); + return sb.toString(); + } + + public void checkTableState() { + if (getTableName().isEmpty()) + throw new IllegalStateException( + "Not in a table context. Please use 'table <tableName>' to switch to a table, or use '-t' to specify a table if option is available."); + } + + private final void printConstraintViolationException(ConstraintViolationException cve) { + printException(cve, ""); + int COL1 = 50, COL2 = 14; + int col3 = Math.max(1, Math.min(Integer.MAX_VALUE, reader.getTerminal().getWidth() - COL1 - COL2 - 6)); + logError(String.format("%" + COL1 + "s-+-%" + COL2 + "s-+-%" + col3 + "s%n", repeat("-", COL1), repeat("-", COL2), repeat("-", col3))); + logError(String.format("%-" + COL1 + "s | %" + COL2 + "s | %-" + col3 + "s%n", "Constraint class", "Violation code", "Violation Description")); + logError(String.format("%" + COL1 + "s-+-%" + COL2 + "s-+-%" + col3 + "s%n", repeat("-", COL1), repeat("-", COL2), repeat("-", col3))); + for (TConstraintViolationSummary cvs : cve.violationSummaries) + logError(String.format("%-" + COL1 + "s | %" + COL2 + "d | %-" + col3 + "s%n", cvs.constrainClass, cvs.violationCode, cvs.violationDescription)); + logError(String.format("%" + COL1 + "s-+-%" + COL2 + "s-+-%" + col3 + "s%n", repeat("-", COL1), repeat("-", COL2), repeat("-", col3))); + } + + public final void printException(Exception e) { + printException(e, e.getMessage()); + } + + private final void printException(Exception e, String msg) { + logError(e.getClass().getName() + (msg != null ? ": " + msg : "")); + log.debug(e.getClass().getName() + (msg != null ? ": " + msg : ""), e); + } + + public static final void setDebugging(boolean debuggingEnabled) { + Logger.getLogger(Constants.CORE_PACKAGE_NAME).setLevel(debuggingEnabled ? Level.TRACE : Level.INFO); + Logger.getLogger(Shell.class.getPackage().getName()).setLevel(debuggingEnabled ? Level.TRACE : Level.INFO); + } + + public static final boolean isDebuggingEnabled() { + return Logger.getLogger(Constants.CORE_PACKAGE_NAME).isTraceEnabled(); + } + + private final void printHelp(String usage, String description, Options opts) { + printHelp(usage, description, opts, Integer.MAX_VALUE); + } + + private final void printHelp(String usage, String description, Options opts, int width) { + // TODO Use the OutputStream from the JLine ConsoleReader if we can ever get access to it + new HelpFormatter().printHelp(writer, width, usage, description, opts, 2, 5, null, true); + writer.flush(); + } + + public int getExitCode() { + return exitCode; + } + + public void resetExitCode() { + exitCode = 0; + } + + public void setExit(boolean exit) { + this.exit = exit; + } + + public boolean getExit() { + return this.exit; + } + + public boolean isVerbose() { + return verbose; + } + + public void setTableName(String tableName) { + this.tableName = (tableName == null || tableName.isEmpty()) ? "" : Tables.qualified(tableName); + } + + public String getTableName() { + return tableName; + } + + public ConsoleReader getReader() { + return reader; + } + + public void updateUser(String principal, AuthenticationToken token) throws AccumuloException, AccumuloSecurityException { + connector = instance.getConnector(principal, token); + this.token = token; + } + + public String getPrincipal() { + return connector.whoami(); + } + + public AuthenticationToken getToken() { + return token; + } + + /** + * Return the formatter for the current table. + * + * @return the formatter class for the current table + */ + public Class<? extends Formatter> getFormatter() { + return getFormatter(this.tableName); + } + + /** + * Return the formatter for the given table. + * + * @param tableName + * the table name + * @return the formatter class for the given table + */ + public Class<? extends Formatter> getFormatter(String tableName) { + Class<? extends Formatter> formatter = FormatterCommand.getCurrentFormatter(tableName, this); + + if (null == formatter) { + logError("Could not load the specified formatter. Using the DefaultFormatter"); + return this.defaultFormatterClass; + } else { + return formatter; + } + } + + public void setLogErrorsToConsole() { + this.logErrorsToConsole = true; + } + + private void logError(String s) { + log.error(s); + if (logErrorsToConsole) { + try { + reader.println("ERROR: " + s); + reader.flush(); + } catch (IOException e) {} + } + } + + public String readMaskedLine(String prompt, Character mask) throws IOException { + this.masking = true; + String s = reader.readLine(prompt, mask); + this.masking = false; + return s; + } + + public boolean isMasking() { + return masking; + } + + public boolean hasExited() { + return exit; + } + + public boolean isTabCompletion() { + return tabCompletion; + } + +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/6df97b89/shell/src/main/java/org/apache/accumulo/shell/commands/CompactCommand.java ---------------------------------------------------------------------- diff --cc shell/src/main/java/org/apache/accumulo/shell/commands/CompactCommand.java index 33a8d45,0000000..f183b25 mode 100644,000000..100644 --- a/shell/src/main/java/org/apache/accumulo/shell/commands/CompactCommand.java +++ b/shell/src/main/java/org/apache/accumulo/shell/commands/CompactCommand.java @@@ -1,219 -1,0 +1,223 @@@ +/* + * 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.shell.commands; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.client.admin.CompactionConfig; +import org.apache.accumulo.core.client.admin.CompactionStrategyConfig; +import org.apache.accumulo.core.compaction.CompactionSettings; +import org.apache.accumulo.shell.Shell; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; + +public class CompactCommand extends TableOperation { + private Option noFlushOption, waitOpt, profileOpt, cancelOpt, strategyOpt, strategyConfigOpt; + + // file selection and file output options + private Option enameOption, epathOption, sizeLtOption, sizeGtOption, minFilesOption, outBlockSizeOpt, outHdfsBlockSizeOpt, outIndexBlockSizeOpt, + outCompressionOpt, outReplication; + + private CompactionConfig compactionConfig = null; + + private boolean cancel = false; + + @Override + public String description() { + return "Initiates a major compaction on tablets within the specified range that have one or more files. If no file selection options are specified, then " + + "all files will be compacted. Options that configure output settings are only applied to this compaction and not later compactions. If multiple " + + "concurrent user initiated compactions specify iterators or a compaction strategy, then all but one will fail to start."; + } + + @Override + protected void doTableOp(final Shell shellState, final String tableName) throws AccumuloException, AccumuloSecurityException { + // compact the tables + + if (cancel) { + try { + shellState.getConnector().tableOperations().cancelCompaction(tableName); + Shell.log.info("Compaction canceled for table " + tableName); + } catch (TableNotFoundException e) { + throw new AccumuloException(e); + } + } else { + try { + if (compactionConfig.getWait()) { + Shell.log.info("Compacting table ..."); + } + ++ for (IteratorSetting iteratorSetting : compactionConfig.getIterators()) { ++ ScanCommand.ensureTserversCanLoadIterator(shellState, tableName, iteratorSetting.getIteratorClass()); ++ } ++ + shellState.getConnector().tableOperations().compact(tableName, compactionConfig); + + Shell.log.info("Compaction of table " + tableName + " " + (compactionConfig.getWait() ? "completed" : "started") + " for given range"); + } catch (Exception ex) { + throw new AccumuloException(ex); + } + } + } + + private void put(CommandLine cl, Map<String,String> opts, Option opt, CompactionSettings setting) { + if (cl.hasOption(opt.getLongOpt())) + setting.put(opts, cl.getOptionValue(opt.getLongOpt())); + } + + private Map<String,String> getConfigurableCompactionStrategyOpts(CommandLine cl) { + Map<String,String> opts = new HashMap<>(); + + put(cl, opts, enameOption, CompactionSettings.SF_NAME_RE_OPT); + put(cl, opts, epathOption, CompactionSettings.SF_PATH_RE_OPT); + put(cl, opts, sizeLtOption, CompactionSettings.SF_LT_ESIZE_OPT); + put(cl, opts, sizeGtOption, CompactionSettings.SF_GT_ESIZE_OPT); + put(cl, opts, minFilesOption, CompactionSettings.MIN_FILES_OPT); + put(cl, opts, outCompressionOpt, CompactionSettings.OUTPUT_COMPRESSION_OPT); + put(cl, opts, outBlockSizeOpt, CompactionSettings.OUTPUT_BLOCK_SIZE_OPT); + put(cl, opts, outHdfsBlockSizeOpt, CompactionSettings.OUTPUT_HDFS_BLOCK_SIZE_OPT); + put(cl, opts, outIndexBlockSizeOpt, CompactionSettings.OUTPUT_INDEX_BLOCK_SIZE_OPT); + put(cl, opts, outReplication, CompactionSettings.OUTPUT_REPLICATION_OPT); + + return opts; + } + + @Override + public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws Exception { + + if (cl.hasOption(cancelOpt.getLongOpt())) { + cancel = true; + + if (cl.getOptions().length > 2) { + throw new IllegalArgumentException("Can not specify other options with cancel"); + } + } else { + cancel = false; + } + + compactionConfig = new CompactionConfig(); + + compactionConfig.setFlush(!cl.hasOption(noFlushOption.getOpt())); + compactionConfig.setWait(cl.hasOption(waitOpt.getOpt())); + compactionConfig.setStartRow(OptUtil.getStartRow(cl)); + compactionConfig.setEndRow(OptUtil.getEndRow(cl)); + + if (cl.hasOption(profileOpt.getOpt())) { + List<IteratorSetting> iterators = shellState.iteratorProfiles.get(cl.getOptionValue(profileOpt.getOpt())); + if (iterators == null) { + Shell.log.error("Profile " + cl.getOptionValue(profileOpt.getOpt()) + " does not exist"); + return -1; + } + + compactionConfig.setIterators(new ArrayList<>(iterators)); + } + + Map<String,String> configurableCompactOpt = getConfigurableCompactionStrategyOpts(cl); + + if (cl.hasOption(strategyOpt.getOpt())) { + if (configurableCompactOpt.size() > 0) + throw new IllegalArgumentException("Can not specify compaction strategy with file selection and file output options."); + + CompactionStrategyConfig csc = new CompactionStrategyConfig(cl.getOptionValue(strategyOpt.getOpt())); + if (cl.hasOption(strategyConfigOpt.getOpt())) { + Map<String,String> props = new HashMap<>(); + String[] keyVals = cl.getOptionValue(strategyConfigOpt.getOpt()).split(","); + for (String keyVal : keyVals) { + String[] sa = keyVal.split("="); + props.put(sa[0], sa[1]); + } + + csc.setOptions(props); + } + + compactionConfig.setCompactionStrategy(csc); + } + + if (configurableCompactOpt.size() > 0) { + CompactionStrategyConfig csc = new CompactionStrategyConfig("org.apache.accumulo.tserver.compaction.strategies.ConfigurableCompactionStrategy"); + csc.setOptions(configurableCompactOpt); + compactionConfig.setCompactionStrategy(csc); + } + + return super.execute(fullCommand, cl, shellState); + } + + private Option newLAO(String lopt, String desc) { + return new Option(null, lopt, true, desc); + } + + @Override + public Options getOptions() { + final Options opts = super.getOptions(); + + opts.addOption(OptUtil.startRowOpt()); + opts.addOption(OptUtil.endRowOpt()); + noFlushOption = new Option("nf", "noFlush", false, "do not flush table data in memory before compacting."); + opts.addOption(noFlushOption); + waitOpt = new Option("w", "wait", false, "wait for compact to finish"); + opts.addOption(waitOpt); + + profileOpt = new Option("pn", "profile", true, "Iterator profile name."); + profileOpt.setArgName("profile"); + opts.addOption(profileOpt); + + strategyOpt = new Option("s", "strategy", true, "compaction strategy class name"); + opts.addOption(strategyOpt); + strategyConfigOpt = new Option("sc", "strategyConfig", true, "Key value options for compaction strategy. Expects <prop>=<value>{,<prop>=<value>}"); + opts.addOption(strategyConfigOpt); + + cancelOpt = new Option(null, "cancel", false, "cancel user initiated compactions"); + opts.addOption(cancelOpt); + + enameOption = newLAO("sf-ename", "Select files using regular expression to match file names. Only matches against last part of path."); + opts.addOption(enameOption); + epathOption = newLAO("sf-epath", "Select files using regular expression to match file paths to compact. Matches against full path."); + opts.addOption(epathOption); + sizeLtOption = newLAO("sf-lt-esize", + "Selects files less than specified size. Uses the estimated size of file in metadata table. Can use K,M, and G suffixes"); + opts.addOption(sizeLtOption); + sizeGtOption = newLAO("sf-gt-esize", + "Selects files greater than specified size. Uses the estimated size of file in metadata table. Can use K,M, and G suffixes"); + opts.addOption(sizeGtOption); + minFilesOption = newLAO("min-files", + "Only compacts if at least the specified number of files are selected. When no file selection criteria are given, all files are selected."); + opts.addOption(minFilesOption); + outBlockSizeOpt = newLAO("out-data-bs", + "Rfile data block size to use for compaction output file. Can use K,M, and G suffixes. Uses table settings if not specified."); + opts.addOption(outBlockSizeOpt); + outHdfsBlockSizeOpt = newLAO("out-hdfs-bs", + "HDFS block size to use for compaction output file. Can use K,M, and G suffixes. Uses table settings if not specified."); + opts.addOption(outHdfsBlockSizeOpt); + outIndexBlockSizeOpt = newLAO("out-index-bs", + "Rfile index block size to use for compaction output file. Can use K,M, and G suffixes. Uses table settings if not specified."); + opts.addOption(outIndexBlockSizeOpt); + outCompressionOpt = newLAO("out-compress", + "Compression to use for compaction output file. Either snappy, gz, lzo, or none. Uses table settings if not specified."); + opts.addOption(outCompressionOpt); + outReplication = newLAO("out-replication", "HDFS replication to use for compaction output file. Uses table settings if not specified."); + opts.addOption(outReplication); + + return opts; + } +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/6df97b89/shell/src/main/java/org/apache/accumulo/shell/commands/ScanCommand.java ---------------------------------------------------------------------- diff --cc shell/src/main/java/org/apache/accumulo/shell/commands/ScanCommand.java index 334ca56,0000000..3531fe9 mode 100644,000000..100644 --- a/shell/src/main/java/org/apache/accumulo/shell/commands/ScanCommand.java +++ b/shell/src/main/java/org/apache/accumulo/shell/commands/ScanCommand.java @@@ -1,324 -1,0 +1,340 @@@ +/* + * 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.shell.commands; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +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.ScannerBase; ++import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.conf.AccumuloConfiguration; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.data.Value; ++import org.apache.accumulo.core.iterators.SortedKeyValueIterator; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.core.util.format.BinaryFormatter; +import org.apache.accumulo.core.util.format.Formatter; +import org.apache.accumulo.core.util.interpret.DefaultScanInterpreter; +import org.apache.accumulo.core.util.interpret.ScanInterpreter; +import org.apache.accumulo.shell.Shell; +import org.apache.accumulo.shell.Shell.Command; +import org.apache.accumulo.shell.Shell.PrintFile; ++import org.apache.accumulo.shell.ShellCommandException; ++import org.apache.accumulo.shell.ShellCommandException.ErrorCode; +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.Options; +import org.apache.hadoop.io.Text; + +public class ScanCommand extends Command { + + private Option scanOptAuths, scanOptRow, scanOptColumns, disablePaginationOpt, showFewOpt, formatterOpt, interpreterOpt, formatterInterpeterOpt, + outputFileOpt; + + protected Option timestampOpt; + private Option optStartRowExclusive; + private Option optEndRowExclusive; + private Option timeoutOption; + private Option profileOpt; + + @Override + public int execute(final String fullCommand, final CommandLine cl, final Shell shellState) throws Exception { + final PrintFile printFile = getOutputFile(cl); + final String tableName = OptUtil.getTableOpt(cl, shellState); + + final Class<? extends Formatter> formatter = getFormatter(cl, tableName, shellState); + final ScanInterpreter interpeter = getInterpreter(cl, tableName, shellState); + + // handle first argument, if present, the authorizations list to + // scan with + final Authorizations auths = getAuths(cl, shellState); + final Scanner scanner = shellState.getConnector().createScanner(tableName, auths); + + // handle session-specific scan iterators + addScanIterators(shellState, cl, scanner, tableName); + + // handle remaining optional arguments + scanner.setRange(getRange(cl, interpeter)); + + // handle columns + fetchColumns(cl, scanner, interpeter); + + // set timeout + scanner.setTimeout(getTimeout(cl), TimeUnit.MILLISECONDS); + + // output the records + if (cl.hasOption(showFewOpt.getOpt())) { + final String showLength = cl.getOptionValue(showFewOpt.getOpt()); + try { + final int length = Integer.parseInt(showLength); + if (length < 1) { + throw new IllegalArgumentException(); + } + BinaryFormatter.getlength(length); + printBinaryRecords(cl, shellState, scanner, printFile); + } catch (NumberFormatException nfe) { + shellState.getReader().println("Arg must be an integer."); + } catch (IllegalArgumentException iae) { + shellState.getReader().println("Arg must be greater than one."); + } + + } else { + printRecords(cl, shellState, scanner, formatter, printFile); + } + if (printFile != null) { + printFile.close(); + } + + return 0; + } + + protected long getTimeout(final CommandLine cl) { + if (cl.hasOption(timeoutOption.getLongOpt())) { + return AccumuloConfiguration.getTimeInMillis(cl.getOptionValue(timeoutOption.getLongOpt())); + } + + return Long.MAX_VALUE; + } + - protected void addScanIterators(final Shell shellState, CommandLine cl, final Scanner scanner, final String tableName) { ++ static void ensureTserversCanLoadIterator(final Shell shellState, String tableName, String classname) throws AccumuloException, AccumuloSecurityException, ++ TableNotFoundException, ShellCommandException { ++ 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()); ++ } ++ } ++ ++ protected void addScanIterators(final Shell shellState, CommandLine cl, final Scanner scanner, final String tableName) throws Exception { + + List<IteratorSetting> tableScanIterators; + if (cl.hasOption(profileOpt.getOpt())) { + String profile = cl.getOptionValue(profileOpt.getOpt()); + tableScanIterators = shellState.iteratorProfiles.get(profile); + + if (tableScanIterators == null) { + throw new IllegalArgumentException("Profile " + profile + " does not exist"); + } ++ ++ for (IteratorSetting iteratorSetting : tableScanIterators) { ++ ensureTserversCanLoadIterator(shellState, tableName, iteratorSetting.getIteratorClass()); ++ } + } else { + tableScanIterators = shellState.scanIteratorOptions.get(tableName); + if (tableScanIterators == null) { + Shell.log.debug("Found no scan iterators to set"); + return; + } + } + + Shell.log.debug("Found " + tableScanIterators.size() + " scan iterators to set"); + + for (IteratorSetting setting : tableScanIterators) { + Shell.log.debug("Setting scan iterator " + setting.getName() + " at priority " + setting.getPriority() + " using class name " + + setting.getIteratorClass()); + for (Entry<String,String> option : setting.getOptions().entrySet()) { + Shell.log.debug("Setting option for " + setting.getName() + ": " + option.getKey() + "=" + option.getValue()); + } + scanner.addScanIterator(setting); + } + } + + protected void printRecords(final CommandLine cl, final Shell shellState, final Iterable<Entry<Key,Value>> scanner, + final Class<? extends Formatter> formatter, PrintFile outFile) throws IOException { + if (outFile == null) { + shellState.printRecords(scanner, cl.hasOption(timestampOpt.getOpt()), !cl.hasOption(disablePaginationOpt.getOpt()), formatter); + } else { + shellState.printRecords(scanner, cl.hasOption(timestampOpt.getOpt()), !cl.hasOption(disablePaginationOpt.getOpt()), formatter, outFile); + } + } + + protected void printBinaryRecords(final CommandLine cl, final Shell shellState, final Iterable<Entry<Key,Value>> scanner, PrintFile outFile) + throws IOException { + if (outFile == null) { + shellState.printBinaryRecords(scanner, cl.hasOption(timestampOpt.getOpt()), !cl.hasOption(disablePaginationOpt.getOpt())); + } else { + shellState.printBinaryRecords(scanner, cl.hasOption(timestampOpt.getOpt()), !cl.hasOption(disablePaginationOpt.getOpt()), outFile); + } + } + + protected ScanInterpreter getInterpreter(final CommandLine cl, final String tableName, final Shell shellState) throws Exception { + + Class<? extends ScanInterpreter> clazz = null; + try { + if (cl.hasOption(interpreterOpt.getOpt())) { + clazz = AccumuloVFSClassLoader.loadClass(cl.getOptionValue(interpreterOpt.getOpt()), ScanInterpreter.class); + } else if (cl.hasOption(formatterInterpeterOpt.getOpt())) { + clazz = AccumuloVFSClassLoader.loadClass(cl.getOptionValue(formatterInterpeterOpt.getOpt()), ScanInterpreter.class); + } + } catch (ClassNotFoundException e) { + shellState.getReader().println("Interpreter class could not be loaded.\n" + e.getMessage()); + } + + if (clazz == null) + clazz = InterpreterCommand.getCurrentInterpreter(tableName, shellState); + + if (clazz == null) + clazz = DefaultScanInterpreter.class; + + return clazz.newInstance(); + } + + protected Class<? extends Formatter> getFormatter(final CommandLine cl, final String tableName, final Shell shellState) throws IOException { + + try { + if (cl.hasOption(formatterOpt.getOpt())) { + return shellState.getClassLoader(cl, shellState).loadClass(cl.getOptionValue(formatterOpt.getOpt())).asSubclass(Formatter.class); + } else if (cl.hasOption(formatterInterpeterOpt.getOpt())) { + return shellState.getClassLoader(cl, shellState).loadClass(cl.getOptionValue(formatterInterpeterOpt.getOpt())).asSubclass(Formatter.class); + } + } catch (Exception e) { + shellState.getReader().println("Formatter class could not be loaded.\n" + e.getMessage()); + } + + return shellState.getFormatter(tableName); + } + + protected void fetchColumns(final CommandLine cl, final ScannerBase scanner, final ScanInterpreter formatter) throws UnsupportedEncodingException { + if (cl.hasOption(scanOptColumns.getOpt())) { + for (String a : cl.getOptionValue(scanOptColumns.getOpt()).split(",")) { + final String sa[] = a.split(":", 2); + if (sa.length == 1) { + scanner.fetchColumnFamily(formatter.interpretColumnFamily(new Text(a.getBytes(Shell.CHARSET)))); + } else { + scanner.fetchColumn(formatter.interpretColumnFamily(new Text(sa[0].getBytes(Shell.CHARSET))), + formatter.interpretColumnQualifier(new Text(sa[1].getBytes(Shell.CHARSET)))); + } + } + } + } + + protected Range getRange(final CommandLine cl, final ScanInterpreter formatter) throws UnsupportedEncodingException { + if ((cl.hasOption(OptUtil.START_ROW_OPT) || cl.hasOption(OptUtil.END_ROW_OPT)) && cl.hasOption(scanOptRow.getOpt())) { + // did not see a way to make commons cli do this check... it has mutually exclusive options but does not support the or + throw new IllegalArgumentException("Options -" + scanOptRow.getOpt() + " AND (-" + OptUtil.START_ROW_OPT + " OR -" + OptUtil.END_ROW_OPT + + ") are mutally exclusive "); + } + + if (cl.hasOption(scanOptRow.getOpt())) { + return new Range(formatter.interpretRow(new Text(cl.getOptionValue(scanOptRow.getOpt()).getBytes(Shell.CHARSET)))); + } else { + Text startRow = OptUtil.getStartRow(cl); + if (startRow != null) + startRow = formatter.interpretBeginRow(startRow); + Text endRow = OptUtil.getEndRow(cl); + if (endRow != null) + endRow = formatter.interpretEndRow(endRow); + final boolean startInclusive = !cl.hasOption(optStartRowExclusive.getOpt()); + final boolean endInclusive = !cl.hasOption(optEndRowExclusive.getOpt()); + return new Range(startRow, startInclusive, endRow, endInclusive); + } + } + + protected Authorizations getAuths(final CommandLine cl, final Shell shellState) throws AccumuloSecurityException, AccumuloException { + final String user = shellState.getConnector().whoami(); + Authorizations auths = shellState.getConnector().securityOperations().getUserAuthorizations(user); + if (cl.hasOption(scanOptAuths.getOpt())) { + auths = ScanCommand.parseAuthorizations(cl.getOptionValue(scanOptAuths.getOpt())); + } + return auths; + } + + static Authorizations parseAuthorizations(final String field) { + if (field == null || field.isEmpty()) { + return Authorizations.EMPTY; + } + return new Authorizations(field.split(",")); + } + + @Override + public String description() { + return "scans the table, and displays the resulting records"; + } + + @Override + public Options getOptions() { + final Options o = new Options(); + + scanOptAuths = new Option("s", "scan-authorizations", true, "scan authorizations (all user auths are used if this argument is not specified)"); + optStartRowExclusive = new Option("be", "begin-exclusive", false, "make start row exclusive (by default it's inclusive)"); + optStartRowExclusive.setArgName("begin-exclusive"); + optEndRowExclusive = new Option("ee", "end-exclusive", false, "make end row exclusive (by default it's inclusive)"); + optEndRowExclusive.setArgName("end-exclusive"); + scanOptRow = new Option("r", "row", true, "row to scan"); + scanOptColumns = new Option("c", "columns", true, "comma-separated columns"); + timestampOpt = new Option("st", "show-timestamps", false, "display timestamps"); + disablePaginationOpt = new Option("np", "no-pagination", false, "disable pagination of output"); + showFewOpt = new Option("f", "show-few", true, "show only a specified number of characters"); + formatterOpt = new Option("fm", "formatter", true, "fully qualified name of the formatter class to use"); + interpreterOpt = new Option("i", "interpreter", true, "fully qualified name of the interpreter class to use"); + formatterInterpeterOpt = new Option("fi", "fmt-interpreter", true, "fully qualified name of a class that is a formatter and interpreter"); + timeoutOption = new Option(null, "timeout", true, + "time before scan should fail if no data is returned. If no unit is given assumes seconds. Units d,h,m,s,and ms are supported. e.g. 30s or 100ms"); + outputFileOpt = new Option("o", "output", true, "local file to write the scan output to"); + + scanOptAuths.setArgName("comma-separated-authorizations"); + scanOptRow.setArgName("row"); + scanOptColumns.setArgName("<columnfamily>[:<columnqualifier>]{,<columnfamily>[:<columnqualifier>]}"); + showFewOpt.setRequired(false); + showFewOpt.setArgName("int"); + formatterOpt.setArgName("className"); + timeoutOption.setArgName("timeout"); + outputFileOpt.setArgName("file"); + + profileOpt = new Option("pn", "profile", true, "iterator profile name"); + profileOpt.setArgName("profile"); + + o.addOption(scanOptAuths); + o.addOption(scanOptRow); + o.addOption(OptUtil.startRowOpt()); + o.addOption(OptUtil.endRowOpt()); + o.addOption(optStartRowExclusive); + o.addOption(optEndRowExclusive); + o.addOption(scanOptColumns); + o.addOption(timestampOpt); + o.addOption(disablePaginationOpt); + o.addOption(OptUtil.tableOpt("table to be scanned")); + o.addOption(showFewOpt); + o.addOption(formatterOpt); + o.addOption(interpreterOpt); + o.addOption(formatterInterpeterOpt); + o.addOption(timeoutOption); + o.addOption(outputFileOpt); + o.addOption(profileOpt); + + return o; + } + + @Override + public int numArgs() { + return 0; + } + + protected PrintFile getOutputFile(final CommandLine cl) throws FileNotFoundException { + final String outputFile = cl.getOptionValue(outputFileOpt.getOpt()); + return (outputFile == null ? null : new PrintFile(outputFile)); + } +}