/*
 * 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.geode.rest.internal.web.controllers;

import java.io.PrintWriter;
import java.io.StringWriter;

import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.management.internal.exceptions.EntityNotFoundException;
import org.apache.geode.rest.internal.web.exception.DataTypeNotSupportedException;
import org.apache.geode.rest.internal.web.exception.GemfireRestException;
import org.apache.geode.rest.internal.web.exception.MalformedJsonException;
import org.apache.geode.rest.internal.web.exception.RegionNotFoundException;
import org.apache.geode.rest.internal.web.exception.ResourceNotFoundException;
import org.apache.geode.security.NotAuthorizedException;

/**
 * The CrudControllerAdvice class handles exception thrown while serving the REST request
 *
 * @since GemFire 8.0
 */
@ControllerAdvice
@SuppressWarnings("unused")
public class BaseControllerAdvice extends AbstractBaseController {

  private static final Logger logger = LogService.getLogger();

  private static final String REST_API_VERSION = "/v1";

  @Override
  protected String getRestApiVersion() {
    return REST_API_VERSION;
  }

  /**
   * Handles both ResourceNotFoundExceptions and specifically, RegionNotFoundExceptions, occurring
   * when a resource or a Region (a.k.a. resource) does not exist in GemFire.
   * <p>
   *
   * @param e the RuntimeException thrown when the accessed/requested resource does not exist in
   *        GemFire.
   * @return the String message from the RuntimeException.
   */
  @ExceptionHandler({RegionNotFoundException.class, ResourceNotFoundException.class})
  @ResponseBody
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public ResponseEntity<String> handle(final RuntimeException e) {
    return convertErrorAsJson(HttpStatus.NOT_FOUND, e.getMessage());
  }

  /**
   * Handles MalformedJsonFoundException, occurring when REST service encounters incorrect or
   * malformed JSON document
   * <p>
   *
   * @param e the RuntimeException thrown when malformed JSON is encounterd.
   * @return the String message from the RuntimeException.
   */
  @ExceptionHandler({MalformedJsonException.class})
  @ResponseBody
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public ResponseEntity<String> handleException(final RuntimeException e) {
    return convertErrorAsJson(HttpStatus.BAD_REQUEST, e.getMessage());
  }

  /**
   * Handles any GemfireRestException thrown by a REST API web service endpoint, HTTP request
   * handler method.
   * <p>
   *
   * @param ge the GemfireRestException thrown when it found problem processing REST request.
   * @return the String message from the RuntimeException.
   */
  @ExceptionHandler(GemfireRestException.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public ResponseEntity<String> handleException(final GemfireRestException ge) {
    return convertErrorAsJson(HttpStatus.INTERNAL_SERVER_ERROR, ge);
  }

  /**
   * Handles any DataTypeNotSupportedException thrown by a REST API web service endpoint, HTTP
   * request handler method.
   * <p>
   *
   * @param tns the DataTypeNotSupportedException thrown if problem occurs in cache values to JSON
   *        conversion.
   * @return the String message from the RuntimeException.
   */
  @ExceptionHandler(DataTypeNotSupportedException.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
  public ResponseEntity<String> handleException(final DataTypeNotSupportedException tns) {
    return convertErrorAsJson(HttpStatus.NOT_ACCEPTABLE, tns.getMessage());
  }

  /**
   * Handles HttpRequestMethodNotSupportedException thrown by a REST API web service when request is
   * received with unsupported HTTP method.
   * <p>
   *
   * @param e the HttpRequestMethodNotSupportedException thrown when REST request is received with
   *        NOT support methods.
   * @return the String message from the RuntimeException.
   */
  @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
  public ResponseEntity<String> handleException(final HttpRequestMethodNotSupportedException e) {
    return convertErrorAsJson(HttpStatus.METHOD_NOT_ALLOWED, e.getMessage());
  }

  /**
   * Handles an AccessDenied Exception thrown by a REST API web service endpoint, HTTP request
   * handler method.
   * <p>
   *
   * @param cause the Exception causing the error.
   * @return a ResponseEntity with an appropriate HTTP status code (403 - Forbidden)
   */
  @ExceptionHandler(AccessDeniedException.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.FORBIDDEN)
  public ResponseEntity<String> handleException(final AccessDeniedException cause) {
    return convertErrorAsJson(HttpStatus.FORBIDDEN, cause.getMessage());
  }

  /**
   * Handles an NotAuthorized Exception thrown by a GeodeSecurityUtil.
   * <p>
   *
   * @param cause the Exception causing the error.
   * @return a ResponseEntity with an appropriate HTTP status code (403 - Forbidden)
   */
  @ExceptionHandler(NotAuthorizedException.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.FORBIDDEN)
  public ResponseEntity<String> handleException(final NotAuthorizedException cause) {
    return convertErrorAsJson(HttpStatus.FORBIDDEN, cause.getMessage());
  }

  /**
   * Handles an EntityNotFound Exception thrown by web service endpoint
   * <p>
   *
   * @param cause the Exception causing the error.
   * @return a ResponseEntity with an appropriate HTTP status code (403 - Forbidden)
   */
  @ExceptionHandler(EntityNotFoundException.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public ResponseEntity<String> handleException(final EntityNotFoundException cause) {
    return convertErrorAsJson(HttpStatus.NOT_FOUND, cause.getMessage());
  }

  /**
   * Handles any Exception thrown by a REST API web service endpoint, HTTP request handler method.
   * <p>
   *
   * @param cause the Exception causing the error.
   * @return a ResponseEntity with an appropriate HTTP status code (500 - Internal Server Error) and
   *         HTTP response body containing the stack trace of the Exception.
   */
  @ExceptionHandler(Throwable.class)
  @ResponseBody
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public ResponseEntity<String> handleException(final Throwable cause) {
    final StringWriter stackTraceWriter = new StringWriter();
    cause.printStackTrace(new PrintWriter(stackTraceWriter));
    final String stackTrace = stackTraceWriter.toString();

    if (logger.isDebugEnabled()) {
      logger.debug(stackTrace);
    }

    return convertErrorAsJson(HttpStatus.INTERNAL_SERVER_ERROR, cause.getMessage());
  }

}
