/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb3.entity;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.id.IdentifierGeneratorFactory;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.transaction.JBossTransactionManagerLookup;
import org.hibernate.transaction.TransactionManagerLookup;
import org.hibernate.type.Type;
import org.hibernate.util.PropertiesHelper;
/**
* A hilo <tt>IdentifierGenerator</tt> that uses a database
* table to store the last generated value.
* <p/>
* <p/>
* This implementation is solely for use inside JBoss using JTA for transactions.
* </p>
* <p/>
* TODO implement sequence allocation
*
* @author <a href="mailto:kr@hbt.de">Klaus Richarz</a>.
* @version <tt>$Revision: 57207 $</tt>
* @see org.hibernate.id.TableGenerator
* @see javax.persistence.TableGenerator
*/
public class JTATableIdGenerator implements PersistentIdentifierGenerator, Configurable
{
/* COLUMN and TABLE should be renamed but it would break the public API */
/**
* The column parameter
*/
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 = "next_hi";
/**
* The allocation-size parameter
*/
public static final String ALLOCATION_SIZE = "allocationSize";
/**
* Default allocation-size
*/
public static final int DEFAULT_ALLOCATION_SIZE = 20;
/**
* logger for JTATableGenerator
*/
private static final Log log = LogFactory.getLog(JTATableIdGenerator.class);
/**
* Holds the name where this generator gets its sequence from
*/
private String tableName;
/**
* Holds the name ofthe column where the next sequence value is stored
*/
private String columnName;
/**
* Holds the sql query to retrieve the next high value
*/
private String query;
/**
* Holds the sql query to increment the sequence
*/
private String update;
/**
* Holds the transaction manager lookup object
*/
private TransactionManagerLookup transactionManagerLookup;
/**
* Holds the class type for the sequence value returned by generate()
*/
private Class returnClass;
/**
* Holds the size for the sequence increment. The allocated sequences are managed in memory
* and may be lost if the system stops.
*/
private int allocationSize;
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.allocationSize = PropertiesHelper.getInt(ALLOCATION_SIZE, params, DEFAULT_ALLOCATION_SIZE);
String schemaName = params.getProperty(SCHEMA);
String catalogName = params.getProperty(CATALOG);
if (true) throw new RuntimeException("DOES ANYBODY USE THIS? It IS CURRENTLY BROKEN");
/*
getSchemaSeparator does not exist in hibernate anymore since 3.1 release
// prepare table name
if (tableName.indexOf(dialect.getSchemaSeparator()) < 0)
{
tableName = Table.qualify(catalogName, schemaName, tableName, dialect.getSchemaSeparator());
}
*/
// prepare SQL statements
query = "select " +
columnName +
" from " +
dialect.appendLockHint(LockMode.UPGRADE, tableName) +
dialect.getForUpdateString();
update = "update " +
tableName +
" set " +
columnName +
" = ? where " +
columnName +
" = ?";
// set up transaction manager lookup
// only JBoss transaction manager is supported
transactionManagerLookup = new JBossTransactionManagerLookup();
// set the sequence type that should be returned
returnClass = type.getReturnedClass();
// debug chosen configuration
if (log.isDebugEnabled())
{
log.debug("configuring id generator: " + this.getClass().getName());
log.debug("tableName=" + tableName);
log.debug("columnName=" + columnName);
log.debug("allocationSize=" + allocationSize);
log.debug("query=" + query);
log.debug("update=" + update);
log.debug("returnClass=" + returnClass);
}
}
public synchronized Serializable generate(SessionImplementor session, Object object)
throws HibernateException
{
// get TransactionManager from JNDI
// no JNDI properties provided -> we are in the container
TransactionManager tm = transactionManagerLookup.getTransactionManager(new Properties());
Transaction surroundingTransaction = null; // for resuming in finally block
Connection conn = null; // for ressource cleanup
String sql = null; // for exception
try
{
long result; // holds the resulting sequence value
// prepare a new transaction context for the generator
surroundingTransaction = tm.suspend();
if (log.isDebugEnabled())
{
log.debug("surrounding tx suspended");
}
tm.begin();
// get connection from managed environment
conn = session.getBatcher().openConnection();
// execute fetching of current sequence value
sql = query;
PreparedStatement qps = conn.prepareStatement(query);
try
{
ResultSet rs = qps.executeQuery();
if (!rs.next())
{
String err = "could not read sequence value - you need to populate the table: " + tableName;
log.error(err);
throw new IdentifierGenerationException(err);
}
result = rs.getLong(1);
rs.close();
}
catch (SQLException sqle)
{
log.error("could not read a sequence value", sqle);
throw sqle;
}
finally
{
qps.close();
}
// increment sequence value
sql = update;
long sequence = result + 1;
PreparedStatement ups = conn.prepareStatement(update);
try
{
ups.setLong(1, sequence);
ups.setLong(2, result);
ups.executeUpdate();
}
catch (SQLException sqle)
{
log.error("could not update sequence value in: " + tableName, sqle);
throw sqle;
}
finally
{
ups.close();
}
// commit transaction to ensure updated sequence is not rolled back
tm.commit();
// transform sequence to the desired type and return the value
Number typedSequence = IdentifierGeneratorFactory.createNumber(sequence, returnClass);
if (log.isDebugEnabled())
{
log.debug("generate() returned: " + typedSequence);
}
return typedSequence;
}
catch (SQLException sqle)
{
throw JDBCExceptionHelper.convert(session.getFactory().getSQLExceptionConverter(),
sqle,
"could not get or update next value",
sql);
}
catch (Exception e)
{
try
{
tm.rollback();
throw new HibernateException(e);
}
catch (SystemException e1)
{
throw new HibernateException(e1);
}
}
finally
{
if (conn != null)
try
{
conn.close();
}
catch (SQLException e)
{
// ignore exception
}
// switch back to surrounding transaction context
if (surroundingTransaction != null)
{
try
{
tm.resume(surroundingTransaction);
if (log.isDebugEnabled())
{
log.debug("surrounding tx resumed");
}
}
catch (Exception e)
{
throw new HibernateException(e);
}
}
}
}
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException
{
return new String[]{
"create table " + tableName + " ( " + columnName + " " + dialect.getTypeName(Types.BIGINT) + " )",
"insert into " + tableName + " values ( 0 )"
};
}
public String[] sqlDropStrings(Dialect dialect)
{
//return "drop table " + tableName + dialect.getCascadeConstraintsString();
StringBuffer sqlDropString = new StringBuffer()
.append("drop table ");
if (dialect.supportsIfExistsBeforeTableName())
{
sqlDropString.append("if exists ");
}
sqlDropString.append(tableName)
.append(dialect.getCascadeConstraintsString());
if (dialect.supportsIfExistsAfterTableName())
{
sqlDropString.append(" if exists");
}
return new String[]{sqlDropString.toString()};
}
public Object generatorKey()
{
return tableName;
}
}
|