Index: src/main/java/org/apache/karaf/main/OracleJDBCLock.java =================================================================== --- src/main/java/org/apache/karaf/main/OracleJDBCLock.java (revision 1140062) +++ src/main/java/org/apache/karaf/main/OracleJDBCLock.java (working copy) @@ -18,6 +18,8 @@ */ package org.apache.karaf.main; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.util.Properties; /** @@ -67,4 +69,37 @@ boolean updateLock() { return aquireLock(); } + + /** + * A SELECT FOR UPDATE does not create a database lock when the SELECT FOR UPDATE is performed + * on an empty selection. So a succesfull call to {@link DefaultJDBCLock#aquireLock()} is not sufficient to + * ensure that we are the only one who have acquired the lock. + */ + @Override + boolean aquireLock() { + return super.aquireLock() && lockAcquiredOnNonEmptySelection(); + } + + //Verify that we have a non empty record set. + private boolean lockAcquiredOnNonEmptySelection() { + String verifySelectionNotEmpytStatement = statements.getLockVerifySelectionNotEmptyStatement(); + PreparedStatement preparedStatement = null; + boolean lockAquired = false; + + try { + preparedStatement = getConnection().prepareStatement(verifySelectionNotEmpytStatement); + preparedStatement.setQueryTimeout(timeout); + ResultSet rs = preparedStatement.executeQuery(); + if (rs.next()) { + lockAquired = rs.getInt(1) > 0; + } else { + LOG.warning("Failed to acquire database lock. Missing database lock record."); + } + } catch (Exception e) { + LOG.warning("Failed to acquire database lock: " + e); + }finally { + closeSafely(preparedStatement); + } + return lockAquired; + } } Index: src/main/java/org/apache/karaf/main/Statements.java =================================================================== --- src/main/java/org/apache/karaf/main/Statements.java (revision 1140062) +++ src/main/java/org/apache/karaf/main/Statements.java (working copy) @@ -30,6 +30,7 @@ private String[] lockCreateSchemaStatements; private String lockCreateStatement; private String lockUpdateStatement; + private String lockVerifySelectionNotEmptyStatement; public String[] getLockCreateSchemaStatements(long moment) { if (lockCreateSchemaStatements == null) { @@ -67,6 +68,20 @@ this.lockUpdateStatement = lockUpdateStatement; } + public void setLockVerifySelectionNotEmptyStatement(String lockVerifySelectionNotEmptyStatement) { + this.lockVerifySelectionNotEmptyStatement = lockVerifySelectionNotEmptyStatement; + } + + public String getLockVerifySelectionNotEmptyStatement() { + if (lockVerifySelectionNotEmptyStatement == null) { + //The lock create and lock update are perfomed on the whole table instead of + //the cluster. So not taking the node into account for now. + lockVerifySelectionNotEmptyStatement = "SELECT COUNT(*) FROM " + getFullLockTableName(); + } + return lockVerifySelectionNotEmptyStatement; + } + + long getCurrentTimeMillis() { return System.currentTimeMillis(); } Index: src/test/java/org/apache/karaf/main/BaseJDBCLockIntegrationTest.java =================================================================== --- src/test/java/org/apache/karaf/main/BaseJDBCLockIntegrationTest.java (revision 1140062) +++ src/test/java/org/apache/karaf/main/BaseJDBCLockIntegrationTest.java (working copy) @@ -167,6 +167,20 @@ } @Test + public void lockShouldReturnFalseIfTheTableIsEmpty() throws Exception { + Connection connection = null; + try { + lock = createLock(props); + truncateTable(); //Empty the table + connection = lock(tableName, clustername); + + assertFalse(lock.lock()); + } finally { + close(connection); + } + } + + @Test public void release() throws Exception { lock = createLock(props); @@ -309,6 +323,11 @@ executeStatement("UPDATE " + tableName + " SET MOMENT = " + System.currentTimeMillis()); } + void truncateTable() throws SQLException, ClassNotFoundException { + executeStatement("TRUNCATE TABLE " + tableName); + } + + Connection lock(String table, String node) throws ClassNotFoundException, SQLException { Connection connection = null; Statement statement = null; Index: src/test/java/org/apache/karaf/main/BaseJDBCLockTest.java =================================================================== --- src/test/java/org/apache/karaf/main/BaseJDBCLockTest.java (revision 1140062) +++ src/test/java/org/apache/karaf/main/BaseJDBCLockTest.java (working copy) @@ -307,4 +307,28 @@ verify(connection, metaData, statement, preparedStatement, resultSet); assertFalse(alive); } + + @Test + public void lockShouldReturnFalseIfTableIsEmpty() throws Exception { + initShouldNotCreateTheSchemaIfItAlreadyExists(); + reset(connection, metaData, statement, preparedStatement, resultSet); + + expect(connection.isClosed()).andReturn(false); + expect(connection.prepareStatement("SELECT * FROM " + tableName + " FOR UPDATE")).andReturn(preparedStatement); + preparedStatement.setQueryTimeout(10); + expect(preparedStatement.execute()).andReturn(true); + preparedStatement.close(); + expect(connection.isClosed()).andReturn(false); + expect(connection.prepareStatement("UPDATE " + tableName + " SET MOMENT = 1")).andReturn(preparedStatement); + preparedStatement.setQueryTimeout(10); + expect(preparedStatement.executeUpdate()).andReturn(0); + preparedStatement.close(); + + replay(connection, metaData, statement, preparedStatement, resultSet); + + boolean lockAquired = lock.lock(); + + verify(connection, metaData, statement, preparedStatement, resultSet); + assertFalse(lockAquired); + } } Index: src/test/java/org/apache/karaf/main/OracleJDBCLockTest.java =================================================================== --- src/test/java/org/apache/karaf/main/OracleJDBCLockTest.java (revision 1140062) +++ src/test/java/org/apache/karaf/main/OracleJDBCLockTest.java (working copy) @@ -74,6 +74,13 @@ preparedStatement.setQueryTimeout(10); expect(preparedStatement.execute()).andReturn(true); preparedStatement.close(); + expect(connection.isClosed()).andReturn(false); + expect(connection.prepareStatement("SELECT COUNT(*) FROM " + tableName)).andReturn(preparedStatement); + preparedStatement.setQueryTimeout(10); + expect(preparedStatement.executeQuery()).andReturn(resultSet); + expect(resultSet.next()).andReturn(Boolean.TRUE); + expect(resultSet.getInt(1)).andReturn(1); + preparedStatement.close(); replay(connection, metaData, statement, preparedStatement, resultSet); @@ -134,6 +141,13 @@ preparedStatement.setQueryTimeout(10); expect(preparedStatement.execute()).andReturn(true); preparedStatement.close(); + expect(connection.isClosed()).andReturn(false); + expect(connection.prepareStatement("SELECT COUNT(*) FROM " + tableName)).andReturn(preparedStatement); + preparedStatement.setQueryTimeout(10); + expect(preparedStatement.executeQuery()).andReturn(resultSet); + expect(resultSet.next()).andReturn(Boolean.TRUE); + expect(resultSet.getInt(1)).andReturn(1); + preparedStatement.close(); replay(connection, metaData, statement, preparedStatement, resultSet); @@ -162,4 +176,30 @@ verify(connection, metaData, statement, preparedStatement, resultSet); assertFalse(alive); } + + @Test + public void lockShouldReturnFalseIfTableIsEmpty() throws Exception { + initShouldNotCreateTheSchemaIfItAlreadyExists(); + reset(connection, metaData, statement, preparedStatement, resultSet); + + expect(connection.isClosed()).andReturn(false); + expect(connection.prepareStatement("SELECT * FROM " + tableName + " FOR UPDATE")).andReturn(preparedStatement); + preparedStatement.setQueryTimeout(10); + expect(preparedStatement.execute()).andReturn(true); + preparedStatement.close(); + expect(connection.isClosed()).andReturn(false); + expect(connection.prepareStatement("SELECT COUNT(*) FROM " + tableName)).andReturn(preparedStatement); + preparedStatement.setQueryTimeout(10); + expect(preparedStatement.executeQuery()).andReturn(resultSet); + expect(resultSet.next()).andReturn(Boolean.TRUE); + expect(resultSet.getInt(1)).andReturn(0); + preparedStatement.close(); + + replay(connection, metaData, statement, preparedStatement, resultSet); + + boolean lockAquired = lock.lock(); + + verify(connection, metaData, statement, preparedStatement, resultSet); + assertFalse(lockAquired); + } }