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