001: /*
002: * hgcommons 7
003: * Hammurapi Group Common Library
004: * Copyright (C) 2003 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.biz/hammurapi-biz/ef/xmenu/hammurapi-group/products/products/hgcommons/index.html
021: * e-Mail: support@hammurapi.biz
022: */
023:
024: package biz.hammurapi.sql;
025:
026: import java.io.PrintWriter;
027: import java.lang.reflect.InvocationHandler;
028: import java.lang.reflect.Method;
029: import java.lang.reflect.Proxy;
030: import java.sql.Connection;
031: import java.sql.Driver;
032: import java.sql.DriverManager;
033: import java.sql.SQLException;
034: import java.util.ArrayList;
035: import java.util.Collection;
036: import java.util.Iterator;
037:
038: import javax.sql.DataSource;
039:
040: /**
041: * Maintains one connection per thread. Connection is allocated on first
042: * getConnection() call. Every next call increments use counter.
043: * Connection closes when it is not used (counter==0) and connectionCloseTimeout passed.
044: *
045: * @author Pavel Vlasov
046: * @version $Revision: 1.7 $
047: */
048: public class ConnectionPerThreadDataSource implements DataSource {
049:
050: private String dbURL;
051: private String user;
052: private String password;
053:
054: private boolean inShutdown;
055: private Collection connections = new ArrayList();
056:
057: private class MasterEntry {
058:
059: {
060: synchronized (ConnectionPerThreadDataSource.this ) {
061: connections.add(this );
062: }
063: }
064:
065: Connection master;
066: int counter;
067:
068: synchronized Connection getMaster() throws SQLException {
069: if (master == null) {
070: master = DriverManager.getConnection(dbURL, user,
071: password);
072: master.setAutoCommit(true);
073: if (initConnectionTransaction != null) {
074: initConnectionTransaction.execute(new SQLProcessor(
075: master, null));
076: }
077: }
078: return master;
079: }
080:
081: public void shutdown() {
082: if (counter == 0) {
083: if (master != null) {
084: try {
085: master.close();
086: } catch (SQLException e) {
087: e.printStackTrace();
088: }
089: }
090: }
091: }
092:
093: public void release() {
094: counter--;
095: if (inShutdown && counter == 0) {
096: if (master != null) {
097: try {
098: master.close();
099: } catch (SQLException e) {
100: e.printStackTrace();
101: }
102: }
103: }
104: }
105:
106: public void use() {
107: counter++;
108: }
109:
110: protected void finalize() throws Throwable {
111: shutdown();
112: super .finalize();
113: }
114: }
115:
116: private ThreadLocal connectionTL = new ThreadLocal() {
117: protected Object initialValue() {
118: return new MasterEntry();
119: }
120: };
121:
122: private Transaction initConnectionTransaction;
123: private ClassLoader classLoader = getClass().getClassLoader();
124:
125: /**
126: * Constructor
127: * @param driverClass
128: * @param dbURL
129: * @param user
130: * @param password
131: * @throws ClassNotFoundException
132: */
133: public ConnectionPerThreadDataSource(String driverClass,
134: String dbURL, String user, String password,
135: Transaction initConnectionTransaction)
136: throws ClassNotFoundException {
137: //"org.hsqldb.jdbcDriver"
138: Class.forName(driverClass);
139: this .dbURL = dbURL;
140: this .user = user;
141: this .password = password;
142: this .initConnectionTransaction = initConnectionTransaction;
143: }
144:
145: /**
146: * Constructor
147: * @param driverClass
148: * @param dbURL
149: * @param user
150: * @param password
151: * @throws SQLException
152: * @throws ClassNotFoundException
153: * @throws IllegalAccessException
154: * @throws InstantiationException
155: */
156: public ConnectionPerThreadDataSource(ClassLoader classLoader,
157: String driverClass, String dbURL, String user,
158: String password, Transaction initConnectionTransaction)
159: throws SQLException, InstantiationException,
160: IllegalAccessException, ClassNotFoundException {
161: if (classLoader == null) {
162: throw new NullPointerException("classLoader==null");
163: }
164: DriverManager.registerDriver((Driver) classLoader.loadClass(
165: driverClass).newInstance());
166: this .dbURL = dbURL;
167: this .user = user;
168: this .password = password;
169: this .initConnectionTransaction = initConnectionTransaction;
170: this .classLoader = classLoader;
171: }
172:
173: private int loginTimeout;
174: private PrintWriter logWriter;
175:
176: public int getLoginTimeout() {
177: return loginTimeout;
178: }
179:
180: public void setLoginTimeout(int seconds) {
181: loginTimeout = seconds;
182: }
183:
184: public PrintWriter getLogWriter() throws SQLException {
185: return logWriter;
186: }
187:
188: public void setLogWriter(PrintWriter out) throws SQLException {
189: logWriter = out;
190: }
191:
192: public Connection getConnection() throws SQLException {
193: return getConnection(user, password);
194: }
195:
196: public Connection getConnection(String user, final String password)
197: throws SQLException {
198: if (inShutdown) {
199: throw new SQLException("Data source is shut down");
200: }
201:
202: final MasterEntry me = (MasterEntry) connectionTL.get();
203: final Connection master = me.getMaster();
204: me.use();
205:
206: InvocationHandler handler = new InvocationHandler() {
207: boolean closed = false;
208:
209: public Object invoke(Object proxy, Method method,
210: Object[] arguments) throws Throwable {
211: if (Connection.class.isAssignableFrom(method
212: .getDeclaringClass())) {
213: if ("close".equals(method.getName())
214: && method.getParameterTypes().length == 0) {
215: if (!closed) {
216: closed = true;
217: master.setAutoCommit(true);
218: me.release();
219: }
220: return null;
221: } else if (closed) {
222: throw new SQLException("Connection is closed");
223: } else {
224: return method.invoke(master, arguments);
225: }
226: }
227:
228: return method.invoke(master, arguments);
229: }
230:
231: };
232:
233: return (Connection) Proxy.newProxyInstance(classLoader,
234: new Class[] { Connection.class }, handler);
235: }
236:
237: /**
238: * Closes all pooled (unused) connections and instructs connections being used
239: * to close immeidatly once they are released.
240: * @throws SQLException
241: */
242: synchronized public void shutdown() {
243: inShutdown = true;
244: Iterator it = connections.iterator();
245: while (it.hasNext()) {
246: ((MasterEntry) it.next()).shutdown();
247: }
248: }
249:
250: protected void finalize() throws Throwable {
251: shutdown();
252: super .finalize();
253: }
254:
255: /**
256: * @return connection initialization transaction
257: */
258: public Transaction getInitConnectionTransaction() {
259: return initConnectionTransaction;
260: }
261: }
|