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