SchemaPoolSwitcher.java

/***************************************************************************
   Copyright 2013 Emily Estes

   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 net.metanotion.sql;


import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;

/** This class is a data source implementation to transparently change the schema before returning a collection from
the data source. This class wraps an existing data source, and when a request for a connection is made, changes the
default search path for the schema to the one provided at created. This is a PostgreSQL specific SQL extension, and
documentation about PostgreSQL's schema's is provided
<a href="http://www.postgresql.org/docs/9.3/static/ddl-schemas.html">here</a>. Schemas function similar to namespaces
and allow several tables with the same name to exist inside the same database. This has many applications for
multi-tenant applications, schema migrations, and quite a few other possibilities. */
public final class SchemaPoolSwitcher implements DataSource {
	private final DataSource ds;
	private final String path;
	/** Wrap a DataSource instance with one that will change the default schema search path for connections.
		@param ds The DataSource to wrap.
		@param path The PostgreSQL Schema search path for connections issued by this data source.
	*/
	public SchemaPoolSwitcher(final DataSource ds, final String path) {
		this.ds = ds;
		this.path = path;
	}

	/** Change the Schema search path on a connection.
		@param conn The connection to change the search path.
		@return A connection with the new default search path.
	*/
	private Connection setup(final Connection conn) throws SQLException {
		try {
			DbUtil.setSearchPath(conn, this.path);
			return conn;
		} catch (final SQLException sqle) {
			try { conn.close(); } catch (SQLException sqle2) { }
			throw sqle;
		}
	}

	@Override public Connection getConnection() throws SQLException {
		return this.setup(ds.getConnection());
	}

	@Override public Connection getConnection(final String username, final String password) throws SQLException {
		return this.setup(ds.getConnection(username, password));
	}

	@Override public int getLoginTimeout() throws SQLException { return ds.getLoginTimeout(); }
	@Override public PrintWriter getLogWriter() throws SQLException { return ds.getLogWriter(); }
	@Override public void setLoginTimeout(final int seconds) throws SQLException { ds.setLoginTimeout(seconds); }
	@Override public void setLogWriter(final PrintWriter out) throws SQLException { ds.setLogWriter(out); }
	@Override public boolean isWrapperFor(final Class<?> iface) { return false; }
	@Override public <T> T unwrap(final Class<T> iface) throws SQLException { throw new SQLException(); }
	@Override public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		throw new SQLFeatureNotSupportedException();
	}

	@Override public boolean equals(final Object obj) {
		if(!(obj instanceof SchemaPoolSwitcher)) { return false; }
		final SchemaPoolSwitcher sps = (SchemaPoolSwitcher) obj;
		if(!(this.ds.equals(sps.ds))) { return false; }
		return this.path.equals(sps.path);
	}
	@Override public int hashCode() { return path.hashCode() + ds.hashCode(); }
}