2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2016 - 2017 AT&T
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
22 * Licensed to the Apache Software Foundation (ASF) under one or more
23 * contributor license agreements. See the NOTICE file distributed with
24 * this work for additional information regarding copyright ownership.
25 * The ASF licenses this file to You under the Apache License, Version 2.0
26 * (the "License"); you may not use this file except in compliance with
27 * the License. You may obtain a copy of the License at
29 * http://www.apache.org/licenses/LICENSE-2.0
31 * Unless required by applicable law or agreed to in writing, software
32 * distributed under the License is distributed on an "AS IS" BASIS,
33 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34 * See the License for the specific language governing permissions and
35 * limitations under the License.
37 package org.apache.tomcat.jdbc.pool;
40 import java.sql.DriverManager;
41 import java.sql.SQLException;
42 import java.sql.Statement;
43 import java.util.HashMap;
44 import java.util.Properties;
45 import java.util.concurrent.atomic.AtomicBoolean;
46 import java.util.concurrent.locks.ReentrantReadWriteLock;
48 import org.apache.juli.logging.Log;
49 import org.apache.juli.logging.LogFactory;
50 import org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
52 import com.mysql.jdbc.Driver;
55 * Represents a pooled connection
56 * and holds a reference to the {@link java.sql.Connection} object
59 public class PooledConnection {
63 private static final Log log = LogFactory.getLog(PooledConnection.class);
65 public static final String PROP_USER = PoolUtilities.PROP_USER;
67 public static final String PROP_PASSWORD = PoolUtilities.PROP_PASSWORD;
70 * Validate when connection is borrowed flag
72 public static final int VALIDATE_BORROW = 1;
74 * Validate when connection is returned flag
76 public static final int VALIDATE_RETURN = 2;
78 * Validate when connection is idle flag
80 public static final int VALIDATE_IDLE = 3;
82 * Validate when connection is initialized flag
84 public static final int VALIDATE_INIT = 4;
86 * The properties for the connection pool
88 protected PoolConfiguration poolProperties;
90 * The underlying database connection
92 private volatile java.sql.Connection connection;
95 * If using a XAConnection underneath.
97 protected volatile javax.sql.XAConnection xaConnection;
99 * When we track abandon traces, this string holds the thread dump
101 private String abandonTrace = null;
103 * Timestamp the connection was last 'touched' by the pool
105 private volatile long timestamp;
107 * Lock for this connection only
109 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);
111 * Set to true if this connection has been discarded by the pool
113 private volatile boolean discarded = false;
115 * The Timestamp when the last time the connect() method was called successfully
117 private volatile long lastConnected = -1;
119 * timestamp to keep track of validation intervals
121 private volatile long lastValidated = System.currentTimeMillis();
125 protected ConnectionPool parent;
127 private HashMap<Object, Object> attributes = new HashMap<>();
129 private volatile long connectionVersion=0;
132 * Weak reference to cache the list of interceptors for this connection
133 * so that we don't create a new list of interceptors each time we borrow
136 private volatile JdbcInterceptor handler = null;
138 private AtomicBoolean released = new AtomicBoolean(false);
140 private volatile boolean suspect = false;
142 private java.sql.Driver driver = null;
146 * @param prop - pool properties
147 * @param parent - the parent connection pool
149 public PooledConnection(PoolConfiguration prop, ConnectionPool parent) {
150 poolProperties = prop;
151 this.parent = parent;
152 connectionVersion = parent.getPoolVersion();
155 public long getConnectionVersion() {
156 return connectionVersion;
160 * @deprecated use {@link #shouldForceReconnect(String, String)}
161 * method kept since it was public, to avoid changing interface.
162 * @param username The user name
163 * @param password The password
164 * @return <code>true</code>if the pool does not need to reconnect
167 public boolean checkUser(String username, String password) {
168 return !shouldForceReconnect(username, password);
172 * Returns true if we must force reconnect based on credentials passed in.
173 * Returns false if {@link PoolConfiguration#isAlternateUsernameAllowed()} method returns false.
174 * Returns false if the username/password has not changed since this connection was connected
175 * @param username the username you wish to connect with, pass in null to accept the default username from {@link PoolConfiguration#getUsername()}
176 * @param password the password you wish to connect with, pass in null to accept the default username from {@link org.apache.tomcat.jdbc.pool.PoolConfiguration#getPassword()}
177 * @return true is the pool must reconnect
179 public boolean shouldForceReconnect(String username, String password) {
181 if (!getPoolProperties().isAlternateUsernameAllowed()) return false;
183 if (username==null) username = poolProperties.getUsername();
184 if (password==null) password = poolProperties.getPassword();
186 String storedUsr = (String)getAttributes().get(PROP_USER);
187 String storedPwd = (String)getAttributes().get(PROP_PASSWORD);
189 boolean noChangeInCredentials = (username==null && storedUsr==null);
190 noChangeInCredentials = (noChangeInCredentials || (username!=null && username.equals(storedUsr)));
192 noChangeInCredentials = noChangeInCredentials && ((password==null && storedPwd==null) || (password!=null && password.equals(storedPwd)));
194 if (username==null) getAttributes().remove(PROP_USER); else getAttributes().put(PROP_USER, username);
195 if (password==null) getAttributes().remove(PROP_PASSWORD); else getAttributes().put(PROP_PASSWORD, password);
197 return !noChangeInCredentials;
201 * Connects the underlying connection to the database.
202 * @throws SQLException if the method {@link #release()} has been called.
203 * @throws SQLException if driver instantiation fails
204 * @throws SQLException if a call to {@link java.sql.Driver#connect(String, java.util.Properties)} fails.
205 * @throws SQLException if default properties are configured and a call to
206 * {@link java.sql.Connection#setAutoCommit(boolean)}, {@link java.sql.Connection#setCatalog(String)},
207 * {@link java.sql.Connection#setTransactionIsolation(int)} or {@link java.sql.Connection#setReadOnly(boolean)} fails.
209 public void connect() throws SQLException {
210 if (released.get()) throw new SQLException("A connection once released, can't be reestablished.");
211 if (connection != null) {
213 this.disconnect(false);
214 } catch (Exception x) {
215 log.debug("Unable to disconnect previous connection.", x);
218 if (poolProperties.getDataSource()==null && poolProperties.getDataSourceJNDI()!=null) {
219 //TODO lookup JNDI name
222 if (poolProperties.getDataSource()!=null) {
223 connectUsingDataSource();
225 connectUsingDriver();
228 //set up the default state, unless we expect the interceptor to do it
229 if (poolProperties.getJdbcInterceptors()==null || poolProperties.getJdbcInterceptors().indexOf(ConnectionState.class.getName())<0 ||
230 poolProperties.getJdbcInterceptors().indexOf(ConnectionState.class.getSimpleName())<0) {
231 if (poolProperties.getDefaultTransactionIsolation()!=DataSourceFactory.UNKNOWN_TRANSACTIONISOLATION) connection.setTransactionIsolation(poolProperties.getDefaultTransactionIsolation());
232 if (poolProperties.getDefaultReadOnly()!=null) connection.setReadOnly(poolProperties.getDefaultReadOnly().booleanValue());
233 if (poolProperties.getDefaultAutoCommit()!=null) connection.setAutoCommit(poolProperties.getDefaultAutoCommit().booleanValue());
234 if (poolProperties.getDefaultCatalog()!=null) connection.setCatalog(poolProperties.getDefaultCatalog());
236 this.discarded = false;
237 this.lastConnected = System.currentTimeMillis();
240 protected void connectUsingDataSource() throws SQLException {
243 if (getAttributes().containsKey(PROP_USER)) {
244 usr = (String) getAttributes().get(PROP_USER);
246 usr = poolProperties.getUsername();
247 getAttributes().put(PROP_USER, usr);
249 if (getAttributes().containsKey(PROP_PASSWORD)) {
250 pwd = (String) getAttributes().get(PROP_PASSWORD);
252 pwd = poolProperties.getPassword();
253 getAttributes().put(PROP_PASSWORD, pwd);
255 if (poolProperties.getDataSource() instanceof javax.sql.XADataSource) {
256 javax.sql.XADataSource xds = (javax.sql.XADataSource)poolProperties.getDataSource();
257 if (usr!=null && pwd!=null) {
258 xaConnection = xds.getXAConnection(usr, pwd);
259 connection = xaConnection.getConnection();
261 xaConnection = xds.getXAConnection();
262 connection = xaConnection.getConnection();
264 } else if (poolProperties.getDataSource() instanceof javax.sql.DataSource){
265 javax.sql.DataSource ds = (javax.sql.DataSource)poolProperties.getDataSource();
266 if (usr!=null && pwd!=null) {
267 connection = ds.getConnection(usr, pwd);
269 connection = ds.getConnection();
271 } else if (poolProperties.getDataSource() instanceof javax.sql.ConnectionPoolDataSource){
272 javax.sql.ConnectionPoolDataSource ds = (javax.sql.ConnectionPoolDataSource)poolProperties.getDataSource();
273 if (usr!=null && pwd!=null) {
274 connection = ds.getPooledConnection(usr, pwd).getConnection();
276 connection = ds.getPooledConnection().getConnection();
279 throw new SQLException("DataSource is of unknown class:"+(poolProperties.getDataSource()!=null?poolProperties.getDataSource().getClass():"null"));
282 protected void connectUsingDriver() throws SQLException {
285 Class.forName("com.mysql.jdbc.Driver") ;
286 Driver dr = new com.mysql.jdbc.Driver();
288 log.warn("Driver NOT CREATED");
289 } catch (ClassNotFoundException e) {
290 log.warn("Driver NOT CREATED", e);
295 if (log.isDebugEnabled()) {
296 log.debug("Instantiating driver using class: "+poolProperties.getDriverClassName()+" [url="+poolProperties.getUrl()+"]");
298 if (poolProperties.getDriverClassName()==null) {
299 //rely on DriverManager
300 log.warn("Not loading a JDBC driver as driverClassName property is null.");
302 driver = (java.sql.Driver)
303 ClassLoaderUtil.loadClass(
304 poolProperties.getDriverClassName(),
305 PooledConnection.class.getClassLoader(),
306 Thread.currentThread().getContextClassLoader()
310 } catch (java.lang.Exception cn) {
311 if (log.isDebugEnabled()) {
312 log.debug("Unable to instantiate JDBC driver.", cn);
314 SQLException ex = new SQLException(cn.getMessage());
318 String driverURL = poolProperties.getUrl();
321 if (getAttributes().containsKey(PROP_USER)) {
322 usr = (String) getAttributes().get(PROP_USER);
324 usr = poolProperties.getUsername();
325 getAttributes().put(PROP_USER, usr);
327 if (getAttributes().containsKey(PROP_PASSWORD)) {
328 pwd = (String) getAttributes().get(PROP_PASSWORD);
330 pwd = poolProperties.getPassword();
331 getAttributes().put(PROP_PASSWORD, pwd);
333 Properties properties = PoolUtilities.clone(poolProperties.getDbProperties());
334 if (usr != null) properties.setProperty(PROP_USER, usr);
335 if (pwd != null) properties.setProperty(PROP_PASSWORD, pwd);
339 connection = DriverManager.getConnection(driverURL, properties);
341 connection = driver.connect(driverURL, properties);
343 } catch (Exception x) {
344 if (log.isDebugEnabled()) {
345 log.debug("Unable to connect to database.", x);
347 if (parent.jmxPool!=null) {
348 parent.jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_CONNECT,
349 ConnectionPool.getStackTrace(x));
351 if (x instanceof SQLException) {
352 throw (SQLException)x;
354 SQLException ex = new SQLException(x.getMessage());
359 if (connection==null) {
360 throw new SQLException("Driver:"+driver+" returned null for URL:"+driverURL);
366 * @return true if connect() was called successfully and disconnect has not yet been called
368 public boolean isInitialized() {
369 return connection!=null;
373 * Returns true if the connection has been connected more than
374 * {@link PoolConfiguration#getMaxAge()} milliseconds. false otherwise.
375 * @return Returns true if the connection has been connected more than
376 * {@link PoolConfiguration#getMaxAge()} milliseconds. false otherwise.
378 public boolean isMaxAgeExpired() {
379 if (getPoolProperties().getMaxAge()>0 ) {
380 return (System.currentTimeMillis() - getLastConnected()) > getPoolProperties().getMaxAge();
386 * Issues a call to {@link #disconnect(boolean)} with the argument false followed by a call to
388 * @throws SQLException if the call to {@link #connect()} fails.
390 public void reconnect() throws SQLException {
391 this.disconnect(false);
396 * Disconnects the connection. All exceptions are logged using debug level.
397 * @param finalize if set to true, a call to {@link ConnectionPool#finalize(PooledConnection)} is called.
399 private void disconnect(boolean finalize) {
400 if (isDiscarded() && connection == null) {
404 if (connection != null) {
406 parent.disconnectEvent(this, finalize);
407 if (xaConnection == null) {
410 xaConnection.close();
412 }catch (Exception ignore) {
413 if (log.isDebugEnabled()) {
414 log.debug("Unable to close underlying SQL connection",ignore);
421 if (finalize) parent.finalize(this);
425 //============================================================================
427 //============================================================================
430 * Returns abandon timeout in milliseconds
431 * @return abandon timeout in milliseconds
433 public long getAbandonTimeout() {
434 if (poolProperties.getRemoveAbandonedTimeout() <= 0) {
435 return Long.MAX_VALUE;
437 return poolProperties.getRemoveAbandonedTimeout() * 1000L;
442 * Returns <code>true</code> if the connection pool is configured
443 * to do validation for a certain action.
444 * @param action The validation action
446 private boolean doValidate(int action) {
447 if (action == PooledConnection.VALIDATE_BORROW &&
448 poolProperties.isTestOnBorrow())
450 else if (action == PooledConnection.VALIDATE_RETURN &&
451 poolProperties.isTestOnReturn())
453 else if (action == PooledConnection.VALIDATE_IDLE &&
454 poolProperties.isTestWhileIdle())
456 else if (action == PooledConnection.VALIDATE_INIT &&
457 poolProperties.isTestOnConnect())
459 else if (action == PooledConnection.VALIDATE_INIT &&
460 poolProperties.getInitSQL()!=null)
467 * Returns <code>true</code> if the object is still valid. if not
468 * the pool will call the getExpiredAction() and follow up with one
469 * of the four expired methods
470 * @param validateAction The value
471 * @return <code>true</code> if the connection is valid
473 public boolean validate(int validateAction) {
474 return validate(validateAction,null);
478 * Validates a connection.
479 * @param validateAction the action used. One of {@link #VALIDATE_BORROW}, {@link #VALIDATE_IDLE},
480 * {@link #VALIDATE_INIT} or {@link #VALIDATE_RETURN}
481 * @param sql the SQL to be used during validation. If the {@link PoolConfiguration#setInitSQL(String)} has been called with a non null
482 * value and the action is {@link #VALIDATE_INIT} the init SQL will be used for validation.
484 * @return true if the connection was validated successfully. It returns true even if validation was not performed, such as when
485 * {@link PoolConfiguration#setValidationInterval(long)} has been called with a positive value.
487 * false if the validation failed. The caller should close the connection if false is returned since a session could have been left in
488 * an unknown state during initialization.
490 public boolean validate(int validateAction,String sql) {
491 if (this.isDiscarded()) {
495 if (!doValidate(validateAction)) {
496 //no validation required, no init sql and props not set
500 //Don't bother validating if already have recently enough
501 long now = System.currentTimeMillis();
502 if (validateAction!=VALIDATE_INIT &&
503 poolProperties.getValidationInterval() > 0 &&
504 (now - this.lastValidated) <
505 poolProperties.getValidationInterval()) {
509 if (poolProperties.getValidator() != null) {
510 if (poolProperties.getValidator().validate(connection, validateAction)) {
511 this.lastValidated = now;
514 if (getPoolProperties().getLogValidationErrors()) {
515 log.error("Custom validation through "+poolProperties.getValidator()+" failed.");
523 if (validateAction == VALIDATE_INIT && poolProperties.getInitSQL() != null) {
524 query = poolProperties.getInitSQL();
528 query = poolProperties.getValidationQuery();
532 int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
533 if (validationQueryTimeout < 0) validationQueryTimeout = 0;
535 if (connection.isValid(validationQueryTimeout)) {
536 this.lastValidated = now;
539 if (getPoolProperties().getLogValidationErrors()) {
540 log.error("isValid() returned false.");
544 } catch (SQLException e) {
545 if (getPoolProperties().getLogValidationErrors()) {
546 log.error("isValid() failed.", e);
547 } else if (log.isDebugEnabled()) {
548 log.debug("isValid() failed.", e);
554 Statement stmt = null;
556 stmt = connection.createStatement();
558 int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
559 if (validationQueryTimeout > 0) {
560 stmt.setQueryTimeout(validationQueryTimeout);
565 this.lastValidated = now;
567 } catch (Exception ex) {
568 if (getPoolProperties().getLogValidationErrors()) {
569 log.warn("SQL Validation error", ex);
570 } else if (log.isDebugEnabled()) {
571 log.debug("Unable to validate object:",ex);
574 try { stmt.close();} catch (Exception ignore2){/*NOOP*/}
580 * The time limit for how long the object
581 * can remain unused before it is released
582 * @return {@link PoolConfiguration#getMinEvictableIdleTimeMillis()}
584 public long getReleaseTime() {
585 return this.poolProperties.getMinEvictableIdleTimeMillis();
589 * This method is called if (Now - timeCheckedIn > getReleaseTime())
590 * This method disconnects the connection, logs an error in debug mode if it happens
591 * then sets the {@link #released} flag to false. Any attempts to connect this cached object again
592 * will fail per {@link #connect()}
593 * The connection pool uses the atomic return value to decrement the pool size counter.
594 * @return true if this is the first time this method has been called. false if this method has been called before.
596 public boolean release() {
599 } catch (Exception x) {
600 if (log.isDebugEnabled()) {
601 log.debug("Unable to close SQL connection",x);
604 return released.compareAndSet(false, true);
609 * The pool will set the stack trace when it is check out and
611 * @param trace the stack trace for this connection
614 public void setStackTrace(String trace) {
615 abandonTrace = trace;
619 * Returns the stack trace from when this connection was borrowed. Can return null if no stack trace was set.
620 * @return the stack trace or null of no trace was set
622 public String getStackTrace() {
627 * Sets a timestamp on this connection. A timestamp usually means that some operation
628 * performed successfully.
629 * @param timestamp the timestamp as defined by {@link System#currentTimeMillis()}
631 public void setTimestamp(long timestamp) {
632 this.timestamp = timestamp;
637 public boolean isSuspect() {
641 public void setSuspect(boolean suspect) {
642 this.suspect = suspect;
646 * An interceptor can call this method with the value true, and the connection will be closed when it is returned to the pool.
647 * @param discarded - only valid value is true
648 * @throws IllegalStateException if this method is called with the value false and the value true has already been set.
650 public void setDiscarded(boolean discarded) {
651 if (this.discarded && !discarded) throw new IllegalStateException("Unable to change the state once the connection has been discarded");
652 this.discarded = discarded;
656 * Set the timestamp the connection was last validated.
657 * This flag is used to keep track when we are using a {@link PoolConfiguration#setValidationInterval(long) validation-interval}.
658 * @param lastValidated a timestamp as defined by {@link System#currentTimeMillis()}
660 public void setLastValidated(long lastValidated) {
661 this.lastValidated = lastValidated;
665 * Sets the pool configuration for this connection and connection pool.
666 * Object is shared with the {@link ConnectionPool}
667 * @param poolProperties The pool properties
669 public void setPoolProperties(PoolConfiguration poolProperties) {
670 this.poolProperties = poolProperties;
674 * Return the timestamps of last pool action. Timestamps are typically set when connections
675 * are borrowed from the pool. It is used to keep track of {@link PoolConfiguration#setRemoveAbandonedTimeout(int) abandon-timeouts}.
676 * This timestamp can also be reset by the {@link org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer#invoke(Object, java.lang.reflect.Method, Object[])}
677 * @return the timestamp of the last pool action as defined by {@link System#currentTimeMillis()}
679 public long getTimestamp() {
684 * Returns the discarded flag.
685 * @return the discarded flag. If the value is true,
686 * either {@link #disconnect(boolean)} has been called or it will be called when the connection is returned to the pool.
688 public boolean isDiscarded() {
693 * Returns the timestamp of the last successful validation query execution.
694 * @return the timestamp of the last successful validation query execution as defined by {@link System#currentTimeMillis()}
696 public long getLastValidated() {
697 return lastValidated;
701 * Returns the configuration for this connection and pool
702 * @return the configuration for this connection and pool
704 public PoolConfiguration getPoolProperties() {
705 return poolProperties;
709 * Locks the connection only if either {@link PoolConfiguration#isPoolSweeperEnabled()} or
710 * {@link PoolConfiguration#getUseLock()} return true. The per connection lock ensures thread safety is
711 * multiple threads are performing operations on the connection.
712 * Otherwise this is a noop for performance
715 if (poolProperties.getUseLock() || this.poolProperties.isPoolSweeperEnabled()) {
716 //optimized, only use a lock when there is concurrency
717 lock.writeLock().lock();
722 * Unlocks the connection only if the sweeper is enabled
723 * Otherwise this is a noop for performance
725 public void unlock() {
726 if (poolProperties.getUseLock() || this.poolProperties.isPoolSweeperEnabled()) {
727 //optimized, only use a lock when there is concurrency
728 lock.writeLock().unlock();
733 * Returns the underlying connection
734 * @return the underlying JDBC connection as it was returned from the JDBC driver
735 * @see javax.sql.PooledConnection#getConnection()
737 public java.sql.Connection getConnection() {
738 return this.connection;
742 * Returns the underlying XA connection
743 * @return the underlying XA connection as it was returned from the Datasource
745 public javax.sql.XAConnection getXAConnection() {
746 return this.xaConnection;
751 * Returns the timestamp of when the connection was last connected to the database.
752 * ie, a successful call to {@link java.sql.Driver#connect(String, java.util.Properties)}.
753 * @return the timestamp when this connection was created as defined by {@link System#currentTimeMillis()}
755 public long getLastConnected() {
756 return lastConnected;
760 * Returns the first handler in the interceptor chain
761 * @return the first interceptor for this connection
763 public JdbcInterceptor getHandler() {
767 public void setHandler(JdbcInterceptor handler) {
768 if (this.handler!=null && this.handler!=handler) {
769 JdbcInterceptor interceptor = this.handler;
770 while (interceptor!=null) {
771 interceptor.reset(null, null);
772 interceptor = interceptor.getNext();
775 this.handler = handler;
779 public String toString() {
780 return "PooledConnection["+(connection!=null?connection.toString():"null")+"]";
784 * Returns true if this connection has been released and wont be reused.
785 * @return true if the method {@link #release()} has been called
787 public boolean isReleased() {
788 return released.get();
791 public HashMap<Object,Object> getAttributes() {