/*
* (C) Copyright 2013-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.inject.Inject;
import javax.transaction.Transaction;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nuxeo.ecm.core.storage.sql.jdbc.JDBCConnection;
import org.nuxeo.ecm.core.storage.sql.jdbc.XAResourceConnectionAdapter;
import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect;
import org.nuxeo.ecm.core.test.CoreFeature;
import org.nuxeo.ecm.core.test.StorageConfiguration;
import org.nuxeo.runtime.datasource.ConnectionHelper;
import org.nuxeo.runtime.test.runner.Features;
import org.nuxeo.runtime.test.runner.FeaturesRunner;
import org.nuxeo.runtime.transaction.TransactionHelper;
/**
* Test that transaction management does the right thing with the connection.
*/
@RunWith(FeaturesRunner.class)
@Features(CoreFeature.class)
public class TestConnectionManagement {
@Inject
protected CoreFeature coreFeature;
/**
* H2 cannot have one connection doing an insert in a tx and another using the same table, as it waits for a lock.
*/
protected boolean canUseTwoConnections() {
StorageConfiguration database = coreFeature.getStorageConfiguration();
return !(database.isVCSH2() || database.isVCSDerby());
}
protected String getValidationQuery(Connection connection) {
return Dialect.createDialect(connection, null).getValidationQuery();
}
protected static void assertEqualsInt(int expected, ResultSet rs) throws SQLException {
assertTrue(rs.next());
int actual = rs.getInt(1);
assertEquals(expected, actual);
assertFalse(rs.next());
rs.close();
}
protected Connection getConnection() throws SQLException {
String repositoryName = coreFeature.getRepositoryName();
return ConnectionHelper.getConnection(JDBCConnection.getDataSourceName(repositoryName));
}
protected Connection getConnectionNoSharing() throws SQLException {
String repositoryName = coreFeature.getRepositoryName();
return ConnectionHelper.getConnection(JDBCConnection.getDataSourceName(repositoryName), true);
}
@Test
public void testNoTxNoBegin() throws Exception {
TransactionHelper.commitOrRollbackTransaction(); // end tx
Connection connection = getConnection();
try {
assertTrue(connection.getAutoCommit());
connection.setAutoCommit(true); // already true, no effect
connection.setAutoCommit(false);
connection.setAutoCommit(true);
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
connection.close(); // close twice ok
assertTrue(connection.isClosed());
}
}
@Test
public void testNoTxBegin() throws Exception {
TransactionHelper.commitOrRollbackTransaction(); // end tx
Connection connection = getConnection();
try {
// first thing set autoCommit=false, but no tx
connection.setAutoCommit(false);
connection.setAutoCommit(false); // already false, no effect
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
connection.commit(); // needed for DB2 before close
connection.setAutoCommit(true);
} finally {
connection.close();
}
}
/*
* Transaction in non-ACTIVE state.
*/
@Test
public void testBadTxBegin() throws Exception {
TransactionHelper.setTransactionRollbackOnly(); // not ACTIVE
Connection connection = getConnection();
try {
// first thing set autoCommit=false, but no tx
connection.setAutoCommit(false);
connection.setAutoCommit(false); // already false, no effect
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
connection.commit(); // needed for DB2 before close
} finally {
connection.close();
}
}
@Test
public void testNoTxSwitchAutoCommit() throws Exception {
TransactionHelper.commitOrRollbackTransaction(); // end tx
Connection connection = getConnection();
try {
// use connection with autoCommit=true
connection.createStatement();
// then set autoCommit=false, but no tx
connection.setAutoCommit(false);
connection.setAutoCommit(false); // already false, no effect
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
connection.commit(); // needed for DB2 before close
connection.setAutoCommit(true);
} finally {
connection.close();
}
}
@Test
public void testNoBegin() throws Exception {
Connection connection = getConnection();
try {
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
}
}
/*
* Does begin as the first thing the connection does.
*/
@Test
public void testManualBegin1() throws Exception {
Connection connection = getConnection();
try {
// first thing set autoCommit=false
connection.setAutoCommit(false);
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
connection.commit();
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
}
TransactionHelper.commitOrRollbackTransaction();
}
/*
* Does begin after the connection has already been used.
*/
@Test
public void testManualBegin2() throws Exception {
Connection connection = getConnection();
try {
// first thing use connection with autoCommit=true
String sql = getValidationQuery(connection);
// switch to no autocommit
connection.setAutoCommit(false);
Statement st = connection.createStatement();
st.execute(sql);
connection.commit();
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
}
TransactionHelper.commitOrRollbackTransaction();
}
/*
* Does work, commit, then more work.
*/
@Test
public void testCommitThenMoreWork() throws Exception {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
connection.createStatement();
connection.commit();
// keep working in transaction mode after commit
connection.createStatement();
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
}
TransactionHelper.commitOrRollbackTransaction();
}
/*
* Does begin, no commit, then close; checks that close auto-commits.
*/
@Test
public void testCloseWithoutCommit() throws Exception {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
// don't commit, close() will do it automatically
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
}
TransactionHelper.commitOrRollbackTransaction();
}
/*
* Does begin, no commit, then setAutoCommit=true; checks that autoCommit change auto-commits.
*/
@Test
public void testEndWithoutCommit() throws Exception {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
// don't commit
connection.setAutoCommit(true); // commits automatically
} finally {
assertFalse(connection.isClosed());
connection.setAutoCommit(false);
connection.close();
assertTrue(connection.isClosed());
}
TransactionHelper.commitOrRollbackTransaction();
}
/*
* Test shared connection use after the transaction has ended.
*/
@Test
public void testUseAfterTxEnd() throws Exception {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
// tx just commits now
// this will log an ERROR
TransactionHelper.commitOrRollbackTransaction();
// now keep using the connection
connection.createStatement();
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
}
}
/*
* Re-begin in a new transaction after a previous use and commit in a previous transaction.
*/
@Test
public void testSeveralTx() throws Exception {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
Statement st = connection.createStatement();
String sql = getValidationQuery(connection);
st.execute(sql);
connection.commit();
TransactionHelper.commitOrRollbackTransaction();
// new tx
TransactionHelper.startTransaction();
connection.setAutoCommit(false);
st = connection.createStatement();
st.execute(sql);
connection.commit();
connection.setAutoCommit(true);
TransactionHelper.commitOrRollbackTransaction();
} finally {
assertFalse(connection.isClosed());
connection.close();
assertTrue(connection.isClosed());
}
}
/*
* Test through XAResource wrapper and transaction enlisting. As a side effect the connection commit is called while
* the transaction is in STATUS_COMMITTING.
*/
@Test
public void testXAResourceBeginDoStuffCommit() throws Exception {
JDBCConnection jdbc = new JDBCConnection();
jdbc.connection = getConnection();
try {
Transaction transaction = TransactionHelper.lookupTransactionManager().getTransaction();
XAResourceConnectionAdapter xaresource = new XAResourceConnectionAdapter(jdbc);
transaction.enlistResource(xaresource);
// use connection
jdbc.connection.createStatement();
// then commit
TransactionHelper.commitOrRollbackTransaction();
} finally {
assertFalse(jdbc.connection.isClosed());
jdbc.connection.close();
assertTrue(jdbc.connection.isClosed());
}
}
/*
* Test through XAResource wrapper and transaction enlisting. But do nothing between tx start and end.
*/
@Test
public void testXAResourceBeginDoNothingCommit() throws Exception {
JDBCConnection jdbc = new JDBCConnection();
jdbc.connection = getConnection();
try {
Transaction transaction = TransactionHelper.lookupTransactionManager().getTransaction();
XAResourceConnectionAdapter xaresource = new XAResourceConnectionAdapter(jdbc);
transaction.enlistResource(xaresource);
// do nothing between start and end
TransactionHelper.commitOrRollbackTransaction();
} finally {
assertFalse(jdbc.connection.isClosed());
jdbc.connection.close();
assertTrue(jdbc.connection.isClosed());
}
}
/*
* Test through XAResource wrapper and transaction enlisting. But do nothing between tx start and end, and do a
* rollback.
*/
@Test
public void testXAResourceBeginDoNothingRollback() throws Exception {
JDBCConnection jdbc = new JDBCConnection();
jdbc.connection = getConnection();
try {
Transaction transaction = TransactionHelper.lookupTransactionManager().getTransaction();
XAResourceConnectionAdapter xaresource = new XAResourceConnectionAdapter(jdbc);
transaction.enlistResource(xaresource);
// do nothing between start and end
// provoke rollback
TransactionHelper.setTransactionRollbackOnly();
TransactionHelper.commitOrRollbackTransaction();
} finally {
assertFalse(jdbc.connection.isClosed());
jdbc.connection.close();
assertTrue(jdbc.connection.isClosed());
}
}
}