package org.myeslib.h2;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
import lombok.extern.slf4j.Slf4j;
import org.h2.api.Trigger;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.tweak.ConnectionFactory;
import org.skife.jdbi.v2.util.BigDecimalMapper;
@Slf4j
public class InventoryItemOptimisticLockingTrigger implements Trigger {
String targetTable; // per convention this has the suffix _uow
String aggregateRootTable; // and the companion aggregate root table has the suffix _ar
@Override
public void init(Connection conn, String schemaName, String triggerName,
String tableName, boolean before, int type) throws SQLException {
this.targetTable = tableName;
this.aggregateRootTable = targetTable.toUpperCase().replace("_UOW", "_AR");
}
@Override
public void fire(final Connection conn, final Object[] oldRow, final Object[] newRow)
throws SQLException {
final String id = (String) newRow[0];
final BigDecimal newVersion = (BigDecimal) newRow[1];
DBI dbi = new DBI(new ConnectionFactory() {
@Override
public Connection openConnection() throws SQLException {
return conn;
}
});
Handle h = dbi.open();
String sql1 = String.format("select version from %s where id = :id", aggregateRootTable);
log.debug(sql1);
BigDecimal lastVersion = h.createQuery(sql1).bind("id", id).map(BigDecimalMapper.FIRST).first();
log.debug("uowTable, arTable, id {}, old version {}, new version {}", targetTable, aggregateRootTable, id, lastVersion, newVersion);
if (lastVersion==null){
validateAndProceedOnAggregateRootTable(id, newVersion, h);
} else {
validateAndProceedOnUnitOfWorkTable(id, newVersion, h, lastVersion);
}
String sqlSequence = String.format("SELECT seq_%s.nextval from dual", targetTable);
BigDecimal next = h.createQuery(sqlSequence).map(BigDecimalMapper.FIRST).first();
newRow[3] = next; // seq_number assignment (does it really work this way ?)
log.debug("next --> {}", next);
}
private void validateAndProceedOnUnitOfWorkTable(final String id, final BigDecimal newVersion, Handle h, BigDecimal lastVersion) throws SQLException {
if (newVersion.intValue() != lastVersion.intValue()+1) {
String msg = String.format("id %s new version ( %s ) does not match the last version +1 ( %s )", id, newVersion.toString(), lastVersion.toString());
log.error(msg);
throw new SQLException(msg);
}
int ok = h.createStatement(String.format("update %s set version = :version, last_update = CURRENT_TIMESTAMP where id = :id", aggregateRootTable))
.bind("id", id)
.bind("version", newVersion)
.execute();
if (ok!=1){
String msg = String.format("aggregateRoot id (%s) was not updated", id);
log.error(msg);
throw new SQLException(msg);
}
}
private void validateAndProceedOnAggregateRootTable(final String id, final BigDecimal newVersion, Handle h) throws SQLException {
if (newVersion.intValue()!=1){
String msg = String.format("id %s new version ( %s ) should be =1 on first unit of work", id, newVersion.toString());
log.error(msg);
throw new SQLException(msg);
}
int ok = h.createStatement(String.format("insert into %s (id, version, last_update) values (:id, :version, CURRENT_TIMESTAMP)", aggregateRootTable))
.bind("id", id)
.bind("version", newVersion)
.execute();
if (ok!=1){
String msg = String.format("aggregateRoot id (%s) was not inserted", id);
log.error(msg);
throw new SQLException(msg);
}
}
@Override
public void close() throws SQLException {
}
@Override
public void remove() throws SQLException {
}
}