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.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; 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.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. */ private 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; try { loadDriver(jdbcDriver == null? null : 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; } public boolean acceptsURL(String u) throws SQLException { return this.driver.acceptsURL(u); } public Connection connect(String u, Properties p) throws SQLException { return this.driver.connect(u, p); } public int getMajorVersion() { return this.driver.getMajorVersion(); } public int getMinorVersion() { return this.driver.getMinorVersion(); } public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException { return this.driver.getPropertyInfo(u, p); } public boolean jdbcCompliant() { return this.driver.jdbcCompliant(); } public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } } private void loadDriver(URL[] jdbcDriverURL) { ClassLoader classLoaderForJdbcDriver = addJarToClasspath(jdbcDriverURL); try { if (classLoaderForJdbcDriver != null) { Driver d; try { d = (Driver) Class.forName(driverClassName, true, classLoaderForJdbcDriver).newInstance(); DriverManager.registerDriver(new DriverShim(d)); } 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 (ClassNotFoundException e) { 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(); for (DBMS c: cs) { if (Pattern.matches(c.getUrlPattern(), dbUrl)) { boolean ok = true; if (c.getTestQuery() != null) { Connection connection = null; try { connection = getConnection(defaultDBMS, false); Statement st = connection.createStatement(); ResultSet rs = st.executeQuery(c.getTestQuery()); while (rs.next()) { } rs.close(); st.close(); } catch (SQLException e) { ok = false; } finally { 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>()); /** * 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()); } con = DriverManager.getConnection(dbUrl, info); } catch (SQLException e2) { // ignore } } if (con == null) { con = DriverManager.getConnection(dbUrl, dbUser, dbPassword); } if (maxPoolSize == 0) { return con; } final Connection finalCon = con; return (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), 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(); } }