Support loading a driver from a JAR specified in the properties

This way drivers can come from anywhere, and don't have to be
packaged into the larger application.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java b/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java
index 724bf94..fe24ce9 100644
--- a/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java
+++ b/src/main/java/com/google/gwtorm/jdbc/SimpleDataSource.java
@@ -14,10 +14,17 @@
 
 package com.google.gwtorm.jdbc;
 
+import java.io.File;
 import java.io.PrintWriter;
+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.SQLException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Properties;
 
 import javax.sql.DataSource;
@@ -26,6 +33,7 @@
 public class SimpleDataSource implements DataSource {
   private final Properties connectionInfo;
   private final String url;
+  private final Driver driver;
   private PrintWriter logWriter;
 
   /**
@@ -48,21 +56,66 @@
       throw new SQLException("Required property 'url' not defined");
     }
 
-    final String driver = (String) connectionInfo.remove("driver");
-    if (driver != null) {
-      loadDriver(driver);
+    final String driverName = (String) connectionInfo.remove("driver");
+    final String classpath = (String) connectionInfo.remove("classpath");
+    if (driverName != null) {
+      ClassLoader cl = threadCL();
+      if (classpath != null && classpath.length() > 0) {
+        final List<URL> urls = new ArrayList<URL>();
+        for (final String path : classpath.split(File.pathSeparator)) {
+          try {
+            urls.add(new URL(path));
+          } catch (MalformedURLException e1) {
+            final File f = new File(path);
+            if (f.exists()) {
+              try {
+                urls.add(f.getAbsoluteFile().toURI().toURL());
+              } catch (MalformedURLException e2) {
+                throw badClasspath(classpath, e2);
+              }
+            } else {
+              throw badClasspath(classpath, e1);
+            }
+          }
+        }
+        cl = new URLClassLoader(urls.toArray(new URL[urls.size()]), cl);
+      }
+      driver = loadDriver(driverName, cl);
+    } else {
+      driver = null;
     }
 
     logWriter = new PrintWriter(System.out);
   }
 
+  private static SQLException badClasspath(final String classpath,
+      final MalformedURLException e1) {
+    final SQLException sqle;
+    sqle = new SQLException("Invalid driver classpath " + classpath);
+    sqle.initCause(e1);
+    return sqle;
+  }
+
   public Connection getConnection() throws SQLException {
+    if (driver != null) {
+      return driver.connect(url, connectionInfo);
+    }
     return DriverManager.getConnection(url, connectionInfo);
   }
 
-  public Connection getConnection(String username, String password)
+  public Connection getConnection(String user, String password)
       throws SQLException {
-    return DriverManager.getConnection(url, username, password);
+    if (driver != null) {
+      final Properties info = new Properties(connectionInfo);
+      if (user != null) {
+        info.put("user", user);
+      }
+      if (password != null) {
+        info.put("password", password);
+      }
+      return driver.connect(url, info);
+    }
+    return DriverManager.getConnection(url, user, password);
   }
 
   public PrintWriter getLogWriter() {
@@ -88,8 +141,8 @@
     throw new SQLException(getClass().getName() + " wraps nothing");
   }
 
-  private static synchronized void loadDriver(final String driver)
-      throws SQLException {
+  private static synchronized Driver loadDriver(final String driver,
+      final ClassLoader loader) throws SQLException {
     // I've seen some drivers (*cough* Informix *cough*) which won't load
     // on multiple threads at the same time. Forcing our code to synchronize
     // around loading the driver ensures we won't ever ask for the same driver
@@ -97,8 +150,8 @@
     // in other parts of the same JVM, but its quite unlikely.
     //
     try {
-      Class.forName(driver, true, threadCL());
-    } catch (ClassNotFoundException err) {
+      return (Driver) Class.forName(driver, true, loader).newInstance();
+    } catch (Throwable err) {
       final SQLException e;
       e = new SQLException("Driver class " + driver + " not available");
       e.initCause(err);
@@ -110,7 +163,7 @@
     try {
       return Thread.currentThread().getContextClassLoader();
     } catch (SecurityException e) {
-      return Database.class.getClassLoader();
+      return SimpleDataSource.class.getClassLoader();
     }
   }
 }