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}