/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.karaf.main.lock; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.apache.felix.utils.properties.Properties; import java.util.logging.Logger; import org.apache.karaf.main.util.BootstrapLogManager; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; public abstract class BaseJDBCLockIntegrationTest { private final Logger LOG = Logger.getLogger(this.getClass().getName()); DefaultJDBCLock lock; Properties props; String user = "root"; String password = ""; String driver; String url; String tableName = "LOCK_TABLE"; String clustername = "karaf_cluster"; int timeout = 10; String momentDatatype = "BIGINT"; String nodeDatatype = "VARCHAR(20)"; abstract DefaultJDBCLock createLock(Properties props); @BeforeClass public static void setUpTestSuite() { Properties properties = new Properties(); properties.put("karaf.bootstrap.log", "target/karaf.log"); BootstrapLogManager.setProperties(properties); } @Before public void setUp() throws Exception { props = new Properties(); props.put("karaf.lock.jdbc.url", url); props.put("karaf.lock.jdbc.driver", driver); props.put("karaf.lock.jdbc.user", user); props.put("karaf.lock.jdbc.password", password); props.put("karaf.lock.jdbc.table", tableName); props.put("karaf.lock.jdbc.clustername", clustername); props.put("karaf.lock.jdbc.timeout", String.valueOf(timeout)); try { executeStatement("DROP TABLE " + tableName); } catch (Exception e) { // expected if the table dosn't exist } } @After public void tearDown() throws Exception { if (lock != null) { lock.release(); } } @Test @Ignore public void lockShouldRestoreTheLockAfterADbFailure() throws Exception { Lock lock1 = createLock(props); assertTrue(lock1.lock()); assertTrue(lock1.isAlive()); // shut down the database assertFalse(lock1.isAlive()); // start the database assertTrue(lock1.lock()); assertTrue(lock1.isAlive()); } @Test public void initShouldCreateTheSchemaIfItNotExists() throws Exception { long start = System.currentTimeMillis(); lock = createLock(props); long end = System.currentTimeMillis(); long moment = queryDatabaseSingleResult("SELECT MOMENT FROM " + tableName); assertTrue(moment >= start); assertTrue(moment <= end); } @Test public void initShouldNotCreateTheSchemaIfItAlreadyExists() throws Exception { executeStatement("CREATE TABLE " + tableName + " (MOMENT " + momentDatatype + ", NODE " + nodeDatatype + ")"); executeStatement("INSERT INTO " + tableName + " (MOMENT, NODE) VALUES (1, '" + clustername + "')"); lock = createLock(props); long moment = queryDatabaseSingleResult("SELECT MOMENT FROM " + tableName); assertEquals(1, moment); } @Test public void lockShouldReturnTrueItTheTableIsNotLocked() throws Exception { lock = createLock(props); assertTrue(lock.lock()); assertTableIsLocked(); } @Test public void lockShouldReturnFalseIfAnotherRowIsLocked() throws Exception { Connection connection = null; try { lock = createLock(props); executeStatement("INSERT INTO " + tableName + " (MOMENT, NODE) VALUES (1, '" + clustername + "_2')"); connection = lock(tableName, clustername + "_2"); // we can't lock only one row for the cluster assertFalse(lock.lock()); } finally { close(connection); } } @Test public void lockShouldReturnFalseIfTheRowIsAlreadyLocked() throws Exception { Connection connection = null; try { lock = createLock(props); connection = lock(tableName, clustername); assertFalse(lock.lock()); } finally { close(connection); } } @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); assertTrue(lock.lock()); lock.release(); assertNull(lock.lockConnection); assertTableIsUnlocked(); } @Test public void releaseShouldSucceedForAnAlreadyClosedConnection() throws Exception { lock = createLock(props); assertTrue(lock.lock()); lock.lockConnection.rollback(); // release the lock lock.lockConnection.close(); lock.release(); assertTableIsUnlocked(); } @Test public void releaseShouldSucceedForANullConnectionReference() throws Exception { lock = createLock(props); assertTrue(lock.lock()); lock.lockConnection.rollback(); // release the lock lock.lockConnection.close(); lock.lockConnection = null; lock.release(); assertTableIsUnlocked(); } @Test public void isAliveShouldReturnTrueIfItHoldsTheLock() throws Exception { lock = createLock(props); assertTrue(lock.lock()); assertTrue(lock.isAlive()); } @Test public void isAliveShouldReturnFalseIfTheConnectionIsClosed() throws Exception { lock = createLock(props); assertTrue(lock.lock()); lock.lockConnection.rollback(); // release the lock lock.lockConnection.close(); assertFalse(lock.isAlive()); } @Test public void isAliveShouldReturnFalseIfTheConnectionIsNull() throws Exception { lock = createLock(props); assertTrue(lock.lock()); lock.lockConnection.rollback(); // release the lock lock.lockConnection.close(); lock.lockConnection = null; assertFalse(lock.isAlive()); } @Test public void isAliveShouldReturnFalseIfItNotHoldsTheLock() throws Exception { Connection connection = null; try { lock = createLock(props); assertTrue(lock.lock()); lock.lockConnection.rollback(); // release the lock connection = lock(tableName, clustername); // another connection locks the table assertFalse(lock.isAlive()); } finally { close(connection); } } Connection getConnection(String url, String user, String password) throws ClassNotFoundException, SQLException { Class.forName(driver); Connection connection = DriverManager.getConnection(url, user, password); connection.setAutoCommit(false); return connection; } void executeStatement(String stmt) throws SQLException, ClassNotFoundException { Connection connection = null; Statement statement = null; try { connection = getConnection(url, user, password); statement = connection.createStatement(); statement.setQueryTimeout(timeout); statement.execute(stmt); connection.commit(); } finally { close(statement); close(connection); } } Long queryDatabaseSingleResult(String query) throws ClassNotFoundException, SQLException { Connection connection = null; Statement statement = null; ResultSet rs = null; try { connection = getConnection(url, user, password); statement = connection.createStatement(); rs = statement.executeQuery(query); rs.next(); return rs.getLong(1); } finally { close(rs); close(statement); close(connection); } } void assertTableIsLocked() throws ClassNotFoundException, SQLException { try { executeStatement("UPDATE " + tableName + " SET MOMENT = " + System.currentTimeMillis()); fail("SQLException for timeout expected because the table should be already locked"); } catch (SQLException sqle) { // expected } } void assertTableIsUnlocked() throws ClassNotFoundException, SQLException { 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; try { connection = getConnection(url, user, password); statement = connection.createStatement(); //statement.execute("SELECT * FROM " + table + " WHERE NODE = '" + node + "' FOR UPDATE"); //statement.execute("UPDATE " + table + " SET MOMENT = " + System.currentTimeMillis() + " WHERE NODE = '" + node + "'"); statement.execute("SELECT * FROM " + table + " FOR UPDATE"); statement.execute("UPDATE " + table + " SET MOMENT = " + System.currentTimeMillis()); } finally { close(statement); // connection must not be closed! } return connection; } void close(ResultSet rs) throws SQLException { if (rs != null) { try { rs.close(); } catch (Exception e) { } } } void close(Statement statement) throws SQLException { if (statement != null) { try { statement.close(); } catch (Exception e) { LOG.severe("Can't close the statement: " + e); } } } void close(Connection connection) throws SQLException { if (connection != null) { try { connection.rollback(); } catch (Exception e) { LOG.severe("Can't rollback the connection: " + e); } try { connection.close(); } catch (Exception e) { LOG.severe("Can't close the connection: " + e); } } } }