/*
* Copyright 2009 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.server.itests.hibernate;
import java.sql.Connection;
import java.util.UUID;
import javax.sql.DataSource;
import ome.conditions.ValidationException;
import ome.model.IObject;
import ome.model.core.Image;
import ome.server.itests.AbstractManagedContextTest;
import ome.services.util.Executor;
import ome.system.ServiceFactory;
import ome.util.SqlAction;
import org.hibernate.Session;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@Test(groups = "ticket:1176")
public class SequencesTest extends AbstractManagedContextTest {
SqlAction sql;
GenericGenerator gg;
String seq_name;
int incr_value;
@BeforeClass
public void setup() {
gg = Image.class.getAnnotation(GenericGenerator.class);
seq_name = gg.name();
incr_value = -999990000;
for (Parameter parameter : gg.parameters()) {
if (parameter.name().equals("increment_size")) {
incr_value = Integer.valueOf(parameter.value());
break;
}
}
}
/**
* Find the first roll-over spot, then create enough images to be one
* creation before the next roll-over. That's where our test beings.
*/
@BeforeMethod
public void reset() {
final long original = getCurrentNextValue(seq_name);
long loop = -1L;
do {
incrementImage();
loop = getCurrentNextValue(seq_name);
} while (original == loop);
for (int i = 0; i < incr_value - 1; i++) {
incrementImage();
}
long current = getCurrentNextValue(seq_name);
assertEquals(loop, current);
}
/**
* For the other logic here to work properly, two consecutive calls to
* DS.getConnection() should return different connections.
*/
public void testConnectionUniqueness() throws Exception {
DataSource ds = (DataSource) applicationContext.getBean("selfCorrectingDataSource");
log.warn("XXXX: GETTING CONNECTIONS");
Connection conn1 = ds.getConnection();
Connection conn2 = ds.getConnection();
assertFalse(conn1.equals(conn2));
log.warn("XXXX: GOT 2 CONNECTIONS");
}
public void testBasics() throws Exception {
String uuid = UUID.randomUUID().toString();
assertEquals(-1, getCurrentNextValue(uuid));
assertEquals(1, callNextValue(uuid, 1));
assertEquals(2, getCurrentNextValue(uuid));
assertEquals(2, callNextValue(uuid, 1));
assertEquals(3, getCurrentNextValue(uuid));
assertEquals(4, callNextValue(uuid, 2));
assertEquals(5, getCurrentNextValue(uuid));
}
public void testSequences() throws Exception {
long valueBefore = getCurrentNextValue(seq_name);
long addedId = incrementImage();
assertEquals(valueBefore, addedId);
long valueAfterFirst = getCurrentNextValue(seq_name);
assertEquals(valueBefore + incr_value, valueAfterFirst);
// The second save shouldn't update seq_table
addedId = incrementImage();
assertEquals(valueBefore + 1, addedId);
addedId = incrementImage();
assertEquals(valueBefore + 2, addedId);
// No change despite the various calls to incrementImage()
long valueAfterSecond = getCurrentNextValue(seq_name);
assertEquals(valueAfterFirst, valueAfterSecond);
// The next 48 should also be the same
for (int i = 0; i < incr_value - 3; i++) {
addedId = incrementImage();
long valueAfterLoop = getCurrentNextValue(seq_name);
assertEquals("Differnt on loop " + i, valueAfterFirst,
valueAfterLoop);
}
// Now another loop of 50 should start
addedId = incrementImage();
assertEquals(valueAfterSecond, addedId);
assertEquals(valueAfterSecond + incr_value,
getCurrentNextValue(seq_name));
// A manual load should increment by 1
long bv = getCurrentNextValue(seq_name);
long nv = sql.nextValue(seq_name, 1);
long cv = getCurrentNextValue(seq_name);
assertEquals(bv, nv);
assertEquals(nv + 1, cv);
}
/**
* Proper functioning of nextval() semantics may depend on the proper
* out-of-transaction logic of Hibernate. Here we check that the Executor
* class can properly wrap calls to Isolator.
*/
public void testThatIsolatorWorksInASeparateThread() {
final int BEFORE = 0;
final int DURING = 1;
final int AFTER = 2;
final int OUTSIDE = 3;
final int IMAGEID = 4;
final long[] values = new long[5];
try {
log.warn("XXXX: EXECUTING");
executor.execute(this.loginAop.p, new Executor.SimpleWork(this,
"isolated") {
@Transactional(readOnly = false)
public Object doWork(Session session, ServiceFactory sf) {
// First we create an image which should not exist after
// the tx is rolled back. Don't use incrementImage since
// that's
// wrapped with AOP
Image i = new Image("1176-inner");
log.warn("XXXX: SAVING");
i = sf.getUpdateService().saveAndReturnObject(i);
values[IMAGEID] = i.getId();
log.warn("XXXX: CHECKING NEXTVAL");
values[BEFORE] = getCurrentNextValue(seq_name);
// This was failing.
// Now we do nextval() in an isolated work
/*
* Isolater.doIsolatedWork(new IsolatedWork() { public void
* doWork(Connection connection) throws HibernateException {
* SingleConnectionDataSource ds = new
* SingleConnectionDataSource( connection, true);
* SimpleJdbcTemplate jdbc = new SimpleJdbcTemplate(ds);
* values[DURING] = jdbc.queryForLong(
* "select ome_nextval(?)", seq_name); } },
* (SessionImplementor) session);
*/
// If instead we get our own datasoure then the connection
// should be an unwrapped one. We are duplicating what is
// done in TableIdGenerator here.
log.warn("XXXX: OME_NEXTVAL");
DataSource ds = (DataSource) applicationContext
.getBean("selfCorrectingDataSource");
PlatformTransactionManager tm = new DataSourceTransactionManager(
ds);
TransactionTemplate tt = new TransactionTemplate(tm);
final SimpleJdbcTemplate jdbc = new SimpleJdbcTemplate(ds);
tt.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
values[DURING] = jdbc.queryForLong(
"select ome_nextval(?)", seq_name);
return null;
}
});
log.warn("XXXX: CHECKING NEXTVAL");
values[AFTER] = getCurrentNextValue(seq_name);
throw new RuntimeException("Should rollback tx");
}
});
fail("must throw");
} catch (Exception e) {
// good
}
log.warn("XXXX: AFTER EXECUTE. CHECKING NEXTVAL");
values[OUTSIDE] = getCurrentNextValue(seq_name);
// First the image must be gone. Most important!
log.warn("XXXX: QUERY.FIND");
assertNull(iQuery.find(Image.class, values[IMAGEID]));
// Then value should have updated despite the rollback.
assertEquals(values[BEFORE], values[DURING]);
assertEquals(values[DURING] + 1, values[AFTER]);
assertEquals(values[AFTER], values[OUTSIDE]);
}
/**
* If the second image in a save throws an exception then the sequence value
* should be updated, but the first image should not be present.
*/
public void testImageIdIsAlsoRolledBack() {
Image[] images = new Image[2];
images[0] = new Image("image rollback");
images[1] = new Image(/* no name */);
try {
iUpdate.saveAndReturnArray(images);
fail("Must throw");
} catch (ValidationException ve) {
// good
}
long rolledBackId = images[0].getId();
assertNull(iQuery.find(Image.class, rolledBackId));
assertTrue(getCurrentNextValue(seq_name) > rolledBackId);
}
/**
* The same test but from inside the executor
*/
public void testImageIdIsAlsoRolledBackInExecutor() {
final Image[] images = new Image[2];
images[0] = new Image("image rollback");
images[1] = new Image(/* no name */);
try {
executor.execute(this.loginAop.p, new Executor.SimpleWork(this,
"rollback") {
@Transactional(readOnly = false)
public Object doWork(Session session, ServiceFactory sf) {
return sf.getUpdateService().saveAndReturnArray(images);
}
});
fail("Must throw");
} catch (ValidationException ve) {
// good
}
long rolledBackId = images[0].getId();
assertNull(iQuery.find(Image.class, rolledBackId));
assertEquals(getCurrentNextValue(seq_name), rolledBackId + incr_value);
}
private long incrementImage() {
Image i;
i = new Image("1176");
i = iUpdate.saveAndReturnObject(i);
return i.getId();
}
private <T extends IObject> long getCurrentNextValue(String seq_name) {
return sql.currValue(seq_name);
}
private <T extends IObject> long callNextValue(String seq_name, int incr) {
return sql.nextValue(seq_name, incr);
}
}