001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.procedure2;
019
020import java.io.IOException;
021import org.apache.hadoop.hbase.util.ForeignExceptionUtil;
022import org.apache.hadoop.ipc.RemoteException;
023import org.apache.yetus.audience.InterfaceAudience;
024import org.apache.yetus.audience.InterfaceStability;
025
026import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos.ForeignExceptionMessage;
027
028/**
029 * A RemoteProcedureException is an exception from another thread or process.
030 * <p>
031 * RemoteProcedureExceptions are sent to 'remote' peers to signal an abort in the face of failures.
032 * When serialized for transmission we encode using Protobufs to ensure version compatibility.
033 * <p>
034 * RemoteProcedureException exceptions contain a Throwable as its cause. This can be a "regular"
035 * exception generated locally or a ProxyThrowable that is a representation of the original
036 * exception created on original 'remote' source. These ProxyThrowables have their their stacks
037 * traces and messages overridden to reflect the original 'remote' exception.
038 */
039@InterfaceAudience.Private
040@InterfaceStability.Evolving
041@SuppressWarnings("serial")
042public class RemoteProcedureException extends ProcedureException {
043  /**
044   * Name of the throwable's source such as a host or thread name. Must be non-null.
045   */
046  private final String source;
047
048  /**
049   * Create a new RemoteProcedureException that can be serialized. It is assumed that this came form
050   * a local source.
051   * @param source the host or thread name of the source
052   * @param cause  the actual cause of the exception
053   */
054  public RemoteProcedureException(String source, Throwable cause) {
055    super(cause);
056    assert source != null;
057    assert cause != null;
058    this.source = source;
059  }
060
061  public String getSource() {
062    return source;
063  }
064
065  public Exception unwrapRemoteException() {
066    final Throwable cause = getCause();
067    if (cause instanceof RemoteException) {
068      return ((RemoteException) cause).unwrapRemoteException();
069    }
070    if (cause instanceof Exception) {
071      return (Exception) cause;
072    }
073    return new Exception(cause);
074  }
075
076  // NOTE: Does not throw DoNotRetryIOE because it does not
077  // have access (DNRIOE is in the client module). Use
078  // MasterProcedureUtil.unwrapRemoteIOException if need to
079  // throw DNRIOE.
080  public IOException unwrapRemoteIOException() {
081    final Exception cause = unwrapRemoteException();
082    if (cause instanceof IOException) {
083      return (IOException) cause;
084    }
085    return new IOException(cause);
086  }
087
088  @Override
089  public String toString() {
090    String className = getCause().getClass().getName();
091    return className + " via " + getSource() + ":" + getLocalizedMessage();
092  }
093
094  /**
095   * Converts a RemoteProcedureException to an array of bytes.
096   * @param source the name of the external exception source
097   * @param t      the "local" external exception (local)
098   * @return protobuf serialized version of RemoteProcedureException
099   */
100  public static byte[] serialize(String source, Throwable t) {
101    return toProto(source, t).toByteArray();
102  }
103
104  /**
105   * Takes a series of bytes and tries to generate an RemoteProcedureException instance for it.
106   * @param bytes the bytes to generate the {@link RemoteProcedureException} from
107   * @return the ForeignExcpetion instance
108   * @throws IOException if there was deserialization problem this is thrown.
109   */
110  public static RemoteProcedureException deserialize(byte[] bytes) throws IOException {
111    return fromProto(ForeignExceptionMessage.parseFrom(bytes));
112  }
113
114  public ForeignExceptionMessage convert() {
115    return ForeignExceptionUtil.toProtoForeignException(getSource(), getCause());
116  }
117
118  public static ForeignExceptionMessage toProto(String source, Throwable t) {
119    return ForeignExceptionUtil.toProtoForeignException(source, t);
120  }
121
122  public static RemoteProcedureException fromProto(final ForeignExceptionMessage eem) {
123    return new RemoteProcedureException(eem.getSource(), ForeignExceptionUtil.toException(eem));
124  }
125}