<commit_before>/*
* Copyright 2015 herd contributors
*
* 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.finra.herd.service.helper;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.DataTruncation;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.servlet.http.HttpServletResponse;

import org.activiti.engine.ActivitiClassLoadingException;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.javax.el.ELException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.quartz.ObjectAlreadyExistsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MissingServletRequestParameterException;
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.method.annotation.ExceptionHandlerMethodResolver;

import org.finra.herd.model.AlreadyExistsException;
import org.finra.herd.model.MethodNotAllowedException;
import org.finra.herd.model.ObjectNotFoundException;
import org.finra.herd.model.api.xml.ErrorInformation;

/**
 * A class that handles various types of exceptions and returns summary error information containing the HTTP status, HTTP status description, and the error
 * message that provides details about the error. Note that all "ExceptionHandler" annotated method that take additional parameters besides the exception itself
 * should ensure that the method can handle cases when the other parameters are null. This is due to the isReportableError method which will invoke the
 * exception handler methods to get the error information which is needed to determine if the exception is reportable.
 */
@Component
public class HerdErrorInformationExceptionHandler
{
    /**
     * The Oracle database specific error code for data too large.
     */
    public static final int ORACLE_DATA_TOO_LARGE_ERROR_CODE = 12899;

    /**
     * The Oracle database specific error code for "can bind a LONG value only for insert into a LONG column". This could happen when the user enters a value
     * that is > 4000 bytes for a VARCHAR.
     */
    public static final int ORACLE_LONG_DATA_IN_LONG_COLUMN_ERROR_CODE = 1461;

    /**
     * Oracle specific SQL state code for generic SQL statement execution errors. https://docs.oracle.com/cd/E15817_01/appdev.111/b31228/appd.htm
     */
    public static final String ORACLE_SQL_STATE_CODE_ERROR = "72000";

    public static final String POSTGRES_SQL_STATE_CODE_FOREIGN_KEY_VIOLATION = "23503";

    public static final String POSTGRES_SQL_STATE_CODE_UNIQUE_INDEX_OR_PRIMARY_KEY_VIOLATION = "23505";

    /**
     * PostgreSQL specific SQL state code for string data truncation errors. http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
     */
    public static final String POSTGRES_SQL_STATE_CODE_TRUNCATION_ERROR = "22001";

    private static final Logger LOGGER = LoggerFactory.getLogger(HerdErrorInformationExceptionHandler.class);

    // A flag that determines whether this class will log errors or not.
    // When using the isReportableError method of this class, we typically don't want to enable logging which is why we're defaulting it to false.
    // When using the class as a normal exception handler (e.g. via a ControllerAdvice bean that extends this class), we typically want to enable logging.
    private boolean loggingEnabled = false;

    // An exception handler method resolver that will resolve exception handling methods based on exceptions.
    @Autowired
    private ExceptionHandlerMethodResolver resolver;

    /**
     * Handle access denied exceptions.
     *
     * @param exception the exception.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ResponseBody
    public ErrorInformation handleAccessDeniedException(Exception exception)
    {
        return getErrorInformation(HttpStatus.FORBIDDEN, exception);
    }

    /**
     * Handle Activiti exceptions. Note that this method properly handles a null response being passed in.
     *
     * @param exception the exception.
     * @param response the response.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = ActivitiException.class)
    @ResponseBody
    public ErrorInformation handleActivitiException(Exception exception, HttpServletResponse response)
    {
        if ((ExceptionUtils.indexOfThrowable(exception, ActivitiClassLoadingException.class) != -1) ||
            (ExceptionUtils.indexOfType(exception, ELException.class) != -1))
        {
            // These exceptions are caused by invalid workflow configurations (i.e. user error) so they are considered a bad request.
            return getErrorInformationAndSetStatus(HttpStatus.BAD_REQUEST, exception, response);
        }
        else
        {
            // For all other exceptions, something is wrong that we weren't expecting so we'll return this as an internal server error and log the error.
            logError("An Activiti error occurred.", exception);
            return getErrorInformationAndSetStatus(HttpStatus.INTERNAL_SERVER_ERROR, exception, response);
        }
    }

    /**
     * Handle exceptions that result in a "bad request" status.
     *
     * @param exception the exception.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = {IllegalArgumentException.class, MissingServletRequestParameterException.class, TypeMismatchException.class,
        UnsupportedEncodingException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorInformation handleBadRequestException(Exception exception)
    {
        return getErrorInformation(HttpStatus.BAD_REQUEST, exception);
    }

    /**
     * Handle Spring HttpMessageNotReadableException.
     *
     * @param exception the exception.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorInformation handleNotReadableException(HttpMessageNotReadableException exception)
    {
        String errorMessage = exception.getMessage();
        if (exception.getRootCause() != null)
        {
            errorMessage = exception.getRootCause().getMessage();
        }
        return getErrorInformation(HttpStatus.BAD_REQUEST, new RuntimeException(errorMessage));
    }

    /**
     * Handle exceptions that result in a "conflict" status.
     */
    @ExceptionHandler(value = {AlreadyExistsException.class, ObjectAlreadyExistsException.class, OptimisticLockException.class})
    @ResponseStatus(HttpStatus.CONFLICT)
    @ResponseBody
    public ErrorInformation handleConflictException(Exception exception)<commit_msg>DM-14329: Remove XSD error messages - Get the original JSON parser error message from Spring HttpMessageNotReadableException wrapped message<commit_after>/*
* Copyright 2015 herd contributors
*
* 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.finra.herd.service.helper;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.DataTruncation;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.servlet.http.HttpServletResponse;

import org.activiti.engine.ActivitiClassLoadingException;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.javax.el.ELException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.quartz.ObjectAlreadyExistsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MissingServletRequestParameterException;
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.method.annotation.ExceptionHandlerMethodResolver;
import org.xml.sax.SAXParseException;

import org.finra.herd.model.AlreadyExistsException;
import org.finra.herd.model.MethodNotAllowedException;
import org.finra.herd.model.ObjectNotFoundException;
import org.finra.herd.model.api.xml.ErrorInformation;

/**
 * A class that handles various types of exceptions and returns summary error information containing the HTTP status, HTTP status description, and the error
 * message that provides details about the error. Note that all "ExceptionHandler" annotated method that take additional parameters besides the exception itself
 * should ensure that the method can handle cases when the other parameters are null. This is due to the isReportableError method which will invoke the
 * exception handler methods to get the error information which is needed to determine if the exception is reportable.
 */
@Component
public class HerdErrorInformationExceptionHandler
{
    /**
     * The Oracle database specific error code for data too large.
     */
    public static final int ORACLE_DATA_TOO_LARGE_ERROR_CODE = 12899;

