001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with this
004 * work for additional information regarding copyright ownership. The ASF
005 * licenses this file to you under the Apache License, Version 2.0 (the
006 * "License"); you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 * License for the specific language governing permissions and limitations
015 * under the License.
016 */
017package org.apache.hadoop.hbase.util;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Comparator;
022import java.util.HashMap;
023import java.util.List;
024
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.hbase.HBaseConfiguration;
027import org.apache.hadoop.util.Tool;
028import org.apache.hadoop.util.ToolRunner;
029import org.apache.yetus.audience.InterfaceAudience;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.apache.hbase.thirdparty.org.apache.commons.cli.BasicParser;
034import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
035import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLineParser;
036import org.apache.hbase.thirdparty.org.apache.commons.cli.DefaultParser;
037import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter;
038import org.apache.hbase.thirdparty.org.apache.commons.cli.MissingOptionException;
039import org.apache.hbase.thirdparty.org.apache.commons.cli.Option;
040import org.apache.hbase.thirdparty.org.apache.commons.cli.Options;
041import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException;
042
043/**
044 * Common base class used for HBase command-line tools. Simplifies workflow and
045 * command-line argument parsing.
046 */
047@InterfaceAudience.Private
048public abstract class AbstractHBaseTool implements Tool {
049  public static final int EXIT_SUCCESS = 0;
050  public static final int EXIT_FAILURE = 1;
051
052  public static final String SHORT_HELP_OPTION = "h";
053  public static final String LONG_HELP_OPTION = "help";
054
055  private static final Option HELP_OPTION = new Option("h", "help", false,
056      "Prints help for this tool.");
057
058  private static final Logger LOG = LoggerFactory.getLogger(AbstractHBaseTool.class);
059
060  protected final Options options = new Options();
061
062  protected Configuration conf = null;
063
064  protected String[] cmdLineArgs = null;
065
066  // To print options in order they were added in help text.
067  private HashMap<Option, Integer> optionsOrder = new HashMap<>();
068  private int optionsCount = 0;
069
070  public class OptionsOrderComparator implements Comparator<Option> {
071    @Override
072    public int compare(Option o1, Option o2) {
073      return optionsOrder.get(o1) - optionsOrder.get(o2);
074    }
075  }
076
077  /**
078   * Override this to add command-line options using {@link #addOptWithArg}
079   * and similar methods.
080   */
081  protected abstract void addOptions();
082
083  /**
084   * This method is called to process the options after they have been parsed.
085   */
086  protected abstract void processOptions(CommandLine cmd);
087
088  /** The "main function" of the tool */
089  protected abstract int doWork() throws Exception;
090
091  /**
092   * For backward compatibility. DO NOT use it for new tools.
093   * We have options in existing tools which can't be ported to Apache CLI's {@link Option}.
094   * (because they don't pass validation, for e.g. "-copy-to". "-" means short name
095   * which doesn't allow '-' in name). This function is to allow tools to have, for time being,
096   * parameters which can't be parsed using {@link Option}.
097   * Overrides should consume all valid legacy arguments. If the param 'args' is not empty on
098   * return, it means there were invalid options, in which case we'll exit from the tool.
099   * Note that it's called before {@link #processOptions(CommandLine)}, which means new options'
100   * values will override old ones'.
101   */
102  protected void processOldArgs(List<String> args) {
103  }
104
105  @Override
106  public Configuration getConf() {
107    return conf;
108  }
109
110  @Override
111  public void setConf(Configuration conf) {
112    this.conf = conf;
113  }
114
115  @Override
116  public int run(String[] args) throws IOException {
117    cmdLineArgs = args;
118    if (conf == null) {
119      LOG.error("Tool configuration is not initialized");
120      throw new NullPointerException("conf");
121    }
122
123    CommandLine cmd;
124    List<String> argsList = new ArrayList<>(args.length);
125    for (String arg : args) {
126      argsList.add(arg);
127    }
128    // For backward compatibility of args which can't be parsed as Option. See javadoc for
129    // processOldArgs(..)
130    processOldArgs(argsList);
131    try {
132      addOptions();
133      if (isHelpCommand(args)) {
134        printUsage();
135        return EXIT_SUCCESS;
136      }
137      String[] remainingArgs = new String[argsList.size()];
138      argsList.toArray(remainingArgs);
139      cmd = newParser().parse(options, remainingArgs);
140    } catch (MissingOptionException e) {
141      LOG.error(e.getMessage());
142      LOG.error("Use -h or --help for usage instructions.");
143      return EXIT_FAILURE;
144    } catch (ParseException e) {
145      LOG.error("Error when parsing command-line arguments", e);
146      LOG.error("Use -h or --help for usage instructions.");
147      return EXIT_FAILURE;
148    }
149
150    processOptions(cmd);
151
152    int ret;
153    try {
154      ret = doWork();
155    } catch (Exception e) {
156      LOG.error("Error running command-line tool", e);
157      return EXIT_FAILURE;
158    }
159    return ret;
160  }
161
162  /**
163   * Create the parser to use for parsing and validating the command line. Since commons-cli lacks
164   * the capability to validate arbitrary combination of options, it may be helpful to bake custom
165   * logic into a specialized parser implementation. See LoadTestTool for examples.
166   * @return a new parser specific to the current tool
167   */
168  protected CommandLineParser newParser() {
169    return new DefaultParser();
170  }
171
172  private boolean isHelpCommand(String[] args) throws ParseException {
173    Options helpOption = new Options().addOption(HELP_OPTION);
174    // this parses the command line but doesn't throw an exception on unknown options
175    CommandLine cl = new DefaultParser().parse(helpOption, args, true);
176    return cl.getOptions().length != 0;
177  }
178
179  protected CommandLine parseArgs(String[] args) throws ParseException {
180    options.addOption(SHORT_HELP_OPTION, LONG_HELP_OPTION, false, "Show usage");
181    addOptions();
182    CommandLineParser parser = new BasicParser();
183    return parser.parse(options, args);
184  }
185
186  protected void printUsage() {
187    printUsage("hbase " + getClass().getName() + " <options>", "Options:", "");
188  }
189
190  protected void printUsage(final String usageStr, final String usageHeader,
191      final String usageFooter) {
192    HelpFormatter helpFormatter = new HelpFormatter();
193    helpFormatter.setWidth(120);
194    helpFormatter.setOptionComparator(new OptionsOrderComparator());
195    helpFormatter.printHelp(usageStr, usageHeader, options, usageFooter);
196  }
197
198  protected void addOption(Option option) {
199    options.addOption(option);
200    optionsOrder.put(option, optionsCount++);
201  }
202
203  protected void addRequiredOption(Option option) {
204    option.setRequired(true);
205    addOption(option);
206  }
207
208  protected void addRequiredOptWithArg(String opt, String description) {
209    Option option = new Option(opt, true, description);
210    option.setRequired(true);
211    addOption(option);
212  }
213
214  protected void addRequiredOptWithArg(String shortOpt, String longOpt, String description) {
215    Option option = new Option(shortOpt, longOpt, true, description);
216    option.setRequired(true);
217    addOption(option);
218  }
219
220  protected void addOptNoArg(String opt, String description) {
221    addOption(new Option(opt, false, description));
222  }
223
224  protected void addOptNoArg(String shortOpt, String longOpt, String description) {
225    addOption(new Option(shortOpt, longOpt, false, description));
226  }
227
228  protected void addOptWithArg(String opt, String description) {
229    addOption(new Option(opt, true, description));
230  }
231
232  protected void addOptWithArg(String shortOpt, String longOpt, String description) {
233    addOption(new Option(shortOpt, longOpt, true, description));
234  }
235
236  public int getOptionAsInt(CommandLine cmd, String opt, int defaultValue) {
237    if (cmd.hasOption(opt)) {
238      return Integer.parseInt(cmd.getOptionValue(opt));
239    } else {
240      return defaultValue;
241    }
242  }
243
244  public long getOptionAsLong(CommandLine cmd, String opt, int defaultValue) {
245    if (cmd.hasOption(opt)) {
246      return Long.parseLong(cmd.getOptionValue(opt));
247    } else {
248      return defaultValue;
249    }
250  }
251
252  public double getOptionAsDouble(CommandLine cmd, String opt, double defaultValue) {
253    if (cmd.hasOption(opt)) {
254      return Double.parseDouble(cmd.getOptionValue(opt));
255    } else {
256      return defaultValue;
257    }
258  }
259
260  /**
261   * Parse a number and enforce a range.
262   */
263  public static long parseLong(String s, long minValue, long maxValue) {
264    long l = Long.parseLong(s);
265    if (l < minValue || l > maxValue) {
266      throw new IllegalArgumentException("The value " + l
267          + " is out of range [" + minValue + ", " + maxValue + "]");
268    }
269    return l;
270  }
271
272  public static int parseInt(String s, int minValue, int maxValue) {
273    return (int) parseLong(s, minValue, maxValue);
274  }
275
276  /** Call this from the concrete tool class's main function. */
277  protected void doStaticMain(String args[]) {
278    int ret;
279    try {
280      ret = ToolRunner.run(HBaseConfiguration.create(), this, args);
281    } catch (Exception ex) {
282      LOG.error("Error running command-line tool", ex);
283      ret = EXIT_FAILURE;
284    }
285    System.exit(ret);
286  }
287
288}