/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, 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.ejb.plugins.keygenerator.hilo;
import org.jboss.ejb.plugins.keygenerator.KeyGenerator;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCUtil;
import org.jboss.logging.Logger;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.Transaction;
import javax.transaction.SystemException;
import java.sql.PreparedStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.ResultSet;
/**
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version <tt>$Revision: 81030 $</tt>
*/
public class HiLoKeyGenerator
implements KeyGenerator
{
private static long highestHi = 0;
public static synchronized long getHighestHi()
{
return highestHi;
}
public static synchronized void setHighestHi(long highestHi)
{
HiLoKeyGenerator.highestHi = highestHi;
}
private final Logger log;
private final DataSource ds;
private final long blockSize;
private long hi;
private long lo;
private TransactionManager tm;
private String updateHiSql;
private String selectHiSql;
public HiLoKeyGenerator(
DataSource ds,
String tableName,
String sequenceColumn,
String sequenceName,
String idColumnName,
String selectHiSql,
long blockSize,
TransactionManager tm
)
{
this.ds = ds;
this.blockSize = blockSize;
this.tm = tm;
this.log = Logger.getLogger(getClass().getName() + "#" + tableName + "_" + sequenceName);
updateHiSql = "update " +
tableName +
" set " +
idColumnName +
"=?" +
" where " + sequenceColumn + "='" + sequenceName + "' and " +
idColumnName + "=?";
this.selectHiSql = selectHiSql;
}
public synchronized Object generateKey()
{
if(lo < hi)
{
++lo;
}
else
{
Transaction curTx = null;
try
{
curTx = tm.suspend();
}
catch(SystemException e)
{
throw new IllegalStateException("Failed to suspend current transaction.");
}
try
{
tm.begin();
}
catch(Exception e)
{
throw new IllegalStateException("Failed to begin a new transaction.");
}
try
{
doGenerate();
tm.commit();
}
catch(SQLException e)
{
log.error("Failed to update table: " + e.getMessage(), e);
try
{
tm.rollback();
}
catch(SystemException e1)
{
log.error("Failed to rollback.", e1);
}
throw new IllegalStateException(e.getMessage());
}
catch(Exception e)
{
log.error("Failed to commit.", e);
}
finally
{
if(curTx != null)
{
try
{
tm.resume(curTx);
}
catch(Exception e)
{
throw new IllegalStateException("Failed to resume transaction: " + e.getMessage());
}
}
}
}
return new Long(lo);
}
private void doGenerate() throws SQLException
{
long curHi;
do
{
curHi = getCurrentHi();
lo = curHi + 1;
hi = curHi + blockSize;
}
while(!updateHi(curHi, hi));
}
private long getCurrentHi() throws SQLException
{
return selectHiSql != null ? selectHi() : getHighestHi();
}
private boolean updateHi(long curHi, long newHi) throws SQLException
{
if(selectHiSql == null)
{
setHighestHi(newHi);
}
return updateTable(curHi, newHi);
}
private long selectHi() throws SQLException
{
Connection con = null;
PreparedStatement selectHiSt = null;
ResultSet rs = null;
if(log.isTraceEnabled())
{
log.trace("Executing SQL: " + selectHiSql);
}
try
{
con = ds.getConnection();
selectHiSt = con.prepareStatement(selectHiSql);
rs = selectHiSt.executeQuery();
if(!rs.next())
{
throw new IllegalStateException("The sequence has not been initialized in the service start phase!");
}
return rs.getLong(1);
}
finally
{
JDBCUtil.safeClose(rs);
JDBCUtil.safeClose(selectHiSt);
JDBCUtil.safeClose(con);
}
}
private boolean updateTable(long curHi, long newHi) throws SQLException
{
Connection con = null;
PreparedStatement updateHi = null;
if(log.isTraceEnabled())
{
log.trace("Executing SQL: " + updateHiSql + ", [" + newHi + "," + curHi + "]");
}
try
{
con = ds.getConnection();
updateHi = con.prepareStatement(updateHiSql);
updateHi.setLong(1, newHi);
updateHi.setLong(2, curHi);
return updateHi.executeUpdate() == 1;
}
finally
{
JDBCUtil.safeClose(updateHi);
JDBCUtil.safeClose(con);
}
}
}