/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev$
* Last modified by $Author$
* $Date$
*/
package tigase.db.jdbc;
import tigase.db.DataRepository;
import tigase.xmpp.BareJID;
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.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Created: Sep 3, 2010 5:55:41 PM
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class DataRepositoryImpl implements DataRepository {
private static final Logger log = Logger.getLogger(DataRepositoryImpl.class.getName());
/** Field description */
public static final String DERBY_CONNVALID_QUERY = "values 1";
/** Field description */
public static final String JDBC_CONNVALID_QUERY = "select 1";
/** Field description */
public static final String MYSQL_CHECK_TABLE_QUERY =
"select * from information_schema.tables where table_name = ? and table_schema = ?";
/** Field description */
public static final String PGSQL_CHECK_TABLE_QUERY =
"select * from pg_tables where tablename = ? and schemaname = ?";
/** Field description */
public static final String DERBY_CHECK_TABLE_QUERY =
"select * from SYS.SYSTABLES where tablename = UPPER(?) and ? is not null";
/** Field description */
public static final String OTHER_CHECK_TABLE_QUERY = "";
/** Field description */
public static final String SP_STARTS_WITH = "{ call";
public static final String QUERY_TIMEOUT_PROP_KEY = "sql-query-timeout";
/** Field description */
public static final int QUERY_TIMEOUT = 10;
public static final String DB_CONN_TIMEOUT_PROP_KEY = "db-conn-timeout";
/** Field description */
public static final int DB_CONN_TIMEOUT = 15;
private Connection conn = null;
private PreparedStatement conn_valid_st = null;
private long connectionValidateInterval = 1000 * 60;
private String db_conn = null;
private long lastConnectionValidated = 0;
private boolean derby_mode = false;
private Map<String, PreparedStatement> db_statements =
new ConcurrentSkipListMap<String, PreparedStatement>();
private Map<String, String> db_queries = new ConcurrentSkipListMap<String, String>();
private String check_table_query = MYSQL_CHECK_TABLE_QUERY;
private String table_schema = null;
private int query_timeout = QUERY_TIMEOUT;
private int db_conn_timeout = DB_CONN_TIMEOUT;
/**
* Method description
*
*
* @param tableName
*
* @return
*
* @throws SQLException
*/
@Override
public boolean checkTable(String tableName) throws SQLException {
PreparedStatement checkTableSt = getPreparedStatement(null, check_table_query);
if (checkTableSt == null) {
return true;
}
boolean result = false;
ResultSet rs = null;
synchronized (checkTableSt) {
try {
checkTableSt.setString(1, tableName);
checkTableSt.setString(2, table_schema);
rs = checkTableSt.executeQuery();
if (rs.next()) {
result = true;
}
} finally {
release(null, rs);
}
}
return result;
}
@Override
public boolean checkTable(String tableName, String createTableQuery)
throws SQLException {
ResultSet rs = null;
Statement st = null;
boolean result = false;
try {
log.log(Level.INFO, "Checking if table {0} exists in DB {1}.", new Object[] {
tableName, table_schema });
if (!checkTable(tableName)) {
log.log(Level.INFO, "Table {0} not found in database, creating: {1}",
new Object[] { tableName, createTableQuery });
st = createStatement(null);
if (!db_conn.contains("derby")) {
st.executeUpdate(createTableQuery);
} else {
String[] queries = createTableQuery.split(";");
for (String query : queries) {
query = query.trim();
if (query.isEmpty())
continue;
st.executeUpdate(query);
}
}
result = true;
} else {
log.log(Level.INFO, "OK table {0} found in database.", tableName);
}
} finally {
release(st, rs);
rs = null;
st = null;
// stmt = null;
}
return result;
}
/**
* Method description
*
*
* @return
*
* @throws SQLException
*/
@Override
public Statement createStatement(BareJID user_id) throws SQLException {
checkConnection();
// This synchronization is used to prevent call when the connection and
// all prepared statements are being recreated.
synchronized (db_statements) {
return conn.createStatement();
}
}
/**
* Method description
*
*
* @param stIdKey
*
* @return
*
* @throws SQLException
*/
@Override
public PreparedStatement getPreparedStatement(BareJID user_id, String stIdKey)
throws SQLException {
checkConnection();
// This synchronization is used to prevent call when the connection and
// all prepared statements are being recreated.
synchronized (db_statements) {
return db_statements.get(stIdKey);
}
}
/**
* Method description
*
*
* @return
*/
@Override
public String getResourceUri() {
return db_conn;
}
/**
* Method description
*
*
* @param key
* @param query
*
* @throws SQLException
*/
@Override
public void initPreparedStatement(String key, String query) throws SQLException {
db_queries.put(key, query);
initStatement(key);
}
/**
* Method description
*
*
* @param resource_uri
* @param params
*
*
* @throws SQLException
*/
@Override
public void initRepository(String resource_uri, Map<String, String> params)
throws SQLException {
db_conn = resource_uri;
db_conn_timeout = getParam(DB_CONN_TIMEOUT_PROP_KEY, params, DB_CONN_TIMEOUT);
query_timeout = getParam(QUERY_TIMEOUT_PROP_KEY, params, QUERY_TIMEOUT);
if (db_conn != null) {
String[] slashes = db_conn.split("/");
table_schema = slashes[slashes.length - 1].split("\\?")[0];
log.log(Level.INFO, "Table schema found: {0}", table_schema);
}
initRepo();
if (db_conn.contains("mysql")) {
check_table_query = MYSQL_CHECK_TABLE_QUERY;
} else if (db_conn.contains("postgresql")) {
check_table_query = PGSQL_CHECK_TABLE_QUERY;
table_schema = "public";
} else if (db_conn.contains("derby")) {
check_table_query = DERBY_CHECK_TABLE_QUERY;
} else {
check_table_query = OTHER_CHECK_TABLE_QUERY;
}
if (!check_table_query.isEmpty()) {
initPreparedStatement(check_table_query, check_table_query);
}
log.log(Level.INFO, "Initialized database connection: {0}", resource_uri);
}
protected int getParam(String key, Map<String, String> params, int def) {
int result = def;
String temp = System.getProperty(key);
if (temp != null) {
try {
result = Integer.parseInt(temp);
} catch (NumberFormatException e) {
result = def;
}
}
if (params != null) {
temp = params.get(key);
if (temp != null) {
try {
result = Integer.parseInt(temp);
} catch (NumberFormatException e) {
result = def;
}
}
}
return result;
}
/**
* Method description
*
*
* @param stmt
* @param rs
*/
@Override
public void release(Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException sqlEx) {
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException sqlEx) {
}
}
}
/**
* <code>checkConnection</code> method checks database connection before any
* query. For some database servers (or JDBC drivers) it happens the
* connection is dropped if not in use for a long time or after certain
* timeout passes. This method allows us to detect the problem and
* reinitialize database connection. This method must not be called
* concurrently, therefore it is synchronized.
*
* @return a <code>boolean</code> value if the database connection is working.
* @exception SQLException
* if an error occurs on database query.
*/
private synchronized boolean checkConnection() throws SQLException {
ResultSet rs = null;
try {
long tmp = System.currentTimeMillis();
// synchronized (conn_valid_st) {
if ((tmp - lastConnectionValidated) >= connectionValidateInterval) {
lastConnectionValidated = tmp;
rs = conn_valid_st.executeQuery();
} // end of if ()
// }
if (((conn_valid_st == null) || conn_valid_st.isClosed())
&& ((tmp - lastConnectionValidated) >= 1000)) {
initRepo();
} // end of if ()
} catch (Exception e) {
initRepo();
} finally {
release(null, rs);
} // end of try-catch
return true;
}
/**
* <code>initPreparedStatements</code> method initializes internal database
* connection variables such as prepared statements.
*
* @exception SQLException
* if an error occurs on database query.
*/
private void initPreparedStatements() throws SQLException {
String query = (derby_mode ? DERBY_CONNVALID_QUERY : JDBC_CONNVALID_QUERY);
conn_valid_st = prepareQuery(query);
try {
conn_valid_st.setQueryTimeout(query_timeout);
} catch (SQLException ex) {
// Ignore for now, it seems that PostgreSQL does not support this method
// call yet
}
for (String key : db_queries.keySet()) {
initStatement(key);
}
}
private void initStatement(String key) throws SQLException {
String query = db_queries.get(key);
PreparedStatement st = prepareQuery(query);
try {
st.setQueryTimeout(query_timeout);
} catch (SQLException ex) {
// Ignore for now, it seems that PostgreSQL does not support this method
// call yet
}
db_statements.put(key, st);
}
/**
* <code>initRepo</code> method initializes database connection and data
* repository.
*
* @exception SQLException
* if an error occurs on database query.
*/
private void initRepo() throws SQLException {
// Statement stmt = null;
ResultSet rs = null;
try {
synchronized (db_statements) {
db_statements.clear();
DriverManager.setLoginTimeout(db_conn_timeout);
conn = DriverManager.getConnection(db_conn);
conn.setAutoCommit(true);
derby_mode = db_conn.startsWith("jdbc:derby");
initPreparedStatements();
// stmt = conn.createStatement();
}
} finally {
release(null, rs);
// release(stmt, rs);
// stmt = null;
rs = null;
}
}
private PreparedStatement prepareQuery(String query) throws SQLException {
if (query.startsWith(SP_STARTS_WITH)) {
return conn.prepareCall(query);
} else {
return conn.prepareStatement(query);
}
}
/*
* (non-Javadoc)
*
* @see tigase.db.DataRepository#takeRepo()
*/
@Override
public DataRepository takeRepoHandle(BareJID user_id) {
return this;
}
/*
* (non-Javadoc)
*
* @see tigase.db.DataRepository#startTransaction()
*/
@Override
public void startTransaction() throws SQLException {
conn.setAutoCommit(false);
}
/*
* (non-Javadoc)
*
* @see tigase.db.DataRepository#commit()
*/
@Override
public void commit() throws SQLException {
conn.commit();
}
/*
* (non-Javadoc)
*
* @see tigase.db.DataRepository#rollback()
*/
@Override
public void rollback() throws SQLException {
conn.rollback();
}
/*
* (non-Javadoc)
*
* @see tigase.db.DataRepository#endTransaction()
*/
@Override
public void endTransaction() throws SQLException {
conn.setAutoCommit(true);
}
/*
* (non-Javadoc)
*
* @see tigase.db.DataRepository#releaseRepoHandle(tigase.db.DataRepository)
*/
@Override
public void releaseRepoHandle(DataRepository repo) {
}
}
// ~ Formatted in Sun Code Convention
// ~ Formatted by Jindent --- http://www.jindent.com