#region License

/*
 * Copyright © 2002-2006 the original author or authors.
 * 
 * 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.
 */

#endregion

#region Imports

using System;
using Common.Logging;
using Spring.Dao;
using Spring.Data.Common;

#endregion

namespace Spring.Data.Support
{
	/// <summary>
	/// Implementation of IAdoExceptionTranslator that analyzes provider specific
	/// error codes and translates into the DAO exception hierarchy.
	/// </summary>
	/// <remarks>This class loads the obtains error codes from
	/// the IDbProvider metadata property ErrorCodes  
	/// which defines error code mappings for various providers.
	/// </remarks>
	/// <author>Mark Pollack (.NET)</author>
	public class ErrorCodeExceptionTranslator : IAdoExceptionTranslator
	{
		#region Fields

	    private ErrorCodes errorCodes;

        private IAdoExceptionTranslator fallbackTranslator;
	    
	    private IDbProvider dbProvider;
	    
		#endregion

		#region Constants

		/// <summary>
		/// The shared log instance for this class (and derived classes). 
		/// </summary>
		protected static readonly ILog log =
			LogManager.GetLogger(typeof (ErrorCodeExceptionTranslator));

		#endregion

		#region Constructor (s)

        /// <summary>
        /// Initializes a new instance of the <see cref="ErrorCodeExceptionTranslator"/> class.
        /// </summary>
        /// <param name="provider">The data provider.</param>
	    public ErrorCodeExceptionTranslator(IDbProvider provider)
	    {
	        DbProvider = provider;
	    }



        /// <summary>
        /// Initializes a new instance of the <see cref="ErrorCodeExceptionTranslator"/> class.
        /// </summary>
        /// <param name="provider">The data provider.</param>
        /// <param name="ec">The error code collection.</param>
        public ErrorCodeExceptionTranslator(IDbProvider provider, ErrorCodes ec)
        {
            DbProvider = provider;
            errorCodes = ec;
        }
        
	  

		#endregion

		#region Properties

        /// <summary>
        /// Sets the db provider.
        /// </summary>
        /// <value>The db provider.</value>
	    public IDbProvider DbProvider
	    {
	        set
	        {
	            dbProvider = value;
                fallbackTranslator = new FallbackExceptionTranslator();
                errorCodes = dbProvider.DbMetadata.ErrorCodes;
	            
	        }
	    }
	    
        /// <summary>
        /// Gets the error codes for the provider
        /// </summary>
        /// <value>The error codes.</value>
	    public ErrorCodes ErrorCodes
	    {
	        get
	        {
	            return errorCodes;
	        }
	    }
	    
		#endregion

        /// <summary>
        /// Gets or sets the fallback translator to use in case error code based translation
        /// fails.
        /// </summary>
        /// <value>The fallback translator.</value>
	    public IAdoExceptionTranslator FallbackTranslator
	    {
	        get
	        {
	            return fallbackTranslator;
	        }
	        set
	        {
	            fallbackTranslator = value;
	        }
	    }
	    

		#region Methods

		#endregion


        /// <summary>
	    /// Translate the given <see cref="System.SystemException"/> into a generic data access exception.
	    /// </summary>
	    /// <param name="task">A readable string describing the task being attempted.</param>
	    /// <param name="sql">The SQL query or update that caused the problem. May be null.</param>
	    /// <param name="exception">
	    /// The <see cref="System.Exception"/> encountered by the ADO.NET implementation.
	    /// </param>
	    /// <returns>
	    /// A <see cref="Spring.Dao.DataAccessException"/> appropriate for the supplied
	    /// <paramref name="exception"/>.
	    /// </returns>
        public virtual DataAccessException Translate(string task, string sql, Exception exception)
        {
            if (task == null)
            {
                task = "";
            }
            if (sql == null)
            {
                sql = "";
            }
            string errorCode = ExtractErrorCode(exception);

            // First, try custom translation from overridden method.
            DataAccessException dex = DoTranslate(task, sql, errorCode, exception);
            if (dex != null)
            {
                // Specific exception match found.
                return dex;
            }
            // Looking for a fallback...
            if (log.IsDebugEnabled)
            {
                log.Debug("Unable to translate exception eith errorCode '" + errorCode + "', will use the fallback translator");
            }
            IAdoExceptionTranslator fallback = FallbackTranslator;
            if (fallback != null)
            {
                return FallbackTranslator.Translate(task, sql, exception);
            }
            // We couldn't identify it more precisely.
            return new UncategorizedAdoException(task, sql, errorCode, exception);
            
            
        }

