FELIX-1975: Add Oracle database support to Karaf locking feature

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@902839 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/main/src/main/java/org/apache/felix/karaf/main/OracleJDBCLock.java b/karaf/main/src/main/java/org/apache/felix/karaf/main/OracleJDBCLock.java
new file mode 100644
index 0000000..f031f38
--- /dev/null
+++ b/karaf/main/src/main/java/org/apache/felix/karaf/main/OracleJDBCLock.java
@@ -0,0 +1,248 @@
+/*
+ * 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.felix.karaf.main;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+/**
+ * Represents an exclusive lock on a database,
+ * used to avoid multiple Karaf instances attempting
+ * to become master.
+ * 
+ * @version $Revision: $
+ */
+public class OracleJDBCLock implements Lock {
+
+    private static final Logger LOG = Logger.getLogger(OracleJDBCLock.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_CLUSTERNAME  = "karaf.lock.jdbc.clustername";
+    private static final String PROPERTY_LOCK_JDBC_TIMEOUT      = "karaf.lock.jdbc.timeout";
+
+    private final Statements statements;
+    private Connection lockConnection;
+    private String url;
+    private String database;
+    private String driver;
+    private String user; 
+    private String password;
+    private String table;
+    private String clusterName;
+    private int timeout;
+
+    public OracleJDBCLock(Properties props) {
+        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);
+        this.password = props.getProperty(PROPERTY_LOCK_JDBC_PASSWORD);
+        this.table = props.getProperty(PROPERTY_LOCK_JDBC_TABLE);
+        this.clusterName = props.getProperty(PROPERTY_LOCK_JDBC_CLUSTERNAME);
+        String time = props.getProperty(PROPERTY_LOCK_JDBC_TIMEOUT);
+        this.lockConnection = null;
+        if (table == null) { table = "KARAF_LOCK"; }
+        if ( clusterName == null) { clusterName = "karaf"; }
+        if (time != null) { 
+            this.timeout = Integer.parseInt(time) * 1000; 
+        } else {
+            this.timeout = 10000; // 10 seconds
+        }
+        if (user == null) { user = ""; }
+        if (password == null) { password = ""; }
+
+        int db = props.getProperty(PROPERTY_LOCK_URL).lastIndexOf(":");
+        this.url = props.getProperty(PROPERTY_LOCK_URL);
+        this.database = props.getProperty(PROPERTY_LOCK_URL).substring(db +1);
+        this.statements = new Statements(database, table, clusterName);
+        statements.setDBCreateStatement("create database " + database);
+        statements.setCreateStatement("create table " + table + " (MOMENT number(20), NODE varchar2(20))");
+        statements.setPopulateStatement("insert into " + table + " (MOMENT, NODE) values ('1', '" + clusterName + "')");
+        statements.setColumnNames("MOMENT", "NODE");
+        testDB();
+    }
+
+    /**
+     * testDB - ensure specified database exists.
+     *
+     */
+    private void testDB() {
+        try {
+            lockConnection = getConnection(driver, url, user, password);
+            lockConnection.setAutoCommit(false);
+            statements.init(lockConnection);
+        } catch (Exception e) {
+            LOG.severe("Error occured while attempting to obtain connection: " + e + " " + e.getMessage());
+        } finally {
+            try {
+                lockConnection.close();
+                lockConnection = null;
+            } catch (Exception f) {
+                LOG.severe("Error occured while cleaning up connection: " + f + " " + f.getMessage());
+            }
+        }
+    }
+
+    /**
+     * setUpdateCursor - Send Update directive to data base server.
+     *
+     * @throws Exception
+     */
+    private boolean setUpdateCursor() throws Exception {
+        PreparedStatement statement = null;
+        boolean result = false;
+        try { 
+            if ((lockConnection == null) || (lockConnection.isClosed())) { 
+                LOG.fine("OracleJDBCLock#setUpdateCursor:: connection: " + url);
+                lockConnection = getConnection(driver, url, user, password);
+                lockConnection.setAutoCommit(false);
+                statements.init(lockConnection);
+            } else {
+                LOG.fine("OracleJDBCLock#setUpdateCursor:: connection already established.");
+                return true; 
+            }
+            String sql = "SELECT * FROM " + table + " FOR UPDATE";
+            statement = lockConnection.prepareStatement(sql);
+            result = statement.execute();
+        } catch (Exception e) {
+            LOG.warning("Could not obtain connection: " + e.getMessage());
+        } finally {
+            if (null != statement) {
+                try {
+                    LOG.severe("Cleaning up DB connection.");
+                    statement.close();
+                } catch (SQLException e1) {
+                    LOG.severe("Caught while closing statement: " + e1.getMessage());
+                }
+                statement = null;
+            }
+        }
+        LOG.fine("Connected to data source: " + url + " With RS: " + result);
+        return result;
+    }
+
+    /**
+     * lock - a KeepAlive function to maintain lock. 
+     *
+     * @return true if connection lock retained, false otherwise.
+     */
+    public boolean lock() {
+        PreparedStatement statement = null;
+        boolean result = false;
+        try {
+            if (!setUpdateCursor()) {
+                LOG.severe("Could not set DB update cursor");
+                return result;
+            }
+            LOG.fine("OracleJDBCLock#lock:: have set Update Cursor, now do update");
+            long time = System.currentTimeMillis();
+            statement = lockConnection.prepareStatement(statements.getLockUpdateStatement(time));
+            int rows = statement.executeUpdate();
+            LOG.fine("OracleJDBCLock#lock:: Number of update rows: " + rows);
+            if (rows >= 1) {
+                result=true;
+            }
+        } catch (Exception e) {
+            LOG.warning("Failed to acquire database lock: " + e.getMessage());
+        }finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (SQLException e) {
+                    LOG.severe("Failed to close statement" + e);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * release - terminate the lock connection safely.
+     */
+    public void release() throws Exception {
+        if (lockConnection != null && !lockConnection.isClosed()) {
+            lockConnection.rollback();
+            lockConnection.close();
+            lockConnection = null;
+        }
+    }
+
+    /**
+     * isAlive - test if lock still exists.
+     */
+    public boolean isAlive() throws Exception {
+        if ((lockConnection == null) || (lockConnection.isClosed())) { 
+            LOG.severe("Lost lock!");
+            return false; 
+        }
+        PreparedStatement statement = null;
+        boolean result = true;
+        try { 
+            long time = System.currentTimeMillis();
+            statement = lockConnection.prepareStatement(statements.getLockUpdateStatement(time));
+            int rows = statement.executeUpdate();
+            if (rows < 1) {
+                result = false;
+            }
+        } catch (Exception ex) {
+            LOG.severe("Error occured while testing lock: " + ex + " " + ex.getMessage());
+            return false;
+        } finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                } catch (Exception ex1) {
+                    LOG.severe("Error occured after testing lock: " + ex1.getMessage());
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * getConnection - Obtain connection to database via jdbc driver.
+     *
+     * @throws Exception
+     * @param driver, the JDBC driver class.
+     * @param url, url to data source.
+     * @param username, user to access data source.
+     * @param password, password for specified user.
+     * @return connection, null returned if conenction fails.
+     */
+    private Connection getConnection(String driver, String url, 
+                                     String username, String password) throws Exception {
+        Connection conn = null;
+        try {
+            Class.forName(driver);
+            conn = DriverManager.getConnection(url, username, password);
+        } catch (Exception e) {
+            LOG.severe("Error occured while setting up JDBC connection: " + e);
+            throw e; 
+        }
+        return conn;
+    }
+
+}
diff --git a/karaf/main/src/main/java/org/apache/felix/karaf/main/Statements.java b/karaf/main/src/main/java/org/apache/felix/karaf/main/Statements.java
index eeb8329..44dbc09 100644
--- a/karaf/main/src/main/java/org/apache/felix/karaf/main/Statements.java
+++ b/karaf/main/src/main/java/org/apache/felix/karaf/main/Statements.java
@@ -30,6 +30,8 @@
     private String lockTableName = "KARAF_LOCK";
     private String clusterName = "karaf";
     private String dbName = "sample";
+    private String time = "TIME";
+    private String cluster = "CLUSTER";
     private String lockCreateStatement;
     private String lockDBCreateStatement;
     private String lockPopulateStatement;
@@ -54,6 +56,22 @@
         this.lockPopulateStatement="insert into " + lockTableName + " (TIME, CLUSTER) values (1, '" + clusterName + "')";
     }
 
+    public void setDBCreateStatement(String createDB) {
+        this.lockDBCreateStatement = createDB;
+    }
+
+    public void setCreateStatement(String createTable) {
+        this.lockCreateStatement = createTable;
+    }
+
+    public void setPopulateStatement(String popTable) {
+        this.lockPopulateStatement = popTable;
+    }
+ 
+    public void setColumnNames(String time, String cluster) {
+        this.time = time;
+        this.cluster = cluster; 
+    } 
 
     public String setUpdateCursor() {
         String test = "SELECT * FROM " + lockTableName + " FOR UPDATE";
@@ -63,8 +81,8 @@
     public String getLockUpdateStatement(long timeStamp) {
         String lockUpdateStatement = "";
         lockUpdateStatement = "UPDATE " + lockTableName + 
-                              " SET TIME=" + timeStamp + 
-                              " WHERE CLUSTER = '" + clusterName + "'";
+                              " SET " + this.time + "=" + timeStamp + 
+                              " WHERE " + this.cluster + " = '" + clusterName + "'";
         return lockUpdateStatement;
     }