diff options
4 files changed, 166 insertions, 1 deletions
diff --git a/common/src/main/java/org/apache/drill/common/exceptions/UserException.java b/common/src/main/java/org/apache/drill/common/exceptions/UserException.java index 3a3ca5af2..3fcdc073f 100644 --- a/common/src/main/java/org/apache/drill/common/exceptions/UserException.java +++ b/common/src/main/java/org/apache/drill/common/exceptions/UserException.java @@ -742,6 +742,9 @@ public class UserException extends DrillRuntimeException { if (getCause() != null) { // some unit tests use this information to make sure a specific exception was thrown in the server builder.setException(ErrorHelper.getWrapper(getCause())); + } else { + // not a wrapper exception + builder.setException(ErrorHelper.getWrapper(this)); } return builder.build(); } diff --git a/common/src/main/java/org/apache/drill/common/exceptions/UserRemoteException.java b/common/src/main/java/org/apache/drill/common/exceptions/UserRemoteException.java index 04c9890e4..4835b2981 100644 --- a/common/src/main/java/org/apache/drill/common/exceptions/UserRemoteException.java +++ b/common/src/main/java/org/apache/drill/common/exceptions/UserRemoteException.java @@ -19,6 +19,8 @@ package org.apache.drill.common.exceptions; import org.apache.drill.exec.proto.UserBitShared.DrillPBError; +import static org.apache.drill.common.util.DrillExceptionUtil.getThrowable; + /** * Wraps a DrillPBError object so we don't need to rebuilt it multiple times when sending it to the client. It also * gives access to the original exception className and message. @@ -28,7 +30,7 @@ public class UserRemoteException extends UserException { private final DrillPBError error; public UserRemoteException(DrillPBError error) { - super(error.getErrorType(), "Drill Remote Exception", null); + super(error.getErrorType(), "Drill Remote Exception", getThrowable(error.getException())); this.error = error; } diff --git a/common/src/main/java/org/apache/drill/common/util/DrillExceptionUtil.java b/common/src/main/java/org/apache/drill/common/util/DrillExceptionUtil.java new file mode 100644 index 000000000..8ae93a82b --- /dev/null +++ b/common/src/main/java/org/apache/drill/common/util/DrillExceptionUtil.java @@ -0,0 +1,107 @@ +/* + * 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.drill.common.util; + +import org.apache.commons.lang3.StringUtils; +import org.apache.drill.exec.proto.UserBitShared; + +import java.lang.reflect.Constructor; +import java.util.Arrays; + +/** + * Utility class which contain methods for conversion Drill ProtoBuf Error and Throwable + */ +public class DrillExceptionUtil { + + /** + * Recreate throwable from exception protoBuf which received from remote or local node. + * if no match constructor found, the base Throwable (Exception or Error) is created as a substitution + * + * @param exceptionWrapper the exception protoBuf + * @return Throwable deserialized from protoBuf + */ + public static Throwable getThrowable(UserBitShared.ExceptionWrapper exceptionWrapper) { + if (exceptionWrapper == null) { + return null; + } + String className = exceptionWrapper.getExceptionClass(); + if (StringUtils.isBlank(className) || exceptionWrapper.getStackTraceCount() < 1) { + return null; + } + Throwable inner = getThrowable(exceptionWrapper.getCause()); + try { + Throwable throwable = getInstance(className, exceptionWrapper.getMessage(), inner); + int size = exceptionWrapper.getStackTraceCount(); + StackTraceElement[] stackTrace = new StackTraceElement[size]; + for (int i = 0; i < size; ++i) { + UserBitShared.StackTraceElementWrapper w = exceptionWrapper.getStackTrace(i); + stackTrace[i] = new StackTraceElement(w.getClassName(), w.getMethodName(), w.getFileName(), w.getLineNumber()); + } + throwable.setStackTrace(stackTrace); + return throwable; + } catch (Throwable t) { + return null; + } + } + + /** + * Get throwable from class name and its constructors, the candidate constructor of exception are: + * 1) ExceptionClass(String message, Throwable t), + * 2) ExceptionClass(Throwable t, String message), + * 3) ExceptionClass(String message). + * if no match constructor found, the base Throwable (Exception or Error) is created as a substitution + * + * @param className the exception class name + * @param message the exception message + * @param inner the parent cause of the exception + * @return Throwable + */ + private static Throwable getInstance(String className, String message, Throwable inner) throws ReflectiveOperationException { + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + return new Exception(message, inner); + } + Constructor<Throwable>[] constructors = clazz.getConstructors(); + Class<?>[] defaultParameterTypes = new Class<?>[]{String.class, Throwable.class}; + Class<?>[] revertParameterTypes = new Class<?>[]{Throwable.class, String.class}; + Class<?>[] singleParameterType = new Class<?>[]{String.class}; + for (Constructor<Throwable> constructor : constructors) { + if (Arrays.equals(defaultParameterTypes, constructor.getParameterTypes())) { + return constructor.newInstance(message, inner); + } + if (Arrays.equals(revertParameterTypes, constructor.getParameterTypes())) { + return constructor.newInstance(inner, message); + } + if (inner == null) { + if (Arrays.equals(singleParameterType, constructor.getParameterTypes())) { + return constructor.newInstance(message); + } + } + } + return getBaseInstance(clazz, message, inner); + } + + private static Throwable getBaseInstance(Class clazz, String message, Throwable inner) { + if (Error.class.isAssignableFrom(clazz)) { + return new Error(message, inner); + } + return new Exception(message, inner); + } +} diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/util/DrillExceptionUtilTest.java b/exec/java-exec/src/test/java/org/apache/drill/exec/util/DrillExceptionUtilTest.java new file mode 100644 index 000000000..a2cb29aea --- /dev/null +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/util/DrillExceptionUtilTest.java @@ -0,0 +1,53 @@ +/* + * 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.drill.exec.util; + +import org.apache.drill.common.exceptions.DrillIOException; +import org.apache.drill.common.exceptions.DrillRuntimeException; +import org.apache.drill.common.exceptions.ErrorHelper; +import org.apache.drill.common.util.DrillExceptionUtil; +import org.apache.drill.exec.proto.UserBitShared; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class DrillExceptionUtilTest { + private static final String ERROR_MESSAGE = "Exception Test"; + private static final String NESTED_ERROR_MESSAGE = "Nested Exception"; + + @Test + public void testUnwrapException() { + Throwable rootThrowable = new DrillRuntimeException(ERROR_MESSAGE); + UserBitShared.ExceptionWrapper exceptionWrapper = ErrorHelper.getWrapper(rootThrowable); + Throwable t = DrillExceptionUtil.getThrowable(exceptionWrapper); + assertEquals("Exception class should match", rootThrowable.getClass(), t.getClass()); + assertEquals("Exception message should match", rootThrowable.getMessage(), t.getMessage()); + } + + @Test + public void testUnwrapNestedException() { + Throwable nested = new DrillIOException(NESTED_ERROR_MESSAGE); + Throwable rootThrowable = new Error(ERROR_MESSAGE, nested); + UserBitShared.ExceptionWrapper exceptionWrapper = ErrorHelper.getWrapper(rootThrowable); + Throwable t = DrillExceptionUtil.getThrowable(exceptionWrapper); + assertEquals("Exception class should match", rootThrowable.getClass(), t.getClass()); + assertEquals("Exception message should match", rootThrowable.getMessage(), t.getMessage()); + assertEquals("Exception cause class should match", rootThrowable.getCause().getClass(), t.getCause().getClass()); + assertEquals("Exception cause message should match", rootThrowable.getCause().getMessage(), t.getCause().getMessage()); + } +} |