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