ApiExceptionHandler.java

/*
 * Copyright 2019 Global Crop Diversity Trust
 *
 * 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.genesys.server.api;

import java.io.EOFException;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;

import org.genesys.blocks.security.NotUniqueUserException;
import org.genesys.filerepository.FileRepositoryException;
import org.genesys.filerepository.NoSuchRepositoryFileException;
import org.genesys.server.exception.ClientDisconnectedException;
import org.genesys.server.exception.DetailedConstraintViolationException;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.service.CRMException;
import org.genesys.server.service.AmphibianService.AmphibianException;
import org.genesys.server.service.AmphibianService.AmphibianNotAvailableException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
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.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

/**
 * API exception handler returns errors in {@link ApiError}.
 *
 * @author Matija Obreza
 */
@ControllerAdvice(basePackages = { "org.genesys.server.api" })
public class ApiExceptionHandler {
	
	/** The log. */
	protected final Logger LOG = LoggerFactory.getLogger(getClass());

	// @ResponseStatus(code = HttpStatus.NOT_FOUND)
	// @ExceptionHandler(NoSuchAccessionException.class)
	// @ResponseBody
	// public ApiError<Exception> handleMissingAccession(NoSuchAccessionException
	// ex, WebRequest request) throws JsonProcessingException {
	// LOG.warn("Returning BrAPI error: " + ex.getMessage());
	// return new ApiError<>(ex);
	// }

	@ExceptionHandler({ EOFException.class, ClientDisconnectedException.class })
	public void handleJettyEof(final HttpServletRequest request) {
		LOG.warn("Client disconnected {} {}", request.getMethod(), request.getRequestURL());
	}

	/**
	 * Handle missing credentials.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
	@ExceptionHandler({ AuthenticationCredentialsNotFoundException.class })
	@ResponseBody
	public ApiError<Exception> handleMissingCredentials(final Exception e, final WebRequest request) {
		LOG.warn("Authentication is required.", e);
		return new ApiError<>(e);
	}

	/**
	 * Handle access denied.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.FORBIDDEN)
	@ExceptionHandler({ AccessDeniedException.class })
	@ResponseBody
	public ApiError<Exception> handleAccessDenied(final Exception e, final HttpServletRequest request) {
		LOG.warn("Access denied {} {}", request.getMethod(), request.getRequestURL());
		return new ApiError<>(e);
	}

	/**
	 * Handle converter error.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler(HttpMessageNotReadableException.class)
	@ResponseBody
	public ApiError<Exception> handleConverterError(final HttpMessageNotReadableException e, final HttpServletRequest request) {
		if (LOG.isDebugEnabled()) {
			LOG.warn("Invalid payload provided {} {}", request.getMethod(), request.getRequestURL(), e);
		} else {
			LOG.warn("Invalid payload provided {} {}", request.getMethod(), request.getRequestURL());
		}
		return new ApiError<>(e);
	}

	/**
	 * Handle javax validation error.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler({ ConstraintViolationException.class, TransactionSystemException.class })
	@ResponseBody
	public ApiError<Exception> handleValidationError(Exception e, final HttpServletRequest request) {
		if (e instanceof ConstraintViolationException) {
			e = new DetailedConstraintViolationException((ConstraintViolationException) e);
		} else if (e instanceof TransactionSystemException) {
			e = new DetailedConstraintViolationException((ConstraintViolationException) Objects.requireNonNull(((TransactionSystemException) e).getRootCause()));
		}
		LOG.warn("{} for {} {}", e.getMessage(), request.getMethod(), request.getRequestURL());
		return new ApiError<>(e);
	}

	/**
	 * Handle converter error.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler(MethodArgumentTypeMismatchException.class)
	@ResponseBody
	public ApiError<Exception> handleConverterArgumentError(final MethodArgumentTypeMismatchException e, final HttpServletRequest request) {
		LOG.warn("Invalid argument {} for {} provided {} {}", e.getName(), e.getParameter(), request.getMethod(), request.getRequestURL());
		return new ApiError<>(e);
	}

	/**
	 * Handle CRM exceptions.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler({ CRMException.class })
	@ResponseBody
	public ApiError<Exception> handleCRMExceptions(final Exception e, final HttpServletRequest request) {
		LOG.warn("CRM: {} for {} {}", e.getMessage(), request.getMethod(), request.getRequestURL());
		return new ApiError<>(e);
	}

	/**
	 * Handle invalid api usage.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler({ AmphibianNotAvailableException.class, AmphibianException.class })
	@ResponseBody
	public ApiError<Exception> handleAmphibianExceptions(final Exception e, final HttpServletRequest request) {
		LOG.warn("Amphibian: {} for {} {}", e.getMessage(), request.getMethod(), request.getRequestURL());
		return new ApiError<>(e);
	}

	/**
	 * Handle invalid api usage.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler({ MethodArgumentNotValidException.class, InvalidApiUsageException.class, DataIntegrityViolationException.class, ConcurrencyFailureException.class, FileRepositoryException.class, NotUniqueUserException.class })
	@ResponseBody
	public ApiError<Exception> handleInvalidApiUsage(final Exception e, final HttpServletRequest request) {
		LOG.warn("{} {}: {}", request.getMethod(), request.getRequestURL(), e.getMessage());
		return new ApiError<>(e);
	}

	/**
	 * Handle not found.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.NOT_FOUND)
	@ExceptionHandler(value = { NoSuchRepositoryFileException.class, NotFoundElement.class })
	@ResponseBody
	public ApiError<Exception> handleNotFound(final Exception e, final HttpServletRequest request) {
		LOG.warn("Element not found {} {}", request.getMethod(), request.getRequestURL());
		return new ApiError<>(e);
	}

	/**
	 * Handle request method fail.
	 *
	 * @param req the req
	 * @param e the e
	 * @return the model and view
	 */
	@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
	@ExceptionHandler(value = { HttpRequestMethodNotSupportedException.class })
	public ApiError<Exception> handleRequestMethodFail(final HttpServletRequest req, final HttpRequestMethodNotSupportedException e) {
		LOG.warn("Request method {} not supported for URL {}", e.getMethod(), req.getRequestURL());
		return new ApiError<>(e);
	}

	/**
	 * Handle server error.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
	@ExceptionHandler(Throwable.class)
	@ResponseBody
	public ApiError<Exception> handleServerError(final Exception e, final WebRequest request) {
		LOG.error("Wow! Such! Exception!", e);
		return new ApiError<>(e);
	}
}