1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.dbcp.datasources;
  17. import java.sql.Connection;
  18. import java.sql.ResultSet;
  19. import java.sql.SQLException;
  20. import java.sql.Statement;
  21. import java.util.HashMap;
  22. import java.util.Map;
  23. import java.util.WeakHashMap;
  24. import javax.sql.ConnectionEvent;
  25. import javax.sql.ConnectionEventListener;
  26. import javax.sql.ConnectionPoolDataSource;
  27. import javax.sql.PooledConnection;
  28. import org.apache.commons.dbcp.SQLNestedException;
  29. import org.apache.commons.pool.KeyedObjectPool;
  30. import org.apache.commons.pool.KeyedPoolableObjectFactory;
  31. /**
  32. * A {*link PoolableObjectFactory} that creates
  33. * {*link PoolableConnection}s.
  34. *
  35. * @author John D. McNally
  36. * @version $Revision: 1.6 $ $Date: 2004/03/07 11:19:25 $
  37. */
  38. class KeyedCPDSConnectionFactory
  39. implements KeyedPoolableObjectFactory, ConnectionEventListener {
  40. private static final String NO_KEY_MESSAGE
  41. = "close() was called on a Connection, but "
  42. + "I have no record of the underlying PooledConnection.";
  43. protected ConnectionPoolDataSource _cpds = null;
  44. protected String _validationQuery = null;
  45. protected KeyedObjectPool _pool = null;
  46. private Map validatingMap = new HashMap();
  47. private WeakHashMap pcMap = new WeakHashMap();
  48. /**
  49. * Create a new <tt>KeyedPoolableConnectionFactory</tt>.
  50. * @param cpds the ConnectionPoolDataSource from which to obtain PooledConnection's
  51. * @param pool the {*link ObjectPool} in which to pool those {*link Connection}s
  52. * @param validationQuery a query to use to {*link #validateObject validate} {*link Connection}s. Should return at least one row. May be <tt>null</tt>
  53. */
  54. public KeyedCPDSConnectionFactory(ConnectionPoolDataSource cpds,
  55. KeyedObjectPool pool,
  56. String validationQuery) {
  57. _cpds = cpds;
  58. _pool = pool;
  59. _pool.setFactory(this);
  60. _validationQuery = validationQuery;
  61. }
  62. /**
  63. * Sets the {*link ConnectionFactory} from which to obtain base {*link Connection}s.
  64. * @param connFactory the {*link ConnectionFactory} from which to obtain base {*link Connection}s
  65. */
  66. synchronized public void setCPDS(ConnectionPoolDataSource cpds) {
  67. _cpds = cpds;
  68. }
  69. /**
  70. * Sets the query I use to {*link #validateObject validate} {*link Connection}s.
  71. * Should return at least one row.
  72. * May be <tt>null</tt>
  73. * @param validationQuery a query to use to {*link #validateObject validate} {*link Connection}s.
  74. */
  75. synchronized public void setValidationQuery(String validationQuery) {
  76. _validationQuery = validationQuery;
  77. }
  78. /**
  79. * Sets the {*link ObjectPool} in which to pool {*link Connection}s.
  80. * @param pool the {*link ObjectPool} in which to pool those {*link Connection}s
  81. */
  82. synchronized public void setPool(KeyedObjectPool pool)
  83. throws SQLException {
  84. if (null != _pool && pool != _pool) {
  85. try {
  86. _pool.close();
  87. } catch (RuntimeException e) {
  88. throw e;
  89. } catch (Exception e) {
  90. throw new SQLNestedException("Cannot set the pool on this factory", e);
  91. }
  92. }
  93. _pool = pool;
  94. }
  95. public KeyedObjectPool getPool() {
  96. return _pool;
  97. }
  98. /**
  99. * @param key
  100. * @throws SQLException if the connection could not be created.
  101. * @see org.apache.commons.pool.KeyedPoolableObjectFactory#makeObject(java.lang.Object)
  102. */
  103. public synchronized Object makeObject(Object key) throws Exception {
  104. Object obj = null;
  105. UserPassKey upkey = (UserPassKey)key;
  106. PooledConnection pc = null;
  107. String username = upkey.getUsername();
  108. String password = upkey.getPassword();
  109. if (username == null) {
  110. pc = _cpds.getPooledConnection();
  111. } else {
  112. pc = _cpds.getPooledConnection(username, password);
  113. }
  114. // should we add this object as a listener or the pool.
  115. // consider the validateObject method in decision
  116. pc.addConnectionEventListener(this);
  117. obj = new PooledConnectionAndInfo(pc, username, password);
  118. pcMap.put(pc, obj);
  119. return obj;
  120. }
  121. public void destroyObject(Object key, Object obj) throws Exception {
  122. if (obj instanceof PooledConnectionAndInfo) {
  123. PooledConnection pc = ((PooledConnectionAndInfo)obj).getPooledConnection();
  124. pcMap.remove(pc);
  125. pc.close();
  126. }
  127. }
  128. public boolean validateObject(Object key, Object obj) {
  129. boolean valid = false;
  130. if (obj instanceof PooledConnectionAndInfo) {
  131. PooledConnection pconn =
  132. ((PooledConnectionAndInfo)obj).getPooledConnection();
  133. String query = _validationQuery;
  134. if (null != query) {
  135. Connection conn = null;
  136. Statement stmt = null;
  137. ResultSet rset = null;
  138. // logical Connection from the PooledConnection must be closed
  139. // before another one can be requested and closing it will
  140. // generate an event. Keep track so we know not to return
  141. // the PooledConnection
  142. validatingMap.put(pconn, null);
  143. try {
  144. conn = pconn.getConnection();
  145. stmt = conn.createStatement();
  146. rset = stmt.executeQuery(query);
  147. if (rset.next()) {
  148. valid = true;
  149. } else {
  150. valid = false;
  151. }
  152. } catch(Exception e) {
  153. valid = false;
  154. } finally {
  155. try {
  156. rset.close();
  157. } catch (Throwable t) {
  158. // ignore
  159. }
  160. try {
  161. stmt.close();
  162. } catch (Throwable t) {
  163. // ignore
  164. }
  165. try {
  166. conn.close();
  167. } catch (Throwable t) {
  168. // ignore
  169. }
  170. validatingMap.remove(pconn);
  171. }
  172. } else {
  173. valid = true;
  174. }
  175. } else {
  176. valid = false;
  177. }
  178. return valid;
  179. }
  180. public void passivateObject(Object key, Object obj) {
  181. }
  182. public void activateObject(Object key, Object obj) {
  183. }
  184. // ***********************************************************************
  185. // java.sql.ConnectionEventListener implementation
  186. // ***********************************************************************
  187. /**
  188. * This will be called if the Connection returned by the getConnection
  189. * method came from a PooledConnection, and the user calls the close()
  190. * method of this connection object. What we need to do here is to
  191. * release this PooledConnection from our pool...
  192. */
  193. public void connectionClosed(ConnectionEvent event) {
  194. PooledConnection pc = (PooledConnection)event.getSource();
  195. // if this event occured becase we were validating, ignore it
  196. // otherwise return the connection to the pool.
  197. if (!validatingMap.containsKey(pc)) {
  198. PooledConnectionAndInfo info =
  199. (PooledConnectionAndInfo) pcMap.get(pc);
  200. if (info == null) {
  201. throw new IllegalStateException(NO_KEY_MESSAGE);
  202. }
  203. try {
  204. _pool.returnObject(info.getUserPassKey(), info);
  205. } catch (Exception e) {
  206. System.err.println("CLOSING DOWN CONNECTION AS IT COULD " +
  207. "NOT BE RETURNED TO THE POOL");
  208. try {
  209. destroyObject(info.getUserPassKey(), info);
  210. } catch (Exception e2) {
  211. System.err.println("EXCEPTION WHILE DESTROYING OBJECT " +
  212. info);
  213. e2.printStackTrace();
  214. }
  215. }
  216. }
  217. }
  218. /**
  219. * If a fatal error occurs, close the underlying physical connection so as
  220. * not to be returned in the future
  221. */
  222. public void connectionErrorOccurred(ConnectionEvent event) {
  223. PooledConnection pc = (PooledConnection)event.getSource();
  224. try {
  225. if (null != event.getSQLException()) {
  226. System.err
  227. .println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" +
  228. event.getSQLException() + ")");
  229. }
  230. //remove this from the listener list because we are no more
  231. //interested in errors since we are about to close this connection
  232. pc.removeConnectionEventListener(this);
  233. } catch (Exception ignore) {
  234. // ignore
  235. }
  236. PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc);
  237. if (info == null) {
  238. throw new IllegalStateException(NO_KEY_MESSAGE);
  239. }
  240. try {
  241. destroyObject(info.getUserPassKey(), info);
  242. } catch (Exception e) {
  243. System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
  244. e.printStackTrace();
  245. }
  246. }
  247. }