content: /*
   * 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.calcite.avatica.remote;
  
  import org.apache.calcite.avatica.AvaticaSeverity;
  import org.apache.calcite.avatica.NoSuchConnectionException;
  import org.apache.calcite.avatica.remote.Service.ErrorResponse;
  import org.apache.calcite.avatica.remote.Service.Request;
  import org.apache.calcite.avatica.remote.Service.Response;
  import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
  
  import java.io.IOException;
  
  /**
   * Abstract base class for {@link Handler}s to extend to inherit functionality common across
   * serialization strategies.
   *
   * @param <T> The format Requests/Responses are serialized as.
   */
  public abstract class AbstractHandler<T> implements Handler<T> {
    private static final String NULL_EXCEPTION_MESSAGE = "(null exception message)";
    protected final Service service;
    private RpcMetadataResponse metadata = null;
  
    public AbstractHandler(Service service) {
      this.service = service;
    }
  
    abstract Request decode(T serializedRequest) throws IOException;
  
    /**
     * Serialize the given {@link Response} per the concrete {@link Handler} implementation.
     *
     * @param response The {@link Response} to serialize.
     * @return A serialized representation of the {@link Response}.
     */
    abstract T encode(Response response) throws IOException;
  
    /**
     * Unwrap Avatica-specific context about a given exception.
     *
     * @param e A caught exception throw by Avatica implementation.
     * @return An {@link ErrorResponse}.
     */
    ErrorResponse unwrapException(Exception e) {
      // By default, we know nothing extra.
      int errorCode = ErrorResponse.UNKNOWN_ERROR_CODE;
      String sqlState = ErrorResponse.UNKNOWN_SQL_STATE;
      AvaticaSeverity severity = AvaticaSeverity.UNKNOWN;
      String errorMsg = null;
  
      // Extract the contextual information if we have it. We may not.
      if (e instanceof AvaticaRuntimeException) {
        AvaticaRuntimeException rte = (AvaticaRuntimeException) e;
        errorCode = rte.getErrorCode();
        sqlState = rte.getSqlState();
        severity = rte.getSeverity();
        errorMsg = rte.getErrorMessage();
      } else if (e instanceof NoSuchConnectionException) {
        errorCode = ErrorResponse.MISSING_CONNECTION_ERROR_CODE;
        severity = AvaticaSeverity.ERROR;
        errorMsg = e.getMessage();
      } else {
        // Try to construct a meaningful error message when the server impl doesn't provide one.
        errorMsg = getCausalChain(e);
      }
  
      return new ErrorResponse(e, errorMsg, errorCode, sqlState, severity, metadata);
    }
  
    /**
     * Compute a response for the given request, handling errors generated by that computation.
     *
     * @param serializedRequest The caller's request.
     * @return A {@link Response} with additional context about that response.
     */
    public HandlerResponse<T> apply(T serializedRequest) {
      try {
        final Service.Request request = decode(serializedRequest);
        final Service.Response response = request.accept(service);
        return new HandlerResponse<>(encode(response), HTTP_OK);
      } catch (Exception e) {
        return convertToErrorResponse(e);
      }
    }
  
    /**
     * Attempts to convert an Exception to an ErrorResponse. If there is an issue in serialization,
     * a RuntimeException is thrown instead (wrapping the original exception if necessary).
     *
     * @param e The exception to convert.
     * @return A HandlerResponse instance.
     */
    public HandlerResponse<T> convertToErrorResponse(Exception e) {
      return createErrorResponse(e, HTTP_INTERNAL_SERVER_ERROR);
    }
  
    /**
     * Attempts to convert an Exception to an ErrorResponse with an HTTP status code of {@code 400}.
     */
    public HandlerResponse<T> badRequestErrorResponse(Exception e) {
      return createErrorResponse(e, HTTP_BAD_REQUEST);
    }
  
    /**
     * Attempts to convert an Exception to an ErrorResponse with an HTTP status code of {@code 401}.
     */
    public HandlerResponse<T> unauthenticatedErrorResponse(Exception e) {
      return createErrorResponse(e, HTTP_UNAUTHENTICATED);
    }
  
    /**
     * Attempts to convert an Exception to an ErrorResponse with an HTTP stauts code of {@code 403}.
     */
    public HandlerResponse<T> unauthorizedErrorResponse(Exception e) {
      return createErrorResponse(e, HTTP_UNAUTHORIZED);
    }
  
    private HandlerResponse<T> createErrorResponse(Exception e, int statusCode) {
      ErrorResponse errorResp = unwrapException(e);
      try {
        return new HandlerResponse<>(encode(errorResp), statusCode);
      } catch (IOException e1) {
        // TODO provide a canned ErrorResponse
  
        // If we can't serialize the error message, we can't give a meaningful error to caller.
        // Just try to not unnecessarily create more exceptions.
        if (e instanceof RuntimeException) {
          throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
      }
    }
  
    /**
     * Constructs a message for the summary of an Exception.
     *
     * @param e The Exception to summarize.
     * @return A summary message for the Exception.
     */
    private String getCausalChain(Exception e) {
      StringBuilder sb = new StringBuilder(16);
      Throwable curr = e;
      // Could use Guava, but that would increase dependency set unnecessarily.
      while (null != curr) {
        if (sb.length() > 0) {
          sb.append(" -> ");
        }
        String message = curr.getMessage();
        sb.append(curr.getClass().getSimpleName()).append(": ");
        sb.append(null == message ? NULL_EXCEPTION_MESSAGE : message);
        curr = curr.getCause();
      }
      if (sb.length() == 0) {
        // Catch the case where we have no error message.
        return "Unknown error message";
      }
      return sb.toString();
    }
  
    @Override public void setRpcMetadata(RpcMetadataResponse metadata) {
      this.metadata = metadata;
    }
  }
  
  // End AbstractHandler.java
  