1 | // Copyright 2004-2007 Jean-Francois Poilpret |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package net.sourceforge.hivetranse.transaction; |
16 | |
17 | import java.util.Iterator; |
18 | |
19 | import org.apache.commons.logging.Log; |
20 | import org.apache.hivemind.Discardable; |
21 | import org.apache.hivemind.PoolManageable; |
22 | import org.apache.hivemind.util.EventListenerList; |
23 | |
24 | import net.sourceforge.hiveutils.collections.Stack; |
25 | import net.sourceforge.hiveutils.collections.impl.StackImpl; |
26 | |
27 | /** |
28 | * Base class for every actual implementation of <code>TransactionService</code>. |
29 | * This class manages the stack of transaction contexts and the notification |
30 | * of <code>TransactionEventListener</code>s. |
31 | * <p> |
32 | * Subclasses just have to implement two abstract methods to begin and close an |
33 | * actual transaction, and possibly a third method to store the changing context |
34 | * somewhere. For those subclasses, the notion of transaction can be any |
35 | * <code>Object</code>. |
36 | * <p> |
37 | * <b>ServiceModel of subclasses must be threaded or pooled</b> |
38 | * |
39 | * @author Jean-Francois Poilpret |
40 | */ |
41 | public abstract class AbstractTransactionService |
42 | implements TransactionService, Discardable, PoolManageable |
43 | { |
44 | protected AbstractTransactionService(Log logger, boolean wrapRuntimeExceptions) |
45 | { |
46 | _logger = logger; |
47 | _wrapRuntimeExceptions = wrapRuntimeExceptions; |
48 | } |
49 | |
50 | // Implementation of Discardable interface |
51 | //----------------------------------------- |
52 | public void threadDidDiscardService() |
53 | { |
54 | cleanUp(); |
55 | } |
56 | |
57 | // Implementation of PoolManageable interface |
58 | //-------------------------------------------- |
59 | public void activateService() |
60 | { |
61 | // Make sure that current contexts are erased |
62 | _stack.clear(); |
63 | _current = null; |
64 | //#### What about listeners? Should be taken care of by HiveMind |
65 | // This must be checked! |
66 | } |
67 | |
68 | public void passivateService() |
69 | { |
70 | cleanUp(); |
71 | } |
72 | |
73 | // Implementation of TransactionService interface |
74 | //------------------------------------------------ |
75 | synchronized public void addTransactionEventListener(TransactionEventListener listener) |
76 | { |
77 | _listeners.addListener(listener); |
78 | } |
79 | |
80 | synchronized public void removeTransactionEventListener(TransactionEventListener listener) |
81 | { |
82 | _listeners.removeListener(listener); |
83 | } |
84 | |
85 | public Object getCurrentTransaction() |
86 | { |
87 | return (_current != null ? _current.getTransaction() : null); |
88 | } |
89 | |
90 | //CSOFF: RedundantThrowsCheck |
91 | public void begin(TransactionDemarcation demarcation) |
92 | throws TransactionException, |
93 | MandatoryTransactionException, |
94 | ForbiddenTransactionException |
95 | { |
96 | boolean mustCleanUp = false; |
97 | TransactionObject currentTx = null; |
98 | if (_current != null) |
99 | { |
100 | currentTx = _current.getTransactionObject(); |
101 | } |
102 | |
103 | // Find out the next Connection to be used |
104 | // (either use the same, use new, or use none) |
105 | switch (demarcation) |
106 | { |
107 | case RequiresNew: |
108 | // Use new Connection |
109 | currentTx = new TransactionObject(beginTransaction()); |
110 | mustCleanUp = true; |
111 | break; |
112 | |
113 | case Required: |
114 | // Use current Connection, or create if no Connection yet |
115 | if (currentTx == null) |
116 | { |
117 | currentTx = new TransactionObject(beginTransaction()); |
118 | mustCleanUp = true; |
119 | } |
120 | break; |
121 | |
122 | case Supports: |
123 | // Use current Connection |
124 | break; |
125 | |
126 | case Mandatory: |
127 | if (currentTx == null) |
128 | { |
129 | _logger.error("No transaction available for Mandatory demarcation"); |
130 | throw new MandatoryTransactionException( |
131 | "No transaction available for Mandatory demarcation"); |
132 | } |
133 | break; |
134 | |
135 | case Never: |
136 | if (currentTx != null) |
137 | { |
138 | _logger.error("Existing transaction for Never demarcation"); |
139 | throw new ForbiddenTransactionException( |
140 | "Existing transaction for Never demarcation"); |
141 | } |
142 | break; |
143 | |
144 | case NotSupported: |
145 | currentTx = null; |
146 | break; |
147 | |
148 | default: |
149 | _logger.error("Unknown transaction demarcation, code=" + demarcation); |
150 | throw new TransactionException("Unknown transaction demarcation"); |
151 | } |
152 | |
153 | _current = new Context(mustCleanUp, currentTx); |
154 | _stack.push(_current); |
155 | storeTransaction(); |
156 | // If a new transaction has been created then fire the matching event |
157 | if (mustCleanUp) |
158 | { |
159 | fireAfterNewTransactionBegins(); |
160 | } |
161 | } |
162 | //CSON: RedundantThrowsCheck |
163 | |
164 | public void end() throws TransactionException |
165 | { |
166 | // Pop current context from stack |
167 | _stack.pop(); |
168 | // Check if there is something to do |
169 | Exception e = cleanUp(_current); |
170 | boolean commit = !_current.isRollbackOnly(); |
171 | // Set the new current transaction context |
172 | _current = (_stack.isEmpty() ? null : _stack.peek()); |
173 | storeTransaction(); |
174 | if (e != null) |
175 | { |
176 | if ( (e instanceof RuntimeException) |
177 | && !_wrapRuntimeExceptions) |
178 | { |
179 | throw (RuntimeException) e; |
180 | } |
181 | if (commit) |
182 | { |
183 | throw new TransactionException("Error occurred during transaction commit", e); |
184 | } |
185 | else |
186 | { |
187 | throw new TransactionException("Error occurred during transaction rollback", e); |
188 | } |
189 | } |
190 | } |
191 | |
192 | public void setRollbackOnly() throws TransactionException |
193 | { |
194 | if (_current == null) |
195 | { |
196 | _logger.error("No transaction available"); |
197 | throw new MandatoryTransactionException("No transaction available"); |
198 | } |
199 | _current.setRollbackOnly(); |
200 | } |
201 | |
202 | protected void fireBeforeNewTransactionBegins() |
203 | { |
204 | Iterator i = _listeners.getListeners(); |
205 | while (i.hasNext()) |
206 | { |
207 | TransactionEventListener l = (TransactionEventListener) i.next(); |
208 | l.beforeNewTransactionBegins(); |
209 | } |
210 | } |
211 | |
212 | protected void fireAfterNewTransactionBegins() |
213 | { |
214 | Iterator i = _listeners.getListeners(); |
215 | while (i.hasNext()) |
216 | { |
217 | TransactionEventListener l = (TransactionEventListener) i.next(); |
218 | l.afterNewTransactionBegins(); |
219 | } |
220 | } |
221 | |
222 | protected void fireBeforeTransactionEnds(boolean committed) |
223 | { |
224 | Iterator i = _listeners.getListeners(); |
225 | while (i.hasNext()) |
226 | { |
227 | TransactionEventListener l = (TransactionEventListener) i.next(); |
228 | l.beforeTransactionEnds(committed); |
229 | } |
230 | } |
231 | |
232 | protected void fireAfterTransactionEnds(boolean committed) |
233 | { |
234 | Iterator i = _listeners.getListeners(); |
235 | while (i.hasNext()) |
236 | { |
237 | TransactionEventListener l = (TransactionEventListener) i.next(); |
238 | l.afterTransactionEnds(committed); |
239 | } |
240 | } |
241 | |
242 | protected void cleanUp() |
243 | { |
244 | // Close all pending connections |
245 | boolean error = false; |
246 | while (!_stack.isEmpty()) |
247 | { |
248 | Context context = _stack.pop(); |
249 | context.setRollbackOnly(); |
250 | if (cleanUp(context) != null) |
251 | { |
252 | error = true; |
253 | } |
254 | } |
255 | if (error) |
256 | { |
257 | _logger.error("threadDidDiscardService could not clean up fully"); |
258 | } |
259 | } |
260 | |
261 | //CSOFF: IllegalCatchCheck |
262 | protected Object beginTransaction() |
263 | { |
264 | _logger.debug("beginTransaction(): starting a new transaction"); |
265 | fireBeforeNewTransactionBegins(); |
266 | try |
267 | { |
268 | Object tx = createTransaction(); |
269 | return tx; |
270 | } |
271 | catch (Exception e) |
272 | { |
273 | _logger.error("beginTransaction()", e); |
274 | if ( (e instanceof RuntimeException) |
275 | && !_wrapRuntimeExceptions) |
276 | { |
277 | throw (RuntimeException) e; |
278 | } |
279 | else |
280 | { |
281 | throw new TransactionException(e); |
282 | } |
283 | } |
284 | } |
285 | //CSON: IllegalCatchCheck |
286 | |
287 | /** |
288 | * Method called when creation of a new "transaction" is needed. |
289 | * It must be implemented by subclasses. |
290 | * <p> |
291 | * Please note that "transaction" may be anything and does not have to be |
292 | * just a transaction, ie it may be a JDBC Connection, a Hibernate Session... |
293 | * @return the new transaction created. Internal storage of this object is |
294 | * performed by <code>AbstractTransactionService</code>. |
295 | * @exception Exception any exception, that will eventually be wrapped into |
296 | * an -unchecked- TransactionException |
297 | */ |
298 | abstract protected Object createTransaction() throws Exception; |
299 | |
300 | /** |
301 | * Method called when the current "transaction" must end, either committed |
302 | * or rolled back. |
303 | * Ending a transaction may have more meaning than just ending an actual |
304 | * transaction, it may as well mean closing a JDBC Connection, an Hibernate |
305 | * Session... |
306 | * @param tx the "transaction" object (ie, the same object returned by |
307 | * <code>createTransaction</code>) |
308 | * @param commit <code>true</code> if the transaction represented by |
309 | * <code>tx</code> must be committed, <code>false</code> if it must be |
310 | * rolled back |
311 | * @exception Exception any exception, that will eventually be wrapped into |
312 | * an -unchecked- TransactionException |
313 | */ |
314 | abstract protected void endTransaction(Object tx, boolean commit) |
315 | throws Exception; |
316 | |
317 | /** |
318 | * Method called whenever the current transaction context changes. |
319 | * Does nothing by default. |
320 | * <p> |
321 | * Override this method if you have specific needs when a transaction |
322 | * context changes occur. For instance, subclasses could store the new |
323 | * current transaction context into ThreadLocalStorage for use by other |
324 | * services. |
325 | * <p> |
326 | * Please carefully note that overriding this method is not equivalent to |
327 | * using TransactionEvents (these are triggered when a Transaction is |
328 | * created or ended, but they do not indicate what the next transaction |
329 | * context is). |
330 | * @param tx new current "transaction" object, as created by |
331 | * <code>createTransaction</code>; might be <code>null</code> to indicate |
332 | * that there is no available transaction context from now on. |
333 | */ |
334 | protected void storeTransaction(Object tx) |
335 | { |
336 | } |
337 | |
338 | protected void storeTransaction() |
339 | { |
340 | Object tx = (_current != null ? _current.getTransaction() : null); |
341 | storeTransaction(tx); |
342 | } |
343 | |
344 | //CSOFF: IllegalCatchCheck |
345 | protected Exception cleanUp(Context context) |
346 | { |
347 | if (!context.mustCleanUp()) |
348 | { |
349 | return null; |
350 | } |
351 | boolean commit = !context.isRollbackOnly(); |
352 | _logger.debug("cleanUp(): ending transaction, commit=" + commit); |
353 | try |
354 | { |
355 | fireBeforeTransactionEnds(commit); |
356 | endTransaction(context.getTransaction(), commit); |
357 | fireAfterTransactionEnds(commit); |
358 | return null; |
359 | } |
360 | catch (Exception e) |
361 | { |
362 | _logger.error("Problem committing transaction", e); |
363 | return e; |
364 | } |
365 | } |
366 | //CSON: IllegalCatchCheck |
367 | |
368 | // For each pass through a TransactionInterceptor, an Context instance is |
369 | // pushed on the stack. |
370 | // Each Context contains the JDBC Connection to be used, and also contains a |
371 | // flag defining whether the Connection is owned by this Context or not. |
372 | // If demarcation disables transactions (ie, Never or NotSupported), then |
373 | // the Connection is null. |
374 | static protected class Context |
375 | { |
376 | public Context(boolean mustCleanUp, TransactionObject tx) |
377 | { |
378 | _mustCleanUp = mustCleanUp; |
379 | _tx = tx; |
380 | } |
381 | |
382 | public TransactionObject getTransactionObject() |
383 | { |
384 | return _tx; |
385 | } |
386 | |
387 | public Object getTransaction() |
388 | { |
389 | if (_tx != null) |
390 | { |
391 | return _tx.getTransaction(); |
392 | } |
393 | else |
394 | { |
395 | return null; |
396 | } |
397 | } |
398 | |
399 | public boolean isRollbackOnly() |
400 | { |
401 | if (_tx != null) |
402 | { |
403 | return _tx.isRollbackOnly(); |
404 | } |
405 | else |
406 | { |
407 | return true; |
408 | } |
409 | } |
410 | |
411 | public void setRollbackOnly() |
412 | { |
413 | if (_tx != null) |
414 | { |
415 | _tx.setRollbackOnly(); |
416 | } |
417 | } |
418 | |
419 | public boolean mustCleanUp() |
420 | { |
421 | return _mustCleanUp; |
422 | } |
423 | |
424 | private final boolean _mustCleanUp; |
425 | private final TransactionObject _tx; |
426 | } |
427 | |
428 | // The TransactionObject embed both the Transaction (Object which meaning |
429 | // depends on actual implementation) AND the rollback status of this |
430 | // transaction object |
431 | static protected class TransactionObject |
432 | { |
433 | public TransactionObject(Object tx) |
434 | { |
435 | _tx = tx; |
436 | _rollbackOnly = false; |
437 | } |
438 | |
439 | public Object getTransaction() |
440 | { |
441 | return _tx; |
442 | } |
443 | |
444 | public boolean isRollbackOnly() |
445 | { |
446 | return _rollbackOnly; |
447 | } |
448 | |
449 | public void setRollbackOnly() |
450 | { |
451 | _rollbackOnly = true; |
452 | } |
453 | |
454 | private final Object _tx; |
455 | private boolean _rollbackOnly; |
456 | } |
457 | |
458 | protected final Log _logger; |
459 | protected final boolean _wrapRuntimeExceptions; |
460 | protected final Stack<Context> _stack = new StackImpl<Context>(); |
461 | protected final EventListenerList _listeners = new EventListenerList(); |
462 | protected Context _current = null; |
463 | } |