1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.jdbc.TransactionResourceImpl |
4 | |
5 | Copyright 1999, 2004 The Apache Software Foundation or its licensors, as applicable. |
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 | |
19 | */ |
20 | |
21 | package org.apache.derby.impl.jdbc; |
22 | |
23 | import org.apache.derby.jdbc.InternalDriver; |
24 | |
25 | import org.apache.derby.iapi.services.context.Context; |
26 | import org.apache.derby.iapi.services.context.ContextService; |
27 | import org.apache.derby.iapi.services.context.ContextManager; |
28 | import org.apache.derby.iapi.services.monitor.Monitor; |
29 | import org.apache.derby.iapi.services.sanity.SanityManager; |
30 | |
31 | import org.apache.derby.iapi.db.Database; |
32 | import org.apache.derby.iapi.error.StandardException; |
33 | import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; |
34 | import org.apache.derby.iapi.error.ExceptionSeverity; |
35 | |
36 | import org.apache.derby.iapi.reference.Attribute; |
37 | import org.apache.derby.iapi.reference.SQLState; |
38 | import org.apache.derby.iapi.reference.Property; |
39 | import org.apache.derby.iapi.util.StringUtil; |
40 | import org.apache.derby.iapi.util.IdUtil; |
41 | |
42 | import java.util.Properties; |
43 | import java.sql.SQLException; |
44 | |
45 | /** |
46 | * An instance of a TransactionResourceImpl is a bundle of things that |
47 | * connects a connection to the database - it is the transaction "context" in |
48 | * a generic sense. It is also the object of synchronization used by the |
49 | * connection object to make sure only one thread is accessing the underlying |
50 | * transaction and context. |
51 | * |
52 | * <P>TransactionResourceImpl not only serves as a transaction "context", it |
53 | * also takes care of: <OL> |
54 | * <LI>context management: the pushing and popping of the context manager in |
55 | * and out of the global context service</LI> |
56 | * <LI>transaction demarcation: all calls to commit/abort/prepare/close a |
57 | * transaction must route thru the transaction resource. |
58 | * <LI>error handling</LI> |
59 | * </OL> |
60 | * |
61 | * <P>The only connection that have access to the TransactionResource is the |
62 | * root connection, all other nested connections (called proxyConnection) |
63 | * accesses the TransactionResource via the root connection. The root |
64 | * connection may be a plain EmbedConnection, or a DetachableConnection (in |
65 | * case of a XATransaction). A nested connection must be a ProxyConnection. |
66 | * A proxyConnection is not detachable and can itself be a XA connection - |
67 | * although an XATransaction may start nested local (proxy) connections. |
68 | * |
69 | * <P> this is an example of how all the objects in this package relate to each |
70 | * other. In this example, the connection is nested 3 deep. |
71 | * DetachableConnection. |
72 | * <P><PRE> |
73 | * |
74 | * lcc cm database jdbcDriver |
75 | * ^ ^ ^ ^ |
76 | * | | | | |
77 | * |======================| |
78 | * | TransactionResource | |
79 | * |======================| |
80 | * ^ | |
81 | * | | |
82 | * | | |---------------rootConnection----------| |
83 | * | | | | |
84 | * | | |- rootConnection-| | |
85 | * | | | | | |
86 | * | V V | | |
87 | *|========================| |=================| |=================| |
88 | *| EmbedConnection | | EmbedConnection | | EmbedConnection | |
89 | *| |<-----| |<-----| | |
90 | *| (DetachableConnection) | | ProxyConnection | | ProxyConnection | |
91 | *|========================| |=================| |=================| |
92 | * ^ | ^ ^ ^ |
93 | * | | | | | |
94 | * ---rootConnection-- | | | |
95 | * | | | |
96 | * | | | |
97 | * |======================| |======================| |======================| |
98 | * | ConnectionChild | | ConnectionChild | | ConnectionChild | |
99 | * | | | | | | |
100 | * | (EmbedStatement) | | (EmbedResultSet) | | (...) | |
101 | * |======================| |======================| |======================| |
102 | * |
103 | * <P>A plain local connection <B>must</B> be attached (doubly linked with) to a |
104 | * TransactionResource at all times. A detachable connection can be without a |
105 | * TransactionResource, and a TransactionResource for an XATransaction |
106 | * (called XATransactionResource) can be without a connection. |
107 | * |
108 | * |
109 | */ |
110 | public final class TransactionResourceImpl |
111 | { |
112 | /* |
113 | ** instance variables set up in the constructor. |
114 | */ |
115 | // conn is only present if TR is attached to a connection |
116 | protected ContextManager cm; |
117 | protected ContextService csf; |
118 | protected String username; |
119 | |
120 | private String dbname; |
121 | private InternalDriver driver; |
122 | private String url; |
123 | private String drdaID; |
124 | |
125 | // set these up after constructor, called by EmbedConnection |
126 | protected Database database; |
127 | protected LanguageConnectionContext lcc; |
128 | |
129 | /* |
130 | * create a brand new connection for a brand new transaction |
131 | */ |
132 | TransactionResourceImpl( |
133 | InternalDriver driver, |
134 | String url, |
135 | Properties info) throws SQLException |
136 | { |
137 | this.driver = driver; |
138 | csf = driver.getContextServiceFactory(); |
139 | dbname = InternalDriver.getDatabaseName(url, info); |
140 | this.url = url; |
141 | |
142 | // the driver manager will push a user name |
143 | // into the properties if its getConnection(url, string, string) |
144 | // interface is used. Thus, we look there first. |
145 | // Default to APP. |
146 | username = IdUtil.getUserNameFromURLProps(info); |
147 | |
148 | drdaID = info.getProperty(Attribute.DRDAID_ATTR, null); |
149 | |
150 | // make a new context manager for this TransactionResource |
151 | |
152 | // note that the Database API requires that the |
153 | // getCurrentContextManager call return the context manager |
154 | // associated with this session. The JDBC driver assumes |
155 | // responsibility (for now) for tracking and installing |
156 | // this context manager for the thread, each time a database |
157 | // call is made. |
158 | cm = csf.newContextManager(); |
159 | } |
160 | |
161 | /* |
162 | * Called only in EmbedConnection construtor. |
163 | * The Local Connection sets up the database in its constructor and sets it |
164 | * here. |
165 | */ |
166 | void setDatabase(Database db) |
167 | { |
168 | if (SanityManager.DEBUG) |
169 | SanityManager.ASSERT(database == null, |
170 | "setting database when it is not null"); |
171 | |
172 | database = db; |
173 | } |
174 | |
175 | /* |
176 | * Called only in EmbedConnection constructor. Create a new transaction |
177 | * by creating a lcc. |
178 | * |
179 | * The arguments are not used by this object, it is used by |
180 | * XATransactionResoruceImpl. Put them here so that there is only one |
181 | * routine to start a local connection. |
182 | */ |
183 | void startTransaction() throws StandardException, SQLException |
184 | { |
185 | // setting up local connection |
186 | lcc = database.setupConnection(cm, username, drdaID, dbname); |
187 | } |
188 | |
189 | /* |
190 | * Return instance variables to EmbedConnection. RESOLVE: given time, we |
191 | * should perhaps stop giving out reference to these things but instead use |
192 | * the transaction resource itself. |
193 | */ |
194 | InternalDriver getDriver() { |
195 | return driver; |
196 | } |
197 | ContextService getCsf() { |
198 | return csf; |
199 | } |
200 | |
201 | /* |
202 | * need to be public because it is in the XATransactionResource interface |
203 | */ |
204 | ContextManager getContextManager() { |
205 | return cm; |
206 | } |
207 | |
208 | LanguageConnectionContext getLcc() { |
209 | return lcc; |
210 | } |
211 | String getDBName() { |
212 | return dbname; |
213 | } |
214 | String getUrl() { |
215 | return url; |
216 | } |
217 | Database getDatabase() { |
218 | return database; |
219 | } |
220 | |
221 | StandardException shutdownDatabaseException() { |
222 | StandardException se = StandardException.newException(SQLState.SHUTDOWN_DATABASE, getDBName()); |
223 | se.setReport(StandardException.REPORT_NEVER); |
224 | return se; |
225 | } |
226 | |
227 | /* |
228 | * local transaction demarcation - note that global or xa transaction |
229 | * cannot commit thru the connection, they can only commit thru the |
230 | * XAResource, which uses the xa_commit or xa_rollback interface as a |
231 | * safeguard. |
232 | */ |
233 | void commit() throws StandardException |
234 | { |
235 | lcc.userCommit(); |
236 | } |
237 | |
238 | void rollback() throws StandardException |
239 | { |
240 | // lcc may be null if this is called in close. |
241 | if (lcc != null) |
242 | lcc.userRollback(); |
243 | } |
244 | |
245 | /* |
246 | * context management |
247 | */ |
248 | |
249 | /** |
250 | * An error happens in the constructor, pop the context. |
251 | */ |
252 | void clearContextInError() |
253 | { |
254 | csf.resetCurrentContextManager(cm); |
255 | cm = null; |
256 | } |
257 | |
258 | /** |
259 | * Resolve: probably superfluous |
260 | */ |
261 | void clearLcc() |
262 | { |
263 | lcc = null; |
264 | } |
265 | |
266 | final void setupContextStack() |
267 | { |
268 | if (SanityManager.DEBUG) { |
269 | SanityManager.ASSERT(cm != null, "setting up null context manager stack"); |
270 | } |
271 | |
272 | csf.setCurrentContextManager(cm); |
273 | } |
274 | |
275 | final void restoreContextStack() { |
276 | |
277 | if ((csf == null) || (cm == null)) |
278 | return; |
279 | csf.resetCurrentContextManager(cm); |
280 | } |
281 | |
282 | /* |
283 | * exception handling |
284 | */ |
285 | |
286 | /* |
287 | * clean up the error and wrap the real exception in some SQLException. |
288 | */ |
289 | final SQLException handleException(Throwable thrownException, |
290 | boolean autoCommit, |
291 | boolean rollbackOnAutoCommit) |
292 | throws SQLException |
293 | { |
294 | try { |
295 | if (SanityManager.DEBUG) |
296 | SanityManager.ASSERT(thrownException != null); |
297 | |
298 | /* |
299 | just pass SQL exceptions right back. We assume that JDBC driver |
300 | code has cleaned up sufficiently. Not passing them through would mean |
301 | that all cleanupOnError methods would require knowledge of Utils. |
302 | */ |
303 | if (thrownException instanceof SQLException) { |
304 | |
305 | return (SQLException) thrownException; |
306 | |
307 | } |
308 | |
309 | boolean checkForShutdown = false; |
310 | if (thrownException instanceof StandardException) |
311 | { |
312 | StandardException se = (StandardException) thrownException; |
313 | int severity = se.getSeverity(); |
314 | if (severity <= ExceptionSeverity.STATEMENT_SEVERITY) |
315 | { |
316 | /* |
317 | ** If autocommit is on, then do a rollback |
318 | ** to release locks if requested. We did a stmt |
319 | ** rollback in the cleanupOnError above, but we still |
320 | ** may hold locks from the stmt. |
321 | */ |
322 | if (autoCommit && rollbackOnAutoCommit) |
323 | { |
324 | se.setSeverity(ExceptionSeverity.TRANSACTION_SEVERITY); |
325 | } |
326 | } else if (SQLState.CONN_INTERRUPT.equals(se.getMessageId())) { |
327 | // an interrupt closed the connection. |
328 | checkForShutdown = true; |
329 | } |
330 | } |
331 | // if cm is null, we don't have a connection context left, |
332 | // it was already removed. all that's left to cleanup is |
333 | // JDBC objects. |
334 | if (cm!=null) { |
335 | boolean isShutdown = cleanupOnError(thrownException); |
336 | if (checkForShutdown && isShutdown) { |
337 | // Change the error message to be a known shutdown. |
338 | thrownException = shutdownDatabaseException(); |
339 | } |
340 | } |
341 | |
342 | |
343 | |
344 | return wrapInSQLException((SQLException) null, thrownException); |
345 | |
346 | } catch (Throwable t) { |
347 | |
348 | if (cm!=null) { // something to let us cleanup? |
349 | cm.cleanupOnError(t); |
350 | } |
351 | /* |
352 | We'd rather throw the Throwable, |
353 | but then javac complains... |
354 | We assume if we are in this degenerate |
355 | case that it is actually a java exception |
356 | */ |
357 | throw wrapInSQLException((SQLException) null, t); |
358 | //throw t; |
359 | } |
360 | |
361 | } |
362 | |
363 | public static final SQLException wrapInSQLException(SQLException sqlException, Throwable thrownException) { |
364 | |
365 | if (thrownException == null) |
366 | return sqlException; |
367 | |
368 | SQLException nextSQLException; |
369 | |
370 | if (thrownException instanceof SQLException) { |
371 | |
372 | // server side JDBC can end up with a SQLException in the nested stack |
373 | nextSQLException = (SQLException) thrownException; |
374 | } |
375 | else if (thrownException instanceof StandardException) { |
376 | |
377 | StandardException se = (StandardException) thrownException; |
378 | |
379 | nextSQLException = Util.generateCsSQLException(se); |
380 | |
381 | wrapInSQLException(nextSQLException, se.getNestedException()); |
382 | |
383 | } else { |
384 | |
385 | nextSQLException = Util.javaException(thrownException); |
386 | |
387 | // special case some java exceptions that have nested exceptions themselves |
388 | Throwable nestedByJVM = null; |
389 | if (thrownException instanceof ExceptionInInitializerError) { |
390 | nestedByJVM = ((ExceptionInInitializerError) thrownException).getException(); |
391 | } else if (thrownException instanceof java.lang.reflect.InvocationTargetException) { |
392 | nestedByJVM = ((java.lang.reflect.InvocationTargetException) thrownException).getTargetException(); |
393 | } |
394 | |
395 | if (nestedByJVM != null) { |
396 | wrapInSQLException(nextSQLException, nestedByJVM); |
397 | } |
398 | |
399 | } |
400 | |
401 | if (sqlException != null) { |
402 | sqlException.setNextException(nextSQLException); |
403 | } |
404 | |
405 | return nextSQLException; |
406 | } |
407 | |
408 | /* |
409 | * TransactionResource methods |
410 | */ |
411 | |
412 | String getUserName() { |
413 | return username; |
414 | } |
415 | |
416 | boolean cleanupOnError(Throwable e) |
417 | { |
418 | if (SanityManager.DEBUG) |
419 | SanityManager.ASSERT(cm != null, "cannot cleanup on error with null context manager"); |
420 | |
421 | return cm.cleanupOnError(e); |
422 | } |
423 | |
424 | boolean isIdle() |
425 | { |
426 | // If no lcc, there is no transaction. |
427 | return (lcc == null || lcc.getTransactionExecute().isIdle()); |
428 | } |
429 | |
430 | |
431 | /* |
432 | * class specific methods |
433 | */ |
434 | |
435 | |
436 | /* |
437 | * is the underlaying database still active? |
438 | */ |
439 | boolean isActive() |
440 | { |
441 | // database is null at connection open time |
442 | return (driver.isActive() && ((database == null) || database.isActive())); |
443 | } |
444 | |
445 | } |
446 | |
447 | |