        /// <summary>
        /// Template method for actually translating the given exception.
        /// given <see cref="System.SystemException"/> into a generic data access exception.
        /// </summary>
        /// <param name="task">A readable string describing the task being attempted.</param>
        /// <param name="sql">The SQL query or update that caused the problem. May be null.</param>
        /// <param name="errorCode">The error code.</param>
        /// <param name="exception">The <see cref="System.Exception"/> encountered by the ADO.NET implementation.</param>
        /// <returns>
        /// A <see cref="Spring.Dao.DataAccessException"/> appropriate for the supplied
        /// <paramref name="exception"/>.
        /// </returns>
        /// <remarks>
        /// The passed-in arguments will have been pre-checked.  furthermore, this method is allowed to
        /// return <code>null</code> to indicate that no exception match has been found and that
        /// fallback translation should kick in.
        /// </remarks>
	    protected virtual DataAccessException DoTranslate(string task, string sql, string errorCode, Exception exception)
	    {
            // First, try custom translation from overridden method.
            DataAccessException dex = TranslateException(task, sql, errorCode, exception);
            if (dex != null)
            {
                return dex;
            }


	        if (errorCodes != null)
	        {

	            if (errorCode != null)
	            {
                    if (Array.IndexOf(errorCodes.BadSqlGrammarCodes, errorCode) >= 0)
	                {
	                    LogTranslation(task, sql, errorCode, exception, false);
	                    return new BadSqlGrammarException(task, sql, exception);
	                }
                    else if (Array.IndexOf(errorCodes.InvalidResultSetAccessCodes, errorCode) >= 0) 
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new InvalidResultSetAccessException(task, sql, exception);
                    }
                    else if (Array.IndexOf(errorCodes.DuplicateKeyCodes, errorCode) >= 0)
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new DuplicateKeyException(task, sql, exception);
                    }
                    else if (Array.IndexOf(errorCodes.DataAccessResourceFailureCodes, errorCode) >= 0) 
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new DataAccessResourceFailureException(BuildMessage(task, sql, exception), exception);
                    }
                    else if (Array.IndexOf(errorCodes.TransientAccessResourceFailureCodes, errorCode) >= 0)
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new TransientDataAccessResourceException(BuildMessage(task, sql, exception), exception);
                    }
                    else if (Array.IndexOf(errorCodes.PermissionDeniedCodes, errorCode) >= 0) 
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new PermissionDeniedDataAccessException(BuildMessage(task, sql, exception), exception);
                    }
                    else if (Array.IndexOf(errorCodes.DataIntegrityViolationCodes, errorCode) >= 0) 
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new DataIntegrityViolationException(BuildMessage(task, sql, exception), exception);
                    }
                    else if (Array.IndexOf(errorCodes.CannotAcquireLockCodes, errorCode) >= 0) 
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new CannotAcquireLockException(BuildMessage(task, sql, exception), exception);
                    }
                    else if (Array.IndexOf(errorCodes.DeadlockLoserCodes, errorCode) >= 0) 
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new DeadlockLoserDataAccessException(BuildMessage(task, sql, exception), exception);
                    }
                    else if (Array.IndexOf(errorCodes.CannotSerializeTransactionCodes, errorCode) >= 0) 
                    {
                        LogTranslation(task, sql, errorCode, exception, false);
                        return new CannotSerializeTransactionException(BuildMessage(task, sql, exception), exception);
                    }
	            }
	        }
	        return null;
	    }

        /// <summary>
        /// Subclasses can override this method to attempt a custom mapping from a data access Exception
        /// to DataAccessException.
        /// </summary>
        /// <param name="task">Readable text describing the task being attempted.</param>
        /// <param name="sql">SQL query or update that caused the problem. May be <code>null</code>.</param>
        /// <param name="errorCode">The error code extracted from the generic data access exception for
        /// a particular provider.</param>
        /// <param name="exception">The exception thrown from a data access operation.</param>
        /// <returns>
        /// null if no custom translation was possible, otherwise a DataAccessException
        /// resulting from custom translation.  This exception should include the exception parameter
        /// as a nested root cause. This implementation always returns null, meaning that
        /// the translator always falls back to the default error codes.
        /// </returns>
	    protected virtual DataAccessException TranslateException(string task, string sql, string errorCode, Exception exception)
	    {
	        return null;
	    }


	    /// <summary>
	    /// Builds the message.
	    /// </summary>
	    /// <param name="task">The task.</param>
	    /// <param name="sql">The SQL.</param>
	    /// <param name="exception">The exception.</param>
	    /// <returns></returns>
	    protected virtual string BuildMessage(string task, string sql, Exception exception)
	    {
	        return task + "; SQL [" + sql + "]; " + exception.Message;
	    }

	    /// <summary>
        /// Determines whether the specified exception is valid data access exception.
        /// </summary>
        /// <param name="e">The exception.</param>
        /// <returns>
        /// 	<c>true</c> if is valid data access exception; otherwise, <c>false</c>.
        /// </returns>
	    public bool IsValidDataAccessException(Exception e)
	    {
	       return dbProvider.IsDataAccessException(e);
        }

        /// <summary>
        /// Logs the translation.
        /// </summary>
        /// <param name="task">The task.</param>
        /// <param name="sql">The SQL.</param>
        /// <param name="errorCode">The error code.</param>
        /// <param name="exception">The exception.</param>
        /// <param name="b">if set to <c>true</c> [b].</param>
	    private void LogTranslation(string task, string sql, string errorCode, Exception exception, bool b)
	    {
            if (log.IsDebugEnabled) 
            {
                String intro = "Translating";
                log.Debug(intro + " ADO exception with error code '" + errorCode
                          + "', message [" + exception.Message +
                    "]; SQL was [" + sql + "] for task [" + task + "]");
            }
	    }

	    private string ExtractErrorCode(Exception exception)
	    {
            // Use provider specific reflection information to extract error code.
	        return dbProvider.ExtractError(exception);
	    }
	}
}
