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.ObjectPool;
  30. import org.apache.commons.pool.PoolableObjectFactory;
  31. /**
  32. * A {@link PoolableObjectFactory} that creates
  33. * {@link PoolableConnection}s.
  34. *
  35. * @author John D. McNally
  36. * @version $Revision: 1.5 $ $Date: 2004/02/28 12:18:17 $
  37. */
  38. class CPDSConnectionFactory
  39. implements PoolableObjectFactory, 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 ObjectPool _pool = null;
  46. protected String _username = null;
  47. protected String _password = null;
  48. private Map validatingMap = new HashMap();
  49. private WeakHashMap pcMap = new WeakHashMap();
  50. /**
  51. * Create a new <tt>PoolableConnectionFactory</tt>.
  52. * @param cpds the ConnectionPoolDataSource from which to obtain
  53. * PooledConnection's
  54. * @param pool the {*link ObjectPool} in which to pool those
  55. * {*link Connection}s
  56. * @param validationQuery a query to use to {*link #validateObject validate}
  57. * {*link Connection}s. Should return at least one row.
  58. * May be <tt>null</tt>
  59. * @param username
  60. * @param password
  61. */
  62. public CPDSConnectionFactory(ConnectionPoolDataSource cpds,
  63. ObjectPool pool,
  64. String validationQuery,
  65. String username,
  66. String password) {
  67. _cpds = cpds;
  68. _pool = pool;
  69. _pool.setFactory(this);
  70. _validationQuery = validationQuery;
  71. _username = username;
  72. _password = password;
  73. }
  74. /**
  75. * Sets the {*link ConnectionFactory} from which to obtain base
  76. * {*link Connection}s.
  77. * @param connFactory the {*link ConnectionFactory} from which to obtain
  78. * base {*link Connection}s
  79. */
  80. public synchronized void setCPDS(ConnectionPoolDataSource cpds) {
  81. _cpds = cpds;
  82. }
  83. /**
  84. * Sets the query I use to {*link #validateObject validate}
  85. * {*link Connection}s.
  86. * Should return at least one row.
  87. * May be <tt>null</tt>
  88. * @param validationQuery a query to use to {*link #validateObject validate}
  89. * {*link Connection}s.
  90. */
  91. public synchronized void setValidationQuery(String validationQuery) {
  92. _validationQuery = validationQuery;
  93. }
  94. /**
  95. * Sets the {*link ObjectPool} in which to pool {*link Connection}s.
  96. * @param pool the {*link ObjectPool} in which to pool those
  97. * {*link Connection}s
  98. */
  99. public synchronized void setPool(ObjectPool pool) throws SQLException {
  100. if (null != _pool && pool != _pool) {
  101. try {
  102. _pool.close();
  103. } catch (RuntimeException e) {
  104. throw e;
  105. } catch (Exception e) {
  106. throw new SQLNestedException("Cannot set the pool on this factory", e);
  107. }
  108. }
  109. _pool = pool;
  110. }
  111. public ObjectPool getPool() {
  112. return _pool;
  113. }
  114. public synchronized Object makeObject() {
  115. Object obj;
  116. try {
  117. PooledConnection pc = null;
  118. if (_username == null) {
  119. pc = _cpds.getPooledConnection();
  120. } else {
  121. pc = _cpds.getPooledConnection(_username, _password);
  122. }
  123. // should we add this object as a listener or the pool.
  124. // consider the validateObject method in decision
  125. pc.addConnectionEventListener(this);
  126. obj = new PooledConnectionAndInfo(pc, _username, _password);
  127. pcMap.put(pc, obj);
  128. } catch (SQLException e) {
  129. throw new RuntimeException(e.getMessage());
  130. }
  131. return obj;
  132. }
  133. public void destroyObject(Object obj) throws Exception {
  134. if (obj instanceof PooledConnectionAndInfo) {
  135. ((PooledConnectionAndInfo) obj).getPooledConnection().close();
  136. }
  137. }
  138. public boolean validateObject(Object obj) {
  139. boolean valid = false;
  140. if (obj instanceof PooledConnectionAndInfo) {
  141. PooledConnection pconn =
  142. ((PooledConnectionAndInfo) obj).getPooledConnection();
  143. String query = _validationQuery;
  144. if (null != query) {
  145. Connection conn = null;
  146. Statement stmt = null;
  147. ResultSet rset = null;
  148. // logical Connection from the PooledConnection must be closed
  149. // before another one can be requested and closing it will
  150. // generate an event. Keep track so we know not to return
  151. // the PooledConnection
  152. validatingMap.put(pconn, null);
  153. try {
  154. conn = pconn.getConnection();
  155. stmt = conn.createStatement();
  156. rset = stmt.executeQuery(query);
  157. if (rset.next()) {
  158. valid = true;
  159. } else {
  160. valid = false;
  161. }
  162. } catch (Exception e) {
  163. valid = false;
  164. } finally {
  165. try {
  166. rset.close();
  167. } catch (Throwable t) {
  168. // ignore
  169. }
  170. try {
  171. stmt.close();
  172. } catch (Throwable t) {
  173. // ignore
  174. }
  175. try {
  176. conn.close();
  177. } catch (Throwable t) {
  178. // ignore
  179. }
  180. validatingMap.remove(pconn);
  181. }
  182. } else {
  183. valid = true;
  184. }
  185. } else {
  186. valid = false;
  187. }
  188. return valid;
  189. }
  190. public void passivateObject(Object obj) {
  191. }
  192. public void activateObject(Object obj) {
  193. }
  194. // ***********************************************************************
  195. // java.sql.ConnectionEventListener implementation
  196. // ***********************************************************************
  197. /**
  198. * This will be called if the Connection returned by the getConnection
  199. * method came from a PooledConnection, and the user calls the close()
  200. * method of this connection object. What we need to do here is to
  201. * release this PooledConnection from our pool...
  202. */
  203. public void connectionClosed(ConnectionEvent event) {
  204. PooledConnection pc = (PooledConnection) event.getSource();
  205. // if this event occured becase we were validating, ignore it
  206. // otherwise return the connection to the pool.
  207. if (!validatingMap.containsKey(pc)) {
  208. Object info = pcMap.get(pc);
  209. if (info == null) {
  210. throw new IllegalStateException(NO_KEY_MESSAGE);
  211. }
  212. try {
  213. _pool.returnObject(info);
  214. } catch (Exception e) {
  215. System.err.println("CLOSING DOWN CONNECTION AS IT COULD "
  216. + "NOT BE RETURNED TO THE POOL");
  217. try {
  218. destroyObject(info);
  219. } catch (Exception e2) {
  220. System.err.println("EXCEPTION WHILE DESTROYING OBJECT "
  221. + info);
  222. e2.printStackTrace();
  223. }
  224. }
  225. }
  226. }
  227. /**
  228. * If a fatal error occurs, close the underlying physical connection so as
  229. * not to be returned in the future
  230. */
  231. public void connectionErrorOccurred(ConnectionEvent event) {
  232. PooledConnection pc = (PooledConnection)event.getSource();
  233. try {
  234. if (null != event.getSQLException()) {
  235. System.err.println(
  236. "CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR ("
  237. + event.getSQLException() + ")");
  238. }
  239. //remove this from the listener list because we are no more
  240. //interested in errors since we are about to close this connection
  241. pc.removeConnectionEventListener(this);
  242. } catch (Exception ignore) {
  243. // ignore
  244. }
  245. Object info = pcMap.get(pc);
  246. if (info == null) {
  247. throw new IllegalStateException(NO_KEY_MESSAGE);
  248. }
  249. try {
  250. destroyObject(info);
  251. } catch (Exception e) {
  252. System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
  253. e.printStackTrace();
  254. }
  255. }
  256. }