blob: f8cef893d7d787fe38b4585e158b2dd8c7d85fcd [file] [log] [blame]
/**
* 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.hbase.util;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos.ForeignExceptionMessage;
import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos.GenericExceptionMessage;
import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos.StackTraceElementMessage;
/**
* Helper to convert Exceptions and StackTraces from/to protobuf.
* (see ErrorHandling.proto for the internal of the proto messages)
*/
@InterfaceAudience.Private
public final class ForeignExceptionUtil {
private ForeignExceptionUtil() { }
public static Exception toException(final ForeignExceptionMessage eem) {
Exception re;
try {
re = createException(Exception.class, eem);
} catch (Throwable e) {
re = new Exception(eem.getGenericException().getMessage());
}
return setExceptionDetails(re, eem);
}
public static IOException toIOException(final ForeignExceptionMessage eem) {
IOException re;
try {
re = createException(IOException.class, eem);
} catch (Throwable e) {
re = new IOException(eem.getGenericException().getMessage());
}
return setExceptionDetails(re, eem);
}
private static <T extends Exception> T createException(final Class<T> clazz,
final ForeignExceptionMessage eem) throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
final GenericExceptionMessage gem = eem.getGenericException();
final Class<?> realClass = Class.forName(gem.getClassName());
final Class<? extends T> cls = realClass.asSubclass(clazz);
final Constructor<? extends T> cn = cls.getConstructor(String.class);
cn.setAccessible(true);
return cn.newInstance(gem.getMessage());
}
private static <T extends Exception> T setExceptionDetails(final T exception,
final ForeignExceptionMessage eem) {
final GenericExceptionMessage gem = eem.getGenericException();
final StackTraceElement[] trace = toStackTrace(gem.getTraceList());
exception.setStackTrace(trace);
return exception;
}
public static ForeignExceptionMessage toProtoForeignException(final Throwable t) {
return toProtoForeignException(null, t);
}
public static ForeignExceptionMessage toProtoForeignException(String source, Throwable t) {
GenericExceptionMessage.Builder gemBuilder = GenericExceptionMessage.newBuilder();
gemBuilder.setClassName(t.getClass().getName());
if (t.getMessage() != null) {
gemBuilder.setMessage(t.getMessage());
}
// set the stack trace, if there is one
List<StackTraceElementMessage> stack = toProtoStackTraceElement(t.getStackTrace());
if (stack != null) {
gemBuilder.addAllTrace(stack);
}
GenericExceptionMessage payload = gemBuilder.build();
ForeignExceptionMessage.Builder exception = ForeignExceptionMessage.newBuilder();
exception.setGenericException(payload);
if (source != null) {
exception.setSource(source);
}
return exception.build();
}
/**
* Convert a stack trace to list of {@link StackTraceElement}.
* @param trace the stack trace to convert to protobuf message
* @return <tt>null</tt> if the passed stack is <tt>null</tt>.
*/
public static List<StackTraceElementMessage> toProtoStackTraceElement(StackTraceElement[] trace) {
// if there is no stack trace, ignore it and just return the message
if (trace == null) {
return null;
}
// build the stack trace for the message
List<StackTraceElementMessage> pbTrace = new ArrayList<>(trace.length);
for (StackTraceElement elem : trace) {
StackTraceElementMessage.Builder stackBuilder = StackTraceElementMessage.newBuilder();
stackBuilder.setDeclaringClass(elem.getClassName());
if (elem.getFileName() != null) {
stackBuilder.setFileName(elem.getFileName());
}
stackBuilder.setLineNumber(elem.getLineNumber());
stackBuilder.setMethodName(elem.getMethodName());
pbTrace.add(stackBuilder.build());
}
return pbTrace;
}
/**
* Unwind a serialized array of {@link StackTraceElementMessage}s to a
* {@link StackTraceElement}s.
* @param traceList list that was serialized
* @return the deserialized list or <tt>null</tt> if it couldn't be unwound (e.g. wasn't set on
* the sender).
*/
public static StackTraceElement[] toStackTrace(List<StackTraceElementMessage> traceList) {
if (traceList == null || traceList.isEmpty()) {
return new StackTraceElement[0]; // empty array
}
StackTraceElement[] trace = new StackTraceElement[traceList.size()];
for (int i = 0; i < traceList.size(); i++) {
StackTraceElementMessage elem = traceList.get(i);
trace[i] = new StackTraceElement(
elem.getDeclaringClass(), elem.getMethodName(),
elem.hasFileName() ? elem.getFileName() : null,
elem.getLineNumber());
}
return trace;
}
}