package org.kairosdb.datastore.h2.orm; import java.sql.*; import javax.sql.DataSource; import java.util.Map; import java.util.HashMap; import java.util.LinkedList; import org.agileclick.genorm.runtime.*; /** <p>This class is at the heart of handling database connections for all Genormous objects. The static methods on this class manipulate connections that are stored on the thread local data. </p> <p>The thread local storage is used so the developer does not have to pass the database connection around. If your application has one database you can set set the default data source by calling {@link #setDataSource(GenOrmDataSource)}, which sets the static member <code>s_dsEnvelope</code>. Then throughout your application you can call <code>GenOrmDataSource.begin()</code> to begin a transaction. A connection will be created from the default data source and the connection will be placed on the thread.</p> <p>You can also provide the connection or data source when you call begin by using either {@link #begin(GenOrmDSEnvelope)} or {@link #begin(Connection)}.</p> <p>Only one connection can be the current one at a time but, you can nest connections</p> <pre> //Open first db connection GenOrmDataSource.begin(); ... //Open second db connection to different db GenOrmDataSource.begin("key to other data source"); //Make calls using second db connection ... GenOrmDataSource.commit(); GenOrmDataSource.close(); //Now make calls to first db connection ... GenOrmDataSource.commit(); GenOrmDataSource.close(); </pre> */ public class GenOrmDataSource { private static class GenOrmThreadLocal extends ThreadLocal<LinkedList<GenOrmConnection>> { @Override protected synchronized LinkedList<GenOrmConnection> initialValue() { return (new LinkedList<GenOrmConnection>()); } /** Adds a connection to the thread */ public void addConnection(GenOrmConnection connection) { get().addFirst(connection); } /** Gets the current connection on the thread or null if none */ public GenOrmConnection getConnection() { GenOrmConnection con = get().peek(); return (con); } /** Removes the connection from the thread. If it is the last connection on the stack the thread local data is removed. */ public GenOrmConnection removeConnection() { GenOrmConnection con = get().remove(); //If it is the last connection we are going to clean up the thread local data if (get().size() == 0) remove(); return (con); } } /** The default data source to use */ public static GenOrmDSEnvelope s_dsEnvelope; /** Map of keys to data sources. */ public static Map<String, GenOrmDSEnvelope> s_dataSourceMap = new HashMap<String, GenOrmDSEnvelope>(); /** The linked list acts as a stack for multiple connections on the same thread. Only the top connection is used at a time. */ private static GenOrmThreadLocal s_tlConnectionList = new GenOrmThreadLocal(); /** Gets the default data source */ public static GenOrmDSEnvelope getDataSource() { return (s_dsEnvelope); } /** Gets the data source for the particular key */ public static GenOrmDSEnvelope getDataSource(String key) { return (s_dataSourceMap.get(key)); } /** Sets the default data source used to create connections for each thread @param ds Envenlope containing the data source to use. */ public static void setDataSource(GenOrmDSEnvelope ds) { s_dsEnvelope = ds; } /** Associates a datasource with a key. Later you can call {@link #begin(String)} and pass the key associated with the datasource @param key Key to store the data source under @param ds Data source envelope */ public static void setDataSource(String key, GenOrmDSEnvelope ds) { s_dataSourceMap.put(key, ds); } /** Begin a transaction using a connection retrieved by first looking up the data source with the <code>source</code> parameter. @param source Key used to lookup the data source to use to create the connection. */ public static void attachAndBegin(String source) { attach(s_dataSourceMap.get(source)).begin(); } //--------------------------------------------------------------------------- /** Begin a transaction using the data source passed into the method @param source Data source used to create a connection. */ public static void attachAndBegin(GenOrmDSEnvelope source) { attach(source).begin(); } //--------------------------------------------------------------------------- /** Begin a transaction using the Connection passed in. @param con Connection to use */ public static void attachAndBegin(Connection con) { GenOrmTransactionConnection gcon = attach(s_dsEnvelope); gcon.setConnection(con); gcon.begin(); } //--------------------------------------------------------------------------- /** Begin a transaction using the default data source that was set using {@link #setDataSource(GenOrmDataSource)} */ public static void attachAndBegin() { attach().begin(); } //--------------------------------------------------------------------------- public static GenOrmTransactionConnection attach(GenOrmDSEnvelope source) { GenOrmTransactionConnection con = new GenOrmTransactionConnection(source); s_tlConnectionList.addConnection(con); return (con); } //--------------------------------------------------------------------------- public static GenOrmTransactionConnection attach() { return (attach(s_dsEnvelope)); } //--------------------------------------------------------------------------- /** Flush all modified records on the current connection */ public static void flush() { s_tlConnectionList.getConnection().flush(); } /** Commit the transaciton on the current connection */ public static void commit() { s_tlConnectionList.getConnection().commit(); } /** Close the current connection */ public static void close() { s_tlConnectionList.removeConnection().close(); } /** Roll back the current connection */ public static void rollback() { s_tlConnectionList.getConnection().rollback(); } /** Return the {@link GenOrmConnection} from off the thread local data. If no connection exists then a {@link GenOrmDudConnection} is returned. The GenOrmDudConnection can be used for transactionless db communication. @return Returns the GenOrmConnection */ public static GenOrmConnection getGenOrmConnection() { GenOrmConnection goCon = s_tlConnectionList.get().peek(); if (goCon == null) goCon = new GenOrmDudConnection(s_dsEnvelope); return (goCon); } /** Return the java.sql.Connection object from off the thread local data @return Returns the java.sql.Connection or null if there is not a current connection set on the thread */ public static Connection getConnection() { GenOrmConnection genCon = getGenOrmConnection(); if (genCon != null) return (genCon.getConnection()); else return (null); } /** Returns the {@link GenOrmKeyGenerator} that is associated with the specified table @param table The SQL table to get the key generator for. @return Returns the GenOrmKeyGenerator or null if there is not a current connection set on the thread. */ public static GenOrmKeyGenerator getKeyGenerator(String table) { GenOrmConnection genCon = getGenOrmConnection(); if (genCon != null) return (genCon.getKeyGenerator(table)); else return (null); } /** Creates a <code>java.sql.Statement</code> using the current connection on the thread @return Returns a Statement or null if there is not a current connection set on the thread */ public static Statement createStatement() throws SQLException { GenOrmConnection genCon = getGenOrmConnection(); return (genCon.createStatement()); } /** Creates a <code>java.sql.PreparedStatement</code> using the current connection on the thread @return Returns a PreparedStatement or null if there is not a current connection set on the thread */ public static PreparedStatement prepareStatement(String sql) throws SQLException { GenOrmConnection goc = getGenOrmConnection(); return (goc.prepareStatement(sql)); } /** Shortcut to process SQL using the current connection. This method is the same as calling <code>Statement stmt = createStatement(); stmt.executeUpdate(sql); stmt.close();</code> @param sql SQL update to process */ public static int rawUpdate(String sql) throws SQLException { Statement stmt = createStatement(); int ret = stmt.executeUpdate(sql); stmt.close(); return (ret); } }