1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations
15   * under the License.
16   */
17  package org.apache.hadoop.hbase.util;
18  
19  import java.util.Set;
20  import java.util.TreeSet;
21  
22  import org.apache.commons.cli.BasicParser;
23  import org.apache.commons.cli.CommandLine;
24  import org.apache.commons.cli.CommandLineParser;
25  import org.apache.commons.cli.HelpFormatter;
26  import org.apache.commons.cli.Options;
27  import org.apache.commons.cli.ParseException;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.classification.InterfaceAudience;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.HBaseConfiguration;
33  import org.apache.hadoop.util.Tool;
34  import org.apache.hadoop.util.ToolRunner;
35  
36  /**
37   * Common base class used for HBase command-line tools. Simplifies workflow and
38   * command-line argument parsing.
39   */
40  @InterfaceAudience.Private
41  public abstract class AbstractHBaseTool implements Tool {
42  
43    private static final int EXIT_SUCCESS = 0;
44    private static final int EXIT_FAILURE = 1;
45  
46    private static final String SHORT_HELP_OPTION = "h";
47    private static final String LONG_HELP_OPTION = "help";
48  
49    private static final Log LOG = LogFactory.getLog(AbstractHBaseTool.class);
50  
51    private final Options options = new Options();
52  
53    protected Configuration conf = null;
54  
55    private static final Set<String> requiredOptions = new TreeSet<String>();
56  
57    /**
58     * Override this to add command-line options using {@link #addOptWithArg}
59     * and similar methods.
60     */
61    protected abstract void addOptions();
62  
63    /**
64     * This method is called to process the options after they have been parsed.
65     */
66    protected abstract void processOptions(CommandLine cmd);
67  
68    /** The "main function" of the tool */
69    protected abstract int doWork() throws Exception;
70  
71    @Override
72    public Configuration getConf() {
73      return conf;
74    }
75  
76    @Override
77    public void setConf(Configuration conf) {
78      this.conf = conf;
79    }
80  
81    @Override
82    public final int run(String[] args) throws Exception {
83      if (conf == null) {
84        LOG.error("Tool configuration is not initialized");
85        throw new NullPointerException("conf");
86      }
87  
88      CommandLine cmd;
89      try {
90        // parse the command line arguments
91        cmd = parseArgs(args);
92      } catch (ParseException e) {
93        LOG.error("Error when parsing command-line arguemnts", e);
94        printUsage();
95        return EXIT_FAILURE;
96      }
97  
98      if (cmd.hasOption(SHORT_HELP_OPTION) || cmd.hasOption(LONG_HELP_OPTION) ||
99          !sanityCheckOptions(cmd)) {
100       printUsage();
101       return EXIT_FAILURE;
102     }
103 
104     processOptions(cmd);
105 
106     int ret = EXIT_FAILURE;
107     try {
108       ret = doWork();
109     } catch (Exception e) {
110       LOG.error("Error running command-line tool", e);
111       return EXIT_FAILURE;
112     }
113     return ret;
114   }
115 
116   private boolean sanityCheckOptions(CommandLine cmd) {
117     boolean success = true;
118     for (String reqOpt : requiredOptions) {
119       if (!cmd.hasOption(reqOpt)) {
120         LOG.error("Required option -" + reqOpt + " is missing");
121         success = false;
122       }
123     }
124     return success;
125   }
126 
127   private CommandLine parseArgs(String[] args) throws ParseException {
128     options.addOption(SHORT_HELP_OPTION, LONG_HELP_OPTION, false, "Show usage");
129     addOptions();
130     CommandLineParser parser = new BasicParser();
131     return parser.parse(options, args);
132   }
133 
134   private void printUsage() {
135     HelpFormatter helpFormatter = new HelpFormatter();
136     helpFormatter.setWidth(80);
137     String usageHeader = "Options:";
138     String usageFooter = "";
139     String usageStr = "bin/hbase " + getClass().getName() + " <options>";
140 
141     helpFormatter.printHelp(usageStr, usageHeader, options,
142         usageFooter);
143   }
144 
145   protected void addRequiredOptWithArg(String opt, String description) {
146     requiredOptions.add(opt);
147     addOptWithArg(opt, description);
148   }
149 
150   protected void addOptNoArg(String opt, String description) {
151     options.addOption(opt, false, description);
152   }
153 
154   protected void addOptNoArg(String shortOpt, String longOpt, String description) {
155     options.addOption(shortOpt, longOpt, false, description);
156   }
157 
158   protected void addOptWithArg(String opt, String description) {
159     options.addOption(opt, true, description);
160   }
161 
162   protected void addOptWithArg(String shortOpt, String longOpt, String description) {
163     options.addOption(shortOpt, longOpt, true, description);
164   }
165 
166   /**
167    * Parse a number and enforce a range.
168    */
169   public static long parseLong(String s, long minValue, long maxValue) {
170     long l = Long.parseLong(s);
171     if (l < minValue || l > maxValue) {
172       throw new IllegalArgumentException("The value " + l
173           + " is out of range [" + minValue + ", " + maxValue + "]");
174     }
175     return l;
176   }
177 
178   public static int parseInt(String s, int minValue, int maxValue) {
179     return (int) parseLong(s, minValue, maxValue);
180   }
181 
182   /** Call this from the concrete tool class's main function. */
183   protected void doStaticMain(String args[]) {
184     int ret;
185     try {
186       ret = ToolRunner.run(HBaseConfiguration.create(), this, args);
187     } catch (Exception ex) {
188       LOG.error("Error running command-line tool", ex);
189       ret = EXIT_FAILURE;
190     }
191     System.exit(ret);
192   }
193 
194 }