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