diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/IOUtils.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/IOUtils.java index 4684fb63ae3..f451ff3212b 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/IOUtils.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/IOUtils.java @@ -19,6 +19,7 @@ package org.apache.hadoop.io; import java.io.*; +import java.lang.reflect.Constructor; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; @@ -35,6 +36,7 @@ import org.apache.commons.logging.Log; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.PathIOException; import org.apache.hadoop.util.Shell; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -450,4 +452,58 @@ public class IOUtils { throw ioe; } } + + /** + * Takes an IOException, file/directory path, and method name and returns an + * IOException with the input exception as the cause and also include the + * file,method details. The new exception provides the stack trace of the + * place where the exception is thrown and some extra diagnostics + * information. + * + * Return instance of same exception if exception class has a public string + * constructor; Otherwise return an PathIOException. + * InterruptedIOException and PathIOException are returned unwrapped. + * + * @param path file/directory path + * @param methodName method name + * @param exception the caught exception. + * @return an exception to throw + */ + public static IOException wrapException(final String path, + final String methodName, final IOException exception) { + + if (exception instanceof InterruptedIOException + || exception instanceof PathIOException) { + return exception; + } else { + String msg = String + .format("Failed with %s while processing file/directory :[%s] in " + + "method:[%s]", + exception.getClass().getName(), path, methodName); + try { + return wrapWithMessage(exception, msg); + } catch (Exception ex) { + // For subclasses which have no (String) constructor throw IOException + // with wrapped message + + return new PathIOException(path, exception); + } + } + } + + @SuppressWarnings("unchecked") + private static T wrapWithMessage( + final T exception, final String msg) throws T { + Class clazz = exception.getClass(); + try { + Constructor ctor = clazz + .getConstructor(String.class); + Throwable t = ctor.newInstance(msg); + return (T) (t.initCause(exception)); + } catch (Throwable e) { + LOG.warn("Unable to wrap exception of type " + + clazz + ": it has no (String) constructor", e); + throw exception; + } + } } diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/Credentials.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/Credentials.java index 3e51249db23..6a9527af375 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/Credentials.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/Credentials.java @@ -207,7 +207,8 @@ public class Credentials implements Writable { in.close(); return credentials; } catch(IOException ioe) { - throw new IOException("Exception reading " + filename, ioe); + throw IOUtils.wrapException(filename.toString(), "Credentials" + + ".readTokenStorageFile", ioe); } finally { IOUtils.cleanupWithLogger(LOG, in); } diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/TestIOUtils.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/TestIOUtils.java index f8b5a48f9e4..2e46c623d9c 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/TestIOUtils.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/TestIOUtils.java @@ -33,13 +33,16 @@ import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.charset.CharacterCodingException; import java.nio.file.Files; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FileUtils;; +import org.apache.hadoop.fs.PathIOException; import org.apache.hadoop.test.GenericTestUtils; +import org.apache.hadoop.test.LambdaTestUtils; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -328,4 +331,24 @@ public class TestIOUtils { } } + + @Test + public void testWrapException() throws Exception { + // Test for IOException with valid (String) constructor + LambdaTestUtils.intercept(EOFException.class, + "Failed with java.io.EOFException while processing file/directory " + + ":[/tmp/abc.txt] in method:[testWrapException]", () -> { + throw IOUtils.wrapException("/tmp/abc.txt", "testWrapException", + new EOFException("EOFException ")); + }); + + // Test for IOException with no (String) constructor + PathIOException returnedEx = LambdaTestUtils + .intercept(PathIOException.class, "Input/output error:", + () -> { + throw IOUtils.wrapException("/tmp/abc.txt", "testWrapEx", + new CharacterCodingException()); + }); + assertEquals("/tmp/abc.txt", returnedEx.getPath().toString()); + } }