package org.openiam.base.id;
import java.io.Serializable;
import java.util.*;
import java.sql.Types;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.TableGenerator;
import org.hibernate.util.*;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.Type;
import org.hibernate.mapping.Table;
import org.hibernate.LockMode;
import org.hibernate.id.IdentifierGenerationException;
/**
* An <tt>IdentifierGenerator</tt> that uses a database
* table to store the last generated value + 1. It is not
* intended that applications use this strategy directly.
* However, it may be used to build other (efficient)
* strategies. The returned type is <tt>String</tt> since
* most objects in OpenIAM use a String for ID fields. Strings
* were selected as ID fields to allow the end corporation to
* to use other types of IDs..<br>
* <br>
* Mapping parameters supported: table, column, attribute
*
* @author Suneet Shah
*/
public class SequenceGenerator extends TableGenerator implements IdentifierGenerator {
public static final String COLUMN = "column";
/** Default column name */
public static final String DEFAULT_COLUMN_NAME = "next_hi";
/** The table parameter */
public static final String TABLE = "table";
/** Default table name */
public static final String DEFAULT_TABLE_NAME = "hibernate_unique_key";
/* attribute parameter */
public static final String ATTRIBUTE = "attribute";
private String tableName;
private String columnName;
private String attributeName;
private String query;
private String update;
private static final Log log = LogFactory.getLog(SequenceGenerator.class);
public void configure(Type type, Properties params, Dialect dialect) {
this.tableName = PropertiesHelper.getString(TABLE, params, DEFAULT_TABLE_NAME);
this.columnName = PropertiesHelper.getString(COLUMN, params, DEFAULT_COLUMN_NAME);
this.attributeName = params.getProperty(ATTRIBUTE);
String schemaName = params.getProperty(SCHEMA);
String catalogName = params.getProperty(CATALOG);
if ( tableName.indexOf( '.' )<0 ) {
tableName = Table.qualify( catalogName, schemaName, tableName );
}
query = "select " +
columnName +
" from " +
dialect.appendLockHint(LockMode.UPGRADE, tableName) +
" where ATTRIBUTE = ?" +
dialect.getForUpdateString();
update = "update " +
tableName +
" set " +
columnName +
" = ? where " +
" ATTRIBUTE = ? " ;
}
public synchronized Serializable generate(SessionImplementor session, Object obj)
throws HibernateException {
int result = ( (Integer) doWorkInNewTransaction(session) ).intValue();
return String.valueOf(result);
}
public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException {
int result;
int rows;
do {
// The loop ensures atomicity of the
// select + update even for no transaction
// or read committed isolation level
sql = query;
//SQL.debug(query);
PreparedStatement qps = conn.prepareStatement(query);
try {
qps.setString(1, attributeName);
ResultSet rs = qps.executeQuery();
if ( !rs.next() ) {
String err = "could not read the current id value - you need to populate the table: " + tableName;
log.error(err);
throw new IdentifierGenerationException(err);
}
result = rs.getInt(1);
rs.close();
}
catch (SQLException sqle) {
log.error("could not read a hi value", sqle);
throw sqle;
}
finally {
qps.close();
}
sql = update;
//SQL.debug(update);
PreparedStatement ups = conn.prepareStatement(update);
try {
ups.setInt( 1, result + 1 );
ups.setString(2, attributeName);
rows = ups.executeUpdate();
}
catch (SQLException sqle) {
log.error("could not update value in: " + tableName, sqle);
throw sqle;
}
finally {
ups.close();
}
}
while (rows==0);
return new Integer(result);
}
}