Use JDBC DataSource to create a Database rather than direct URLs
This way the database can be configured in a web application container
as a JNDI resource that applications can lookup, making it easier to
deploy a web application whose schema is managed by gwtorm.
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/com/google/gwtorm/jdbc/Database.java b/src/com/google/gwtorm/jdbc/Database.java
index 954b5bc..7f575f6 100644
--- a/src/com/google/gwtorm/jdbc/Database.java
+++ b/src/com/google/gwtorm/jdbc/Database.java
@@ -27,13 +27,13 @@
import com.google.gwtorm.schema.sql.SqlDialect;
import java.sql.Connection;
-import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
-import java.util.Properties;
import java.util.WeakHashMap;
+import javax.sql.DataSource;
+
/**
* Constructor for application {@link Schema} extensions.
* <p>
@@ -53,56 +53,37 @@
private static final Map<Class<?>, String> schemaFactoryNames =
Collections.synchronizedMap(new WeakHashMap<Class<?>, String>());
- private final Properties connectionInfo;
- private final String url;
+ private final DataSource dataSource;
private final JavaSchemaModel schemaModel;
private final AbstractSchemaFactory<T> implFactory;
private final SqlDialect implDialect;
/**
* Create a new database interface, generating the interface implementations.
- * <p>
- * The JDBC properties information must define at least <code>url</code> and
- * <code>driver</code>, but may also include driver specific properties such
- * as <code>username</code> and <code>password</code>.
*
- * @param dbInfo JDBC connection information. The property table is copied.
+ * @param ds JDBC connection information
* @param schema application extension of the Schema interface to implement.
* @throws OrmException the schema interface is incorrectly defined, or the
* driver class is not available through the current class loader.
*/
- public Database(final Properties dbInfo, final Class<T> schema)
+ public Database(final DataSource ds, final Class<T> schema)
throws OrmException {
- connectionInfo = new Properties();
- connectionInfo.putAll(dbInfo);
+ dataSource = ds;
- url = (String) connectionInfo.remove("url");
- if (url == null) {
- throw new OrmException("Required property 'url' not defined");
- }
-
- final String driver = (String) connectionInfo.remove("driver");
- if (driver != null) {
- loadDriver(driver);
+ final String url;
+ try {
+ final Connection c = ds.getConnection();
+ try {
+ url = c.getMetaData().getURL();
+ } finally {
+ c.close();
+ }
+ } catch (SQLException e) {
+ throw new OrmException("Unable to determine driver URL", e);
}
final SqlDialect dialect;
- String dialectName = (String) connectionInfo.remove("dialect");
- if (dialectName != null) {
- if (!dialectName.contains(".")) {
- final String n = SqlDialect.class.getName();
- dialectName = n.substring(0, n.lastIndexOf('.') + 1) + dialectName;
- }
- try {
- dialect = (SqlDialect) Class.forName(dialectName).newInstance();
- } catch (InstantiationException e) {
- throw new OrmException("Dialect " + dialectName + " not available", e);
- } catch (IllegalAccessException e) {
- throw new OrmException("Dialect " + dialectName + " not available", e);
- } catch (ClassNotFoundException e) {
- throw new OrmException("Dialect " + dialectName + " not found", e);
- }
- } else if (url.startsWith("jdbc:postgresql:")) {
+ if (url.startsWith("jdbc:postgresql:")) {
dialect = new DialectPostgreSQL();
} else if (url.startsWith("jdbc:h2:")) {
dialect = new DialectH2();
@@ -161,7 +142,7 @@
public T open() throws OrmException {
final Connection conn;
try {
- conn = DriverManager.getConnection(url, connectionInfo);
+ conn = dataSource.getConnection();
} catch (SQLException e) {
throw new OrmException("Cannot open database connection", e);
}
@@ -184,27 +165,4 @@
private static <T> GeneratedClassLoader newLoader(final Class<T> schema) {
return new GeneratedClassLoader(schema.getClassLoader());
}
-
- private static synchronized void loadDriver(final String driver)
- throws OrmException {
- // 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
- // to initialize from different threads. Of course that could still happen
- // in other parts of the same JVM, but its quite unlikely.
- //
- try {
- Class.forName(driver, true, threadCL());
- } catch (ClassNotFoundException err) {
- throw new OrmException("Driver class " + driver + " not available", err);
- }
- }
-
- private static ClassLoader threadCL() {
- try {
- return Thread.currentThread().getContextClassLoader();
- } catch (SecurityException e) {
- return Database.class.getClassLoader();
- }
- }
}
diff --git a/src/com/google/gwtorm/jdbc/SimpleDataSource.java b/src/com/google/gwtorm/jdbc/SimpleDataSource.java
new file mode 100644
index 0000000..724bf94
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/SimpleDataSource.java
@@ -0,0 +1,116 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+/** A simple non-pooling DataSource representation. */
+public class SimpleDataSource implements DataSource {
+ private final Properties connectionInfo;
+ private final String url;
+ private PrintWriter logWriter;
+
+ /**
+ * Create a non-pooling data source.
+ * <p>
+ * The JDBC properties information must define at least <code>url</code> and
+ * <code>driver</code>, but may also include driver specific properties such
+ * as <code>username</code> and <code>password</code>.
+ *
+ * @param dbInfo JDBC connection information. The property table is copied.
+ * @throws SQLException the driver class is not available through the current
+ * class loader.
+ */
+ public SimpleDataSource(final Properties dbInfo) throws SQLException {
+ connectionInfo = new Properties();
+ connectionInfo.putAll(dbInfo);
+
+ url = (String) connectionInfo.remove("url");
+ if (url == null) {
+ throw new SQLException("Required property 'url' not defined");
+ }
+
+ final String driver = (String) connectionInfo.remove("driver");
+ if (driver != null) {
+ loadDriver(driver);
+ }
+
+ logWriter = new PrintWriter(System.out);
+ }
+
+ public Connection getConnection() throws SQLException {
+ return DriverManager.getConnection(url, connectionInfo);
+ }
+
+ public Connection getConnection(String username, String password)
+ throws SQLException {
+ return DriverManager.getConnection(url, username, password);
+ }
+
+ public PrintWriter getLogWriter() {
+ return logWriter;
+ }
+
+ public void setLogWriter(final PrintWriter out) {
+ logWriter = out;
+ }
+
+ public int getLoginTimeout() {
+ return 0;
+ }
+
+ public void setLoginTimeout(int seconds) {
+ }
+
+ public boolean isWrapperFor(Class<?> iface) {
+ return false;
+ }
+
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ throw new SQLException(getClass().getName() + " wraps nothing");
+ }
+
+ private static synchronized void loadDriver(final String driver)
+ 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
+ // to initialize from different threads. Of course that could still happen
+ // in other parts of the same JVM, but its quite unlikely.
+ //
+ try {
+ Class.forName(driver, true, threadCL());
+ } catch (ClassNotFoundException err) {
+ final SQLException e;
+ e = new SQLException("Driver class " + driver + " not available");
+ e.initCause(err);
+ throw e;
+ }
+ }
+
+ private static ClassLoader threadCL() {
+ try {
+ return Thread.currentThread().getContextClassLoader();
+ } catch (SecurityException e) {
+ return Database.class.getClassLoader();
+ }
+ }
+}
diff --git a/test/com/google/gwtorm/server/PhoneBookDbTestCase.java b/test/com/google/gwtorm/server/PhoneBookDbTestCase.java
index 32b164d..ba19836 100644
--- a/test/com/google/gwtorm/server/PhoneBookDbTestCase.java
+++ b/test/com/google/gwtorm/server/PhoneBookDbTestCase.java
@@ -21,6 +21,7 @@
import com.google.gwtorm.data.TestPerson;
import com.google.gwtorm.jdbc.Database;
import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.jdbc.SimpleDataSource;
import junit.framework.TestCase;
@@ -44,7 +45,7 @@
final Properties p = new Properties();
p.setProperty("driver", org.h2.Driver.class.getName());
p.setProperty("url", "jdbc:h2:mem:PhoneBookDb" + (runCount++));
- db = new Database<PhoneBookDb>(p, PhoneBookDb.class);
+ db = new Database<PhoneBookDb>(new SimpleDataSource(p), PhoneBookDb.class);
openSchemas = new ArrayList<PhoneBookDb>();
}