diff --git a/main/src/main/java/org/apache/karaf/main/GenericJDBCLock.java b/main/src/main/java/org/apache/karaf/main/GenericJDBCLock.java new file mode 100644 index 0000000..3a821c3 --- /dev/null +++ b/main/src/main/java/org/apache/karaf/main/GenericJDBCLock.java @@ -0,0 +1,581 @@ +package org.apache.karaf.main; + +/** + * 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. + */ + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This classs is the base class used to provide a master/slave configuration for + * a given set of active karaf instances using JDBC.
+ * + * This implementation uses two different tables. A KARAF_NODE_ID, and KARAF_LOCK tables. The + * KARAF_NODE_ID table is used to generate a unique id for each instance in the cluster. While + * the KARAF_LOCK table is used to determine who is the master of these instances. + * + * The tables configurations for the different tables are. + * + *+ * CREATE TABLE KARAF_NODE_ID ( ID INTEGER DEFAULT 0 ) + * CREATE TABLE KARAF_LOCK ( ID INTEGER DEFAULT 0, STATE INTEGER DEFAULT 0, LOCK_DELAY INTEGER DEFAULT 0 ) + *+ * + * The two tables will include a single row each that is created by a single instance in the cluster. + * + * The KARAF_NODE_ID table will be updated once for each active karaf instance with there unique id compared + * to the other instances within the cluster. The single row will contain the next available unique id and + * will not include each clustered instance unique id since these instances can come and go throughout the + * system lifetime. + * + * The KARAF_LOCK table will be used to determine which of the instances will become the master. The master + * will set the STATE to an initial value and the LOCK_DELAY to a time in milliseconds of when the + * table will be updated. It is the responsibility of the master instance to update the STATE field by the + * allocated lock delay by incrementing the state value. If the STATE value has not been updated by the + * LOCK_DELAY time then a slave has permission to attempt to become the master. + * + * While the overview does not describe exactly how this is implemented. Here is a description of how this + * is done and what is provides as a fail safe solution. + * + * Each instance of this class provides an initialization step, a lock, isAlive and release interface. + * + * INITIALIZE: + * + * During the initialization step it will determine if the given tables exist within the database. We only + * check for a single table since we assume that if one is available then the other must exist. We then + * add a row to each of the tables. The added row to the KARAF_NODE_ID table will set the ID to zero since + * this is consider a non-existent karaf instance. The added row to the KARAF_LOCK will set the ID to zero + * which allows a karaf instances to acquire the lock and become the master instance. + * + * + * LOCK: + * + * The current instance will try to acquire the master lock by using the following sql statement. + * + *
+ * UPDATE KARAF_LOCK SET ID = unique_id, STATE = state, LOCK_DELAY = lock_delay + * WHERE ID = 0 OR ID = curId + *+ * + * Now you must be asking why are we using this update statement? The reason is that the statement will + * guarantee that only one instance will be able to update this row. The curId is set to this instance + * unique id or to the prior master unique id if the row was not updated within that master lock_delay. + * + * The current update command will set the curId to this instance unique id. If this fails then it will + * determine if the current master has not updated the row within its lock_delay. If it hasn't updated + * the row within the allocated time then this instance can try to become the master. + * + * The current slave instance will then try to steal the lock from the master instance. Why are we trying + * to steal the lock from the master? The reason is that it is possible that the master instance had a + * hard failure and there is no mechanisms to determine if that is the case. We then assume that it has + * crashed without releasing the lock gracefully. The slave instance then used the following update statement. + * + *
+ * UPDATE KARAF_LOCK SET ID = unique_id, STATE = state, LOCK_DELAY = lock_delay + * WHERE ( ID = 0 OR ID = curId ) AND STATE = curState + *+ * + * Now why are we using the state value as part of the where clause? The reason that even though the row was + * not updated by the allocated delay time. It is possible that the update statement was performed just after + * the current slave check. This update will insure that the row will be updated if and only if the state was + * also not updated. It is possible that the master instance updated the row after the current slave check + * and we do not want the slave to update the row and make itself the master. This will insure that that will + * not be the case. + * + * ISALIVE: + * + * This just checks if the connection is active and then just updates the row's STATE by using the lock + * update call mentioned above. + * + * RELEASE: + * + * The release process just updates the KARAF_LOCK ID to zero so that other instances will have a chance + * to become the master. + * + * There are two main scenarios that we need to worry about. Soft and Hard failures. The soft failure + * basically allows the master instance to release the master lock and allow other instances to become + * the master. As for a hard failure, the current karaf instance crashes and does not release the lock + * then the other karaf instances will notice that the KARAF_LOCK has not been updated for the current + * master id and then they can compete for the master lock. + * + * @author Claudio Corsi + * + */ +public class GenericJDBCLock implements Lock { + + private static final Logger LOG = Logger.getLogger(GenericJDBCLock.class.getName()); + + private static final String PROPERTY_LOCK_URL = "karaf.lock.jdbc.url"; + private static final String PROPERTY_LOCK_JDBC_DRIVER = "karaf.lock.jdbc.driver"; + private static final String PROPERTY_LOCK_JDBC_USER = "karaf.lock.jdbc.user"; + private static final String PROPERTY_LOCK_JDBC_PASSWORD = "karaf.lock.jdbc.password"; + private static final String PROPERTY_LOCK_JDBC_TABLE = "karaf.lock.jdbc.table"; + private static final String PROPERTY_LOCK_JDBC_TABLE_ID = "karaf.lock.jdbc.table_id"; + private static final String PROPERTY_LOCK_JDBC_CLUSTERNAME = "karaf.lock.jdbc.clustername"; + + private static final String DEFAULT_PASSWORD = ""; + private static final String DEFAULT_USER = ""; + private static final String DEFAULT_TABLE = "KARAF_LOCK"; + private static final String DEFAULT_TABLE_ID = "KARAF_NODE_ID"; + private static final String DEFAULT_CLUSTERNAME = "karaf"; + + protected final JDBCStatements statements; + protected Connection lockConnection; + protected String url; + protected String driver; + protected String user; + protected String password; + protected String table; + protected String clusterName; + protected String table_id; + protected int lock_delay; + + // My lock settings + private int uniqueId = 0; + private int state = 0; + + // Current master instance lock settings + private int currentId = 0; + private int currentState = 0; + + // The last clock time that the master instance state was updated as noticed by this instance. + private long currentStateTime; + // The master lock delay time in milliseconds that the master is expected to update the karaf_lock + // table state + private int currentLockDelay; + + public GenericJDBCLock(Properties props) { + if (Boolean.getBoolean("noAppender") == false) + LOG.addHandler(BootstrapLogManager.getDefaultHandler()); + + this.url = props.getProperty(PROPERTY_LOCK_URL); + this.driver = props.getProperty(PROPERTY_LOCK_JDBC_DRIVER); + this.user = props.getProperty(PROPERTY_LOCK_JDBC_USER, DEFAULT_USER); + this.password = props.getProperty(PROPERTY_LOCK_JDBC_PASSWORD, DEFAULT_PASSWORD); + this.table = props.getProperty(PROPERTY_LOCK_JDBC_TABLE, DEFAULT_TABLE); + this.clusterName = props.getProperty(PROPERTY_LOCK_JDBC_CLUSTERNAME, DEFAULT_CLUSTERNAME); + this.table_id = props.getProperty(PROPERTY_LOCK_JDBC_TABLE_ID, DEFAULT_TABLE_ID); + this.lock_delay = Integer.parseInt(props.getProperty(Main.PROPERTY_LOCK_DELAY, Main.DEFAULT_LOCK_DELAY)); + + this.statements = createStatements(); + + init(); + } + + /** + * This method is called to create an instance of the JDBCStatements instance. + * + * @return an instance of a JDBCStatement object + */ + JDBCStatements createStatements() { + JDBCStatements statements = new JDBCStatements(table, table_id, clusterName); + return statements; + } + + protected void init() { + try { + createDatabase(); + createSchema(); + generateUniqueId(); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error occured while attempting to obtain connection", e); + } + } + + protected void createDatabase() { + // do nothing in the default implementation + } + + /** + * This method is called to check and create the required schemas that are used by this instance. + */ + protected void createSchema() { + if (schemaExists()) { + return; + } + + String[] createStatments = this.statements.getLockCreateSchemaStatements(); + Statement statement = null; + Connection connection = null; + + try { + connection = getConnection(); + statement = connection.createStatement(); + connection.setAutoCommit(false); + + for (String stmt : createStatments) { + LOG.info("Executing statement: " + stmt); + statement.execute(stmt); + } + + connection.commit(); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Could not create schema", e ); + try { + // Rollback transaction if and only if there was a failure... + if (connection != null) + connection.rollback(); + } catch (Exception ie) { + // Do nothing.... + } + } finally { + closeSafely(statement); + try { + // Reset the auto commit to true + connection.setAutoCommit(true); + } catch (SQLException ignored) { + LOG.log(Level.FINE, "Exception while setting the connection auto commit", ignored); + } + } + } + + /** + * This method is called to determine if the required database schemas have already been created or not. + * + * @return true, if the schemas are available else false. + */ + protected boolean schemaExists() { + return (schemaExist(this.statements.getLockTableName())) ? (schemaExist(this.statements + .getLockIdTableName())) : false; + } + + /** + * This method is called to determine if the required table is available or not. + * + * @param tableName The name of the table to determine if it exists + * + * @return true, if the table exists else false + */ + private boolean schemaExist(String tableName) { + ResultSet rs = null; + boolean schemaExists = false; + try { + rs = getConnection().getMetaData().getTables(null, null, tableName, new String[] {"TABLE"}); + schemaExists = rs.next(); + } catch (Exception ignore) { + LOG.log(Level.SEVERE, "Error testing for db table", ignore); + } finally { + closeSafely(rs); + } + return schemaExists; + } + + /** + * This method will generate a unique id for this instance that is part of an active set of instances. + * This method uses a simple algorithm to insure that the id will be unique for all cases. + */ + protected void generateUniqueId() { + boolean uniqueIdSet = false; + String selectString = this.statements.getLockIdSelectStatement(); + PreparedStatement selectStatement = null, updateStatement = null; + try { + selectStatement = getConnection().prepareStatement(selectString); + + // This loop can only be performed for so long and the chances that this will be + // looping for more than a few times is unlikely since there will always be at + // least one instance that is successful. + while (!uniqueIdSet) { + + ResultSet rs = null; + + try { + // Get the current ID from the karaf ids table + rs = selectStatement.executeQuery(); + + // Check if we were able to retrieve the result... + if (rs.next()) { + // Update the row with the next available id + int currentId = this.statements.getIdFromLockIdSelectStatement(rs); + + String updateString = this.statements.getLockIdUpdateIdStatement(currentId + 1, currentId); + + updateStatement = getConnection().prepareStatement(updateString); + + int count = updateStatement.executeUpdate(); + + // Set the uniqueId if and only if is it greater that zero + uniqueId = ( uniqueIdSet = count > 0 ) ? currentId + 1 : 0; + + if (count > 1) { + LOG.severe("OOPS there are more than one row within the table ids..."); + } + } else { + LOG.severe("No rows were found...."); + } + } catch (SQLException e) { + LOG.log(Level.SEVERE, "Received an SQL exception while processing result set", e); + } finally { + this.closeSafely(rs); + } + } + } catch (SQLException e) { + LOG.log(Level.SEVERE, "Received an SQL exception while generating a prepate statement", e); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Received an exception while trying to get a reference to a connection", e); + } finally { + closeSafely(selectStatement); + } + LOG.info("INSTANCE unique id: " + uniqueId); + } + + /** + * This method is called to determine if this instance jdbc connection is + * still connected. + * + * @return true, if the connection is still connected else false + * + * @throws SQLException + */ + protected boolean isConnected() throws SQLException { + return lockConnection != null && !lockConnection.isClosed(); + } + + /** + * This method is called to safely close a Statement. + * + * @param preparedStatement The statement to be closed + */ + protected void closeSafely(Statement preparedStatement) { + if (preparedStatement != null) { + try { + preparedStatement.close(); + } catch (SQLException e) { + LOG.log(Level.SEVERE, "Failed to close statement", e); + } + } + } + + /** + * This method is called to safely close a ResultSet instance. + * + * @param rs The result set to be closed + */ + protected void closeSafely(ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + LOG.log(Level.SEVERE, "Error occured while releasing ResultSet", e); + } + } + } + + /** + * This method will return an active connection for this given jdbc driver. + * + * @return jdbc Connection instance + * + * @throws Exception + */ + protected Connection getConnection() throws Exception { + if (!isConnected()) { + lockConnection = createConnection(driver, url, user, password); + } + + return lockConnection; + } + + /** + * Create a new jdbc connection. + * + * @param driver The fully qualified driver class name + * @param url The database connection url + * @param username The username for the database + * @param password The password for the data + * @return a new jdbc connection + * @throws Exception + */ + protected Connection createConnection(String driver, String url, String username, String password) throws Exception { + if (url.toLowerCase().startsWith("jdbc:derby")) { + url = (url.toLowerCase().contains("create=true")) ? url : url + ";create=true"; + } + + try { + return doCreateConnection(driver, url, username, password); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error occured while setting up JDBC connection", e); + throw e; + } + } + + /** + * This method could be used to inject a mock jdbc connection for testing purposes. + * + * @param driver + * @param url + * @param username + * @param password + * @return + * @throws ClassNotFoundException + * @throws SQLException + */ + protected Connection doCreateConnection(String driver, String url, String username, String password) throws ClassNotFoundException, SQLException { + Class.forName(driver); + return DriverManager.getConnection(url, username, password); + } + + /** + * This method is called whenever we want to acquire/steal or update the lock. + * The different option depend if we are the competing for the master or the lock delay time + * has been exceeded or that we are the master and are update the state. + * + * @return true, if we are the master instance else false + * + * @see org.apache.karaf.main.lock.Lock#lock() + */ + public boolean lock() throws Exception { + + // Try to acquire/update the lock state + boolean lockAquired = acquireLock(statements.getLockUpdateIdStatement(uniqueId, ++state, lock_delay, uniqueId)); + + if (!lockAquired) { + + String lockSelectStatement = statements.getLockSelectStatement(); + PreparedStatement statement = null; + ResultSet rs = null; + + try { + statement = getConnection().prepareStatement(lockSelectStatement); + // Get the current master id and compare with information that we have locally.... + rs = statement.executeQuery(); + + if (rs.next()) { + int currentId = statements.getIdFromLockSelectStatement(rs); // The current master unique id or 0 + int currentState = statements.getStateFromLockSelectStatement(rs); // The current master state or whatever + + if (this.currentId == currentId) { + // It is the same instance that locked the table + if (this.currentState == currentState) { + // Its state has not been updated.... + if ( (this.currentStateTime + this.currentLockDelay + this.currentLockDelay) < System.currentTimeMillis() ) { + // The state was not been updated for more than twice the lock_delay value of the current master... + // Try to steal the lock.... + lockAquired = acquireLock(statements.getLockUpdateIdStatementToStealLock(uniqueId, state, lock_delay, currentId, currentState)); + } + } else { + // Set the current time to be used to determine if we can + // try to steal the lock later... + this.currentStateTime = System.currentTimeMillis(); + this.currentState = currentState; + } + } else { + // This is a different currentId that is being used... + // at this time, it does not matter if the new master id is zero we can try to acquire it + // during the next lock call... + this.currentId = currentId; + this.currentState = currentState; + // Update the current state time since this is a new lock service... + this.currentStateTime = System.currentTimeMillis(); + // Get the lock delay value which is specific to the current master... + this.currentLockDelay = statements.getLockDelayFromLockSelectStatement(rs); + } + } + } catch( Exception e ) { + LOG.log(Level.SEVERE, "Unable to determine if the lock was obtain", e); + } finally { + closeSafely(statement); + closeSafely(rs); + } + } + + return lockAquired; + } + + /** + * This method is called to try and acquire the lock and/or update the state for when this instance + * is already the master instance. It will try to update the row given the passed data and will + * succeed if and only if the generated where clause was valid else it would not update the row. + * + * @param lockUpdateIdStatement The sql statement used to execute the update + * + * @return true, if the row was updated else false + */ + private boolean acquireLock(String lockUpateIdStatement) { + PreparedStatement preparedStatement = null; + boolean lockAquired = false; + + try { + preparedStatement = getConnection().prepareStatement(lockUpateIdStatement); + // This will only update the row that contains the ID of 0 or curId + lockAquired = preparedStatement.executeUpdate() > 0; + } catch (Exception e) { + // Do we want to display this message everytime??? + LOG.log(Level.WARNING, "Failed to acquire database lock", e); + } finally { + closeSafely(preparedStatement); + } + + return lockAquired; + } + + /** + * This method will release the lock that the current master has by setting the karaf_lock table + * id to 0. This tells the others that the master has relinquished the lock and someone else can + * try to acquire that lock and become a master. + * + * @see org.apache.karaf.main.lock.Lock#release() + */ + public void release() throws Exception { + if (isConnected()) { + String lockResetIdStatement = statements.getLockResetIdStatement(uniqueId); + PreparedStatement preparedStatement = null; + + try { + preparedStatement = getConnection().prepareStatement(lockResetIdStatement); + // This statement will set the ID to 0 and allow others to steal the lock... + preparedStatement.executeUpdate(); + } catch (SQLException e) { + LOG.log(Level.SEVERE, "Exception while rollbacking the connection on release", e); + } finally { + closeSafely(preparedStatement); + try { + getConnection().close(); + } catch (SQLException ignored) { + LOG.log(Level.FINE, "Exception while closing connection on release", ignored); + } + } + } + + lockConnection = null; + } + + /** + * This method will check if the jdbc connection is still active and if we were able to + * acquire or update the karaf_table information. + * + * @return true, if the connection is still active and we still have the lock + * + * @see org.apache.karaf.main.lock.Lock#isAlive() + * + */ + public boolean isAlive() throws Exception { + if (!isConnected()) { + LOG.severe("Lost lock!"); + return false; + } + + return lock(); + } + +} diff --git a/main/src/main/java/org/apache/karaf/main/JDBCStatements.java b/main/src/main/java/org/apache/karaf/main/JDBCStatements.java new file mode 100644 index 0000000..ad81e41 --- /dev/null +++ b/main/src/main/java/org/apache/karaf/main/JDBCStatements.java @@ -0,0 +1,243 @@ +/** + * 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 java.sql.ResultSet; +import java.sql.SQLException; + +/** + * This class is used to create the sql statements for the karak lock tables that are used + * for clustering of karak instances. + * + * It will generate sql statement to create two separate tables, a lock table and a lock id table + * + * CREATE TABLE LOCK ( ID INTEGER DEFAULT 0, STATE INTEGER DEFAULT 0, LOCK_DELAY INTEGER DEFAULT 0 ) + * + * CREATE TABLE LOCK_ID ( ID INTEGER DEFAULT 0 ) + * + * @author Claudio Corsi + * + */ +public class JDBCStatements { + + private final String lockTableName; + private final String lockIdTableName; + private final String clusterName; + + /** + * This constructor is used to determine the name of the karaf lock table, the karaf lock id + * table and the name of the clustered instances. + * + * @param lockTableName The name of the karaf lock table + * @param lockIdTableName The name of the karaf lock id table + * @param clusterName the name of the cluster being used + */ + public JDBCStatements(String lockTableName, String lockIdTableName, String clusterName) { + this.lockTableName = lockTableName; + this.lockIdTableName = lockIdTableName; + this.clusterName = clusterName; + } + + /** + * This method will return the name of the cluster that the instances are using to compete for the + * master lock. + * + * @return cluster node name + */ + public final String getNodeName() { + return this.clusterName; + } + + /** + * This method will return the name of the karaf lock table. + * + * @return name of the karaf lock table + */ + public final String getLockTableName() { + return lockTableName; + } + + /** + * This method will return the insert statement used to create a row in the Lock table and will + * generate the following sql statement. + * + * INSERT INTO KARAF_LOCK (ID, STATE, LOCK_DELAY) VALUES (0, 0, 0) + * + * @return sql insert statement + */ + private String getLockTableInitialInsertStatement() { + return "INSERT INTO " + this.getLockTableName() + "(ID, STATE, LOCK_DELAY) VALUES (0, 0, 0)"; + } + + /** + * This will be called when trying to acquire the lock and will generate the following sql statemnt. + * + * UPDATE KARAF_LOCK SET ID = ?, STATE = ?, LOCK_DELAY = ? WHERE ID = 0 OR ID = ? + * + * You are then expected to assign the values associated with the sql statement. + * + * @return sql update statement + */ + public String getLockUpdateIdStatement(int id, int state, int lock_delay, int curId) { + return String.format("UPDATE %s SET ID = %d, STATE = %d, LOCK_DELAY = %d WHERE ID = 0 OR ID = %d", + this.getLockTableName(), id, state, lock_delay, curId); + } + + /** + * This will be called when trying to steal the lock and will generate the following sql statemnt. + * + * UPDATE KARAF_LOCK SET ID = ?, STATE = ?, LOCK_DELAY = ? WHERE ( ID = 0 OR ID = ? ) AND STATE = ? + * + * You are then responsible to assign the values of the different fields using standard jdbc statement + * calls. + * + * @return sql update statement + */ + public String getLockUpdateIdStatementToStealLock(int id, int state, int lock_delay, int curId, int curState) { + return String.format("UPDATE %s SET ID = %d, STATE = %d, LOCK_DELAY = %d WHERE ( ID = 0 OR ID = %d ) AND STATE = %d", + this.getLockTableName(), id, state, lock_delay, curId, curState) ; + } + + /** + * This method is called only when we are releasing the lock and will generate the following sql + * statement. + * + * UPDATE KARAF_LOCK SET ID = 0 WHERE ID = ? + * + * @return sql update statement + */ + public String getLockResetIdStatement(int id) { + return String.format("UPDATE %s SET ID = 0 WHERE ID = %d", this.getLockTableName(), id); + } + + /** + * This will be called to determine the current master instance for the lock table and will + * generate the following sql statement. + * + * SELECT ID, STATE, LOCK_DELAY FROM KARAF_LOCK + * + * @return sql select statement + */ + public String getLockSelectStatement() { + return "SELECT ID, STATE, LOCK_DELAY FROM " + this.getLockTableName(); + } + + public int getIdFromLockSelectStatement(ResultSet rs) throws SQLException { + return rs.getInt(1); + } + + public int getStateFromLockSelectStatement(ResultSet rs) throws SQLException { + return rs.getInt(2); + } + + public int getLockDelayFromLockSelectStatement(ResultSet rs) throws SQLException { + return rs.getInt(3); + } + + /** + * This method should only be called during the creation of the KARAF_LOCK table and will + * generate the following sql statement. + * + * CREATE TABLE KARAF_LOCK (ID INTEGER DEFAULT 0, STATE INTEGER DEFAULT 0, LOCK_DELAY INTEGER DEFAULT 0) + * + * @return sql create table statement + */ + private String getLockTableCreateStatement() { + return "CREATE TABLE " + this.getLockTableName() + + " ( ID INTEGER DEFAULT 0, STATE INTEGER DEFAULT 0 , LOCK_DELAY INTEGER DEFAULT 0 )"; + } + + + // ================== LOCK ID TABLE ======================== + + /** + * This method will generate the create table sql statement to create the karaf id table and will + * generate the following sql statement. + * + * CREATE TABLE KARAF_ID ( ID INTEGER DEFAULT 0 ) + * + * @return sql create table statement + */ + private String getLockIdTableCreateStatement() { + return "CREATE TABLE " + this.getLockIdTableName() + + " ( ID INTEGER DEFAULT 0 )"; + } + + /** + * This method will return the sql statement to retreive the id of the lock id table and will + * generate the following sql statement. + * + * SELECT ID FROM KARAF_ID + * + * @return sql select statement + */ + public String getLockIdSelectStatement() { + return "SELECT ID FROM " + this.getLockIdTableName(); + } + + public int getIdFromLockIdSelectStatement(ResultSet rs) throws SQLException { + return rs.getInt(1); + } + + /** + * This method will return the update statement for the lock id table and will generate the + * following sql statement. + * + * UPDATE KARAF_ID SET ID = ? WHERE ID = ? + * + * @return sql update statement + */ + public String getLockIdUpdateIdStatement(int id, int curId) { + return String.format("UPDATE %s SET ID = %d WHERE ID = %d", this.getLockIdTableName(), id, curId); + } + + /** + * This method will return the name of the karaf lock id table. + * + * @return name of the karaf lock id table + */ + public final String getLockIdTableName() { + return lockIdTableName; + } + + /** + * This method will return the required sql statements to initialize the lock database. + * + * @return array of sql statements + */ + public String[] getLockCreateSchemaStatements() { + return new String[] { + getLockTableCreateStatement(), + getLockIdTableCreateStatement(), + getLockTableInitialInsertStatement(), + getLockIdTableInitialInsertStatement(), + }; + } + + /** + * This method will return the insert statement to insert a row in the lock id table and will + * generate the following sql statement. + * + * INSERT INTO KARAF_ID (ID) VALUES (0) + * + * @return sql insert statement + */ + private String getLockIdTableInitialInsertStatement() { + return "INSERT INTO " + this.getLockIdTableName() + "(ID) VALUES (0)"; + } + +} diff --git a/main/src/main/java/org/apache/karaf/main/Main.java b/main/src/main/java/org/apache/karaf/main/Main.java index 630b71e..ffd1a51 100644 --- a/main/src/main/java/org/apache/karaf/main/Main.java +++ b/main/src/main/java/org/apache/karaf/main/Main.java @@ -182,6 +182,8 @@ public class Main { public static final String OPTIONALS_PROPERTY = "${optionals}"; // optionals includes + public static final String DEFAULT_LOCK_DELAY = "1000"; + Logger LOG = Logger.getLogger(this.getClass().getName()); private File karafHome; @@ -195,7 +197,7 @@ public class Main { private Lock lock; private int defaultStartLevel = 100; private int lockStartLevel = 1; - private int lockDelay = 1000; + private int lockDelay = Integer.parseInt( DEFAULT_LOCK_DELAY ); private int shutdownTimeout = 5 * 60 * 1000; private boolean exiting = false; private ShutdownCallback shutdownCallback; @@ -252,7 +254,7 @@ public class Main { defaultStartLevel = Integer.parseInt(configProps.getProperty(Constants.FRAMEWORK_BEGINNING_STARTLEVEL)); lockStartLevel = Integer.parseInt(configProps.getProperty(PROPERTY_LOCK_LEVEL, Integer.toString(lockStartLevel))); - lockDelay = Integer.parseInt(configProps.getProperty(PROPERTY_LOCK_DELAY, Integer.toString(lockDelay))); + lockDelay = Integer.parseInt(configProps.getProperty(PROPERTY_LOCK_DELAY, DEFAULT_LOCK_DELAY)); configProps.setProperty(Constants.FRAMEWORK_BEGINNING_STARTLEVEL, Integer.toString(lockStartLevel)); shutdownTimeout = Integer.parseInt(configProps.getProperty(KARAF_SHUTDOWN_TIMEOUT, Integer.toString(shutdownTimeout))); // Start up the OSGI framework