[SDNC-5] Rebase sdnc-core
[sdnc/core.git] / dblib / common / src / main / java / org / apache / tomcat / jdbc / pool / PooledConnection.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * openecomp
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 /*
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
28  *
29  *      http://www.apache.org/licenses/LICENSE-2.0
30  *
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.
36  */
37 package org.apache.tomcat.jdbc.pool;
38
39
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;
47
48 import org.apache.juli.logging.Log;
49 import org.apache.juli.logging.LogFactory;
50 import org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
51
52 import com.mysql.jdbc.Driver;
53
54 /**
55  * Represents a pooled connection
56  * and holds a reference to the {@link java.sql.Connection} object
57  * @version 1.0
58  */
59 public class PooledConnection {
60     /**
61      * Logger
62      */
63     private static final Log log = LogFactory.getLog(PooledConnection.class);
64
65     public static final String PROP_USER = PoolUtilities.PROP_USER;
66
67     public static final String PROP_PASSWORD = PoolUtilities.PROP_PASSWORD;
68
69     /**
70      * Validate when connection is borrowed flag
71      */
72     public static final int VALIDATE_BORROW = 1;
73     /**
74      * Validate when connection is returned flag
75      */
76     public static final int VALIDATE_RETURN = 2;
77     /**
78      * Validate when connection is idle flag
79      */
80     public static final int VALIDATE_IDLE = 3;
81     /**
82      * Validate when connection is initialized flag
83      */
84     public static final int VALIDATE_INIT = 4;
85     /**
86      * The properties for the connection pool
87      */
88     protected PoolConfiguration poolProperties;
89     /**
90      * The underlying database connection
91      */
92     private volatile java.sql.Connection connection;
93
94     /**
95      * If using a XAConnection underneath.
96      */
97     protected volatile javax.sql.XAConnection xaConnection;
98     /**
99      * When we track abandon traces, this string holds the thread dump
100      */
101     private String abandonTrace = null;
102     /**
103      * Timestamp the connection was last 'touched' by the pool
104      */
105     private volatile long timestamp;
106     /**
107      * Lock for this connection only
108      */
109     private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);
110     /**
111      * Set to true if this connection has been discarded by the pool
112      */
113     private volatile boolean discarded = false;
114     /**
115      * The Timestamp when the last time the connect() method was called successfully
116      */
117     private volatile long lastConnected = -1;
118     /**
119      * timestamp to keep track of validation intervals
120      */
121     private volatile long lastValidated = System.currentTimeMillis();
122     /**
123      * The parent
124      */
125     protected ConnectionPool parent;
126
127     private HashMap<Object, Object> attributes = new HashMap<>();
128
129     private volatile long connectionVersion=0;
130
131     /**
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
134      * the connection
135      */
136     private volatile JdbcInterceptor handler = null;
137
138     private AtomicBoolean released = new AtomicBoolean(false);
139
140     private volatile boolean suspect = false;
141
142     private java.sql.Driver driver = null;
143
144     /**
145      * Constructor
146      * @param prop - pool properties
147      * @param parent - the parent connection pool
148      */
149     public PooledConnection(PoolConfiguration prop, ConnectionPool parent) {
150         poolProperties = prop;
151         this.parent = parent;
152         connectionVersion = parent.getPoolVersion();
153     }
154
155     public long getConnectionVersion() {
156         return connectionVersion;
157     }
158
159     /**
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
165      */
166     @Deprecated
167     public boolean checkUser(String username, String password) {
168         return !shouldForceReconnect(username, password);
169     }
170
171     /**
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
178      */
179     public boolean shouldForceReconnect(String username, String password) {
180
181         if (!getPoolProperties().isAlternateUsernameAllowed()) return false;
182
183         if (username==null) username = poolProperties.getUsername();
184         if (password==null) password = poolProperties.getPassword();
185
186         String storedUsr = (String)getAttributes().get(PROP_USER);
187         String storedPwd = (String)getAttributes().get(PROP_PASSWORD);
188
189         boolean noChangeInCredentials = (username==null && storedUsr==null);
190         noChangeInCredentials = (noChangeInCredentials || (username!=null && username.equals(storedUsr)));
191
192         noChangeInCredentials = noChangeInCredentials && ((password==null && storedPwd==null) || (password!=null && password.equals(storedPwd)));
193
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);
196
197         return !noChangeInCredentials;
198     }
199
200     /**
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.
208      */
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) {
212             try {
213                 this.disconnect(false);
214             } catch (Exception x) {
215                 log.debug("Unable to disconnect previous connection.", x);
216             } //catch
217         } //end if
218         if (poolProperties.getDataSource()==null && poolProperties.getDataSourceJNDI()!=null) {
219             //TODO lookup JNDI name
220         }
221
222         if (poolProperties.getDataSource()!=null) {
223             connectUsingDataSource();
224         } else {
225             connectUsingDriver();
226         }
227
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());
235         }
236         this.discarded = false;
237         this.lastConnected = System.currentTimeMillis();
238     }
239
240     protected void connectUsingDataSource() throws SQLException {
241         String usr = null;
242         String pwd = null;
243         if (getAttributes().containsKey(PROP_USER)) {
244             usr = (String) getAttributes().get(PROP_USER);
245         } else {
246             usr = poolProperties.getUsername();
247             getAttributes().put(PROP_USER, usr);
248         }
249         if (getAttributes().containsKey(PROP_PASSWORD)) {
250             pwd = (String) getAttributes().get(PROP_PASSWORD);
251         } else {
252             pwd = poolProperties.getPassword();
253             getAttributes().put(PROP_PASSWORD, pwd);
254         }
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();
260             } else {
261                 xaConnection = xds.getXAConnection();
262                 connection = xaConnection.getConnection();
263             }
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);
268             } else {
269                 connection = ds.getConnection();
270             }
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();
275             } else {
276                 connection = ds.getPooledConnection().getConnection();
277             }
278         } else {
279             throw new SQLException("DataSource is of unknown class:"+(poolProperties.getDataSource()!=null?poolProperties.getDataSource().getClass():"null"));
280         }
281     }
282     protected void connectUsingDriver() throws SQLException {
283         
284                 try {
285                         Class.forName("com.mysql.jdbc.Driver") ;
286                         Driver dr = new com.mysql.jdbc.Driver();
287                         if(dr == null)
288                                 log.warn("Driver NOT CREATED");
289                 } catch (ClassNotFoundException e) {
290                         log.warn("Driver NOT CREATED", e);
291                 }
292
293         try {
294             if (driver==null) {
295                 if (log.isDebugEnabled()) {
296                     log.debug("Instantiating driver using class: "+poolProperties.getDriverClassName()+" [url="+poolProperties.getUrl()+"]");
297                 }
298                 if (poolProperties.getDriverClassName()==null) {
299                     //rely on DriverManager
300                     log.warn("Not loading a JDBC driver as driverClassName property is null.");
301                 } else {
302                     driver = (java.sql.Driver)
303                         ClassLoaderUtil.loadClass(
304                             poolProperties.getDriverClassName(),
305                             PooledConnection.class.getClassLoader(),
306                             Thread.currentThread().getContextClassLoader()
307                         ).newInstance();
308                 }
309             }
310         } catch (java.lang.Exception cn) {
311             if (log.isDebugEnabled()) {
312                 log.debug("Unable to instantiate JDBC driver.", cn);
313             }
314             SQLException ex = new SQLException(cn.getMessage());
315             ex.initCause(cn);
316             throw ex;
317         }
318         String driverURL = poolProperties.getUrl();
319         String usr = null;
320         String pwd = null;
321         if (getAttributes().containsKey(PROP_USER)) {
322             usr = (String) getAttributes().get(PROP_USER);
323         } else {
324             usr = poolProperties.getUsername();
325             getAttributes().put(PROP_USER, usr);
326         }
327         if (getAttributes().containsKey(PROP_PASSWORD)) {
328             pwd = (String) getAttributes().get(PROP_PASSWORD);
329         } else {
330             pwd = poolProperties.getPassword();
331             getAttributes().put(PROP_PASSWORD, pwd);
332         }
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);
336
337         try {
338             if (driver==null) {
339                 connection = DriverManager.getConnection(driverURL, properties);
340             } else {
341                 connection = driver.connect(driverURL, properties);
342             }
343         } catch (Exception x) {
344             if (log.isDebugEnabled()) {
345                 log.debug("Unable to connect to database.", x);
346             }
347             if (parent.jmxPool!=null) {
348                 parent.jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_CONNECT,
349                         ConnectionPool.getStackTrace(x));
350             }
351             if (x instanceof SQLException) {
352                 throw (SQLException)x;
353             } else {
354                 SQLException ex = new SQLException(x.getMessage());
355                 ex.initCause(x);
356                 throw ex;
357             }
358         }
359         if (connection==null) {
360             throw new SQLException("Driver:"+driver+" returned null for URL:"+driverURL);
361         }
362     }
363
364     /**
365      *
366      * @return true if connect() was called successfully and disconnect has not yet been called
367      */
368     public boolean isInitialized() {
369         return connection!=null;
370     }
371
372     /**
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.
377      */
378     public boolean isMaxAgeExpired() {
379         if (getPoolProperties().getMaxAge()>0 ) {
380             return (System.currentTimeMillis() - getLastConnected()) > getPoolProperties().getMaxAge();
381         } else {
382             return false;
383         }
384     }
385     /**
386      * Issues a call to {@link #disconnect(boolean)} with the argument false followed by a call to
387      * {@link #connect()}
388      * @throws SQLException if the call to {@link #connect()} fails.
389      */
390     public void reconnect() throws SQLException {
391         this.disconnect(false);
392         this.connect();
393     } //reconnect
394
395     /**
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.
398      */
399     private void disconnect(boolean finalize) {
400         if (isDiscarded() && connection == null) {
401             return;
402         }
403         setDiscarded(true);
404         if (connection != null) {
405             try {
406                 parent.disconnectEvent(this, finalize);
407                 if (xaConnection == null) {
408                     connection.close();
409                 } else {
410                     xaConnection.close();
411                 }
412             }catch (Exception ignore) {
413                 if (log.isDebugEnabled()) {
414                     log.debug("Unable to close underlying SQL connection",ignore);
415                 }
416             }
417         }
418         connection = null;
419         xaConnection = null;
420         lastConnected = -1;
421         if (finalize) parent.finalize(this);
422     }
423
424
425 //============================================================================
426 //
427 //============================================================================
428
429     /**
430      * Returns abandon timeout in milliseconds
431      * @return abandon timeout in milliseconds
432      */
433     public long getAbandonTimeout() {
434         if (poolProperties.getRemoveAbandonedTimeout() <= 0) {
435             return Long.MAX_VALUE;
436         } else {
437             return poolProperties.getRemoveAbandonedTimeout() * 1000L;
438         } //end if
439     }
440
441     /**
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
445      */
446     private boolean doValidate(int action) {
447         if (action == PooledConnection.VALIDATE_BORROW &&
448             poolProperties.isTestOnBorrow())
449             return true;
450         else if (action == PooledConnection.VALIDATE_RETURN &&
451                  poolProperties.isTestOnReturn())
452             return true;
453         else if (action == PooledConnection.VALIDATE_IDLE &&
454                  poolProperties.isTestWhileIdle())
455             return true;
456         else if (action == PooledConnection.VALIDATE_INIT &&
457                  poolProperties.isTestOnConnect())
458             return true;
459         else if (action == PooledConnection.VALIDATE_INIT &&
460                  poolProperties.getInitSQL()!=null)
461            return true;
462         else
463             return false;
464     }
465
466     /**
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
472      */
473     public boolean validate(int validateAction) {
474         return validate(validateAction,null);
475     }
476
477     /**
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.
483      *
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.
486      * <p>
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.
489      */
490     public boolean validate(int validateAction,String sql) {
491         if (this.isDiscarded()) {
492             return false;
493         }
494
495         if (!doValidate(validateAction)) {
496             //no validation required, no init sql and props not set
497             return true;
498         }
499
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()) {
506             return true;
507         }
508
509         if (poolProperties.getValidator() != null) {
510             if (poolProperties.getValidator().validate(connection, validateAction)) {
511                 this.lastValidated = now;
512                 return true;
513             } else {
514                 if (getPoolProperties().getLogValidationErrors()) {
515                     log.error("Custom validation through "+poolProperties.getValidator()+" failed.");
516                 }
517                 return false;
518             }
519         }
520
521         String query = sql;
522
523         if (validateAction == VALIDATE_INIT && poolProperties.getInitSQL() != null) {
524             query = poolProperties.getInitSQL();
525         }
526
527         if (query == null) {
528             query = poolProperties.getValidationQuery();
529         }
530
531         if (query == null) {
532             int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
533             if (validationQueryTimeout < 0) validationQueryTimeout = 0;
534             try {
535                 if (connection.isValid(validationQueryTimeout)) {
536                     this.lastValidated = now;
537                     return true;
538                 } else {
539                     if (getPoolProperties().getLogValidationErrors()) {
540                         log.error("isValid() returned false.");
541                     }
542                     return false;
543                 }
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);
549                 }
550                 return false;
551             }
552         }
553
554         Statement stmt = null;
555         try {
556             stmt = connection.createStatement();
557
558             int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
559             if (validationQueryTimeout > 0) {
560                 stmt.setQueryTimeout(validationQueryTimeout);
561             }
562
563             stmt.execute(query);
564             stmt.close();
565             this.lastValidated = now;
566             return true;
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);
572             }
573             if (stmt!=null)
574                 try { stmt.close();} catch (Exception ignore2){/*NOOP*/}
575         }
576         return false;
577     } //validate
578
579     /**
580      * The time limit for how long the object
581      * can remain unused before it is released
582      * @return {@link PoolConfiguration#getMinEvictableIdleTimeMillis()}
583      */
584     public long getReleaseTime() {
585         return this.poolProperties.getMinEvictableIdleTimeMillis();
586     }
587
588     /**
589      * This method is called if (Now - timeCheckedIn &gt; 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.
595      */
596     public boolean release() {
597         try {
598             disconnect(true);
599         } catch (Exception x) {
600             if (log.isDebugEnabled()) {
601                 log.debug("Unable to close SQL connection",x);
602             }
603         }
604         return released.compareAndSet(false, true);
605
606     }
607
608     /**
609      * The pool will set the stack trace when it is check out and
610      * checked in
611      * @param trace the stack trace for this connection
612      */
613
614     public void setStackTrace(String trace) {
615         abandonTrace = trace;
616     }
617
618     /**
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
621      */
622     public String getStackTrace() {
623         return abandonTrace;
624     }
625
626     /**
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()}
630      */
631     public void setTimestamp(long timestamp) {
632         this.timestamp = timestamp;
633         setSuspect(false);
634     }
635
636
637     public boolean isSuspect() {
638         return suspect;
639     }
640
641     public void setSuspect(boolean suspect) {
642         this.suspect = suspect;
643     }
644
645     /**
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.
649      */
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;
653     }
654
655     /**
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()}
659      */
660     public void setLastValidated(long lastValidated) {
661         this.lastValidated = lastValidated;
662     }
663
664     /**
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
668      */
669     public void setPoolProperties(PoolConfiguration poolProperties) {
670         this.poolProperties = poolProperties;
671     }
672
673     /**
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()}
678      */
679     public long getTimestamp() {
680         return timestamp;
681     }
682
683     /**
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.
687      */
688     public boolean isDiscarded() {
689         return discarded;
690     }
691
692     /**
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()}
695      */
696     public long getLastValidated() {
697         return lastValidated;
698     }
699
700     /**
701      * Returns the configuration for this connection and pool
702      * @return the configuration for this connection and pool
703      */
704     public PoolConfiguration getPoolProperties() {
705         return poolProperties;
706     }
707
708     /**
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
713      */
714     public void lock() {
715         if (poolProperties.getUseLock() || this.poolProperties.isPoolSweeperEnabled()) {
716             //optimized, only use a lock when there is concurrency
717             lock.writeLock().lock();
718         }
719     }
720
721     /**
722      * Unlocks the connection only if the sweeper is enabled
723      * Otherwise this is a noop for performance
724      */
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();
729         }
730     }
731
732     /**
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()
736      */
737     public java.sql.Connection getConnection() {
738         return this.connection;
739     }
740
741     /**
742      * Returns the underlying XA connection
743      * @return the underlying XA connection as it was returned from the Datasource
744      */
745     public javax.sql.XAConnection getXAConnection() {
746         return this.xaConnection;
747     }
748
749
750     /**
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()}
754      */
755     public long getLastConnected() {
756         return lastConnected;
757     }
758
759     /**
760      * Returns the first handler in the interceptor chain
761      * @return the first interceptor for this connection
762      */
763     public JdbcInterceptor getHandler() {
764         return handler;
765     }
766
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();
773             }//while
774         }//end if
775         this.handler = handler;
776     }
777
778     @Override
779     public String toString() {
780         return "PooledConnection["+(connection!=null?connection.toString():"null")+"]";
781     }
782
783     /**
784      * Returns true if this connection has been released and wont be reused.
785      * @return true if the method {@link #release()} has been called
786      */
787     public boolean isReleased() {
788         return released.get();
789     }
790
791     public HashMap<Object,Object> getAttributes() {
792         return attributes;
793     }
794
795 }