PageRenderTime 28ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/src/java/org/apache/hadoop/fs/shell/Command.java

https://github.com/RS1999ent/hadoop-common
Java | 409 lines | 183 code | 36 blank | 190 comment | 17 complexity | f52704050c4f0f1c02a751c922f32b73 MD5 | raw file
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.hadoop.fs.shell;
  19. import java.io.FileNotFoundException;
  20. import java.io.IOException;
  21. import java.io.PrintStream;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.LinkedList;
  25. import java.util.List;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. import org.apache.hadoop.classification.InterfaceAudience;
  29. import org.apache.hadoop.classification.InterfaceStability;
  30. import org.apache.hadoop.conf.Configuration;
  31. import org.apache.hadoop.conf.Configured;
  32. import org.apache.hadoop.fs.Path;
  33. import org.apache.hadoop.fs.shell.PathExceptions.PathNotFoundException;
  34. import org.apache.hadoop.util.StringUtils;
  35. /**
  36. * An abstract class for the execution of a file system command
  37. */
  38. @InterfaceAudience.Private
  39. @InterfaceStability.Evolving
  40. abstract public class Command extends Configured {
  41. protected String[] args;
  42. protected String name;
  43. protected int exitCode = 0;
  44. protected int numErrors = 0;
  45. protected boolean recursive = false;
  46. protected ArrayList<Exception> exceptions = new ArrayList<Exception>();
  47. private static final Log LOG = LogFactory.getLog(Command.class);
  48. /** allows stdout to be captured if necessary */
  49. public PrintStream out = System.out;
  50. /** allows stderr to be captured if necessary */
  51. public PrintStream err = System.err;
  52. /** Constructor */
  53. protected Command() {
  54. out = System.out;
  55. err = System.err;
  56. }
  57. /** Constructor */
  58. protected Command(Configuration conf) {
  59. super(conf);
  60. }
  61. /** @return the command's name excluding the leading character - */
  62. abstract public String getCommandName();
  63. /**
  64. * Name the command
  65. * @param cmdName as invoked
  66. */
  67. public void setCommandName(String cmdName) {
  68. name = cmdName;
  69. }
  70. protected void setRecursive(boolean flag) {
  71. recursive = flag;
  72. }
  73. protected boolean isRecursive() {
  74. return recursive;
  75. }
  76. /**
  77. * Execute the command on the input path
  78. *
  79. * @param path the input path
  80. * @throws IOException if any error occurs
  81. */
  82. abstract protected void run(Path path) throws IOException;
  83. /**
  84. * For each source path, execute the command
  85. *
  86. * @return 0 if it runs successfully; -1 if it fails
  87. */
  88. public int runAll() {
  89. int exitCode = 0;
  90. for (String src : args) {
  91. try {
  92. PathData[] srcs = PathData.expandAsGlob(src, getConf());
  93. for (PathData s : srcs) {
  94. run(s.path);
  95. }
  96. } catch (IOException e) {
  97. exitCode = -1;
  98. displayError(e);
  99. }
  100. }
  101. return exitCode;
  102. }
  103. /**
  104. * Invokes the command handler. The default behavior is to process options,
  105. * expand arguments, and then process each argument.
  106. * <pre>
  107. * run
  108. * \-> {@link #processOptions(LinkedList)}
  109. * \-> {@link #expandArguments(LinkedList)} -> {@link #expandArgument(String)}*
  110. * \-> {@link #processArguments(LinkedList)}
  111. * \-> {@link #processArgument(PathData)}*
  112. * \-> {@link #processPathArgument(PathData)}
  113. * \-> {@link #processPaths(PathData, PathData...)}
  114. * \-> {@link #processPath(PathData)}*
  115. * \-> {@link #processNonexistentPath(PathData)}
  116. * </pre>
  117. * Most commands will chose to implement just
  118. * {@link #processOptions(LinkedList)} and {@link #processPath(PathData)}
  119. *
  120. * @param argv the list of command line arguments
  121. * @return the exit code for the command
  122. * @throws IllegalArgumentException if called with invalid arguments
  123. */
  124. public int run(String...argv) {
  125. LinkedList<String> args = new LinkedList<String>(Arrays.asList(argv));
  126. try {
  127. if (isDeprecated()) {
  128. displayWarning(
  129. "DEPRECATED: Please use '"+ getReplacementCommand() + "' instead.");
  130. }
  131. processOptions(args);
  132. processArguments(expandArguments(args));
  133. } catch (IOException e) {
  134. displayError(e);
  135. }
  136. return (numErrors == 0) ? exitCode : exitCodeForError();
  137. }
  138. /**
  139. * The exit code to be returned if any errors occur during execution.
  140. * This method is needed to account for the inconsistency in the exit
  141. * codes returned by various commands.
  142. * @return a non-zero exit code
  143. */
  144. protected int exitCodeForError() { return 1; }
  145. /**
  146. * Must be implemented by commands to process the command line flags and
  147. * check the bounds of the remaining arguments. If an
  148. * IllegalArgumentException is thrown, the FsShell object will print the
  149. * short usage of the command.
  150. * @param args the command line arguments
  151. * @throws IOException
  152. */
  153. protected void processOptions(LinkedList<String> args) throws IOException {}
  154. /**
  155. * Expands a list of arguments into {@link PathData} objects. The default
  156. * behavior is to call {@link #expandArgument(String)} on each element
  157. * which by default globs the argument. The loop catches IOExceptions,
  158. * increments the error count, and displays the exception.
  159. * @param args strings to expand into {@link PathData} objects
  160. * @return list of all {@link PathData} objects the arguments
  161. * @throws IOException if anything goes wrong...
  162. */
  163. protected LinkedList<PathData> expandArguments(LinkedList<String> args)
  164. throws IOException {
  165. LinkedList<PathData> expandedArgs = new LinkedList<PathData>();
  166. for (String arg : args) {
  167. try {
  168. expandedArgs.addAll(expandArgument(arg));
  169. } catch (IOException e) { // other exceptions are probably nasty
  170. displayError(e);
  171. }
  172. }
  173. return expandedArgs;
  174. }
  175. /**
  176. * Expand the given argument into a list of {@link PathData} objects.
  177. * The default behavior is to expand globs. Commands may override to
  178. * perform other expansions on an argument.
  179. * @param arg string pattern to expand
  180. * @return list of {@link PathData} objects
  181. * @throws IOException if anything goes wrong...
  182. */
  183. protected List<PathData> expandArgument(String arg) throws IOException {
  184. PathData[] items = PathData.expandAsGlob(arg, getConf());
  185. if (items.length == 0) {
  186. // it's a glob that failed to match
  187. throw new PathNotFoundException(arg);
  188. }
  189. return Arrays.asList(items);
  190. }
  191. /**
  192. * Processes the command's list of expanded arguments.
  193. * {@link #processArgument(PathData)} will be invoked with each item
  194. * in the list. The loop catches IOExceptions, increments the error
  195. * count, and displays the exception.
  196. * @param args a list of {@link PathData} to process
  197. * @throws IOException if anything goes wrong...
  198. */
  199. protected void processArguments(LinkedList<PathData> args)
  200. throws IOException {
  201. for (PathData arg : args) {
  202. try {
  203. processArgument(arg);
  204. } catch (IOException e) {
  205. displayError(e);
  206. }
  207. }
  208. }
  209. /**
  210. * Processes a {@link PathData} item, calling
  211. * {@link #processPathArgument(PathData)} or
  212. * {@link #processNonexistentPath(PathData)} on each item.
  213. * @param arg {@link PathData} item to process
  214. * @throws IOException if anything goes wrong...
  215. */
  216. protected void processArgument(PathData item) throws IOException {
  217. if (item.exists) {
  218. processPathArgument(item);
  219. } else {
  220. processNonexistentPath(item);
  221. }
  222. }
  223. /**
  224. * This is the last chance to modify an argument before going into the
  225. * (possibly) recursive {@link #processPaths(PathData, PathData...)}
  226. * -> {@link #processPath(PathData)} loop. Ex. ls and du use this to
  227. * expand out directories.
  228. * @param item a {@link PathData} representing a path which exists
  229. * @throws IOException if anything goes wrong...
  230. */
  231. protected void processPathArgument(PathData item) throws IOException {
  232. // null indicates that the call is not via recursion, ie. there is
  233. // no parent directory that was expanded
  234. processPaths(null, item);
  235. }
  236. /**
  237. * Provides a hook for handling paths that don't exist. By default it
  238. * will throw an exception. Primarily overriden by commands that create
  239. * paths such as mkdir or touch.
  240. * @param item the {@link PathData} that doesn't exist
  241. * @throws FileNotFoundException if arg is a path and it doesn't exist
  242. * @throws IOException if anything else goes wrong...
  243. */
  244. protected void processNonexistentPath(PathData item) throws IOException {
  245. throw new PathNotFoundException(item.toString());
  246. }
  247. /**
  248. * Iterates over the given expanded paths and invokes
  249. * {@link #processPath(PathData)} on each element. If "recursive" is true,
  250. * will do a post-visit DFS on directories.
  251. * @param parent if called via a recurse, will be the parent dir, else null
  252. * @param items a list of {@link PathData} objects to process
  253. * @throws IOException if anything goes wrong...
  254. */
  255. protected void processPaths(PathData parent, PathData ... items)
  256. throws IOException {
  257. // TODO: this really should be iterative
  258. for (PathData item : items) {
  259. try {
  260. processPath(item);
  261. if (recursive && item.stat.isDirectory()) {
  262. recursePath(item);
  263. }
  264. } catch (IOException e) {
  265. displayError(e);
  266. }
  267. }
  268. }
  269. /**
  270. * Hook for commands to implement an operation to be applied on each
  271. * path for the command. Note implementation of this method is optional
  272. * if earlier methods in the chain handle the operation.
  273. * @param item a {@link PathData} object
  274. * @throws RuntimeException if invoked but not implemented
  275. * @throws IOException if anything else goes wrong in the user-implementation
  276. */
  277. protected void processPath(PathData item) throws IOException {
  278. throw new RuntimeException("processPath() is not implemented");
  279. }
  280. /**
  281. * Gets the directory listing for a path and invokes
  282. * {@link #processPaths(PathData, PathData...)}
  283. * @param item {@link PathData} for directory to recurse into
  284. * @throws IOException if anything goes wrong...
  285. */
  286. protected void recursePath(PathData item) throws IOException {
  287. processPaths(item, item.getDirectoryContents());
  288. }
  289. /**
  290. * Display an exception prefaced with the command name. Also increments
  291. * the error count for the command which will result in a non-zero exit
  292. * code.
  293. * @param e exception to display
  294. */
  295. public void displayError(Exception e) {
  296. // build up a list of exceptions that occurred
  297. exceptions.add(e);
  298. String errorMessage = e.getLocalizedMessage();
  299. if (errorMessage == null) {
  300. // this is an unexpected condition, so dump the whole exception since
  301. // it's probably a nasty internal error where the backtrace would be
  302. // useful
  303. errorMessage = StringUtils.stringifyException(e);
  304. LOG.debug(errorMessage);
  305. } else {
  306. errorMessage = errorMessage.split("\n", 2)[0];
  307. }
  308. displayError(errorMessage);
  309. }
  310. /**
  311. * Display an error string prefaced with the command name. Also increments
  312. * the error count for the command which will result in a non-zero exit
  313. * code.
  314. * @param message error message to display
  315. */
  316. public void displayError(String message) {
  317. numErrors++;
  318. displayWarning(message);
  319. }
  320. /**
  321. * Display an warning string prefaced with the command name.
  322. * @param message warning message to display
  323. */
  324. public void displayWarning(String message) {
  325. err.println(getCommandName() + ": " + message);
  326. }
  327. /**
  328. * The short usage suitable for the synopsis
  329. * @return "name options"
  330. */
  331. public String getUsage() {
  332. String cmd = "-" + getCommandName();
  333. String usage = isDeprecated() ? "" : getCommandField("USAGE");
  334. return usage.isEmpty() ? cmd : cmd + " " + usage;
  335. }
  336. /**
  337. * The long usage suitable for help output
  338. * @return text of the usage
  339. */
  340. public String getDescription() {
  341. return isDeprecated()
  342. ? "(DEPRECATED) Same as '" + getReplacementCommand() + "'"
  343. : getCommandField("DESCRIPTION");
  344. }
  345. /**
  346. * Is the command deprecated?
  347. * @return boolean
  348. */
  349. public final boolean isDeprecated() {
  350. return (getReplacementCommand() != null);
  351. }
  352. /**
  353. * The replacement for a deprecated command
  354. * @return null if not deprecated, else alternative command
  355. */
  356. public String getReplacementCommand() {
  357. return null;
  358. }
  359. /**
  360. * Get a public static class field
  361. * @param field the field to retrieve
  362. * @return String of the field
  363. */
  364. private String getCommandField(String field) {
  365. String value;
  366. try {
  367. value = (String)this.getClass().getField(field).get(null);
  368. } catch (Exception e) {
  369. throw new RuntimeException(StringUtils.stringifyException(e));
  370. }
  371. return value;
  372. }
  373. }