package org.seqcode.data.connections;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.net.URL;
import java.io.*;
import java.util.regex.*;
/**
* <code>DatabaseFactory</code> is the class that all code should use to obtain
* database connections (don't use CxnPool directly). DatabaseFactory
* provides database connections for *roles* and manages the connection pool for you.
* A role is a database resource that you want to access, eg, <b>ucsc_SGDv1</b> for
* annotations for SGDv1 or <b>core</b> for the core db.
*
* @author <a href="mailto:arolfe@mit.edu">Alex Rolfe</a>
* @version 1.0
*/
public abstract class DatabaseFactory {
static Pattern oraclePattern = Pattern.compile(".*oracle.*",Pattern.CASE_INSENSITIVE);
static Pattern mysqlPattern = Pattern.compile(".*mysql.*",Pattern.CASE_INSENSITIVE);
public static int ORACLE = 1, MYSQL = 2, UNKNOWN;
/*
* Static Methods
*/
private static Map<String,CxnPool>pools = new HashMap<String,CxnPool>();
private static List<String> usedRoles = new ArrayList<String>();
private static Map<String,String>defaultUsers = new HashMap<String,String>();
private static Map<String,String>defaultSchemas = new HashMap<String,String>();
private static Map<Connection, CxnPool> cxnSource = new HashMap<Connection, CxnPool>();
/**
* returns the default read-only connection for this role
*/
public static Connection getConnection(String role) throws SQLException, UnknownRoleException {
Properties props = null;
try {
role = getRealRole(role);
if(!usedRoles.contains(role))
usedRoles.add(role);
String user, schema;
if (defaultUsers.containsKey(role) &&
defaultSchemas.containsKey(role)) {
user = defaultUsers.get(role);
schema = defaultSchemas.get(role);
} else {
props = getPropertiesForRole(role);
user = props.getProperty("user");
schema = props.getProperty("schema");
defaultUsers.put(role,user);
defaultSchemas.put(role,schema);
}
String key = role + user + schema;
if (!pools.containsKey(key)) {
if (props == null) {
props = getPropertiesForRole(role);
}
addPool(key,props);
}
CxnPool pool = pools.get(key);
Connection cxn = pool.getConnection();
cxnSource.put(cxn,pool);
return cxn;
} catch (IOException ex) {
throw new RuntimeException("Couldn't read properties for " + role,ex);
}
}
/**
* Return the username associated with the role
* @param role
* @return
*/
public static String getUsername(String role){
Properties props = null;
try {
role = getRealRole(role);
String user;
if (defaultUsers.containsKey(role) ) {
user = defaultUsers.get(role);
} else {
props = getPropertiesForRole(role);
user = props.getProperty("user");
defaultUsers.put(role,user);
}
return user;
} catch (IOException ex) {
throw new RuntimeException("Couldn't read properties for " + role,ex);
}
}
/** for a given role name, eg chipchip, look up
* CHIPCHIPROLE in the environment. If it's set,
* use its value as the return value which is the
* actual role to use.
*
* For example, you might set CHIPCHIPROLE=finkchipchip
* to connect to a chipchip schema that isn't the one
* in your .chipchip_passwd
*/
public static String getRealRole(String role) {
String envkey = role.toUpperCase() + "ROLE";
String val = System.getenv(envkey);
if (val == null) {
return role;
} else {
return val;
}
}
public static void addPool(String key, Properties props) throws SQLException{
String cs = props.getProperty("jdbcconnectstring");
Matcher o = oraclePattern.matcher(cs);
Matcher m = mysqlPattern.matcher(cs);
if (o.matches()) {
pools.put(key,new OracleCxnPool(props));
} else if (m.matches()) {
pools.put(key,new MySQLCxnPool(props));
} else {
throw new SQLException("Unknown database type in " + cs);
}
}
/**
* Returns the Connection to the appropriate pool
* or frees it if it didn't come from any CxnPool.
*/
public static void freeConnection(Connection c) {
CxnPool p = cxnSource.get(c);
if (p != null) {
p.freeConnection(c);
} else {
try {
c.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static Properties getPropertiesForRole(String role) throws UnknownRoleException, IOException {
role = role.replaceAll("\\W+","_");
if (System.getenv(role.toUpperCase() + "ROLE") != null) {
role = System.getenv(role.toUpperCase() + "ROLE");
}
String homedir = System.getenv("HOME");
//System.err.println(String.format("ENV variable HOME=%s", homedir));
String basename = role + "_passwd";
String fname = homedir + "/." + basename;
File propfile = new File(fname);
// System.err.println("Trying to connect as role " + role);
if (propfile.exists() && propfile.canRead()) {
//System.err.println(String.format("Reading HOME/. properties file %s: \"%s\"", propfile.getName(), propfile.getAbsolutePath()));
if (System.getenv("DEBUGPW") != null) {
System.err.println("Opening database properties for " + role + " from " + propfile);
}
return readPasswd(propfile);
}
fname = homedir + "/" + basename;
propfile = new File(fname);
if (propfile.exists() && propfile.canRead()) {
//System.err.println(String.format("Reading HOME properties file %s: \"%s\"", propfile.getName(), propfile.getAbsolutePath()));
if (System.getenv("DEBUGPW") != null) {
System.err.println("Opening database properties for " + role + " from " + propfile);
}
return readPasswd(propfile);
} else {
//System.err.println("Looking in classpath for properties file...");
try {
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL url = cl.getResource(basename);
if (url != null) {
//System.err.println(String.format("Found properties URL: %s", url.toString()));
if (System.getenv("DEBUGPW") != null) {
System.err.println("Opening database properties for " + role + " from " + url);
}
return readPasswdStream(url.openStream());
}
} catch (Exception ex) {
throw new UnknownRoleException("Couldn't find properties file for role " + role,ex);
}
throw new UnknownRoleException("Couldn't find properties file for role " + role);
}
}
private static Properties readPasswd(File propfile) throws IOException {
return readPasswdStream(new FileInputStream(propfile));
}
private static Properties readPasswdStream(InputStream propstream) throws IOException {
String line;
Properties props = new Properties();
BufferedReader reader = new BufferedReader(new InputStreamReader(propstream));
while ((line = reader.readLine()) != null) {
try {
int p = line.indexOf('=');
if(p < 0) {
continue;
}
String key = line.substring(0,p);
String value = line.substring(p+1);
props.setProperty(key,value);
} catch(RuntimeException e) {
System.err.println(line);
throw e;
}
}
reader.close();
return props;
}
/**
* returns the type (ORACLE, MYSQL, UNKNOWN) of this
* Connection
*/
public static int getType(Connection c) {
CxnPool p = cxnSource.get(c);
if (p == null) {
return UNKNOWN;
} else {
return p.getType();
}
}
/**
* Returns true iff this is a connection to an Oracle DB
*/
public static boolean isOracle(Connection c){
return getType(c) == ORACLE;
}
public static boolean isMySQL(Connection c){
return getType(c) == MYSQL;
}
public static boolean isPostGres(Connection c){
return false;
}
public static boolean isSQLLite(Connection c){
return false;
}
/**
* Reestabish all connections.
* May be buggy and error-prone, but this is for a drastic case where db connections are down.
*/
public static void reestablishConnections(){
//Drastically drop all existing pools
pools = new HashMap<String,CxnPool>();
cxnSource = new HashMap<Connection, CxnPool>();
for(String r : usedRoles){
try {
getConnection(r);
} catch (UnknownRoleException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}