/**
 * 
 */


package gov.va.med.cds.aspects.exception;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * Swallow exception that is invoked with 'around' advice.
 *  
 * When an exception is thrown from an advised call:
 * 1. Check to see if only a specific exception has to be handled
 * 		-- The exception thrown is matched against the specified criteria
 * 		-- If criteria is not set, all exceptions are handled
 * 2. If only specific exception is to be handled, look for exception
 *    in the exception message or in the exception stack trace
 * 		-- This is determined by the criteria type that has been set
 * 3. Once it is determined that the exception thrown should be handled, swallow the exception
 * 
 * @author susarlan
 *
 */
public class SwallowAspect
    extends
        LogAspect
{
    /**
     * Exception criteria types
     * These values are used to determine what to match the criteria against:
     * exception message (exception.getMessage()) or exception stack elements (exception.getStackTrace())
     * 
     */
    private enum CriteriaTypeEnum
    {
        MESSAGE, STACKTRACE
    };

    /**
     * Properties that should be set up in order to ensure 'advice' on specific exceptions.
     * 
     */
    protected CriteriaTypeEnum criteriaType = null;
    protected String criteria = null;


    public void setCriteriaType( String criteriaType )
    {
        this.criteriaType = CriteriaTypeEnum.valueOf( criteriaType );
    }


    public void setCriteria( String criteria )
    {
        this.criteria = criteria;
    }


    /***
     * Log the exception and do not propagate any further.
     * Calls logException in order to log the exception
     * to a specified error logger or to a default logger
     * prior to propagating the exception 
     * 
     * Example point-cut that indicates the method around which this
     * advice will be executed.
     * ----Around("execution(* *..*.response.util.ResponseErrorSectionExtractor.check*(..))")
     *
     * 
     * @param call - the call that was interrupted
     * @param call
     * @return
     * @throws Throwable
     */
    public Object doAround( ProceedingJoinPoint call )
        throws Throwable
    {
        Object retValue = null;

        /*
         * use a try/catch block to ensure that any exceptions
         * that may occur during the call are handled as required.
         */
        try
        {
            // proceed with the call
            return call.proceed();
        }
        catch ( Throwable t )
        {
            handleException( t );
        }
        return retValue;
    }


    /**
     * Indicate whether exception is to be logged
     * 
     * @return
     */
    protected boolean isLog( )
    {
        return false;
    }


    /**
     * Check for criteria
     * 
     * @return
     */
    protected boolean checkCriteria( )
    {
        return ( ( criteria == null || criteriaType == null ) ? true : false );
    }


    /***
     * Swallow the exception
     * 
     * @param t
     * @throws Throwable
     */
    protected void handleException( Throwable t )
        throws Throwable
    {
        boolean foundCriteria = inspectException( t );

        if ( !foundCriteria )
        {
            throw t;
        }

        if ( isLog() )
        {
            logException( t );
        }
    }


    /***
     * Inspect the exception to se if it matches the criteria
     * @param t
     * @return
     */
    protected boolean inspectException( Throwable t )
    {
        boolean foundCriteria = checkCriteria();
        if ( foundCriteria )
        {
            return true;
        }

		switch(criteriaType)
		{
			case MESSAGE:
			{
				// Check if the message contains the specified criteria
				String message = t.getMessage();
				if(message != null && message.contains(this.criteria))
					foundCriteria = true;
					break;
			}
			case STACKTRACE:
			{
				if(t.getClass().getName().equals(this.criteria))
				{
					foundCriteria = true;
				}
				else
				{
					if(t.toString().contains(this.criteria))
					{
						foundCriteria = true;
					}
					else
					{
						StackTraceElement[] ste = t.getStackTrace();
						for(StackTraceElement ex : ste)
						{
							if(ex.getClassName().equals(this.criteria))
							{
								foundCriteria = true;
								break;
							}
						}
					}
				}
				break;
			}
		}
		return foundCriteria;
	}
}
