/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
// Package
package gov.va.med.fw.report.data.hibernate;

// Java classes
import java.util.List;

// Library classes
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.HibernateException;
import org.apache.commons.lang.Validate;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.dao.DataAccessException;

// Framework classes
import gov.va.med.fw.persistent.hibernate.AbstractDAOImpl;
import gov.va.med.fw.report.ReportConfiguration;
import gov.va.med.fw.report.data.ReportDataException;
import gov.va.med.fw.report.data.ReportDataDAO;
import gov.va.med.fw.report.data.PostProcessedList;

/**
 * Encapsulates logics to execute a query to retrieve report data 
 *
 * Project: Framework</br>
 * Created on: 4:32:29 PM </br>
 *
 * @author DNS   LEV
 */
public abstract class HibernateReportDataDAO extends AbstractDAOImpl implements ReportDataDAO {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 2573263207581367146L;
	
	/**
	 * A name of a named query in a hibernate mapping file
	 */
	private String queryName = null;

	/**
	 * An instance of fetchSize
	 */
	private int fetchSize = 100;
	
	/**
	 * An instance of cacheable
	 */
	private boolean cacheable = true;
	
	/**
	 * A flag to indicate whether to paginate
	 */
	private boolean paginated = true;
	
	/**
	 * An instance of postProcessRequired
	 */
	private boolean postProcessRequired = true;
	   
	/**
	 * A default constructor
	 */
	protected HibernateReportDataDAO() {
		super();
	}
	
	/**
	 * @see org.springframework.dao.support.DaoSupport#initDao()
	 */
	protected void initDao() throws Exception {
		super.initDao();
		Validate.notNull( this.queryName, "A hibernate quey name must be configured" );
	}

	/** Returns a flag to determine if post processing is required
	 * @return True if required to perform post processing. False otherwise
	 */
	public boolean isPostProcessRequired() {
		return postProcessRequired;
	}

	/** Sets a flag to indicate if post processing is required
	 * @param postProcessRequired
	 */
	public void setPostProcessRequired(boolean postProcessRequired) {
		this.postProcessRequired = postProcessRequired;
	}

	/** The maximum number of records to be returned in a result list at one time
	 * @param fetchSize The fetchSize to set.
	 */
	public void setFetchSize(int fetchSize) {
		this.fetchSize = fetchSize;
	}
	
	/**
	 * @param queryName The queryName to set.
	 */
	public void setQueryName(String queryName) {
		this.queryName = queryName;
	}

	/**
	 * @param cacheable The cacheable to set.
	 */
	public void setCacheable(boolean cacheable) {
		this.cacheable = cacheable;
	}

	/**
	 * @param paginated The paginated to set.
	 */
	public void setPaginated(boolean paginated) {
		this.paginated = paginated;
	}

	/**
    * Returns an instance of cacheable
    * @return boolean cacheable.
    */
   public boolean isCacheable() {
      return cacheable;
   }

   /**
    * Returns an instance of fetchSize
    * @return int fetchSize.
    */
   public int getFetchSize() {
      return fetchSize;
   }

   /**
    * Returns an instance of paginated
    * @return boolean paginated.
    */
   public boolean isPaginated() {
      return paginated;
   }

	/**
	 * @see gov.va.med.fw.report.data.ReportDataDAO#getRecords(gov.va.med.fw.report.ReportCriteria, int, int)
	 */
	public List getRecords( final ReportConfiguration config, final int firstRecord, final int maxRecord ) throws ReportDataException {

		Validate.notNull( config, "A report configuration must not be NULL" );
		Validate.notNull( config.getQueryCriteria(), "A report criteria must not be NULL" );

      HibernateCallback callback = new HibernateCallback() {
         public Object doInHibernate( Session session ) {

            List results = null; 
            
            try {
               results = buildQuery( config, session ).setCacheable( isCacheable() )
                                                      .setFirstResult( firstRecord )
                                                      .setMaxResults( isPaginated() ? maxRecord : Integer.MAX_VALUE )
                                                      .setFetchSize( maxRecord > getFetchSize() ? maxRecord : getFetchSize() )
                                                      .list();
            }
            catch( ReportDataException e ) {
               throw new HibernateException( "Failed to retrieve a result set in a report dao", e );
            }
            return results;
         }
      };
      
      List results = null;
      try {
         HibernateTemplate tpl = this.getHibernateTemplate();
         
         results = tpl.executeFind(callback);
         
         if( !results.isEmpty() && this.isPostProcessRequired() ) {
            results = new PostProcessedList( this.postDataRetrieval( results, config ), results );
         }
      }
      catch( DataAccessException e ) {
         throw new ReportDataException( "Failed to execute a query to retrieve report data", e);
      }
     
		return results;
	}
	
	/** Allows concrete implementation class to perform any pre process before data is retrieved 
	 * @param config A report configuration 
	 * @throws ReportDataException In case of errors accessing a collection
	 */
	public void preDataRetrieval( final ReportConfiguration config )  throws ReportDataException {
		// Empty implementation
	}
	
	/** Allows a derived class to perform any post process after data is retrieved 
	 * 
	 * @param results A collection of result items
	 * @param config A report configuration 
	 * @return A collection returned from a postDataRetrieval method is called
	 * @throws ReportDataException In case of errors accessing a collection
	 */
	public List postDataRetrieval( final List results, final ReportConfiguration config )  throws ReportDataException {
		return results;
	}
	
   /** Returns the query name 
    * @return Query name string 
    */
   protected String getQueryName() {
      return this.queryName;
   }
       
   /** Returns a named query using a query name from a setQueryName method
    * @return A hibernate query 
    */
   protected Query getNamedQuery( Session session ) {
      return this.getNamedQuery( this.queryName, session );
   }
   
   /** Returns the specific named query defined in a mapping file
    *  
    * @param name A name of a query string to look for in a mapping file
    * @return A hibernate query
    */
   protected Query getNamedQuery( String name, Session session ) {
      return session != null ? session.getNamedQuery( name ) : null;
   }
   
	/** Allows a derived class to build its specific report query
	 * @param config encapsulates parameter to build a report query
	 * @return A query to execute
	 */
	protected abstract Query buildQuery( ReportConfiguration config, Session session ) throws ReportDataException;
}