mirror of
https://github.com/Wisser/Jailer.git
synced 2026-05-20 02:55:00 -05:00
514 lines
14 KiB
Java
514 lines
14 KiB
Java
package net.sf.jailer.database;
|
|
|
|
import java.io.File;
|
|
import java.io.PrintWriter;
|
|
import java.lang.reflect.InvocationHandler;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Proxy;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.sql.Connection;
|
|
import java.sql.Driver;
|
|
import java.sql.DriverManager;
|
|
import java.sql.DriverPropertyInfo;
|
|
import java.sql.ResultSet;
|
|
import java.sql.SQLException;
|
|
import java.sql.SQLFeatureNotSupportedException;
|
|
import java.sql.Statement;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.IdentityHashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Properties;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
import java.util.regex.Pattern;
|
|
|
|
import javax.sql.DataSource;
|
|
|
|
import org.apache.log4j.Logger;
|
|
|
|
import net.sf.jailer.configuration.Configuration;
|
|
import net.sf.jailer.configuration.Configuration.UrlRewriteRule;
|
|
import net.sf.jailer.configuration.DBMS;
|
|
|
|
/**
|
|
* Basic implementation of {@link DataSource}. Uses {@link DriverManager} to create connections.
|
|
*
|
|
* @author Ralf Wisser
|
|
*/
|
|
public class BasicDataSource implements DataSource {
|
|
|
|
/**
|
|
* The logger.
|
|
*/
|
|
private static final Logger _log = Logger.getLogger(BasicDataSource.class);
|
|
|
|
/**
|
|
* Name of JDBC-driver class.
|
|
*/
|
|
final String driverClassName;
|
|
|
|
/**
|
|
* The DB URL.
|
|
*/
|
|
final String dbUrl;
|
|
|
|
/**
|
|
* The DB user.
|
|
*/
|
|
final String dbUser;
|
|
|
|
/**
|
|
* The DB password.
|
|
*/
|
|
private final String dbPassword;
|
|
|
|
/**
|
|
* The DBMS.
|
|
*/
|
|
public final DBMS dbms;
|
|
|
|
/**
|
|
* Maximum number of pooled connections.
|
|
*/
|
|
private final int maxPoolSize;
|
|
|
|
/**
|
|
* Constructor. Derives DBMS from URL.
|
|
*
|
|
* @param driverClassName name of JDBC-driver class
|
|
* @param dbUrl the URL
|
|
* @param dbUser the user
|
|
* @param dbPassword the password
|
|
* @param maxPoolSize maximum number of pooled connections
|
|
* @param jdbcDriver driver jar file
|
|
*/
|
|
public BasicDataSource(String driverClassName, String dbUrl, String dbUser, String dbPassword, int maxPoolSize, File jdbcDriver) {
|
|
this.driverClassName = driverClassName;
|
|
this.dbUrl = dbUrl;
|
|
this.dbUser = dbUser;
|
|
this.dbPassword = dbPassword;
|
|
this.maxPoolSize = maxPoolSize;
|
|
if (jdbcDriver != null) {
|
|
try {
|
|
loadDriver(new URL[] { jdbcDriver.toURI().toURL() });
|
|
} catch (MalformedURLException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
this.dbms = findDBMS();
|
|
}
|
|
|
|
|
|
/**
|
|
* Constructor. Derives DBMS from URL.
|
|
*
|
|
* @param driverClassName name of JDBC-driver class
|
|
* @param dbUrl the URL
|
|
* @param dbUser the user
|
|
* @param dbPassword the password
|
|
* @param maxPoolSize maximum number of pooled connections
|
|
* @param jdbcDriverURL URL of driver jar file
|
|
*/
|
|
public BasicDataSource(String driverClassName, String dbUrl, String dbUser, String dbPassword, int maxPoolSize, URL... jdbcDriverURL) {
|
|
this.driverClassName = driverClassName;
|
|
this.dbUrl = dbUrl;
|
|
this.dbUser = dbUser;
|
|
this.dbPassword = dbPassword;
|
|
this.maxPoolSize = maxPoolSize;
|
|
loadDriver(jdbcDriverURL);
|
|
this.dbms = findDBMS();
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param driverClassName name of JDBC-driver class
|
|
* @param dbUrl the URL
|
|
* @param dbUser the user
|
|
* @param dbPassword the password
|
|
* @param maxPoolSize maximum number of pooled connections
|
|
* @param dbms the DBMS
|
|
*/
|
|
public BasicDataSource(String driverClassName, String dbUrl, String dbUser, String dbPassword, DBMS dbms, int maxPoolSize, URL... jdbcDriverURL) {
|
|
this.driverClassName = driverClassName;
|
|
this.dbUrl = dbUrl;
|
|
this.dbUser = dbUser;
|
|
this.dbPassword = dbPassword;
|
|
this.maxPoolSize = maxPoolSize;
|
|
loadDriver(jdbcDriverURL);
|
|
this.dbms = dbms;
|
|
}
|
|
|
|
/**
|
|
* Closes all pooled connections.
|
|
*/
|
|
public void close() {
|
|
synchronized (pool) {
|
|
for (Connection connection: pool) {
|
|
try {
|
|
connection.close();
|
|
} catch (SQLException e) {
|
|
// ignore
|
|
}
|
|
}
|
|
pool.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wraps a Jdbc-Driver.
|
|
*/
|
|
public static class DriverShim implements Driver {
|
|
private Driver driver;
|
|
public DriverShim(Driver d) {
|
|
this.driver = d;
|
|
}
|
|
@Override
|
|
public boolean acceptsURL(String u) throws SQLException {
|
|
return this.driver.acceptsURL(u);
|
|
}
|
|
@Override
|
|
public Connection connect(String u, Properties p) throws SQLException {
|
|
return this.driver.connect(u, p);
|
|
}
|
|
@Override
|
|
public int getMajorVersion() {
|
|
return this.driver.getMajorVersion();
|
|
}
|
|
@Override
|
|
public int getMinorVersion() {
|
|
return this.driver.getMinorVersion();
|
|
}
|
|
@Override
|
|
public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
|
|
return this.driver.getPropertyInfo(u, p);
|
|
}
|
|
@Override
|
|
public boolean jdbcCompliant() {
|
|
return this.driver.jdbcCompliant();
|
|
}
|
|
@Override
|
|
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
|
throw new SQLFeatureNotSupportedException();
|
|
}
|
|
}
|
|
|
|
private static Set<String> registeredDriverClassNames = new HashSet<String>();
|
|
|
|
private DriverShim currentDriver;
|
|
private static Map<Class<Driver>, DriverShim> drivers = new IdentityHashMap<Class<Driver>, BasicDataSource.DriverShim>();
|
|
|
|
private void loadDriver(URL[] jdbcDriverURL) {
|
|
ClassLoader classLoaderForJdbcDriver = addJarToClasspath(jdbcDriverURL);
|
|
try {
|
|
if (classLoaderForJdbcDriver != null) {
|
|
try {
|
|
@SuppressWarnings("unchecked")
|
|
Class<Driver> driverClass = (Class<Driver>) Class.forName(driverClassName, true, classLoaderForJdbcDriver);
|
|
synchronized (drivers) {
|
|
currentDriver = drivers.get(driverClass);
|
|
if (currentDriver == null) {
|
|
currentDriver = new DriverShim((Driver) driverClass.newInstance());
|
|
drivers.put(driverClass, currentDriver);
|
|
}
|
|
}
|
|
DriverManager.registerDriver(currentDriver);
|
|
registeredDriverClassNames.add(driverClassName);
|
|
} catch (InstantiationException e) {
|
|
throw new RuntimeException(e);
|
|
} catch (IllegalAccessException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
} else {
|
|
Class.forName(driverClassName);
|
|
}
|
|
} catch (SQLException e) {
|
|
throw new RuntimeException(e);
|
|
} catch (UnsupportedClassVersionError e) {
|
|
throw new UnsupportedClassVersionError("This Java VM (" + System.getProperty("java.version") + ") is too old to support the JDBC driver. \nPlease update the Java VM.");
|
|
} catch (ClassNotFoundException e) {
|
|
if (!registeredDriverClassNames.contains(driverClassName)) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private DBMS findDBMS() {
|
|
if (perUrl.containsKey(dbUrl)) {
|
|
return perUrl.get(dbUrl);
|
|
}
|
|
DBMS defaultDBMS = DBMS.forDBMS(null);
|
|
List<DBMS> cs = Configuration.getInstance().getDBMS();
|
|
List<String> rwUrls = new ArrayList<String>();
|
|
rwUrls.add(dbUrl);
|
|
List<UrlRewriteRule> urlRewriteRules = Configuration.getInstance().getUrlRewriteRules();
|
|
if (urlRewriteRules != null) {
|
|
for (UrlRewriteRule rule: urlRewriteRules) {
|
|
String rwUrl = dbUrl.replaceAll(rule.getPattern(), rule.getReplacement());
|
|
if (!rwUrl.equals(dbUrl)) {
|
|
rwUrls.add(rwUrl);
|
|
}
|
|
}
|
|
}
|
|
for (String rwUrl: rwUrls) {
|
|
for (DBMS c: cs) {
|
|
if (Pattern.matches(c.getUrlPattern(), rwUrl)) {
|
|
boolean ok = true;
|
|
if (c.getTestQuery() != null) {
|
|
Connection connection = null;
|
|
Statement st = null;
|
|
try {
|
|
connection = getConnection(defaultDBMS, false);
|
|
st = connection.createStatement();
|
|
ResultSet rs = st.executeQuery(c.getTestQuery());
|
|
while (rs.next()) {
|
|
}
|
|
rs.close();
|
|
} catch (SQLException e) {
|
|
ok = false;
|
|
} finally {
|
|
if (st != null) {
|
|
try {
|
|
st.close();
|
|
} catch (SQLException e) {
|
|
}
|
|
}
|
|
if (connection != null) {
|
|
try {
|
|
connection.close();
|
|
} catch (SQLException e) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (ok) {
|
|
perUrl.put(dbUrl, c);
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
perUrl.put(dbUrl, defaultDBMS);
|
|
return defaultDBMS;
|
|
}
|
|
|
|
private final List<Connection> pool = Collections.synchronizedList(new LinkedList<Connection>());
|
|
|
|
public static boolean findDBMSNeedsConnection(String dbUrl) {
|
|
if (perUrl.containsKey(dbUrl)) {
|
|
return false;
|
|
}
|
|
List<DBMS> cs = Configuration.getInstance().getDBMS();
|
|
List<String> rwUrls = new ArrayList<String>();
|
|
rwUrls.add(dbUrl);
|
|
List<UrlRewriteRule> urlRewriteRules = Configuration.getInstance().getUrlRewriteRules();
|
|
if (urlRewriteRules != null) {
|
|
for (UrlRewriteRule rule: urlRewriteRules) {
|
|
String rwUrl = dbUrl.replaceAll(rule.getPattern(), rule.getReplacement());
|
|
if (!rwUrl.equals(dbUrl)) {
|
|
rwUrls.add(rwUrl);
|
|
}
|
|
}
|
|
}
|
|
for (String rwUrl: rwUrls) {
|
|
for (DBMS c: cs) {
|
|
if (Pattern.matches(c.getUrlPattern(), rwUrl)) {
|
|
if (c.getTestQuery() != null) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Creates a new connection.
|
|
*
|
|
* @param theDbms the DBMS to use
|
|
* @return new connection
|
|
*/
|
|
private Connection getConnection(DBMS theDbms, boolean usePool) throws SQLException {
|
|
Connection con = null;
|
|
|
|
if (usePool) {
|
|
synchronized (pool) {
|
|
if (pool.size() > 0) {
|
|
con = pool.remove(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
Map<String, String> jdbcProperties = theDbms.getJdbcProperties();
|
|
if (con == null && jdbcProperties != null) {
|
|
try {
|
|
java.util.Properties info = new java.util.Properties();
|
|
if (dbUser != null) {
|
|
info.put("user", dbUser);
|
|
}
|
|
if (dbPassword != null) {
|
|
info.put("password", dbPassword);
|
|
}
|
|
for (Map.Entry<String, String> entry: jdbcProperties.entrySet()) {
|
|
info.put(entry.getKey(), entry.getValue());
|
|
}
|
|
try {
|
|
con = DriverManager.getConnection(dbUrl, info);
|
|
} catch (SQLException e3) {
|
|
if (currentDriver != null && currentDriver.acceptsURL(dbUrl)) {
|
|
con = currentDriver.connect(dbUrl, info);
|
|
}
|
|
}
|
|
} catch (SQLException e2) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
if (con == null) {
|
|
try {
|
|
con = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
|
|
} catch (SQLException e) {
|
|
try {
|
|
if (currentDriver != null && currentDriver.acceptsURL(dbUrl)) {
|
|
java.util.Properties info = new java.util.Properties();
|
|
|
|
if (dbUser != null) {
|
|
info.put("user", dbUser);
|
|
}
|
|
if (dbPassword != null) {
|
|
info.put("password", dbPassword);
|
|
}
|
|
con = currentDriver.connect(dbUrl, info);
|
|
}
|
|
} catch (SQLException e1) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (maxPoolSize == 0) {
|
|
return con;
|
|
}
|
|
|
|
final Connection finalCon = con;
|
|
return (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[] { Connection.class } /* con.getClass().getInterfaces() */, new InvocationHandler() {
|
|
private volatile boolean valid = true;
|
|
@Override
|
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
|
if ("close".equals(method.getName())) {
|
|
if (valid && pool.size() < maxPoolSize) {
|
|
try {
|
|
finalCon.rollback();
|
|
pool.add(finalCon);
|
|
return null;
|
|
} catch (SQLException e) {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
return method.invoke(finalCon, args);
|
|
} catch (InvocationTargetException e) {
|
|
if (e.getCause() instanceof SQLException) {
|
|
try {
|
|
valid = finalCon.isValid(10);
|
|
} catch (SQLException e2) {
|
|
valid = false;
|
|
} catch (Throwable t) {
|
|
valid = true;
|
|
}
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Holds all class-loader in order to prevent loading a jar twice.
|
|
*/
|
|
private static Map<String, URLClassLoader> classloaders = new HashMap<String, URLClassLoader>();
|
|
|
|
/**
|
|
* Adds jars to classpath.
|
|
*/
|
|
private static synchronized ClassLoader addJarToClasspath(URL[] jdbcDriverURL) {
|
|
if (jdbcDriverURL.length == 0) {
|
|
return null;
|
|
}
|
|
TreeSet<String> s = new TreeSet<String>();
|
|
for (URL url: jdbcDriverURL) {
|
|
s.add(url.toString());
|
|
}
|
|
String mapKey = s.toString();
|
|
if (classloaders.containsKey(mapKey)) {
|
|
return classloaders.get(mapKey);
|
|
}
|
|
_log.info("added '" + mapKey + "' to classpath");
|
|
URLClassLoader urlLoader = new URLClassLoader(jdbcDriverURL);
|
|
classloaders.put(mapKey, urlLoader);
|
|
return urlLoader;
|
|
}
|
|
|
|
/**
|
|
* Holds configurations.
|
|
*/
|
|
private static Map<String, DBMS> perUrl = new HashMap<String, DBMS>();
|
|
|
|
@Override
|
|
public Connection getConnection() throws SQLException {
|
|
return getConnection(dbms, true);
|
|
}
|
|
|
|
@Override
|
|
public PrintWriter getLogWriter() throws SQLException {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public int getLoginTimeout() throws SQLException {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public void setLogWriter(PrintWriter arg0) throws SQLException {
|
|
throw new UnsupportedOperationException();
|
|
|
|
}
|
|
|
|
@Override
|
|
public void setLoginTimeout(int arg0) throws SQLException {
|
|
throw new UnsupportedOperationException();
|
|
|
|
}
|
|
|
|
@Override
|
|
public boolean isWrapperFor(Class<?> arg0) throws SQLException {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public <T> T unwrap(Class<T> arg0) throws SQLException {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public Connection getConnection(String username, String password) throws SQLException {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
}
|