/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/

// Package
package gov.va.med.fw.persistent.hibernate;

// Java Classes
import java.io.Serializable;
import java.util.List;

// Library classes
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

// Framework classes
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.model.AbstractKeyedEntity;
import gov.va.med.fw.model.BaseEntityKey;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.persistent.DAO;
import gov.va.med.fw.persistent.DAOException;

/**
 * An abstract base class for all DAO classes that use hibernate framework to
 * persist data
 *
 * @author Vu Le
 * @version 1.0
 */
public class AbstractDAOImpl extends HibernateDaoSupport
   implements DAO, BeanNameAware, ApplicationContextAware {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = -126845924476530922L;

	/**
	 * An instance of name
	 */
	private String name = null;

   /**
    * An instance of context
    */
   private ApplicationContext context = null;

	/**
	 * A default constructor
	 */
	public AbstractDAOImpl() {
		super();
	}

	/**
	 * Provides concrete implementation using Hibernate to return a object by
	 * its id from a database
	 */
	public EntityKey insertObject(Object obj) throws DAOException {
		EntityKey key = null;
		try {
			Serializable id = getHibernateTemplate().save(obj);
			if(obj instanceof AbstractKeyedEntity)
				key = ((AbstractKeyedEntity) obj).getEntityKey();
			else
				key = new BaseEntityKey(id, obj.getClass());
		}
		catch (DataAccessException e) {
			throw new DAOException("exception loading '"
					+ obj.getClass().getName(), e);
		}
		return key;
	}

	/**
	 * Provides concrete implementation using Hibernate to return a object by
	 * its id from a database
	 */
	public Object getObject(Object obj, String id) throws DAOException {

		Object value = null;
		try {
			value = getHibernateTemplate().get(obj.getClass(), id);
		}
		catch (DataAccessException e) {
			throw new DAOException("exception loading '" + id.getClass().getName() + "' with id '" + id + "'", e);
		}
		return value;
	}

	/**
	 * Generic method to return a persistent instance of a given entity class
	 * with the given identifier, or null if not found
	 *
	 * @param obj
	 *            An entity to query
	 * @param id
	 *            An entity's id
	 * @return a populated object (or null if id doesn't exist)
	 * @throws DAOException
	 *             Thrown if failed to query an object
	 */
	public Object getByKey(EntityKey key) throws DAOException {

		if (key.getEntityClass() == null)
			throw new IllegalArgumentException(
					"Must specify a non-null entity class in the EntityKey");

		Object value = null;
		try {
			value = getHibernateTemplate().get(key.getEntityClass(), key.getKeyValue());
		} catch (DataAccessException e) {
			throw new DAOException("exception loading '"
					+ key.getEntityClass().getName() + "' with key '" + key
					+ "'", e);
		}
		return value;
	}

	/**
	 * Saves an object to a database using Hibernate technology
	 *
	 * @see gov.va.med.fw.persistent.DAO#saveObject(java.lang.Object)
	 */
	public void saveObject(Object obj) throws DAOException {
		try {
			if(obj instanceof AbstractKeyedEntity) {
				AbstractKeyedEntity keyedObject = (AbstractKeyedEntity) obj;
				if(keyedObject.getEntityKey() != null) {
					// (if same reference, this should be a no-op)
					getHibernateTemplate().saveOrUpdate(obj);
				} else {
					getHibernateTemplate().save(keyedObject);
				}

			} else {
				getHibernateTemplate().saveOrUpdate(obj);
			}
		} catch (DataAccessException e) {
            if (logger.isErrorEnabled())
            {
                logger.error("Error during save: " + e.getMessage());
                logger.error("Failed to persist an entity"); // + obj);
            }
            Throwable rootCause =  e.getCause();
    		while ( rootCause.getCause() != null ) {
    			rootCause = e.getCause();
    			rootCause.printStackTrace();
    		}
    		rootCause.printStackTrace();
			throw new DAOException("Failed to persist an entity ", e);
		}
	}

	/**
	 * Remove an object from a database using Hibernate technology
	 *
	 * @see gov.va.med.fw.persistent.DAO#removeObject(java.lang.Object,
	 *      java.lang.String)
	 */
	public void removeObject(EntityKey id) throws DAOException {
		Object value = getByKey(id);
		if (value != null) {
			try {
				getHibernateTemplate().delete(value);
			} catch (DataAccessException e) {
				throw new DAOException("Failed to remove an entity with id: "
						+ id, e);
			}
		}
	}

	/**
	 * Generic method to execute a simple query
	 *
	 * @param query A named query to execute. Implemented as the key for a named query.
	 * @throws DAOException Thrown if failed to remove an object
	 */
	public List find( String query ) throws DAOException {
		//CCR12710
		if( logger.isDebugEnabled() ) {
            logger.debug( "AbstractDAOImpl running query:" + query );
         }

		List list = null;
		if (query != null)
			try {
				list = getHibernateTemplate().findByNamedQuery(query);
			} catch (DataAccessException e) {
				throw new DAOException("Failed to get an entity by HSQL:"
						+ query, e);
			}
		return list;
	}

   /**
	 * Sets a component name to identify this component in a container
	 *
	 * @param name
	 *            A component name
	 */
	public void setBeanName(String name) {
		this.name = name;
	}

	/**
	 * Returns a name identifying this component in a container
	 *
	 * @return A bean's name.
	 */
	public String getBeanName() {
		return name;
	}

   /**
    * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
    */
   public void setApplicationContext(ApplicationContext applicationContext) {
      this.context = applicationContext;
   }

   /**
    * Provides a convenient method to obtain a bean from a spring
    * context.
    *
    * @param name A bean name to look up in a context
    * @return A bean defined in the context
    * @throws BeansException If failed to retrieve a bean
    */
   protected Object getComponent( String name )  throws ServiceException {
      try {
         return this.context != null ? this.context.getBean( name ) : null;
      }
      catch( BeansException e ) {
         throw new ServiceException( "Failed to obtain a service " + name, e );
      }
   }

   /**
    * Provides a convenient method to obtain a bean from a spring
    * context for the specific type.
    *
    * @param name A bean's name defined in spring context
    * @param type A bean's class type
    * @return A bean defined in the context
    * @throws BeansException If failed to retrieve a bean
    */
   protected Object getComponent( String name, Class type )  throws ServiceException {
      try {
         return this.context != null ? this.context.getBean( name, type ) : null;
      }
      catch( BeansException e ) {
         throw new ServiceException( "Failed to obtain a service " + name + " type " + type, e );
      }
   }

   /**
    * @see org.springframework.dao.support.DaoSupport#initDao()
    */
   protected void initDao() throws Exception {
      super.initDao();
      Validate.notNull( this.context, "An application context must not be null" );
   }
}