001 /* 002 @license.text@ 003 */ 004 005 package biz.hammurapi.sql; 006 007 import java.io.PrintWriter; 008 import java.lang.reflect.InvocationHandler; 009 import java.lang.reflect.Method; 010 import java.lang.reflect.Proxy; 011 import java.sql.Connection; 012 import java.sql.Driver; 013 import java.sql.DriverManager; 014 import java.sql.SQLException; 015 import java.util.ArrayList; 016 import java.util.Collection; 017 import java.util.Iterator; 018 019 import javax.sql.DataSource; 020 021 /** 022 * Maintains one connection per thread. Connection is allocated on first 023 * getConnection() call. Every next call increments use counter. 024 * Connection closes when it is not used (counter==0) and connectionCloseTimeout passed. 025 * 026 * @author Pavel Vlasov 027 * @version $Revision: 1.7 $ 028 */ 029 public class ConnectionPerThreadDataSource implements DataSource { 030 031 private String dbURL; 032 private String user; 033 private String password; 034 035 private boolean inShutdown; 036 private Collection connections=new ArrayList(); 037 038 private class MasterEntry { 039 040 { 041 synchronized (ConnectionPerThreadDataSource.this) { 042 connections.add(this); 043 } 044 } 045 046 Connection master; 047 int counter; 048 049 synchronized Connection getMaster() throws SQLException { 050 if (master==null) { 051 master=DriverManager.getConnection(dbURL, user, password); 052 master.setAutoCommit(true); 053 if (initConnectionTransaction!=null) { 054 initConnectionTransaction.execute(new SQLProcessor(master, null)); 055 } 056 } 057 return master; 058 } 059 060 public void shutdown() { 061 if (counter == 0) { 062 if (master!=null) { 063 try { 064 master.close(); 065 } catch (SQLException e) { 066 e.printStackTrace(); 067 } 068 } 069 } 070 } 071 072 public void release() { 073 counter--; 074 if (inShutdown && counter ==0) { 075 if (master!=null) { 076 try { 077 master.close(); 078 } catch (SQLException e) { 079 e.printStackTrace(); 080 } 081 } 082 } 083 } 084 085 public void use() { 086 counter++; 087 } 088 089 protected void finalize() throws Throwable { 090 shutdown(); 091 super.finalize(); 092 } 093 } 094 095 private ThreadLocal connectionTL=new ThreadLocal() { 096 protected Object initialValue() { 097 return new MasterEntry(); 098 } 099 }; 100 101 private Transaction initConnectionTransaction; 102 private ClassLoader classLoader = getClass().getClassLoader(); 103 104 /** 105 * Constructor 106 * @param driverClass 107 * @param dbURL 108 * @param user 109 * @param password 110 * @throws ClassNotFoundException 111 */ 112 public ConnectionPerThreadDataSource( 113 String driverClass, 114 String dbURL, 115 String user, 116 String password, 117 Transaction initConnectionTransaction) throws ClassNotFoundException { 118 //"org.hsqldb.jdbcDriver" 119 Class.forName(driverClass); 120 this.dbURL=dbURL; 121 this.user=user; 122 this.password=password; 123 this.initConnectionTransaction=initConnectionTransaction; 124 } 125 126 /** 127 * Constructor 128 * @param driverClass 129 * @param dbURL 130 * @param user 131 * @param password 132 * @throws SQLException 133 * @throws ClassNotFoundException 134 * @throws IllegalAccessException 135 * @throws InstantiationException 136 */ 137 public ConnectionPerThreadDataSource( 138 ClassLoader classLoader, 139 String driverClass, 140 String dbURL, 141 String user, 142 String password, 143 Transaction initConnectionTransaction) throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException { 144 if (classLoader==null) { 145 throw new NullPointerException("classLoader==null"); 146 } 147 DriverManager.registerDriver((Driver) classLoader.loadClass(driverClass).newInstance()); 148 this.dbURL=dbURL; 149 this.user=user; 150 this.password=password; 151 this.initConnectionTransaction=initConnectionTransaction; 152 this.classLoader=classLoader; 153 } 154 155 private int loginTimeout; 156 private PrintWriter logWriter; 157 158 public int getLoginTimeout() { 159 return loginTimeout; 160 } 161 162 public void setLoginTimeout(int seconds) { 163 loginTimeout=seconds; 164 } 165 166 public PrintWriter getLogWriter() throws SQLException { 167 return logWriter; 168 } 169 170 public void setLogWriter(PrintWriter out) throws SQLException { 171 logWriter=out; 172 } 173 174 public Connection getConnection() throws SQLException { 175 return getConnection(user,password); 176 } 177 178 public Connection getConnection(String user, final String password) throws SQLException { 179 if (inShutdown) { 180 throw new SQLException("Data source is shut down"); 181 } 182 183 final MasterEntry me=(MasterEntry) connectionTL.get(); 184 final Connection master=me.getMaster(); 185 me.use(); 186 187 InvocationHandler handler=new InvocationHandler() { 188 boolean closed = false; 189 190 public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable { 191 if (Connection.class.isAssignableFrom(method.getDeclaringClass())) { 192 if ("close".equals(method.getName()) && method.getParameterTypes().length==0) { 193 if (!closed) { 194 closed=true; 195 master.setAutoCommit(true); 196 me.release(); 197 } 198 return null; 199 } else if (closed) { 200 throw new SQLException("Connection is closed"); 201 } else { 202 return method.invoke(master, arguments); 203 } 204 } 205 206 return method.invoke(master, arguments); 207 } 208 209 }; 210 211 return (Connection) Proxy.newProxyInstance(classLoader, new Class[] {Connection.class}, handler); 212 } 213 214 /** 215 * Closes all pooled (unused) connections and instructs connections being used 216 * to close immeidatly once they are released. 217 * @throws SQLException 218 */ 219 synchronized public void shutdown() { 220 inShutdown=true; 221 Iterator it=connections.iterator(); 222 while (it.hasNext()) { 223 ((MasterEntry) it.next()).shutdown(); 224 } 225 } 226 227 protected void finalize() throws Throwable { 228 shutdown(); 229 super.finalize(); 230 } 231 232 /** 233 * @return connection initialization transaction 234 */ 235 public Transaction getInitConnectionTransaction() { 236 return initConnectionTransaction; 237 } 238 } 239