| /* |
| * 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; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.sql.Connection; |
| import java.sql.DriverManager; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.Properties; |
| import java.util.logging.Logger; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| |
| 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); |
| } |
| } |
| } |
| } |