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