68
|
1 /*
|
|
2 Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com>
|
|
3
|
|
4 Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5 of this software and associated documentation files (the "Software"), to deal
|
|
6 in the Software without restriction, including without limitation the rights
|
|
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8 copies of the Software, and to permit persons to whom the Software is
|
|
9 furnished to do so, subject to the following conditions:
|
|
10
|
|
11 The above copyright notice and this permission notice shall be included in
|
|
12 all copies or substantial portions of the Software.
|
|
13
|
|
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20 THE SOFTWARE.
|
|
21 */
|
|
22
|
|
23 package fschmidt.db.pool;
|
|
24
|
|
25 import java.sql.Connection;
|
|
26 import java.sql.SQLException;
|
|
27 import java.sql.Statement;
|
|
28 import java.sql.ResultSet;
|
|
29 import java.util.List;
|
|
30 import java.util.ArrayList;
|
|
31 import java.util.Set;
|
|
32 import java.util.HashSet;
|
|
33 import org.slf4j.Logger;
|
|
34 import org.slf4j.LoggerFactory;
|
|
35 import fschmidt.db.DbDatabase;
|
|
36 import fschmidt.db.DbObject;
|
|
37 import fschmidt.db.DbKey;
|
|
38 import fschmidt.db.DbObjectFactory;
|
|
39 import fschmidt.db.SQLRuntimeException;
|
|
40 import fschmidt.db.extend.DbDatabaseExt;
|
|
41 import fschmidt.db.extend.DbTableExt;
|
|
42 import fschmidt.db.extend.DbTransaction;
|
|
43 import fschmidt.db.extend.DbRecordExt;
|
|
44 import fschmidt.db.extend.FilterDatabase;
|
|
45 import fschmidt.util.java.Stack;
|
|
46 import fschmidt.util.java.ArrayStack;
|
|
47
|
|
48
|
|
49 public final class PooledConnection {
|
|
50 private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class);
|
|
51
|
|
52 private final Pool pool;
|
|
53 private Connection con;
|
|
54 private int trans = 0;
|
|
55 final List<Runnable> beforeCommitList = new ArrayList<Runnable>();
|
|
56 final List<Runnable> afterCommitList = new ArrayList<Runnable>();
|
|
57 DbTransaction dbTrans = null;
|
|
58 long lastUsed;
|
|
59 boolean ignoreRunnablesInThisTransaction = false;
|
|
60 private final Stack<NestedConnection> nesting = new ArrayStack<NestedConnection>();
|
|
61 private volatile String user = null;
|
|
62
|
|
63 PooledConnection(DbDatabaseImpl db)
|
|
64 throws SQLException
|
|
65 {
|
|
66 this.pool = db.pool;
|
|
67 this.con = db.database().getConnection();
|
|
68 }
|
|
69
|
|
70 protected void finalize() throws Throwable {
|
|
71 super.finalize();
|
|
72 if( con == null )
|
|
73 return;
|
|
74 if( !nesting.isEmpty() )
|
|
75 logger.error("connection lost from pool: opened="+nesting.size()+" trans="+trans,nesting.peek().initException);
|
|
76 try {
|
|
77 this.con.close();
|
|
78 } catch(SQLException e) {
|
|
79 } finally {
|
|
80 this.con = null;
|
|
81 }
|
|
82 }
|
|
83
|
|
84 void setUser(String user) throws SQLException {
|
|
85 if( this.user == user ) {
|
|
86 /*
|
|
87 Statement stmt = con.createStatement();
|
|
88 ResultSet rs = stmt.executeQuery("select CURRENT_USER");
|
|
89 rs.next();
|
|
90 String s = rs.getString("CURRENT_USER");
|
|
91 rs.close();
|
|
92 stmt.close();
|
|
93 if( s.equals(user) )
|
|
94 return;
|
|
95 logger.error("setUser error, user should be "+user+" but is "+s,new Exception());
|
|
96 */
|
|
97 return;
|
|
98 }
|
|
99 this.user = null;
|
|
100 Statement stmt = con.createStatement();
|
|
101 stmt.executeUpdate(
|
|
102 "set role " + user
|
|
103 );
|
|
104 stmt.close();
|
|
105 this.user = user;
|
|
106 }
|
|
107
|
|
108 Connection nest(String user) {
|
|
109 NestedConnection nc = new NestedConnection(this,user);
|
|
110 nesting.push(nc);
|
|
111 return nc.proxyCon;
|
|
112 }
|
|
113
|
|
114 private void transOver()
|
|
115 throws SQLException
|
|
116 {
|
|
117 con.setAutoCommit(true);
|
|
118 trans = 0;
|
|
119 dbTrans = null;
|
|
120 ignoreRunnablesInThisTransaction = false;
|
|
121 beforeCommitList.clear();
|
|
122 afterCommitList.clear();
|
|
123 }
|
|
124
|
|
125 void setAutoCommit(boolean autoCommit)
|
|
126 throws SQLException
|
|
127 {
|
|
128 int opened = nesting.size();
|
|
129 if( autoCommit==true ) {
|
|
130 //logger.warn("setAutoCommit true not well supported");
|
|
131 if( trans > 0 ) {
|
|
132 if( trans != opened )
|
|
133 throw new IllegalStateException("setAutoCommit to true with unclosed connections");
|
|
134 transOver();
|
|
135 return;
|
|
136 }
|
|
137 } else if( trans == 0 ) {
|
|
138 trans = opened;
|
|
139 dbTrans = new DbTransaction();
|
|
140 }
|
|
141 con.setAutoCommit(autoCommit);
|
|
142 }
|
|
143
|
|
144 void commit()
|
|
145 throws SQLException
|
|
146 {
|
|
147 int opened = nesting.size();
|
|
148 if( trans != opened )
|
|
149 throw new IllegalStateException("commit failed: trans="+trans+" opened="+opened);
|
|
150 final int opened2 = opened;
|
|
151 while( !beforeCommitList.isEmpty() ) {
|
|
152 Runnable[] a = beforeCommitList.toArray(new Runnable[0]);
|
|
153 beforeCommitList.clear();
|
|
154 for( int i=0; i<a.length; i++ ) {
|
|
155 a[i].run();
|
|
156 if( opened != opened2 ) {
|
|
157 logger.error("before commit opened="+opened+" opened2="+opened2+" runnable="+a[i]);
|
|
158 opened = opened2;
|
|
159 }
|
|
160 a[i] = null;
|
|
161 }
|
|
162 }
|
|
163 { // for now, to catch aborted transactions
|
|
164 Statement stmt = con.createStatement();
|
|
165 stmt.executeQuery("select 1");
|
|
166 stmt.close();
|
|
167 }
|
|
168 con.commit();
|
|
169 if( afterCommitList.isEmpty() ) {
|
|
170 dbTrans = new DbTransaction();
|
|
171 } else {
|
|
172 Runnable[] a = afterCommitList.toArray(new Runnable[0]);
|
|
173 setAutoCommit(true);
|
|
174 for( int i=0; i<a.length; i++ ) {
|
|
175 a[i].run();
|
|
176 if( opened != opened2 ) {
|
|
177 logger.error("after commit opened="+opened+" opened2="+opened2+" runnable="+a[i]);
|
|
178 opened = opened2;
|
|
179 }
|
|
180 a[i] = null;
|
|
181 }
|
|
182 setAutoCommit(false);
|
|
183 }
|
|
184 }
|
|
185
|
|
186 void rollback()
|
|
187 throws SQLException
|
|
188 {
|
|
189 int opened = nesting.size();
|
|
190 if( trans != opened )
|
|
191 logger.warn("rollback called in nested transaction");
|
|
192 con.rollback();
|
|
193 beforeCommitList.clear();
|
|
194 afterCommitList.clear();
|
|
195 }
|
|
196
|
|
197 void close(NestedConnection nestedCon)
|
|
198 throws SQLException
|
|
199 {
|
|
200 int i = nesting.indexOf(nestedCon);
|
|
201 if( trans==nesting.size() ) {
|
|
202 if( i != trans - 1 )
|
|
203 logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException));
|
|
204 con.rollback(); // rollback everything since last commit
|
|
205 transOver();
|
|
206 }
|
|
207 if( i < trans )
|
|
208 logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException));
|
|
209 nesting.remove(nestedCon);
|
|
210 if( !nesting.isEmpty() ) {
|
|
211 nesting.get(nesting.size()-1).setUser(); // best guess
|
|
212 return;
|
|
213 }
|
|
214 if( trans != 0 )
|
|
215 logger.error("trans = "+trans,new Exception());
|
|
216 pool.localCon.remove();
|
|
217 if( !beforeCommitList.isEmpty() ) {
|
|
218 logger.error("beforeCommitList = "+beforeCommitList,new Exception());
|
|
219 beforeCommitList.clear();
|
|
220 }
|
|
221 if( !afterCommitList.isEmpty() ) {
|
|
222 logger.error("afterCommitList = "+afterCommitList,new Exception());
|
|
223 afterCommitList.clear();
|
|
224 }
|
|
225 lastUsed = System.currentTimeMillis();
|
|
226 synchronized(pool) {
|
|
227 if( pool.isExpired(lastUsed) ) {
|
|
228 con.close();
|
|
229 con = null;
|
|
230 } else {
|
|
231 if( con.getAutoCommit() == false ) {
|
|
232 logger.error("autoCommit is false at close",new Exception());
|
|
233 con.setAutoCommit(true);
|
|
234 }
|
|
235 pool.stack.push(this);
|
|
236 }
|
|
237 }
|
|
238 }
|
|
239
|
|
240 Connection con() {
|
|
241 return con;
|
|
242 }
|
|
243
|
|
244 boolean isInTransaction() {
|
|
245 return dbTrans!=null;
|
|
246 }
|
|
247
|
|
248 void commitTransaction() {
|
|
249 if( !isInTransaction() )
|
|
250 throw new IllegalStateException("commitTransaction called outside of transaction");
|
|
251 try {
|
|
252 if( con.getAutoCommit()==true )
|
|
253 throw new IllegalStateException("commitTransaction called outside of transaction");
|
|
254 int opened = nesting.size();
|
|
255 if( opened < trans )
|
|
256 throw new IllegalStateException();
|
|
257 if( opened > trans )
|
|
258 throw new IllegalStateException("commitTransaction called with unclosed connections");
|
|
259 commit();
|
|
260 setAutoCommit(true);
|
|
261 } catch(SQLException e) {
|
|
262 throw new SQLRuntimeException(e);
|
|
263 }
|
|
264 }
|
|
265
|
|
266 void endTransaction() {
|
|
267 try {
|
|
268 if( con.getAutoCommit()==false && nesting.size() > trans ) {
|
|
269 logger.error("endTransaction called with unclosed connections, closing them now",new Exception(nesting.peek().initException));
|
|
270 while( nesting.size() > trans ) {
|
|
271 nesting.peek().close();
|
|
272 }
|
|
273 }
|
|
274 nesting.peek().close();
|
|
275 } catch(SQLException e) {
|
|
276 throw new SQLRuntimeException(e);
|
|
277 }
|
|
278 }
|
|
279
|
|
280 void forceClose() {
|
|
281 logger.error("connection never closed: opened="+nesting.size()+" trans="+trans+" user="+user,nesting.peek().initException);
|
|
282 pool.localCon.remove();
|
|
283 try {
|
|
284 con.close();
|
|
285 } catch(SQLException e) {
|
|
286 logger.error("",e);
|
|
287 }
|
|
288 con = null;
|
|
289 }
|
|
290
|
|
291 }
|