/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This software 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.ut.biolab.medsavant.server.db;
import java.io.IOException;
import java.rmi.RemoteException;
import org.ut.biolab.medsavant.shared.model.SessionExpiredException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ut.biolab.medsavant.server.serverapi.SettingsManager;
import org.ut.biolab.medsavant.shared.util.VersionSettings;
import org.xml.sax.SAXException;
/**
*
* @author mfiume
*/
public class ConnectionController {
private static final Log LOG = LogFactory.getLog(ConnectionController.class);
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String PROPS = "enableQueryTimeouts=false&autoReconnect=true";//"useCompression=true"; //"useCompression=true&enableQueryTimeouts=false";
private static final Map<String, ConnectionPool> sessionPoolMap = new ConcurrentHashMap<String, ConnectionPool>();
private static final ExecutorService executor = Executors.newCachedThreadPool();
private static String dbHost;
private static int dbPort = -1;
public static void setHost(String value) {
dbHost = value;
}
public static void setPort(int value) {
dbPort = value;
}
private static String getHost() {
return dbHost;
}
private static int getPort() {
return dbPort;
}
static String getConnectionString(String host, int port, String db) {
return String.format("jdbc:mysql://%s:%d/%s?%s", host, port, db, PROPS);
}
static String getConnectionString(String db) {
return getConnectionString(dbHost, dbPort, db);
}
public static void revalidate(String user, String pass, String sessionID) throws SQLException {
connectOnce(dbHost, dbPort, getDBName(sessionID), user, pass);
}
public static Connection connectOnce(String host, int port, String db, String user, String pass) throws SQLException {
try {
Class.forName(DRIVER).newInstance();
} catch (Exception ex) {
if (ex instanceof ClassNotFoundException || ex instanceof InstantiationException) {
throw new SQLException("Unable to load MySQL driver.");
}
}
return DriverManager.getConnection(getConnectionString(host, port, db), user, pass);
}
public static PooledConnection connectPooled(String sessID) throws SQLException, SessionExpiredException {
synchronized (sessionPoolMap) {
if (!sessionPoolMap.containsKey(sessID)) {
throw new SessionExpiredException();
}
ConnectionPool pool = sessionPoolMap.get(sessID);
if (pool != null) {
return pool.getConnection();
}
}
return null;
}
public static ResultSet executeQuery(String sessID, String query) throws SQLException, SessionExpiredException {
PooledConnection conn = connectPooled(sessID);
try {
return conn.executeQuery(query);
} finally {
conn.close();
}
}
public static ResultSet executePreparedQuery(String sessID, String query, Object... args) throws SQLException, SessionExpiredException {
PooledConnection conn = connectPooled(sessID);
try {
return conn.executePreparedQuery(query, args);
} finally {
conn.close();
}
}
public static void executeUpdate(String sessID, String query) throws SQLException, SessionExpiredException {
PooledConnection conn = connectPooled(sessID);
try {
conn.executeUpdate(query);
} finally {
conn.close();
}
}
public static void executePreparedUpdate(String sessID, String query, Object... args) throws SQLException, SessionExpiredException {
PooledConnection conn = connectPooled(sessID);
try {
conn.executePreparedUpdate(query, args);
} finally {
conn.close();
}
}
/**
* Register credentials for the given session.
*/
public static void registerCredentials(String sessID, String user, String pass, String db) throws SQLException, RemoteException, Exception {
//LOG.debug(String.format("ConnectionController.registerCredentials(%s, %s, %s, %s)", sessID, user, pass, db));
ConnectionPool pool = new ConnectionPool(db, user, pass);
LOG.debug(String.format("sc=%s", pool));
synchronized (sessionPoolMap) {
sessionPoolMap.put(sessID, pool);
Connection c = null;
try {
c = pool.getConnection();
// check that the database is not outdated, i.e. compatible with this server
if (db != null && !db.equals("")) {
String dbVersion = SettingsManager.getInstance().getServerVersionWhenDatabaseCreated(sessID);
String serverVersion = VersionSettings.getVersionString();
LOG.info("Checking compatibility of database " + dbVersion + " to server " + serverVersion);
try {
if (!VersionSettings.isDatabaseCompatibleWithServer(dbVersion, serverVersion)) {
LOG.info("Database " + dbVersion + " and server " + serverVersion + " are NOT compatible");
throw new Exception("Database (" + dbVersion + ") is not compatible with server (" + serverVersion + ")");
} else {
LOG.info("Database " + dbVersion + " and server " + serverVersion + " are compatible");
}
} catch (ParserConfigurationException e) {
throw new Exception("Problem checking compatibility between server and database");
}
}
} finally {
if (c != null) {
c.close();
}
}
}
}
/*
* Make sure you get a new connection after this!
*/
public static void switchDatabases(String sessID, String db) {
synchronized (sessionPoolMap) {
sessionPoolMap.get(sessID).setDBName(db);
}
}
public static String getDBName(String sessID) {
synchronized (sessionPoolMap) {
if(sessionPoolMap.get(sessID) == null){
throw new IllegalArgumentException("Can't fetch database name -- session expired or unregistered.");
}
return sessionPoolMap.get(sessID).getDBName();
}
}
public static String getUserForSession(String sessID) {
synchronized (sessionPoolMap) {
return sessionPoolMap.get(sessID).getUser();
}
}
public static boolean sessionExists(String sessID){
synchronized (sessionPoolMap) {
return sessionPoolMap.containsKey(sessID);
}
}
public static synchronized void removeSession(String sessID) throws SQLException {
executor.submit(new CloseConnectionWhenDone(sessID));
}
public static Collection<String> getSessionIDs() {
synchronized (sessionPoolMap) {
return sessionPoolMap.keySet();
}
}
public static Collection<String> getDBNames() {
List<String> result = new ArrayList<String>();
for (ConnectionPool pool : sessionPoolMap.values()) {
if (!result.contains(pool.getDBName())) {
result.add(pool.getDBName());
}
}
return result;
}
public static void registerAdditionalSessionForSession(String fromSessionID, String toSessionID) {
ConnectionPool pool = sessionPoolMap.get(fromSessionID);
sessionPoolMap.put(toSessionID, pool);
}
/**
* Closes the connection pool for a session once no background users of the
* session are left (immediately if nobody is using it right now).
*/
private static class CloseConnectionWhenDone implements Callable<Boolean> {
/**
* The session to close.
*/
private final String sessionId;
/**
* Simple constructor.
*
* @param sessionId the session to close
*/
CloseConnectionWhenDone(String sessionId) {
this.sessionId = sessionId;
}
@Override
public Boolean call() throws Exception {
synchronized (sessionPoolMap) {
if (sessionPoolMap.containsKey(sessionId)) {
// dissassociate this session with its connection pool
ConnectionPool pool = sessionPoolMap.remove(sessionId);
LOG.info("Unregistered session " + sessionId);
// some other session may be using this pool, so check first
if (!sessionPoolMap.containsValue(pool)) {
pool.close();
LOG.info("Reaped connections for " + sessionId);
} else {
LOG.info("Connection pool still in use for " + sessionId + ", not reaping connections");
}
LOG.info(sessionPoolMap.keySet().size() + " active sessions:");
for (String id : sessionPoolMap.keySet()) {
LOG.info("\t" + id);
}
}
}
return Boolean.TRUE;
}
}
}