    /**
     * The Oracle database specific error code for "can bind a LONG value only for insert into a LONG column". This could happen when the user enters a value
     * that is > 4000 bytes for a VARCHAR.
     */
    public static final int ORACLE_LONG_DATA_IN_LONG_COLUMN_ERROR_CODE = 1461;

    /**
     * Oracle specific SQL state code for generic SQL statement execution errors. https://docs.oracle.com/cd/E15817_01/appdev.111/b31228/appd.htm
     */
    public static final String ORACLE_SQL_STATE_CODE_ERROR = "72000";

    public static final String POSTGRES_SQL_STATE_CODE_FOREIGN_KEY_VIOLATION = "23503";

    public static final String POSTGRES_SQL_STATE_CODE_UNIQUE_INDEX_OR_PRIMARY_KEY_VIOLATION = "23505";

    /**
     * PostgreSQL specific SQL state code for string data truncation errors. http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
     */
    public static final String POSTGRES_SQL_STATE_CODE_TRUNCATION_ERROR = "22001";

    private static final Logger LOGGER = LoggerFactory.getLogger(HerdErrorInformationExceptionHandler.class);

    // A flag that determines whether this class will log errors or not.
    // When using the isReportableError method of this class, we typically don't want to enable logging which is why we're defaulting it to false.
    // When using the class as a normal exception handler (e.g. via a ControllerAdvice bean that extends this class), we typically want to enable logging.
    private boolean loggingEnabled = false;

    // An exception handler method resolver that will resolve exception handling methods based on exceptions.
    @Autowired
    private ExceptionHandlerMethodResolver resolver;

    /**
     * Handle access denied exceptions.
     *
     * @param exception the exception.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ResponseBody
    public ErrorInformation handleAccessDeniedException(Exception exception)
    {
        return getErrorInformation(HttpStatus.FORBIDDEN, exception);
    }

    /**
     * Handle Activiti exceptions. Note that this method properly handles a null response being passed in.
     *
     * @param exception the exception.
     * @param response the response.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = ActivitiException.class)
    @ResponseBody
    public ErrorInformation handleActivitiException(Exception exception, HttpServletResponse response)
    {
        if ((ExceptionUtils.indexOfThrowable(exception, ActivitiClassLoadingException.class) != -1) ||
            (ExceptionUtils.indexOfType(exception, ELException.class) != -1))
        {
            // These exceptions are caused by invalid workflow configurations (i.e. user error) so they are considered a bad request.
            return getErrorInformationAndSetStatus(HttpStatus.BAD_REQUEST, exception, response);
        }
        else
        {
            // For all other exceptions, something is wrong that we weren't expecting so we'll return this as an internal server error and log the error.
            logError("An Activiti error occurred.", exception);
            return getErrorInformationAndSetStatus(HttpStatus.INTERNAL_SERVER_ERROR, exception, response);
        }
    }

    /**
     * Handle exceptions that result in a "bad request" status.
     *
     * @param exception the exception.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = {IllegalArgumentException.class, MissingServletRequestParameterException.class, TypeMismatchException.class,
        UnsupportedEncodingException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorInformation handleBadRequestException(Exception exception)
    {
        return getErrorInformation(HttpStatus.BAD_REQUEST, exception);
    }

    /**
     * Handle Spring HttpMessageNotReadableException.
     *
     * @param exception the exception.
     *
     * @return the error information.
     */
    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorInformation handleNotReadableException(HttpMessageNotReadableException exception)
    {
        String errorMessage = exception.getMessage();
        // Return clean and meaningful root cause message in case of XML parser error
        Throwable rootCause = exception.getRootCause();
        if (rootCause instanceof SAXParseException)
        {
            errorMessage = rootCause.getMessage();
        }
        // Get the original JSON parser error message from Spring HttpMessageNotReadableException wrapped message
        else if (exception.getCause() != null)
        {
            errorMessage = errorMessage.replace(String.format("; nested exception is %s", exception.getCause().toString()), "");
        }
        return getErrorInformation(HttpStatus.BAD_REQUEST, new RuntimeException(errorMessage));
    }

    /**
     * Handle exceptions that result in a "conflict" status.
     */
    @ExceptionHandler(value = {AlreadyExistsException.class, ObjectAlreadyExistsException.class, OptimisticLockException.class})
    @ResponseStatus(HttpStatus.CONFLICT)
    @ResponseBody
    public ErrorInformation handleConflictException(Exception exception)
{'fraction_non_alphanumeric': 0.07120141342756184, 'fraction_numerical': 0.007832744405182567, 'mean_word_length': 4.068955223880597, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 3}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4160029', 'n_tokens_mistral': 4356, 'n_tokens_neox': 4214, 'n_words': 1335}