

package gov.va.med.cds.ars.exceptionframework;


import gov.va.med.cds.exception.AbstractCdsBaseException;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.ars.exception.InitializationException;
import gov.va.med.cds.exceptionframework.ExceptionInfo;
import gov.va.med.cds.exceptionframework.ExceptionLoggerInterface;
import gov.va.med.cds.exceptionframework.ExceptionUtil;
import gov.va.med.cds.exceptionframework.LoggingSeverity;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * This class is used for: logging the exceptions using loggers getting the error codes getting client messages placing
 * exceptions on communication queues (not implemented)
 * 
 */
public class BaseExceptionHandler
    implements
        ExceptionHandlerInterface
{
    private ExceptionLoggerInterface guaranteedLogger = null;

    private Map<String, List<ExceptionInfo>> exceptionInfoListMap = new HashMap<String, List<ExceptionInfo>>();


    // handle ars exceptions to get messages and error codes and other neat stuff.
    private void handleArsException( AbstractCdsBaseException businessException, String entryPoint, String requestId, String uniqueIdentifier, String applicationName )
    {
        ErrorCodeEnum errorCodeEnum = businessException.getErrorCode();

        String clientMessage = null;
        if ( errorCodeEnum != null )
        { // handle default behavior for mapped exceptions

            clientMessage = ExceptionUtil.formatMessage( errorCodeEnum.getClientMessage(), businessException.getCustomMsgValues() );
            populateExceptionInfo( uniqueIdentifier, businessException, clientMessage, errorCodeEnum, errorCodeEnum.getLoggingSeverity() );
            // why are we not using ExceptionInfo
            for ( ExceptionLoggerInterface logger : errorCodeEnum.getLoggers() )
            {
                try
                {
                    logger.log( getExceptionInfoList(uniqueIdentifier), errorCodeEnum.getLoggingSeverity(), entryPoint, requestId, applicationName );// loggers like EELS, perhaps auditing
                }
                catch ( RuntimeException e )
                {
                    getGuaranteedLogger().log( populateExceptionInfo( uniqueIdentifier, businessException, "RuntimeException" ),
                                    "logger " + logger + "not configured.", entryPoint, requestId, applicationName );
                }
            }

            getGuaranteedLogger().log( getExceptionInfoList(uniqueIdentifier), errorCodeEnum.getLoggingSeverity(), entryPoint, requestId, applicationName );

            if ( errorCodeEnum.getLoggingSeverity() == LoggingSeverity.FATAL )
            {
                throw businessException;
            }

        }
        else
        {

            // log that the exception needs to be mapped
            // log the business exception and populate in the fatal error list
            // throw business exception

            String msg = "Exception not mapped: ";
            clientMessage = msg + businessException.getClass().getName();

            getGuaranteedLogger().log( populateExceptionInfo( uniqueIdentifier, businessException, clientMessage ), clientMessage, entryPoint, requestId,
                            applicationName );
            throw businessException;
        }
    }


    private ExceptionInfo populateExceptionInfo( String uniqueIdentifier, Throwable businessException, String clientMessage, ErrorCodeEnum errorCode,
                    LoggingSeverity loggingSeverity )
    {
        ExceptionInfo exceptionInfo = new ExceptionInfo();

        exceptionInfo.setClientMessage( clientMessage );
        exceptionInfo.setErrorCode( errorCode );
        exceptionInfo.setException( businessException );
        exceptionInfo.setExceptionMessage( businessException.getMessage() );
        exceptionInfo.setLoggingSeverity( loggingSeverity );

        getExceptionInfoList(uniqueIdentifier).add( exceptionInfo );

        return exceptionInfo;
    }


    /**
     * Wrap the runtime exception that occurs to be passed to the module which logs the errors
     * 
     * @param runtimeException
     * @param clientMessage
     * @return
     */
    private ExceptionInfo populateExceptionInfo( String uniqueIdentifier, RuntimeException runtimeException, String clientMessage )
    {
        ExceptionInfo exceptionInfo = new ExceptionInfo();

        exceptionInfo.setClientMessage( clientMessage );
        exceptionInfo.setRuntimeException( runtimeException );

        getExceptionInfoList(uniqueIdentifier).add( exceptionInfo );

        return exceptionInfo;
    }


    /**
     * 
     * @see gov.gov.va.med.cds.ars.exceptionframework.ExceptionHandlerInterface#handleException(RuntimeException, String,
     *      String, String)
     */
    public void handleException( RuntimeException exception, String entryPoint, String requestId, String uniqueIdentifier, String applicationName )
    {
        if ( getGuaranteedLogger() == null )
        {
            throw new InitializationException( ErrorCodeEnum.GUARANTEED_LOGGER_NOT_CONFIGURED_AND_NULL );
        }

        if ( exception instanceof AbstractCdsBaseException )
        {
            handleArsException( ( AbstractCdsBaseException )exception, entryPoint, requestId, uniqueIdentifier, applicationName );
        }
        else
        { // runtime exceptions don't have CDS error codes, so handle them specially.
            String clientMessage = "Runtime exception occured: " + exception.getClass().getName();

            ExceptionInfo exp = populateExceptionInfo( uniqueIdentifier, exception, clientMessage, ErrorCodeEnum.ROOT_CAUSE_MSG, LoggingSeverity.ERROR );

            getGuaranteedLogger().log( exp, clientMessage, entryPoint, requestId, applicationName );
            throw exception;
        }
    }


    /**
     * 
     * @see gov.gov.va.med.cds.ars.exceptionframework.ExceptionHandlerInterface#clear()
     */
    public synchronized void clear( String uniqueIdentifier )
    {
        if ( exceptionInfoListMap.containsKey( uniqueIdentifier ) )
        {
            exceptionInfoListMap.remove( uniqueIdentifier );
        }
    }


    /**
     * Gets the CDS default logger.
     * 
     * @return CDS default logger.
     */
    public ExceptionLoggerInterface getGuaranteedLogger( )
    {
        return guaranteedLogger;
    }


    /**
     * Sets the CDS default logger.
     * 
     * @param guaranteedLogger CDS default logger.
     */
    public void setGuaranteedLogger( ExceptionLoggerInterface guaranteedLogger )
    {
        this.guaranteedLogger = guaranteedLogger;
    }


    /**
     * 
     * @see gov.gov.va.med.cds.ars.exceptionframework.ExceptionHandlerInterface#getExceptionInfoList()
     */
    public synchronized List<ExceptionInfo> getExceptionInfoList( String uniqueIdentifier )
    {
        // TODO: since this class is instantiated as a single runtime instance
        // this method had to be synchronized - consider a different architecture
        // where a single BasicExceptionHandler instance is created per
        // request invocation - this would need to be accomplished through AOP
        // so that the exception handler doesn't have to be passed through all
        // method calls - TLH 07/25/2007
        if ( !exceptionInfoListMap.containsKey( uniqueIdentifier ) )
        {
            exceptionInfoListMap.put( uniqueIdentifier, new ArrayList<ExceptionInfo>() );
        }

        return exceptionInfoListMap.get( uniqueIdentifier );
    }


    /**
     * 
     * @see gov.gov.va.med.cds.ars.exceptionframework.ExceptionHandlerInterface#hasErrors()
     */
    public boolean hasErrors( String uniqueIdentifier )
    {
        return ( getExceptionInfoList(uniqueIdentifier).size() > 0 ) ? true : false;
    }
}
