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.Comparator; 023import java.util.HashMap; 024import java.util.List; 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 command-line 045 * 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 = 056 new Option("h", "help", false, "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} and similar methods. 079 */ 080 protected abstract void addOptions(); 081 082 /** 083 * This method is called to process the options after they have been parsed. 084 */ 085 protected abstract void processOptions(CommandLine cmd); 086 087 /** The "main function" of the tool */ 088 protected abstract int doWork() throws Exception; 089 090 /** 091 * For backward compatibility. DO NOT use it for new tools. We have options in existing tools 092 * which can't be ported to Apache CLI's {@link Option}. (because they don't pass validation, for 093 * e.g. "-copy-to". "-" means short name which doesn't allow '-' in name). This function is to 094 * allow tools to have, for time being, parameters which can't be parsed using {@link Option}. 095 * Overrides should consume all valid legacy arguments. If the param 'args' is not empty on 096 * return, it means there were invalid options, in which case we'll exit from the tool. Note that 097 * it's called before {@link #processOptions(CommandLine)}, which means new options' values will 098 * override old ones'. 099 */ 100 protected void processOldArgs(List<String> args) { 101 } 102 103 @Override 104 public Configuration getConf() { 105 return conf; 106 } 107 108 @Override 109 public void setConf(Configuration conf) { 110 this.conf = conf; 111 } 112 113 @Override 114 public int run(String[] args) throws IOException { 115 cmdLineArgs = args; 116 if (conf == null) { 117 LOG.error("Tool configuration is not initialized"); 118 throw new NullPointerException("conf"); 119 } 120 121 CommandLine cmd; 122 List<String> argsList = new ArrayList<>(args.length); 123 for (String arg : args) { 124 argsList.add(arg); 125 } 126 // For backward compatibility of args which can't be parsed as Option. See javadoc for 127 // processOldArgs(..) 128 processOldArgs(argsList); 129 try { 130 addOptions(); 131 if (isHelpCommand(args)) { 132 printUsage(); 133 return EXIT_SUCCESS; 134 } 135 String[] remainingArgs = new String[argsList.size()]; 136 argsList.toArray(remainingArgs); 137 cmd = newParser().parse(options, remainingArgs); 138 } catch (MissingOptionException e) { 139 LOG.error(e.getMessage()); 140 LOG.error("Use -h or --help for usage instructions."); 141 return EXIT_FAILURE; 142 } catch (ParseException e) { 143 LOG.error("Error when parsing command-line arguments", e); 144 LOG.error("Use -h or --help for usage instructions."); 145 return EXIT_FAILURE; 146 } 147 148 processOptions(cmd); 149 150 int ret; 151 try { 152 ret = doWork(); 153 } catch (Exception e) { 154 LOG.error("Error running command-line tool", e); 155 return EXIT_FAILURE; 156 } 157 return ret; 158 } 159 160 /** 161 * Create the parser to use for parsing and validating the command line. Since commons-cli lacks 162 * the capability to validate arbitrary combination of options, it may be helpful to bake custom 163 * logic into a specialized parser implementation. See LoadTestTool for examples. 164 * @return a new parser specific to the current tool 165 */ 166 protected CommandLineParser newParser() { 167 return new DefaultParser(); 168 } 169 170 private boolean isHelpCommand(String[] args) throws ParseException { 171 Options helpOption = new Options().addOption(HELP_OPTION); 172 // this parses the command line but doesn't throw an exception on unknown options 173 CommandLine cl = new DefaultParser().parse(helpOption, args, true); 174 return cl.getOptions().length != 0; 175 } 176 177 protected CommandLine parseArgs(String[] args) throws ParseException { 178 options.addOption(SHORT_HELP_OPTION, LONG_HELP_OPTION, false, "Show usage"); 179 addOptions(); 180 CommandLineParser parser = new BasicParser(); 181 return parser.parse(options, args); 182 } 183 184 protected void printUsage() { 185 printUsage("hbase " + getClass().getName() + " <options>", "Options:", ""); 186 } 187 188 protected void printUsage(final String usageStr, final String usageHeader, 189 final String usageFooter) { 190 HelpFormatter helpFormatter = new HelpFormatter(); 191 helpFormatter.setWidth(120); 192 helpFormatter.setOptionComparator(new OptionsOrderComparator()); 193 helpFormatter.printHelp(usageStr, usageHeader, options, usageFooter); 194 } 195 196 protected void addOption(Option option) { 197 options.addOption(option); 198 optionsOrder.put(option, optionsCount++); 199 } 200 201 protected void addRequiredOption(Option option) { 202 option.setRequired(true); 203 addOption(option); 204 } 205 206 protected void addRequiredOptWithArg(String opt, String description) { 207 Option option = new Option(opt, true, description); 208 option.setRequired(true); 209 addOption(option); 210 } 211 212 protected void addRequiredOptWithArg(String shortOpt, String longOpt, String description) { 213 Option option = new Option(shortOpt, longOpt, true, description); 214 option.setRequired(true); 215 addOption(option); 216 } 217 218 protected void addOptNoArg(String opt, String description) { 219 addOption(new Option(opt, false, description)); 220 } 221 222 protected void addOptNoArg(String shortOpt, String longOpt, String description) { 223 addOption(new Option(shortOpt, longOpt, false, description)); 224 } 225 226 protected void addOptWithArg(String opt, String description) { 227 addOption(new Option(opt, true, description)); 228 } 229 230 protected void addOptWithArg(String shortOpt, String longOpt, String description) { 231 addOption(new Option(shortOpt, longOpt, true, description)); 232 } 233 234 public int getOptionAsInt(CommandLine cmd, String opt, int defaultValue) { 235 return getOptionAsInt(cmd, opt, defaultValue, 10); 236 } 237 238 public int getOptionAsInt(CommandLine cmd, String opt, int defaultValue, int radix) { 239 if (cmd.hasOption(opt)) { 240 return Integer.parseInt(cmd.getOptionValue(opt), radix); 241 } else { 242 return defaultValue; 243 } 244 } 245 246 public long getOptionAsLong(CommandLine cmd, String opt, int defaultValue) { 247 return getOptionAsLong(cmd, opt, defaultValue, 10); 248 } 249 250 public long getOptionAsLong(CommandLine cmd, String opt, int defaultValue, int radix) { 251 if (cmd.hasOption(opt)) { 252 return Long.parseLong(cmd.getOptionValue(opt), radix); 253 } else { 254 return defaultValue; 255 } 256 } 257 258 public double getOptionAsDouble(CommandLine cmd, String opt, double defaultValue) { 259 if (cmd.hasOption(opt)) { 260 return Double.parseDouble(cmd.getOptionValue(opt)); 261 } else { 262 return defaultValue; 263 } 264 } 265 266 /** 267 * Parse a number and enforce a range. 268 */ 269 public static long parseLong(String s, long minValue, long maxValue) { 270 long l = Long.parseLong(s); 271 if (l < minValue || l > maxValue) { 272 throw new IllegalArgumentException( 273 "The value " + l + " is out of range [" + minValue + ", " + maxValue + "]"); 274 } 275 return l; 276 } 277 278 public static int parseInt(String s, int minValue, int maxValue) { 279 return (int) parseLong(s, minValue, maxValue); 280 } 281 282 /** Call this from the concrete tool class's main function. */ 283 protected void doStaticMain(String args[]) { 284 int ret; 285 try { 286 ret = ToolRunner.run(HBaseConfiguration.create(), this, args); 287 } catch (Exception ex) { 288 LOG.error("Error running command-line tool", ex); 289 ret = EXIT_FAILURE; 290 } 291 System.exit(ret); 292 } 293 294}