From 199d342df7c441ebfe5a4c0b1979f547cbb2a9d8 Mon Sep 17 00:00:00 2001 From: Jarek Jarcec Cecho Date: Mon, 11 Aug 2014 17:40:04 -0700 Subject: [PATCH] SQOOP-1426: Sqoop2: ThrowableBean should correct reconstruct SqoopException --- .../apache/sqoop/common/SqoopException.java | 9 +++ .../org/apache/sqoop/json/ThrowableBean.java | 35 +++++++-- .../apache/sqoop/json/TestThrowableBean.java | 75 ++++++++++++------- 3 files changed, 89 insertions(+), 30 deletions(-) diff --git a/common/src/main/java/org/apache/sqoop/common/SqoopException.java b/common/src/main/java/org/apache/sqoop/common/SqoopException.java index 6caed13a..98f9dc7d 100644 --- a/common/src/main/java/org/apache/sqoop/common/SqoopException.java +++ b/common/src/main/java/org/apache/sqoop/common/SqoopException.java @@ -28,28 +28,37 @@ public class SqoopException extends RuntimeException { private final ErrorCode code; + private final String originalMessage; public SqoopException(ErrorCode code) { super(code.getCode() + ":" + code.getMessage()); this.code = code; + originalMessage = null; } public SqoopException(ErrorCode code, String extraInfo) { super(code.getCode() + ":" + code.getMessage() + " - " + extraInfo); this.code = code; + originalMessage = extraInfo; } public SqoopException(ErrorCode code, Throwable cause) { super(code.getCode() + ":" + code.getMessage(), cause); this.code = code; + originalMessage = null; } public SqoopException(ErrorCode code, String extraInfo, Throwable cause) { super(code.getCode() + ":" + code.getMessage() + " - " + extraInfo, cause); this.code = code; + originalMessage = extraInfo; } public ErrorCode getErrorCode() { return code; } + + public String getOriginalMessage() { + return originalMessage; + } } diff --git a/common/src/main/java/org/apache/sqoop/json/ThrowableBean.java b/common/src/main/java/org/apache/sqoop/json/ThrowableBean.java index 86b637ad..fc63ed4a 100644 --- a/common/src/main/java/org/apache/sqoop/json/ThrowableBean.java +++ b/common/src/main/java/org/apache/sqoop/json/ThrowableBean.java @@ -17,6 +17,7 @@ */ package org.apache.sqoop.json; +import org.apache.sqoop.common.SqoopException; import org.apache.sqoop.utils.ClassUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -36,6 +37,8 @@ public class ThrowableBean implements JsonBean { public static final String FILE = "file"; public static final String LINE = "line"; public static final String CAUSE = "cause"; + public static final String ERROR_CODE = "error-code"; + public static final String ERROR_CODE_CLASS = "error-code-class"; private Throwable throwable; @@ -60,6 +63,14 @@ public JSONObject extract(boolean skipSensitive) { result.put(MESSAGE, throwable.getMessage()); result.put(CLASS, throwable.getClass().getName()); + if(throwable instanceof SqoopException ) { + SqoopException sqoopException = (SqoopException) throwable; + result.put(ERROR_CODE, sqoopException.getErrorCode().getCode()); + result.put(ERROR_CODE_CLASS, sqoopException.getErrorCode().getClass().getName()); + // Override message with the original message + result.put(MESSAGE, sqoopException.getOriginalMessage()); + } + JSONArray st = new JSONArray(); for(StackTraceElement element : throwable.getStackTrace()) { JSONObject obj = new JSONObject(); @@ -91,11 +102,25 @@ public void restore(JSONObject jsonObject) { message = ""; } - // Let's firstly try to instantiate same class that was originally on remote - // side. Fallback to generic Throwable in case that this particular - // exception is not known to this JVM (for example during server-client - // exchange). - throwable = (Throwable) ClassUtils.instantiate(exceptionClass, message); + // Special handling for SqoopException as we need to transfer ERROR_CODE from the other side + if(jsonObject.containsKey(ERROR_CODE_CLASS)) { + Class e = ClassUtils.loadClass((String) jsonObject.get(ERROR_CODE_CLASS)); + + // Only if the error code class is known to this JVM, let's instantiate the real SqoopException + if( e != null) { + String errorCode = (String) jsonObject.get(ERROR_CODE); + Enum enumValue = Enum.valueOf(e, errorCode); + throwable = (Throwable) ClassUtils.instantiate(exceptionClass, enumValue, message); + } + } + + // Let's try to instantiate same class that was originally on remote side. + if(throwable == null) { + throwable = (Throwable) ClassUtils.instantiate(exceptionClass, message); + } + + // Fallback to generic Throwable in case that this particular exception is not known + // to this JVM (for example during server-client exchange). if(throwable == null) { throwable = new Throwable(message); } diff --git a/common/src/test/java/org/apache/sqoop/json/TestThrowableBean.java b/common/src/test/java/org/apache/sqoop/json/TestThrowableBean.java index 2c98d4fd..3614ec5c 100644 --- a/common/src/test/java/org/apache/sqoop/json/TestThrowableBean.java +++ b/common/src/test/java/org/apache/sqoop/json/TestThrowableBean.java @@ -17,35 +17,60 @@ */ package org.apache.sqoop.json; -import junit.framework.TestCase; +import org.apache.sqoop.common.SqoopException; +import org.apache.sqoop.json.util.SerializationError; import org.json.simple.JSONObject; import org.json.simple.JSONValue; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; /** * */ -public class TestThrowableBean extends TestCase { -// public void testSerialization() { -// Throwable ex = new RuntimeException("A"); -// ex.initCause(new Exception("B")); -// -// // Serialize it to JSON object -// ThrowableBean bean = new ThrowableBean(ex); -// JSONObject json = bean.extract(false); -// -// // "Move" it across network in text form -// String string = json.toJSONString(); -// -// // Retrieved transferred object -// JSONObject retrievedJson = (JSONObject) JSONValue.parse(string); -// ThrowableBean retrievedBean = new ThrowableBean(); -// retrievedBean.restore(retrievedJson); -// Throwable retrieved = retrievedBean.getThrowable(); -// -// assertEquals("A", retrieved.getMessage()); -// assertEquals(RuntimeException.class, retrieved.getClass()); -// assertEquals("B", retrieved.getCause().getMessage()); -// assertEquals(Exception.class, retrieved.getCause().getClass()); -// assertNull(retrieved.getCause().getCause()); -// } +public class TestThrowableBean { + + @Test + public void testSerialization() { + Throwable ex = new RuntimeException("A"); + ex.initCause(new Exception("B")); + + Throwable retrieved = transfer(ex); + + assertEquals("A", retrieved.getMessage()); + assertEquals(RuntimeException.class, retrieved.getClass()); + assertEquals("B", retrieved.getCause().getMessage()); + assertEquals(Exception.class, retrieved.getCause().getClass()); + assertNull(retrieved.getCause().getCause()); + } + + @Test + public void testSqoopException() { + SqoopException ex = new SqoopException(SerializationError.SERIALIZATION_001, "Secret"); + Throwable retrieved = transfer(ex); + + assertNotNull(retrieved); + assertEquals(SqoopException.class, retrieved.getClass()); + SqoopException sqoopRetrieved = (SqoopException) retrieved; + assertEquals(SerializationError.class, sqoopRetrieved.getErrorCode().getClass()); + assertEquals(SerializationError.SERIALIZATION_001, sqoopRetrieved.getErrorCode()); + assertEquals("SERIALIZATION_001:Attempt to pass a non-map object to MAP type. - Secret", sqoopRetrieved.getMessage()); + } + + public Throwable transfer(Throwable source) { + // Serialize it to JSON object + ThrowableBean bean = new ThrowableBean(source); + JSONObject json = bean.extract(false); + + // "Move" it across network in text form + String string = json.toJSONString(); + + // Retrieved transferred object + JSONObject retrievedJson = (JSONObject) JSONValue.parse(string); + ThrowableBean retrievedBean = new ThrowableBean(); + retrievedBean.restore(retrievedJson); + return retrievedBean.getThrowable(); + } }