diff --git a/pom.xml b/pom.xml index f456677..e367311 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,12 @@ 2.5 provided + + postgresql + postgresql + 8.3-606.jdbc4 + provided + diff --git a/src/main/java/com/threerings/util/ConnectionProviderUtil.java b/src/main/java/com/threerings/util/ConnectionProviderUtil.java new file mode 100644 index 0000000..9fb3d10 --- /dev/null +++ b/src/main/java/com/threerings/util/ConnectionProviderUtil.java @@ -0,0 +1,73 @@ +// +// ooo-util - a place for OOO utilities +// Copyright (C) 2011 Three Rings Design, Inc., All Rights Reserved +// http://github.com/threerings/ooo-util/blob/master/LICENSE + +package com.threerings.util; + +import java.util.Properties; + +import com.samskivert.util.Config; + +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.StaticConnectionProvider; + +public class ConnectionProviderUtil +{ + public enum DB { + postgres, hsqldb; + } + + /** + * Creates a ConnectionProvider for the type of databases specified by db.type in + * config. + *

Types

+ *
+ *
postgres
+ *
Uses {@link PostgresUtil#createPoolingProvider(Config, String)} with the given config + * and ident.
+ *
hsqldb
+ *
a {@link StaticConnectionProvider} is created from + * the sub-properties under db. It defaults to an in-memory database if + * db.default.url isn't defined. + *
+ *
+ * + * @throws IllegalArgumentException - if db.type doesn't match a known type. + */ + public static ConnectionProvider createProvider (Config config, String ident) + { + return createProvider(config, ident, "db"); + } + + /** + * Creates a ConnectionProvider as in {@link #createProvider(Config,String)}, but uses + * the supplied {@code prefix} in place of {@code db}. + */ + public static ConnectionProvider createProvider (Config config, String ident, String prefix) + { + DB type = DB.valueOf(config.getValue(prefix + ".type", "postgres")); + switch(type) { + case postgres: + return PostgresUtil.createPoolingProvider(config, ident, prefix); + case hsqldb: + Properties sub = config.getSubProperties(prefix); + putIfNotPresent(sub, "default.driver", "org.hsqldb.jdbcDriver"); + putIfNotPresent(sub, "default.url", "jdbc:hsqldb:mem:."); + putIfNotPresent(sub, "default.username", "sa"); + putIfNotPresent(sub, "default.password", ""); + return new StaticConnectionProvider(sub); + default: + throw new IllegalArgumentException("Unknown db type " + + config.getValue(prefix + ".type", (String)null)); + } + } + + protected static void putIfNotPresent (Properties props, String key, String value) + { + if (!props.containsKey(key)) { + props.put(key, value); + } + } + +} diff --git a/src/main/java/com/threerings/util/PostgresUtil.java b/src/main/java/com/threerings/util/PostgresUtil.java new file mode 100644 index 0000000..e9cf46b --- /dev/null +++ b/src/main/java/com/threerings/util/PostgresUtil.java @@ -0,0 +1,105 @@ +// +// ooo-util - a place for OOO utilities +// Copyright (C) 2011 Three Rings Design, Inc., All Rights Reserved +// http://github.com/threerings/ooo-util/blob/master/LICENSE + +package com.threerings.util; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.sql.DataSource; + +import org.postgresql.jdbc2.optional.PoolingDataSource; + +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.DataSourceConnectionProvider; +import com.samskivert.util.Config; +import com.samskivert.util.MissingPropertyException; + +import static com.samskivert.util.PropertiesUtil.requireProperty; + +/** + * Postgres related utility methods. + */ +public class PostgresUtil +{ + /** + * Creates a ConnectionProvider that communicates with a Postgres database server using + * read-only and read-write connections via a connection pooled data source. The supplied + * config must provide the following configuration: + *
+     * db.default.server = DBHOST
+     * db.default.port = 5432
+     * db.default.database = DATABASE
+     * db.default.username = USERNAME
+     * db.default.password = PASSWORD
+     * db.readonly.maxconns = 1
+     * db.readwrite.maxconns = 1
+     * 
+ * It can optionally provide overrides for the server, port, database, username and password + * fields for the readonly and readwrite conncetions: + *
+     * db.readonly.server = READSLAVE
+     * db.readonly.username = READUSER
+     * db.readonly.password = READPASS
+     * db.readwrite.server = MASTER
+     * ...
+     * 
+ * + * @param config used to obtain the configuration data. + * @param projectId a unique identifier for the system creating this provider which will be + * used to prefix the identifiers of the data source names (which are JVM global). + */ + public static ConnectionProvider createPoolingProvider (Config config, String projectId) + { + return createPoolingProvider(config, projectId, "db"); + } + + /** + * Creates a ConnectionProvider as in {@link #createPoolingProvider(Config,String)}, but uses + * the supplied {@code prefix} in place of {@code db}. + */ + public static ConnectionProvider createPoolingProvider ( + Config config, String projectId, String prefix) + { + final DataSource[] sources = new DataSource[2]; + String[] modes = new String[] { "readonly", "readwrite" }; + String defPrefix = prefix + ".default"; + for (int ii = 0; ii < sources.length; ii++) { + String modePrefix = prefix + "." + modes[ii]; + try { + // start with defaults, then apply overrides + Properties props = config.getSubProperties(defPrefix); + config.getSubProperties(modePrefix, props); + PoolingDataSource source = new PoolingDataSource(); + source.setDataSourceName(projectId + "." + modes[ii]); + source.setServerName(requireProperty(props, "server")); + source.setDatabaseName(requireProperty(props, "database")); + source.setPortNumber(Integer.parseInt(requireProperty(props, "port"))); + source.setUser(requireProperty(props, "username")); + source.setPassword(requireProperty(props, "password")); + source.setMaxConnections(Integer.parseInt(props.getProperty("maxconns", "1"))); + sources[ii] = source; + } catch (MissingPropertyException mpe) { + String key = mpe.getKey(); + throw new MissingPropertyException( + key, "Unable to locate required property '" + key + "' as '" + + defPrefix + "." + key + "' or '" + modePrefix + "." + key + "'."); + } + } + return new DataSourceConnectionProvider("jdbc:postgresql", sources[0], sources[1]) { + @Override public void shutdown () { + if (_shutdown.getAndSet(true)) { + // Only shutdown the sources once; Postgres' pooling data source doesn't like + // multiple shutdown calls + return; + } + for (DataSource source : sources) { + ((PoolingDataSource)source).close(); + } + } + protected AtomicBoolean _shutdown = new AtomicBoolean(); + }; + } +}