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