View Javadoc
1   /*
2    *    Copyright 2009-2023 the original author or authors.
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    *       https://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.ibatis.datasource.pooled;
17  
18  import java.lang.reflect.InvocationHandler;
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Proxy;
21  import java.sql.Connection;
22  import java.sql.SQLException;
23  
24  import org.apache.ibatis.reflection.ExceptionUtil;
25  
26  /**
27   * @author Clinton Begin
28   */
29  class PooledConnection implements InvocationHandler {
30  
31    private static final String CLOSE = "close";
32    private static final Class<?>[] IFACES = { Connection.class };
33  
34    private final int hashCode;
35    private final PooledDataSource dataSource;
36    private final Connection realConnection;
37    private final Connection proxyConnection;
38    private long checkoutTimestamp;
39    private long createdTimestamp;
40    private long lastUsedTimestamp;
41    private int connectionTypeCode;
42    private boolean valid;
43  
44    /**
45     * Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in.
46     *
47     * @param connection
48     *          - the connection that is to be presented as a pooled connection
49     * @param dataSource
50     *          - the dataSource that the connection is from
51     */
52    public PooledConnection(Connection connection, PooledDataSource dataSource) {
53      this.hashCode = connection.hashCode();
54      this.realConnection = connection;
55      this.dataSource = dataSource;
56      this.createdTimestamp = System.currentTimeMillis();
57      this.lastUsedTimestamp = System.currentTimeMillis();
58      this.valid = true;
59      this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
60    }
61  
62    /**
63     * Invalidates the connection.
64     */
65    public void invalidate() {
66      valid = false;
67    }
68  
69    /**
70     * Method to see if the connection is usable.
71     *
72     * @return True if the connection is usable
73     */
74    public boolean isValid() {
75      return valid && realConnection != null && dataSource.pingConnection(this);
76    }
77  
78    /**
79     * Getter for the *real* connection that this wraps.
80     *
81     * @return The connection
82     */
83    public Connection getRealConnection() {
84      return realConnection;
85    }
86  
87    /**
88     * Getter for the proxy for the connection.
89     *
90     * @return The proxy
91     */
92    public Connection getProxyConnection() {
93      return proxyConnection;
94    }
95  
96    /**
97     * Gets the hashcode of the real connection (or 0 if it is null).
98     *
99     * @return The hashcode of the real connection (or 0 if it is null)
100    */
101   public int getRealHashCode() {
102     return realConnection == null ? 0 : realConnection.hashCode();
103   }
104 
105   /**
106    * Getter for the connection type (based on url + user + password).
107    *
108    * @return The connection type
109    */
110   public int getConnectionTypeCode() {
111     return connectionTypeCode;
112   }
113 
114   /**
115    * Setter for the connection type.
116    *
117    * @param connectionTypeCode
118    *          - the connection type
119    */
120   public void setConnectionTypeCode(int connectionTypeCode) {
121     this.connectionTypeCode = connectionTypeCode;
122   }
123 
124   /**
125    * Getter for the time that the connection was created.
126    *
127    * @return The creation timestamp
128    */
129   public long getCreatedTimestamp() {
130     return createdTimestamp;
131   }
132 
133   /**
134    * Setter for the time that the connection was created.
135    *
136    * @param createdTimestamp
137    *          - the timestamp
138    */
139   public void setCreatedTimestamp(long createdTimestamp) {
140     this.createdTimestamp = createdTimestamp;
141   }
142 
143   /**
144    * Getter for the time that the connection was last used.
145    *
146    * @return - the timestamp
147    */
148   public long getLastUsedTimestamp() {
149     return lastUsedTimestamp;
150   }
151 
152   /**
153    * Setter for the time that the connection was last used.
154    *
155    * @param lastUsedTimestamp
156    *          - the timestamp
157    */
158   public void setLastUsedTimestamp(long lastUsedTimestamp) {
159     this.lastUsedTimestamp = lastUsedTimestamp;
160   }
161 
162   /**
163    * Getter for the time since this connection was last used.
164    *
165    * @return - the time since the last use
166    */
167   public long getTimeElapsedSinceLastUse() {
168     return System.currentTimeMillis() - lastUsedTimestamp;
169   }
170 
171   /**
172    * Getter for the age of the connection.
173    *
174    * @return the age
175    */
176   public long getAge() {
177     return System.currentTimeMillis() - createdTimestamp;
178   }
179 
180   /**
181    * Getter for the timestamp that this connection was checked out.
182    *
183    * @return the timestamp
184    */
185   public long getCheckoutTimestamp() {
186     return checkoutTimestamp;
187   }
188 
189   /**
190    * Setter for the timestamp that this connection was checked out.
191    *
192    * @param timestamp
193    *          the timestamp
194    */
195   public void setCheckoutTimestamp(long timestamp) {
196     this.checkoutTimestamp = timestamp;
197   }
198 
199   /**
200    * Getter for the time that this connection has been checked out.
201    *
202    * @return the time
203    */
204   public long getCheckoutTime() {
205     return System.currentTimeMillis() - checkoutTimestamp;
206   }
207 
208   @Override
209   public int hashCode() {
210     return hashCode;
211   }
212 
213   /**
214    * Allows comparing this connection to another.
215    *
216    * @param obj
217    *          - the other connection to test for equality
218    *
219    * @see Object#equals(Object)
220    */
221   @Override
222   public boolean equals(Object obj) {
223     if (obj instanceof PooledConnection) {
224       return realConnection.hashCode() == ((PooledConnection) obj).realConnection.hashCode();
225     }
226     if (obj instanceof Connection) {
227       return hashCode == obj.hashCode();
228     } else {
229       return false;
230     }
231   }
232 
233   /**
234    * Required for InvocationHandler implementation.
235    *
236    * @param proxy
237    *          - not used
238    * @param method
239    *          - the method to be executed
240    * @param args
241    *          - the parameters to be passed to the method
242    *
243    * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
244    */
245   @Override
246   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
247     String methodName = method.getName();
248     if (CLOSE.equals(methodName)) {
249       dataSource.pushConnection(this);
250       return null;
251     }
252     try {
253       if (!Object.class.equals(method.getDeclaringClass())) {
254         // issue #579 toString() should never fail
255         // throw an SQLException instead of a Runtime
256         checkConnection();
257       }
258       return method.invoke(realConnection, args);
259     } catch (Throwable t) {
260       throw ExceptionUtil.unwrapThrowable(t);
261     }
262 
263   }
264 
265   private void checkConnection() throws SQLException {
266     if (!valid) {
267       throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
268     }
269   }
270 
271 }