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.datasources;
019:
020: import java.sql.Connection;
021: import java.sql.ResultSet;
022: import java.sql.SQLException;
023: import java.sql.Statement;
024: import java.util.HashMap;
025: import java.util.Map;
026: import java.util.WeakHashMap;
027:
028: import javax.sql.ConnectionEvent;
029: import javax.sql.ConnectionEventListener;
030: import javax.sql.ConnectionPoolDataSource;
031: import javax.sql.PooledConnection;
032:
033: import org.apache.commons.dbcp.SQLNestedException;
034: import org.apache.commons.pool.KeyedObjectPool;
035: import org.apache.commons.pool.KeyedPoolableObjectFactory;
036:
037: /**
038: * A {*link PoolableObjectFactory} that creates
039: * {*link PoolableConnection}s.
040: *
041: * @author John D. McNally
042: * @version $Revision: 479137 $ $Date: 2006-11-25 08:51:48 -0700 (Sat, 25 Nov 2006) $
043: */
044: class KeyedCPDSConnectionFactory implements KeyedPoolableObjectFactory,
045: ConnectionEventListener {
046:
047: private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but "
048: + "I have no record of the underlying PooledConnection.";
049:
050: protected ConnectionPoolDataSource _cpds = null;
051: protected String _validationQuery = null;
052: protected boolean _rollbackAfterValidation = false;
053: protected KeyedObjectPool _pool = null;
054: private Map validatingMap = new HashMap();
055: private WeakHashMap pcMap = new WeakHashMap();
056:
057: /**
058: * Create a new <tt>KeyedPoolableConnectionFactory</tt>.
059: * @param cpds the ConnectionPoolDataSource from which to obtain PooledConnection's
060: * @param pool the {*link ObjectPool} in which to pool those {*link Connection}s
061: * @param validationQuery a query to use to {*link #validateObject validate} {*link Connection}s. Should return at least one row. May be <tt>null</tt>
062: */
063: public KeyedCPDSConnectionFactory(ConnectionPoolDataSource cpds,
064: KeyedObjectPool pool, String validationQuery) {
065: _cpds = cpds;
066: _pool = pool;
067: _pool.setFactory(this );
068: _validationQuery = validationQuery;
069: }
070:
071: /**
072: * Create a new <tt>KeyedPoolableConnectionFactory</tt>.
073: * @param cpds the ConnectionPoolDataSource from which to obtain
074: * PooledConnections
075: * @param pool the {@link ObjectPool} in which to pool those
076: * {@link Connection}s
077: * @param validationQuery a query to use to {@link #validateObject validate}
078: * {@link Connection}s. Should return at least one row. May be <tt>null</tt>
079: * @param rollbackAfterValidation whether a rollback should be issued after
080: * {@link #validateObject validating} {@link Connection}s.
081: */
082: public KeyedCPDSConnectionFactory(ConnectionPoolDataSource cpds,
083: KeyedObjectPool pool, String validationQuery,
084: boolean rollbackAfterValidation) {
085: this (cpds, pool, validationQuery);
086: _rollbackAfterValidation = rollbackAfterValidation;
087: }
088:
089: /**
090: * Sets the {@link ConnectionFactory} from which to obtain base {@link Connection}s.
091: * @param connFactory the {*link ConnectionFactory} from which to obtain base {@link Connection}s
092: */
093: synchronized public void setCPDS(ConnectionPoolDataSource cpds) {
094: _cpds = cpds;
095: }
096:
097: /**
098: * Sets the query I use to {*link #validateObject validate} {*link Connection}s.
099: * Should return at least one row.
100: * May be <tt>null</tt>
101: * @param validationQuery a query to use to {*link #validateObject validate} {*link Connection}s.
102: */
103: synchronized public void setValidationQuery(String validationQuery) {
104: _validationQuery = validationQuery;
105: }
106:
107: /**
108: * Sets whether a rollback should be issued after
109: * {*link #validateObject validating}
110: * {*link Connection}s.
111: * @param rollbackAfterValidation whether a rollback should be issued after
112: * {*link #validateObject validating}
113: * {*link Connection}s.
114: */
115: public synchronized void setRollbackAfterValidation(
116: boolean rollbackAfterValidation) {
117: _rollbackAfterValidation = rollbackAfterValidation;
118: }
119:
120: /**
121: * Sets the {*link ObjectPool} in which to pool {*link Connection}s.
122: * @param pool the {*link ObjectPool} in which to pool those {*link Connection}s
123: */
124: synchronized public void setPool(KeyedObjectPool pool)
125: throws SQLException {
126: if (null != _pool && pool != _pool) {
127: try {
128: _pool.close();
129: } catch (RuntimeException e) {
130: throw e;
131: } catch (Exception e) {
132: throw new SQLNestedException(
133: "Cannot set the pool on this factory", e);
134: }
135: }
136: _pool = pool;
137: }
138:
139: public KeyedObjectPool getPool() {
140: return _pool;
141: }
142:
143: /**
144: * @param key
145: * @throws SQLException if the connection could not be created.
146: * @see org.apache.commons.pool.KeyedPoolableObjectFactory#makeObject(java.lang.Object)
147: */
148: public synchronized Object makeObject(Object key) throws Exception {
149: Object obj = null;
150: UserPassKey upkey = (UserPassKey) key;
151:
152: PooledConnection pc = null;
153: String username = upkey.getUsername();
154: String password = upkey.getPassword();
155: if (username == null) {
156: pc = _cpds.getPooledConnection();
157: } else {
158: pc = _cpds.getPooledConnection(username, password);
159: }
160: // should we add this object as a listener or the pool.
161: // consider the validateObject method in decision
162: pc.addConnectionEventListener(this );
163: obj = new PooledConnectionAndInfo(pc, username, password);
164: pcMap.put(pc, obj);
165:
166: return obj;
167: }
168:
169: public void destroyObject(Object key, Object obj) throws Exception {
170: if (obj instanceof PooledConnectionAndInfo) {
171: PooledConnection pc = ((PooledConnectionAndInfo) obj)
172: .getPooledConnection();
173: pcMap.remove(pc);
174: pc.close();
175: }
176: }
177:
178: public boolean validateObject(Object key, Object obj) {
179: boolean valid = false;
180: if (obj instanceof PooledConnectionAndInfo) {
181: PooledConnection pconn = ((PooledConnectionAndInfo) obj)
182: .getPooledConnection();
183: String query = _validationQuery;
184: if (null != query) {
185: Connection conn = null;
186: Statement stmt = null;
187: ResultSet rset = null;
188: // logical Connection from the PooledConnection must be closed
189: // before another one can be requested and closing it will
190: // generate an event. Keep track so we know not to return
191: // the PooledConnection
192: validatingMap.put(pconn, null);
193: try {
194: conn = pconn.getConnection();
195: stmt = conn.createStatement();
196: rset = stmt.executeQuery(query);
197: if (rset.next()) {
198: valid = true;
199: } else {
200: valid = false;
201: }
202: if (_rollbackAfterValidation) {
203: conn.rollback();
204: }
205: } catch (Exception e) {
206: valid = false;
207: } finally {
208: if (rset != null) {
209: try {
210: rset.close();
211: } catch (Throwable t) {
212: // ignore
213: }
214: }
215: if (stmt != null) {
216: try {
217: stmt.close();
218: } catch (Throwable t) {
219: // ignore
220: }
221: }
222: if (conn != null) {
223: try {
224: conn.close();
225: } catch (Throwable t) {
226: // ignore
227: }
228: }
229: validatingMap.remove(pconn);
230: }
231: } else {
232: valid = true;
233: }
234: } else {
235: valid = false;
236: }
237: return valid;
238: }
239:
240: public void passivateObject(Object key, Object obj) {
241: }
242:
243: public void activateObject(Object key, Object obj) {
244: }
245:
246: // ***********************************************************************
247: // java.sql.ConnectionEventListener implementation
248: // ***********************************************************************
249:
250: /**
251: * This will be called if the Connection returned by the getConnection
252: * method came from a PooledConnection, and the user calls the close()
253: * method of this connection object. What we need to do here is to
254: * release this PooledConnection from our pool...
255: */
256: public void connectionClosed(ConnectionEvent event) {
257: PooledConnection pc = (PooledConnection) event.getSource();
258: // if this event occured becase we were validating, ignore it
259: // otherwise return the connection to the pool.
260: if (!validatingMap.containsKey(pc)) {
261: PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap
262: .get(pc);
263: if (info == null) {
264: throw new IllegalStateException(NO_KEY_MESSAGE);
265: }
266: try {
267: _pool.returnObject(info.getUserPassKey(), info);
268: } catch (Exception e) {
269: System.err
270: .println("CLOSING DOWN CONNECTION AS IT COULD "
271: + "NOT BE RETURNED TO THE POOL");
272: try {
273: destroyObject(info.getUserPassKey(), info);
274: } catch (Exception e2) {
275: System.err
276: .println("EXCEPTION WHILE DESTROYING OBJECT "
277: + info);
278: e2.printStackTrace();
279: }
280: }
281: }
282: }
283:
284: /**
285: * If a fatal error occurs, close the underlying physical connection so as
286: * not to be returned in the future
287: */
288: public void connectionErrorOccurred(ConnectionEvent event) {
289: PooledConnection pc = (PooledConnection) event.getSource();
290: try {
291: if (null != event.getSQLException()) {
292: System.err
293: .println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR ("
294: + event.getSQLException() + ")");
295: }
296: //remove this from the listener list because we are no more
297: //interested in errors since we are about to close this connection
298: pc.removeConnectionEventListener(this );
299: } catch (Exception ignore) {
300: // ignore
301: }
302:
303: PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap
304: .get(pc);
305: if (info == null) {
306: throw new IllegalStateException(NO_KEY_MESSAGE);
307: }
308: try {
309: destroyObject(info.getUserPassKey(), info);
310: } catch (Exception e) {
311: System.err.println("EXCEPTION WHILE DESTROYING OBJECT "
312: + info);
313: e.printStackTrace();
314: }
315: }
316: }
|