001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.dbcp;
019    
020    import java.sql.CallableStatement;
021    import java.sql.Connection;
022    import java.sql.PreparedStatement;
023    import java.sql.SQLException;
024    
025    import java.util.NoSuchElementException;
026    
027    import org.apache.commons.pool.KeyedObjectPool;
028    import org.apache.commons.pool.KeyedPoolableObjectFactory;
029    
030    /**
031     * A {@link DelegatingConnection} that pools {@link PreparedStatement}s.
032     * <p>
033     * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement
034     * each time, may actually pull the statement from a pool of unused statements.
035     * The {@link PreparedStatement#close} method of the returned statement doesn't
036     * actually close the statement, but rather returns it to the pool. 
037     * (See {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.)
038     * 
039     *
040     * @see PoolablePreparedStatement
041     * @author Rodney Waldhoff
042     * @author Dirk Verbeeck
043     * @version $Revision: 885261 $ $Date: 2009-11-29 15:07:02 -0500 (Sun, 29 Nov 2009) $
044     */
045    public class PoolingConnection extends DelegatingConnection implements Connection, KeyedPoolableObjectFactory {
046        /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
047        protected KeyedObjectPool _pstmtPool = null;
048    
049        /** Prepared Statement type */
050        private static final byte STATEMENT_PREPAREDSTMT = 0;
051        
052        /** Callable Statement type */
053        private static final byte STATEMENT_CALLABLESTMT = 1;
054         
055        
056        /**
057         * Constructor.
058         * @param c the underlying {@link Connection}.
059         */
060        public PoolingConnection(Connection c) {
061            super(c);
062        }
063    
064        /**
065         * Constructor.
066         * @param c the underlying {@link Connection}.
067         * @param pool {@link KeyedObjectPool} of {@link PreparedStatement}s and {@link CallableStatement}s.
068         */
069        public PoolingConnection(Connection c, KeyedObjectPool pool) {
070            super(c);
071            _pstmtPool = pool;
072        }
073    
074    
075        /**
076         * Close and free all {@link PreparedStatement}s or {@link CallableStatement} from the pool, and
077         * close the underlying connection.
078         */
079        public synchronized void close() throws SQLException {
080            if(null != _pstmtPool) {
081                KeyedObjectPool oldpool = _pstmtPool;            
082                _pstmtPool = null;
083                try {
084                    oldpool.close();
085                } catch(RuntimeException e) {
086                    throw e;
087                } catch(SQLException e) {
088                    throw e;
089                } catch(Exception e) {
090                    throw (SQLException) new SQLException("Cannot close connection").initCause(e);
091                }
092            }
093            getInnermostDelegate().close();
094        }
095    
096        /**
097         * Create or obtain a {@link PreparedStatement} from the pool.
098         * @param sql the sql string used to define the PreparedStatement
099         * @return a {@link PoolablePreparedStatement}
100         */
101        public PreparedStatement prepareStatement(String sql) throws SQLException {
102            if (null == _pstmtPool) {
103                throw new SQLException(
104                        "Statement pool is null - closed or invalid PoolingConnection.");
105            }
106            try {
107                return(PreparedStatement)(_pstmtPool.borrowObject(createKey(sql)));
108            } catch(NoSuchElementException e) {
109                throw (SQLException) new SQLException("MaxOpenPreparedStatements limit reached").initCause(e); 
110            } catch(RuntimeException e) {
111                throw e;
112            } catch(Exception e) {
113                throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
114            }
115        }
116    
117        /**
118         * Create or obtain a {@link PreparedStatement} from the pool.
119         * @param sql the sql string used to define the PreparedStatement
120         * @param resultSetType result set type
121         * @param resultSetConcurrency result set concurrency
122         * @return a {@link PoolablePreparedStatement}
123         */
124        public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
125            if (null == _pstmtPool) {
126                throw new SQLException(
127                        "Statement pool is null - closed or invalid PoolingConnection.");
128            }
129            try {
130                return(PreparedStatement)(_pstmtPool.borrowObject(createKey(sql,resultSetType,resultSetConcurrency)));
131            } catch(NoSuchElementException e) {
132                throw (SQLException) new SQLException("MaxOpenPreparedStatements limit reached").initCause(e); 
133            } catch(RuntimeException e) {
134                throw e;
135            } catch(Exception e) {
136                throw (SQLException) new SQLException("Borrow prepareStatement from pool failed").initCause(e);
137            }
138        }
139        
140        /**
141         * Create or obtain a {@link CallableStatement} from the pool.
142         * @param sql the sql string used to define the CallableStatement
143         * @return a {@link PoolableCallableStatement}
144         * @throws SQLException
145         * @since 1.3
146         */
147        public CallableStatement prepareCall(String sql) throws SQLException {
148            try {
149                return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql, STATEMENT_CALLABLESTMT)));
150            } catch (NoSuchElementException e) {
151                throw new SQLNestedException("MaxOpenCallableStatements limit reached", e);
152            } catch (RuntimeException e) {
153                throw e;
154            } catch (Exception e) {
155                throw new SQLNestedException("Borrow callableStatement from pool failed", e);
156            }
157        }
158        
159        /**
160         * Create or obtain a {@link CallableStatement} from the pool.
161         * @param sql the sql string used to define the CallableStatement
162         * @param resultSetType result set type
163         * @param resultSetConcurrency result set concurrency
164         * @return a {@link PoolableCallableStatement}
165         * @throws SQLException
166         * @since 1.3
167         */
168        public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
169            try {
170                return (CallableStatement) (_pstmtPool.borrowObject(createKey(sql, resultSetType,
171                                resultSetConcurrency, STATEMENT_CALLABLESTMT)));
172            } catch (NoSuchElementException e) {
173                throw new SQLNestedException("MaxOpenCallableStatements limit reached", e);
174            } catch (RuntimeException e) {
175                throw e;
176            } catch (Exception e) {
177                throw new SQLNestedException("Borrow callableStatement from pool failed", e);
178            }
179        }
180        
181    
182    //    TODO: possible enhancement, cache these preparedStatements as well
183    
184    //    public PreparedStatement prepareStatement(String sql, int resultSetType,
185    //                                              int resultSetConcurrency,
186    //                                              int resultSetHoldability)
187    //        throws SQLException {
188    //        return super.prepareStatement(
189    //            sql, resultSetType, resultSetConcurrency, resultSetHoldability);
190    //    }
191    //
192    //    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
193    //        throws SQLException {
194    //        return super.prepareStatement(sql, autoGeneratedKeys);
195    //    }
196    //
197    //    public PreparedStatement prepareStatement(String sql, int columnIndexes[])
198    //        throws SQLException {
199    //        return super.prepareStatement(sql, columnIndexes);
200    //    }
201    //
202    //    public PreparedStatement prepareStatement(String sql, String columnNames[])
203    //        throws SQLException {
204    //        return super.prepareStatement(sql, columnNames);
205    //    }
206    
207        /**
208         * Create a PStmtKey for the given arguments.
209         * @param sql the sql string used to define the statement
210         * @param resultSetType result set type
211         * @param resultSetConcurrency result set concurrency
212         */
213        protected Object createKey(String sql, int resultSetType, int resultSetConcurrency) {
214            String catalog = null;
215            try {
216                catalog = getCatalog();
217            } catch (SQLException e) {}
218            return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency);
219        }
220        
221        /**
222         * Create a PStmtKey for the given arguments.
223         * @param sql the sql string used to define the statement
224         * @param resultSetType result set type
225         * @param resultSetConcurrency result set concurrency
226         * @param stmtType statement type - either {@link #STATEMENT_CALLABLESTMT} or {@link #STATEMENT_PREPAREDSTMT}
227         */
228        protected Object createKey(String sql, int resultSetType, int resultSetConcurrency, byte stmtType) {
229            String catalog = null;
230            try {
231                catalog = getCatalog();
232            } catch (SQLException e) {}
233            return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency, stmtType);
234        }
235    
236        /**
237         * Create a PStmtKey for the given arguments.
238         * @param sql the sql string used to define the statement
239         */
240        protected Object createKey(String sql) {
241            String catalog = null;
242            try {
243                catalog = getCatalog();
244            } catch (SQLException e) {}
245            return new PStmtKey(normalizeSQL(sql), catalog);
246        }
247        
248        /**
249         * Create a PStmtKey for the given arguments.
250         * @param sql the sql string used to define the statement
251         * @param stmtType statement type - either {@link #STATEMENT_CALLABLESTMT} or {@link #STATEMENT_PREPAREDSTMT}
252         */
253        protected Object createKey(String sql, byte stmtType) {
254            String catalog = null;
255            try {
256                catalog = getCatalog();
257            } catch (SQLException e) {}
258            return new PStmtKey(normalizeSQL(sql), catalog, stmtType);
259        }
260    
261        /**
262         * Normalize the given SQL statement, producing a
263         * cannonical form that is semantically equivalent to the original.
264         */
265        protected String normalizeSQL(String sql) {
266            return sql.trim();
267        }
268    
269        /**
270         * {@link KeyedPoolableObjectFactory} method for creating
271         * {@link PoolablePreparedStatement}s or {@link PoolableCallableStatement}s.
272         * The <code>stmtType</code> field in the key determines whether 
273         * a PoolablePreparedStatement or PoolableCallableStatement is created.
274         * 
275         * @param obj the key for the {@link PreparedStatement} to be created
276         * @see #createKey(String, int, int, byte)
277         */
278        public Object makeObject(Object obj) throws Exception {
279            if(null == obj || !(obj instanceof PStmtKey)) {
280                throw new IllegalArgumentException("Prepared statement key is null or invalid.");
281            } else {
282                PStmtKey key = (PStmtKey)obj;
283                if( null == key._resultSetType && null == key._resultSetConcurrency ) {
284                    if (key._stmtType == STATEMENT_PREPAREDSTMT ) {
285                        return new PoolablePreparedStatement(getDelegate().prepareStatement( key._sql), key, _pstmtPool, this); 
286                    } else {
287                        return new PoolableCallableStatement(getDelegate().prepareCall( key._sql), key, _pstmtPool, this);
288                    }
289                } else { // Both _resultSetType and _resultSetConcurrency are non-null here (both or neither are set by constructors)
290                    if(key._stmtType == STATEMENT_PREPAREDSTMT) {
291                        return new PoolablePreparedStatement(getDelegate().prepareStatement(
292                            key._sql, key._resultSetType.intValue(),key._resultSetConcurrency.intValue()), key, _pstmtPool, this);
293                    } else {
294                        return new PoolableCallableStatement( getDelegate().prepareCall(
295                            key._sql,key._resultSetType.intValue(), key._resultSetConcurrency.intValue()), key, _pstmtPool, this);
296                    }
297                }
298            }
299        }
300    
301        /**
302         * {@link KeyedPoolableObjectFactory} method for destroying
303         * PoolablePreparedStatements and PoolableCallableStatements.
304         * Closes the underlying statement.
305         * 
306         * @param key ignored
307         * @param obj the pooled statement to be destroyed.
308         */
309        public void destroyObject(Object key, Object obj) throws Exception {
310            if(obj instanceof DelegatingPreparedStatement) {
311                ((DelegatingPreparedStatement)obj).getInnermostDelegate().close();
312            } else {
313                ((PreparedStatement)obj).close();
314            }
315        }
316    
317        /**
318         * {@link KeyedPoolableObjectFactory} method for validating
319         * pooled statements. Currently always returns true.
320         * 
321         * @param key ignored
322         * @param obj ignored
323         * @return <tt>true</tt>
324         */
325        public boolean validateObject(Object key, Object obj) {
326            return true;
327        }
328    
329        /**
330         * {@link KeyedPoolableObjectFactory} method for activating
331         * pooled statements.
332         * 
333         * @param key ignored
334         * @param obj pooled statement to be activated
335         */
336        public void activateObject(Object key, Object obj) throws Exception {
337            ((DelegatingPreparedStatement)obj).activate();
338        }
339    
340        /**
341         * {@link KeyedPoolableObjectFactory} method for passivating
342         * {@link PreparedStatement}s or {@link CallableStatement}s.
343         * Invokes {@link PreparedStatement#clearParameters}.
344         * 
345         * @param key ignored
346         * @param obj a {@link PreparedStatement}
347         */
348        public void passivateObject(Object key, Object obj) throws Exception {
349            ((PreparedStatement)obj).clearParameters();
350            ((DelegatingPreparedStatement)obj).passivate();
351        }
352    
353        public String toString() {
354            if (_pstmtPool != null ) {
355                return "PoolingConnection: " + _pstmtPool.toString();
356            } else {
357                return "PoolingConnection: null";
358            }
359        }
360    
361        /**
362         * A key uniquely identifiying {@link PreparedStatement}s.
363         */
364        static class PStmtKey {
365            
366            /** SQL defining Prepared or Callable Statement */
367            protected String _sql = null;
368            
369            /** Result set type */
370            protected Integer _resultSetType = null;
371            
372            /** Result set concurrency */
373            protected Integer _resultSetConcurrency = null;
374            
375            /** Database catalog */
376            protected String _catalog = null;
377            
378            /** 
379             *  Statement type. Either STATEMENT_PREPAREDSTMT (PreparedStatement)
380             *  or STATEMENT_CALLABLESTMT (CallableStatement) 
381             */
382            protected byte _stmtType = STATEMENT_PREPAREDSTMT;
383            
384            PStmtKey(String sql) {
385                _sql = sql;
386            }
387    
388            PStmtKey(String sql, String catalog) {
389                _sql = sql;
390                _catalog = catalog;
391            }
392            
393            PStmtKey(String sql, String catalog, byte stmtType) {
394                _sql = sql;
395                _catalog = catalog;
396                _stmtType = stmtType;
397            }
398    
399            PStmtKey(String sql, int resultSetType, int resultSetConcurrency) {
400                _sql = sql;
401                _resultSetType = new Integer(resultSetType);
402                _resultSetConcurrency = new Integer(resultSetConcurrency);
403            }
404    
405            PStmtKey(String sql, String catalog, int resultSetType, int resultSetConcurrency) {
406                _sql = sql;
407                _catalog = catalog;
408                _resultSetType = new Integer(resultSetType);
409                _resultSetConcurrency = new Integer(resultSetConcurrency);
410            }
411            
412            PStmtKey(String sql, String catalog, int resultSetType, int resultSetConcurrency, byte stmtType) {
413                _sql = sql;
414                _catalog = catalog;
415                _resultSetType = new Integer(resultSetType);
416                _resultSetConcurrency = new Integer(resultSetConcurrency);
417                _stmtType = stmtType;
418            }
419    
420            public boolean equals(Object that) {
421                try {
422                    PStmtKey key = (PStmtKey)that;
423                    return( ((null == _sql && null == key._sql) || _sql.equals(key._sql)) &&  
424                            ((null == _catalog && null == key._catalog) || _catalog.equals(key._catalog)) &&
425                            ((null == _resultSetType && null == key._resultSetType) || _resultSetType.equals(key._resultSetType)) &&
426                            ((null == _resultSetConcurrency && null == key._resultSetConcurrency) || _resultSetConcurrency.equals(key._resultSetConcurrency)) &&
427                            (_stmtType == key._stmtType)
428                          );
429                } catch(ClassCastException e) {
430                    return false;
431                } catch(NullPointerException e) {
432                    return false;
433                }
434            }
435    
436            public int hashCode() {
437                if (_catalog==null)
438                    return(null == _sql ? 0 : _sql.hashCode());
439                else
440                    return(null == _sql ? _catalog.hashCode() : (_catalog + _sql).hashCode());
441            }
442    
443            public String toString() {
444                StringBuffer buf = new StringBuffer();
445                buf.append("PStmtKey: sql=");
446                buf.append(_sql);
447                buf.append(", catalog=");
448                buf.append(_catalog);
449                buf.append(", resultSetType=");
450                buf.append(_resultSetType);
451                buf.append(", resultSetConcurrency=");
452                buf.append(_resultSetConcurrency);
453                buf.append(", statmentType=");
454                buf.append(_stmtType);
455                return buf.toString();
456            }
457        }
458    }