content: /*
   * Copyright 2017 The Apache Software Foundation.
   *
   * Licensed 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.bookkeeper.client.api;
  
  import java.lang.reflect.Field;
  import java.util.function.Function;
  import org.apache.bookkeeper.client.LedgerHandleAdv;
  import org.apache.bookkeeper.common.annotation.InterfaceAudience.Public;
  import org.apache.bookkeeper.common.annotation.InterfaceStability.Unstable;
  
  /**
   * Super class for all errors which occur using BookKeeper client.
   *
   * @since 4.6
   */
  @Public
  @Unstable
  public class BKException extends Exception {
      static final Function<Throwable, BKException> HANDLER = cause -> {
          if (cause == null) {
              return null;
          }
          if (cause instanceof BKException) {
              return (BKException) cause;
          } else {
              BKException ex = new BKException(Code.UnexpectedConditionException);
              ex.initCause(cause);
              return ex;
          }
      };
  
      protected final int code;
  
      private static final LogMessagePool logMessagePool = new LogMessagePool();
  
      /**
       * Create a new exception.
       *
       * @param code the error code
       *
       * @see Code
       */
      public BKException(int code) {
          super(getMessage(code));
          this.code = code;
      }
  
      /**
       * Create a new exception with the <tt>cause</tt>.
       *
       * @param code exception code
       * @param cause the exception cause
       */
      public BKException(int code, Throwable cause) {
          super(getMessage(code), cause);
          this.code = code;
      }
  
      /**
       * Get the return code for the exception.
       *
       * @return the error code
       *
       * @see Code
       */
      public final int getCode() {
          return this.code;
      }
  
      /**
       * Returns a lazy error code formatter suitable to pass to log functions.
       *
       * @param code the error code value
       *
       * @return lazy error code log formatter
       */
      public static Object codeLogger(int code) {
          return logMessagePool.get(code);
      }
  
      /**
       * Describe an error code.
       *
       * @param code the error code value
       *
       * @return the description of the error code
       */
      public static String getMessage(int code) {
          switch (code) {
          case Code.OK:
              return "No problem";
          case Code.ReadException:
              return "Error while reading ledger";
          case Code.QuorumException:
              return "Invalid quorum size on ensemble size";
          case Code.NoBookieAvailableException:
              return "No bookie available";
          case Code.DigestNotInitializedException:
              return "Digest engine not initialized";
          case Code.DigestMatchException:
              return "Entry digest does not match";
          case Code.NotEnoughBookiesException:
              return "Not enough non-faulty bookies available";
          case Code.NoSuchLedgerExistsException:
              return "No such ledger exists on Bookies";
          case Code.NoSuchLedgerExistsOnMetadataServerException:
              return "No such ledger exists on Metadata Server";
          case Code.BookieHandleNotAvailableException:
              return "Bookie handle is not available";
          case Code.ZKException:
              return "Error while using ZooKeeper";
          case Code.MetaStoreException:
              return "Error while using MetaStore";
          case Code.LedgerExistException:
              return "Ledger existed";
          case Code.LedgerRecoveryException:
              return "Error while recovering ledger";
          case Code.LedgerClosedException:
              return "Attempt to write to a closed ledger";
          case Code.WriteException:
              return "Write failed on bookie";
          case Code.NoSuchEntryException:
              return "No such entry";
          case Code.IncorrectParameterException:
              return "Incorrect parameter input";
          case Code.InterruptedException:
              return "Interrupted while waiting for permit";
          case Code.ProtocolVersionException:
              return "Bookie protocol version on server is incompatible with client";
          case Code.MetadataVersionException:
              return "Bad ledger metadata version";
          case Code.DuplicateEntryIdException:
              return "Attempted to add Duplicate entryId";
          case Code.LedgerFencedException:
              return "Ledger has been fenced off. Some other client must have opened it to read";
          case Code.UnauthorizedAccessException:
              return "Attempted to access ledger using the wrong password";
          case Code.UnclosedFragmentException:
              return "Attempting to use an unclosed fragment; This is not safe";
          case Code.WriteOnReadOnlyBookieException:
              return "Attempting to write on ReadOnly bookie";
          case Code.TooManyRequestsException:
              return "Too many requests to the same Bookie";
          case Code.LedgerIdOverflowException:
              return "Next ledgerID is too large.";
          case Code.ReplicationException:
              return "Errors in replication pipeline";
          case Code.ClientClosedException:
              return "BookKeeper client is closed";
          case Code.IllegalOpException:
              return "Invalid operation";
          case Code.AddEntryQuorumTimeoutException:
              return "Add entry quorum wait timed out";
          case Code.TimeoutException:
              return "Bookie operation timeout";
          case Code.SecurityException:
              return "Failed to establish a secure connection";
          case Code.MetadataSerializationException:
              return "Failed to serialize metadata";
          case Code.DataUnknownException:
              return "Ledger in limbo";
          default:
              return "Unexpected condition";
          }
      }
  
      /**
       * Codes which represent the various exception types.
       */
      public interface Code {
          /** A placer holder (unused). */
          int UNINITIALIZED = 1;
          /** Everything is OK. */
          int OK = 0;
          /** Read operations failed (bookie error). */
          int ReadException = -1;
          /** Unused. */
          int QuorumException = -2;
          /** Unused. */
          int NoBookieAvailableException = -3;
          /** Digest Manager is not initialized (client error). */
          int DigestNotInitializedException = -4;
          /** Digest doesn't match on returned entries. */
          int DigestMatchException = -5;
          /** Not enough bookies available to form an ensemble. */
          int NotEnoughBookiesException = -6;
          /** No such ledger exists. */
          int NoSuchLedgerExistsException = -7;
          /** Bookies are not available. */
          int BookieHandleNotAvailableException = -8;
          /** ZooKeeper operations failed. */
          int ZKException = -9;
          /** Ledger recovery operations failed. */
          int LedgerRecoveryException = -10;
          /** Executing operations on a closed ledger handle. */
          int LedgerClosedException = -11;
          /** Write operations failed (bookie error). */
          int WriteException = -12;
          /** No such entry exists. */
          int NoSuchEntryException = -13;
          /** Incorrect parameters (operations are absolutely not executed). */
          int IncorrectParameterException = -14;
          /** Synchronous operations are interrupted. */
          int InterruptedException = -15;
          /** Protocol version is wrong (operations are absolutely not executed). */
          int ProtocolVersionException = -16;
          /** Bad version on executing metadata operations. */
          int MetadataVersionException = -17;
          /** Meta store operations failed. */
          int MetaStoreException = -18;
          /** Executing operations on a closed client. */
          int ClientClosedException = -19;
          /** Ledger already exists. */
          int LedgerExistException = -20;
          /**
           * Add entry operation timeouts on waiting quorum responses.
           *
           * @since 4.5
           */
          int AddEntryQuorumTimeoutException = -21;
          /**
           * Duplicated entry id is found when {@link LedgerHandleAdv#addEntry(long, byte[])}.
           *
           * @since 4.5
           */
          int DuplicateEntryIdException = -22;
          /**
           * Operations timeouts.
           *
           * @since 4.5
           */
          int TimeoutException = -23;
          int SecurityException = -24;
  
          /** No such ledger exists one metadata server. */
          int NoSuchLedgerExistsOnMetadataServerException = -25;
  
          /**
           * Operation is illegal.
           */
          int IllegalOpException = -100;
          /**
           * Operations failed due to ledgers are fenced.
           */
          int LedgerFencedException = -101;
          /**
           * Operations failed due to unauthorized.
           */
          int UnauthorizedAccessException = -102;
          /**
           * Replication failed due to unclosed fragments.
           */
          int UnclosedFragmentException = -103;
          /**
           * Write operations failed due to bookies are readonly.
           */
          int WriteOnReadOnlyBookieException = -104;
          /**
           * Operations failed due to too many requests in the queue.
           */
          int TooManyRequestsException = -105;
          /**
           * Ledger id overflow happens on ledger manager.
           *
           * @since 4.5
           */
          int LedgerIdOverflowException = -106;
  
          /**
           * Failure to serialize metadata.
           *
           * @since 4.9
           */
          int MetadataSerializationException = -107;
  
          /**
           * Operations failed due to ledger data in an unknown state.
           */
          int DataUnknownException = -108;
  
          /**
           * Generic exception code used to propagate in replication pipeline.
           */
          int ReplicationException = -200;
  
          /**
           * Unexpected condition.
           */
          int UnexpectedConditionException = -999;
      }
  
      /**
       * Code log message pool.
       */
      private static class LogMessagePool {
          private final int minCode;
          private final String[] pool;
  
          private LogMessagePool() {
              Field[] fields = Code.class.getDeclaredFields();
              this.minCode = minCode(fields);
              this.pool = new String[-minCode + 2]; // UnexpectedConditionException is an outlier
              initPoolMessages(fields);
          }
  
          private int minCode(Field[] fields) {
              int min = 0;
              for (Field field : fields) {
                  int code = getFieldInt(field);
                  if (code < min && code > Code.UnexpectedConditionException) {
                      min = code;
                  }
              }
              return min;
          }
  
          private void initPoolMessages(Field[] fields) {
              for (Field field : fields) {
                  int code = getFieldInt(field);
                  int index = poolIndex(code);
                  if (index >= 0) {
                      pool[index] = String.format("%s: %s", field.getName(), getMessage(code));
                  }
              }
          }
  
          private static int getFieldInt(Field field) {
              try {
                  return field.getInt(null);
              } catch (IllegalAccessException e) {
                  return -1;
              }
          }
  
          private Object get(int code) {
              int index = poolIndex(code);
              String logMessage = index >= 0 ? pool[index] : null;
              return logMessage != null ? logMessage : new UnrecognizedCodeLogFormatter(code);
          }
  
          private int poolIndex(int code) {
              switch (code) {
              case Code.UnexpectedConditionException:
                  return -minCode + 1;
              default:
                  return code <= 0 && code >= minCode ? -minCode + code : -1;
              }
          }
  
          /**
           * Unrecognized code lazy log message formatter.
           */
          private static class UnrecognizedCodeLogFormatter {
              private final int code;
  
              private UnrecognizedCodeLogFormatter(int code) {
                  this.code = code;
              }
  
              @Override
              public String toString() {
                  return String.format("%d: %s", code, getMessage(code));
              }
          }
      }
  }
  