alluxio-master-2.x/core/server/proxy/src/main/java/alluxio/proxy/s3/S3ErrorResponse.java
/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.proxy.s3;

import alluxio.exception.runtime.AlluxioRuntimeException;
import alluxio.exception.runtime.NotFoundRuntimeException;
import alluxio.exception.status.AlluxioStatusException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.IOException;
import javax.ws.rs.core.Response;

/**
 * Utilities for creating HTTP Responses for the S3 API.
 */
public class S3ErrorResponse {
  private static final Logger LOG = LoggerFactory.getLogger(S3ErrorResponse.class);

  /**
   * Creates an error response using the given exception.
   *
   * @param e a {@link Throwable} object
   * @param resource an S3 resource key
   * @return response Http {@link Response}
   */
  public static Response createErrorResponse(Throwable e, String resource) {
    if (e instanceof AlluxioStatusException) {
      return createErrorResponse((AlluxioStatusException) e, resource);
    } else if (e instanceof AlluxioRuntimeException) {
      return createErrorResponse((AlluxioRuntimeException) e, resource);
    } else if (e instanceof S3Exception) {
      return createErrorResponse((S3Exception) e, resource);
    } else if (e instanceof IOException) {
      return createErrorResponse((IOException) e, resource);
    } else {
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
          .entity(e.getMessage()).build();
    }
  }

  /**
   * Creates an error response using the given exception.
   *
   * @param e the exception to be converted into {@link Error} and encoded into XML
   * @param resource resource
   * @return the response
   */
  private static Response createErrorResponse(S3Exception e, String resource) {
    S3Error errorResponse = new S3Error(resource, e.getErrorCode());
    // Need to explicitly encode the string as XML because Jackson will not do it automatically.
    XmlMapper mapper = new XmlMapper();
    try {
      return Response.status(e.getErrorCode().getStatus())
          .entity(mapper.writeValueAsString(errorResponse)).build();
    } catch (JsonProcessingException e2) {
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
          .entity("Failed to encode XML: " + e2.getMessage()).build();
    }
  }

  /**
   * convert the AlluxioStatusException to the HTTP Response.
   * @param e AlluxioStatusException
   * @param resource resource
   * @return response Http Response
   */
  private static Response createErrorResponse(AlluxioStatusException e, String resource) {
    XmlMapper mapper = new XmlMapper();
    S3ErrorCode s3ErrorCode;
    // TODO(WYY): we need to handle more exception in the future.
    if (e instanceof alluxio.exception.status.NotFoundException) {
      // 404
      s3ErrorCode = S3ErrorCode.NO_SUCH_KEY;
    } else if (e instanceof alluxio.exception.status.InvalidArgumentException) {
      // 400
      s3ErrorCode = S3ErrorCode.INVALID_ARGUMENT;
    } else if (e instanceof alluxio.exception.status.PermissionDeniedException) {
      // 403
      s3ErrorCode = S3ErrorCode.ACCESS_DENIED_ERROR;
    } else if (e instanceof alluxio.exception.status.FailedPreconditionException) {
      // 412
      s3ErrorCode = S3ErrorCode.PRECONDITION_FAILED;
    } else {
      // 500
      s3ErrorCode = S3ErrorCode.INTERNAL_ERROR;
    }
    S3Error errorResponse = new S3Error(resource, s3ErrorCode);
    errorResponse.setMessage(e.getMessage());
    try {
      return Response.status(s3ErrorCode.getStatus())
          .entity(mapper.writeValueAsString(errorResponse)).build();
    } catch (JsonProcessingException e2) {
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
          .entity("Failed to encode XML: " + e2.getMessage()).build();
    } finally {
      LOG.warn("mapper convert exception {} to {}.", e.getClass().getName(),
          s3ErrorCode.getStatus().toString());
    }
  }

  /**
   * convert the IOException to the HTTP Response.
   * @param e IOException
   * @param resource resource
   * @return response Http Response
   */
  private static Response createErrorResponse(IOException e, String resource) {
    XmlMapper mapper = new XmlMapper();
    S3ErrorCode s3ErrorCode;
    // TODO(WYY): we need to handle more exception in the future.
    if (e instanceof FileNotFoundException) {
      // 404
      s3ErrorCode = S3ErrorCode.NO_SUCH_KEY;
    } else {
      // 500
      s3ErrorCode = S3ErrorCode.INTERNAL_ERROR;
    }
    S3Error errorResponse = new S3Error(resource, s3ErrorCode);
    errorResponse.setMessage(e.getMessage());
    try {
      return Response.status(s3ErrorCode.getStatus())
          .entity(mapper.writeValueAsString(errorResponse)).build();
    } catch (JsonProcessingException e2) {
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
          .entity("Failed to encode XML: " + e2.getMessage()).build();
    } finally {
      LOG.warn("mapper convert exception {} to {}.", e.getClass().getName(),
          s3ErrorCode.getStatus().toString());
    }
  }

  /**
   * convert the IOException to the HTTP Response.
   * @param e AlluxioRuntimeException
   * @param resource resource
   * @return response Http Response
   */
  private static Response createErrorResponse(AlluxioRuntimeException e, String resource) {
    XmlMapper mapper = new XmlMapper();
    S3ErrorCode s3ErrorCode;
    // TODO(WYY): we need to handle more exception in the future.
    if (e instanceof NotFoundRuntimeException) {
      // 404
      s3ErrorCode = S3ErrorCode.NO_SUCH_KEY;
    } else {
      // 500
      s3ErrorCode = S3ErrorCode.INTERNAL_ERROR;
    }
    S3Error errorResponse = new S3Error(resource, s3ErrorCode);
    errorResponse.setMessage(e.getMessage());
    try {
      return Response.status(s3ErrorCode.getStatus())
          .entity(mapper.writeValueAsString(errorResponse)).build();
    } catch (JsonProcessingException e2) {
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
          .entity("Failed to encode XML: " + e2.getMessage()).build();
    } finally {
      LOG.warn("mapper convert exception {} to {}.", e.getClass().getName(),
          s3ErrorCode.getStatus().toString());
    }
  }
}
