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.gringlobal.api;

import java.io.EOFException;

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

import lombok.extern.slf4j.Slf4j;
import org.genesys.blocks.security.NoUserFoundException;
import org.genesys.blocks.security.NotUniqueUserException;
import org.genesys.blocks.security.service.PasswordPolicy;
import org.genesys.filerepository.FileRepositoryException;
import org.genesys.filerepository.NoSuchRepositoryFileException;
import org.genesys.filerepository.NoSuchRepositoryFolderException;
import org.gringlobal.api.exception.DetailedConstraintViolationException;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.exception.NotFoundElement;
import org.hibernate.hql.internal.ast.InvalidPathException;
import org.hibernate.hql.internal.ast.QuerySyntaxException;
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.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
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.gringlobal.api" })
@Slf4j
public class ApiExceptionHandler {

	// @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 })
	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("Authentication is required {} {}", 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) {
		log.warn("Invalid payload provided {} {}", request.getMethod(), request.getRequestURL(), e);
		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)
	@ResponseBody
	public ApiError<Exception> handleValidationError(final ConstraintViolationException e, final HttpServletRequest request) {
		final DetailedConstraintViolationException exception = new DetailedConstraintViolationException(e);
		log.warn("{} for {} {}", exception.getMessage(), request.getMethod(), request.getRequestURL(), e);
		return new ApiError<>(exception);
	}

	/**
	 * 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(), e.getMessage());
		return new ApiError<>(e);
	}

	/**
	 * Handle missing request parameters.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler(MissingServletRequestParameterException.class)
	@ResponseBody
	public ApiError<Exception> handleConverterArgumentError(final MissingServletRequestParameterException e, final HttpServletRequest request) {
		log.warn("Missing request parameter {} for {} {}: {}", e.getParameterName(), request.getMethod(), request.getRequestURL(), e.getMessage());
		return new ApiError<>(e);
	}

	/**
	 * Handle password policy mismatch.
	 *
	 * @param e the e
	 * @param request the request
	 * @return the api error
	 */
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ExceptionHandler(PasswordPolicy.PasswordPolicyException.class)
	@ResponseBody
	public ApiError<Exception> handlePasswordPolicyMismatch(final PasswordPolicy.PasswordPolicyException e, final HttpServletRequest request) {
		log.warn("{} 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({ InvalidApiUsageException.class, DataIntegrityViolationException.class, ConcurrencyFailureException.class, NotUniqueUserException.class, InvalidPathException.class, QuerySyntaxException.class, FileRepositoryException.class, MethodArgumentNotValidException.class })
	@ResponseBody
	public ApiError<Exception> handleInvalidApiUsage(final Exception e, final HttpServletRequest request) {
		log.warn("{} for {} {}", e.getMessage(), request.getMethod(), request.getRequestURL(), e);
		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 = { EntityNotFoundException.class, NotFoundElement.class, NoSuchRepositoryFileException.class, NoSuchRepositoryFolderException.class, NoUserFoundException.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.BAD_REQUEST)
	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseBody
	public ApiError<Exception> handleIllegalArgumentException(final Exception e, final WebRequest request) {
		log.warn("Illegal argument: {}", e.getMessage());
		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.warn("Wow! Such! Exception!", e);
		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(NullPointerException.class)
	@ResponseBody
	public ApiError<Exception> handleNPE(final Exception e, final WebRequest request) {
		log.error("NPE in {}", request.getDescription(true), e);
		return new ApiError<>(e);
	}
}