/********************************************************************
 * 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)
	 * 
	 * @seeorg.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);
	}

}