/src/java/org/apache/hadoop/fs/shell/Command.java
Java | 409 lines | 183 code | 36 blank | 190 comment | 17 complexity | f52704050c4f0f1c02a751c922f32b73 MD5 | raw file
- /**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.hadoop.fs.shell;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.PrintStream;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.LinkedList;
- import java.util.List;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.apache.hadoop.classification.InterfaceAudience;
- import org.apache.hadoop.classification.InterfaceStability;
- import org.apache.hadoop.conf.Configuration;
- import org.apache.hadoop.conf.Configured;
- import org.apache.hadoop.fs.Path;
- import org.apache.hadoop.fs.shell.PathExceptions.PathNotFoundException;
- import org.apache.hadoop.util.StringUtils;
- /**
- * An abstract class for the execution of a file system command
- */
- @InterfaceAudience.Private
- @InterfaceStability.Evolving
- abstract public class Command extends Configured {
- protected String[] args;
- protected String name;
- protected int exitCode = 0;
- protected int numErrors = 0;
- protected boolean recursive = false;
- protected ArrayList<Exception> exceptions = new ArrayList<Exception>();
- private static final Log LOG = LogFactory.getLog(Command.class);
- /** allows stdout to be captured if necessary */
- public PrintStream out = System.out;
- /** allows stderr to be captured if necessary */
- public PrintStream err = System.err;
- /** Constructor */
- protected Command() {
- out = System.out;
- err = System.err;
- }
-
- /** Constructor */
- protected Command(Configuration conf) {
- super(conf);
- }
-
- /** @return the command's name excluding the leading character - */
- abstract public String getCommandName();
-
- /**
- * Name the command
- * @param cmdName as invoked
- */
- public void setCommandName(String cmdName) {
- name = cmdName;
- }
-
- protected void setRecursive(boolean flag) {
- recursive = flag;
- }
-
- protected boolean isRecursive() {
- return recursive;
- }
- /**
- * Execute the command on the input path
- *
- * @param path the input path
- * @throws IOException if any error occurs
- */
- abstract protected void run(Path path) throws IOException;
-
- /**
- * For each source path, execute the command
- *
- * @return 0 if it runs successfully; -1 if it fails
- */
- public int runAll() {
- int exitCode = 0;
- for (String src : args) {
- try {
- PathData[] srcs = PathData.expandAsGlob(src, getConf());
- for (PathData s : srcs) {
- run(s.path);
- }
- } catch (IOException e) {
- exitCode = -1;
- displayError(e);
- }
- }
- return exitCode;
- }
- /**
- * Invokes the command handler. The default behavior is to process options,
- * expand arguments, and then process each argument.
- * <pre>
- * run
- * \-> {@link #processOptions(LinkedList)}
- * \-> {@link #expandArguments(LinkedList)} -> {@link #expandArgument(String)}*
- * \-> {@link #processArguments(LinkedList)}
- * \-> {@link #processArgument(PathData)}*
- * \-> {@link #processPathArgument(PathData)}
- * \-> {@link #processPaths(PathData, PathData...)}
- * \-> {@link #processPath(PathData)}*
- * \-> {@link #processNonexistentPath(PathData)}
- * </pre>
- * Most commands will chose to implement just
- * {@link #processOptions(LinkedList)} and {@link #processPath(PathData)}
- *
- * @param argv the list of command line arguments
- * @return the exit code for the command
- * @throws IllegalArgumentException if called with invalid arguments
- */
- public int run(String...argv) {
- LinkedList<String> args = new LinkedList<String>(Arrays.asList(argv));
- try {
- if (isDeprecated()) {
- displayWarning(
- "DEPRECATED: Please use '"+ getReplacementCommand() + "' instead.");
- }
- processOptions(args);
- processArguments(expandArguments(args));
- } catch (IOException e) {
- displayError(e);
- }
-
- return (numErrors == 0) ? exitCode : exitCodeForError();
- }
- /**
- * The exit code to be returned if any errors occur during execution.
- * This method is needed to account for the inconsistency in the exit
- * codes returned by various commands.
- * @return a non-zero exit code
- */
- protected int exitCodeForError() { return 1; }
-
- /**
- * Must be implemented by commands to process the command line flags and
- * check the bounds of the remaining arguments. If an
- * IllegalArgumentException is thrown, the FsShell object will print the
- * short usage of the command.
- * @param args the command line arguments
- * @throws IOException
- */
- protected void processOptions(LinkedList<String> args) throws IOException {}
- /**
- * Expands a list of arguments into {@link PathData} objects. The default
- * behavior is to call {@link #expandArgument(String)} on each element
- * which by default globs the argument. The loop catches IOExceptions,
- * increments the error count, and displays the exception.
- * @param args strings to expand into {@link PathData} objects
- * @return list of all {@link PathData} objects the arguments
- * @throws IOException if anything goes wrong...
- */
- protected LinkedList<PathData> expandArguments(LinkedList<String> args)
- throws IOException {
- LinkedList<PathData> expandedArgs = new LinkedList<PathData>();
- for (String arg : args) {
- try {
- expandedArgs.addAll(expandArgument(arg));
- } catch (IOException e) { // other exceptions are probably nasty
- displayError(e);
- }
- }
- return expandedArgs;
- }
- /**
- * Expand the given argument into a list of {@link PathData} objects.
- * The default behavior is to expand globs. Commands may override to
- * perform other expansions on an argument.
- * @param arg string pattern to expand
- * @return list of {@link PathData} objects
- * @throws IOException if anything goes wrong...
- */
- protected List<PathData> expandArgument(String arg) throws IOException {
- PathData[] items = PathData.expandAsGlob(arg, getConf());
- if (items.length == 0) {
- // it's a glob that failed to match
- throw new PathNotFoundException(arg);
- }
- return Arrays.asList(items);
- }
- /**
- * Processes the command's list of expanded arguments.
- * {@link #processArgument(PathData)} will be invoked with each item
- * in the list. The loop catches IOExceptions, increments the error
- * count, and displays the exception.
- * @param args a list of {@link PathData} to process
- * @throws IOException if anything goes wrong...
- */
- protected void processArguments(LinkedList<PathData> args)
- throws IOException {
- for (PathData arg : args) {
- try {
- processArgument(arg);
- } catch (IOException e) {
- displayError(e);
- }
- }
- }
- /**
- * Processes a {@link PathData} item, calling
- * {@link #processPathArgument(PathData)} or
- * {@link #processNonexistentPath(PathData)} on each item.
- * @param arg {@link PathData} item to process
- * @throws IOException if anything goes wrong...
- */
- protected void processArgument(PathData item) throws IOException {
- if (item.exists) {
- processPathArgument(item);
- } else {
- processNonexistentPath(item);
- }
- }
- /**
- * This is the last chance to modify an argument before going into the
- * (possibly) recursive {@link #processPaths(PathData, PathData...)}
- * -> {@link #processPath(PathData)} loop. Ex. ls and du use this to
- * expand out directories.
- * @param item a {@link PathData} representing a path which exists
- * @throws IOException if anything goes wrong...
- */
- protected void processPathArgument(PathData item) throws IOException {
- // null indicates that the call is not via recursion, ie. there is
- // no parent directory that was expanded
- processPaths(null, item);
- }
-
- /**
- * Provides a hook for handling paths that don't exist. By default it
- * will throw an exception. Primarily overriden by commands that create
- * paths such as mkdir or touch.
- * @param item the {@link PathData} that doesn't exist
- * @throws FileNotFoundException if arg is a path and it doesn't exist
- * @throws IOException if anything else goes wrong...
- */
- protected void processNonexistentPath(PathData item) throws IOException {
- throw new PathNotFoundException(item.toString());
- }
- /**
- * Iterates over the given expanded paths and invokes
- * {@link #processPath(PathData)} on each element. If "recursive" is true,
- * will do a post-visit DFS on directories.
- * @param parent if called via a recurse, will be the parent dir, else null
- * @param items a list of {@link PathData} objects to process
- * @throws IOException if anything goes wrong...
- */
- protected void processPaths(PathData parent, PathData ... items)
- throws IOException {
- // TODO: this really should be iterative
- for (PathData item : items) {
- try {
- processPath(item);
- if (recursive && item.stat.isDirectory()) {
- recursePath(item);
- }
- } catch (IOException e) {
- displayError(e);
- }
- }
- }
- /**
- * Hook for commands to implement an operation to be applied on each
- * path for the command. Note implementation of this method is optional
- * if earlier methods in the chain handle the operation.
- * @param item a {@link PathData} object
- * @throws RuntimeException if invoked but not implemented
- * @throws IOException if anything else goes wrong in the user-implementation
- */
- protected void processPath(PathData item) throws IOException {
- throw new RuntimeException("processPath() is not implemented");
- }
- /**
- * Gets the directory listing for a path and invokes
- * {@link #processPaths(PathData, PathData...)}
- * @param item {@link PathData} for directory to recurse into
- * @throws IOException if anything goes wrong...
- */
- protected void recursePath(PathData item) throws IOException {
- processPaths(item, item.getDirectoryContents());
- }
- /**
- * Display an exception prefaced with the command name. Also increments
- * the error count for the command which will result in a non-zero exit
- * code.
- * @param e exception to display
- */
- public void displayError(Exception e) {
- // build up a list of exceptions that occurred
- exceptions.add(e);
-
- String errorMessage = e.getLocalizedMessage();
- if (errorMessage == null) {
- // this is an unexpected condition, so dump the whole exception since
- // it's probably a nasty internal error where the backtrace would be
- // useful
- errorMessage = StringUtils.stringifyException(e);
- LOG.debug(errorMessage);
- } else {
- errorMessage = errorMessage.split("\n", 2)[0];
- }
- displayError(errorMessage);
- }
-
- /**
- * Display an error string prefaced with the command name. Also increments
- * the error count for the command which will result in a non-zero exit
- * code.
- * @param message error message to display
- */
- public void displayError(String message) {
- numErrors++;
- displayWarning(message);
- }
-
- /**
- * Display an warning string prefaced with the command name.
- * @param message warning message to display
- */
- public void displayWarning(String message) {
- err.println(getCommandName() + ": " + message);
- }
-
- /**
- * The short usage suitable for the synopsis
- * @return "name options"
- */
- public String getUsage() {
- String cmd = "-" + getCommandName();
- String usage = isDeprecated() ? "" : getCommandField("USAGE");
- return usage.isEmpty() ? cmd : cmd + " " + usage;
- }
- /**
- * The long usage suitable for help output
- * @return text of the usage
- */
- public String getDescription() {
- return isDeprecated()
- ? "(DEPRECATED) Same as '" + getReplacementCommand() + "'"
- : getCommandField("DESCRIPTION");
- }
- /**
- * Is the command deprecated?
- * @return boolean
- */
- public final boolean isDeprecated() {
- return (getReplacementCommand() != null);
- }
-
- /**
- * The replacement for a deprecated command
- * @return null if not deprecated, else alternative command
- */
- public String getReplacementCommand() {
- return null;
- }
- /**
- * Get a public static class field
- * @param field the field to retrieve
- * @return String of the field
- */
- private String getCommandField(String field) {
- String value;
- try {
- value = (String)this.getClass().getField(field).get(null);
- } catch (Exception e) {
- throw new RuntimeException(StringUtils.stringifyException(e));
- }
- return value;
- }
- }