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

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

//Java classes
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.type.Type;
import org.hibernate.util.JDBCExceptionReporter;
import org.hibernate.util.PropertiesHelper;

import gov.va.med.fw.model.BaseEntityKey;
import gov.va.med.fw.model.EntityKey;

/**
 * Generates <tt>BigDecimal</tt>,<tt>Long</tt>,<tt>Integer</tt>, or
 * <tt>Short</tt> values using an oracle-style sequence.<br>
 * <br>
 * Mapping parameters supported: sequence.
 * 
 * @see org.hibernate.id.SequenceGenerator
 * @author Martin Francisco
 * @version 1.0
 */
public class BigSequenceGenerator
    implements IdentifierGenerator, Configurable
{
    /**
     * The sequence parameter
     */
    public static final String SEQUENCE = "sequence";

    private String sequenceName;
    private Type type;
    private String sql;

    private static final Log log = LogFactory
        .getLog(BigSequenceGenerator.class);

    /**
     * Default constructor.
     */
    public BigSequenceGenerator()
    {
        super();
    }

    /** Could be extended by subclasses */
    protected EntityKey getEntityKey(BigDecimal val, Class targetClass) {
    	return new BaseEntityKey(val, targetClass);
    }
    
    private Serializable get(
        ResultSet rs,
        Type type,
        SessionImplementor session,
        Object owner) throws SQLException, IdentifierGenerationException
    {
        Class clazz = type.getReturnedClass();
        if(clazz == BigDecimal.class)
        {
            return rs.getBigDecimal(1);
        }
        else if(clazz == Long.class)
        {
            return new Long(rs.getLong(1));
        }
        else if(clazz == Integer.class)
        {
            return new Integer(rs.getInt(1));
        }
        else if(clazz == Short.class)
        {
            return new Short(rs.getShort(1));
        }
        else if(EntityKey.class.isAssignableFrom(clazz))
        {
            return getEntityKey(rs.getBigDecimal(1), owner.getClass());
        }        
        else
        {
            throw new IdentifierGenerationException(
                "this id generator generates BigDecimal, Long, Integer, Short, EntityKey.  It was handed a type: " + clazz.getName());
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.hibernate.id.IdentifierGenerator#generate(org.hibernate.engine.SessionImplementor,
     *      java.lang.Object)
     */
    public Serializable generate(SessionImplementor session, Object obj)
        throws HibernateException
    {
		PreparedStatement st = null;
		HibernateException cause = null;
        try
        {
	        st = session.getBatcher().prepareStatement(sql);			
            ResultSet rs = st.executeQuery();
            final Serializable result;
            try
            {
                rs.next();
                result = this.get(rs, type, session, obj);
            }
            finally
            {
                rs.close();
            }
            if(log.isDebugEnabled())
                log.debug("Sequence identifier generated: " + result);
            return result;
        }
        catch(SQLException sqle)
        {
            JDBCExceptionReporter.logExceptions(sqle);
			cause = new HibernateException(sqle);
			throw cause;
        }
        finally
        {
			try {
				session.getBatcher().closeStatement(st);
			} catch(SQLException e) {
				log.error("Exception when closing statement");
				if(cause != null)
					throw cause; // make sure we don't lose the real reason
				else
					throw new HibernateException(e);
			}
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.hibernate.id.Configurable#configure(org.hibernate.type.Type,
     *      java.util.Properties, org.hibernate.dialect.Dialect)
     */
    public void configure(Type type, Properties params, Dialect dialect)
        throws MappingException
    {
        this.sequenceName = PropertiesHelper.getString(SEQUENCE, params,
            "hibernate_sequence");
        this.type = type;
        sql = dialect.getSequenceNextValString(sequenceName);
    }

}