Class | Line # | Actions | |||||
---|---|---|---|---|---|---|---|
TransactionRunnable | 43 | 89 | 0% | 42 | 9 | ||
TransactionRunnable.ExceptionThrowingRunnable | 498 | 0 | - | 0 | 0 |
1 | /* | |
2 | * See the NOTICE file distributed with this work for additional | |
3 | * information regarding copyright ownership. | |
4 | * | |
5 | * This is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU Lesser General Public License as | |
7 | * published by the Free Software Foundation; either version 2.1 of | |
8 | * the License, or (at your option) any later version. | |
9 | * | |
10 | * This software is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | * Lesser General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU Lesser General Public | |
16 | * License along with this software; if not, write to the Free | |
17 | * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | |
18 | * 02110-1301 USA, or see the FSF site: http://www.fsf.org. | |
19 | */ | |
20 | package org.xwiki.store; | |
21 | ||
22 | import java.util.ArrayList; | |
23 | import java.util.List; | |
24 | import java.util.ListIterator; | |
25 | ||
26 | /** | |
27 | * A construct for altering storage in a safe way. | |
28 | * TransactionRunnable represents a storage transaction or a unit of work which may be done inside | |
29 | * of a transaction. It provides hooks to acquire locks, do work, commit and or roll the work back, | |
30 | * and release locks and cleanup temporary storage. Unless a TransactionRunnable extends | |
31 | * StartableTransactionRunnable, it must be run inside of another transaction and it may by the generic | |
32 | * {@code <T>} specify that it can only be run inside of a certain type of TransactionRunnable. | |
33 | * | |
34 | * @param <T> The type of TransactionRunnable which this TransactionRunnable must be run inside of. | |
35 | * A TransactionRunnable which alters a database through Hibernate would fail if it was | |
36 | * started outside of a TransactionRunnable which began and committed a transaction around | |
37 | * it. A class which extends {@code TransactionRunnable<DatabaseTransactionRunnable>} can only be | |
38 | * run inside of a DatabaseTransactionRunnable or a subclass of it. Breaking that rule will | |
39 | * be a compile time error. | |
40 | * @version $Id: 39a3363e4f4fb9b09df0086435007b5bd6ceea46 $ | |
41 | * @since 3.0M2 | |
42 | */ | |
43 | public class TransactionRunnable<T> | |
44 | { | |
45 | /** | |
46 | * All of the runnables to be run then committed. | |
47 | */ | |
48 | private final List<TransactionRunnable> allRunnables = new ArrayList<TransactionRunnable>(); | |
49 | ||
50 | /** | |
51 | * The runnable which this runnable is being run inside of. | |
52 | * Used to check for loops and double assignment. | |
53 | */ | |
54 | private TransactionRunnable<T> parent; | |
55 | ||
56 | /** | |
57 | * If true then this runnable has already started and nothing else may be runIn it. | |
58 | */ | |
59 | private boolean hasPreRun; | |
60 | ||
61 | /** | |
62 | * Run this TransactionRunnable inside of a "parent" runnable. | |
63 | * This runnable will not be pre-run until after the parent is pre-run and this will not be | |
64 | * run until after the parent runnable is run. This runnable will be committed before the parent | |
65 | * and if something goes wrong, this runnable will be rolled back before it's parent. | |
66 | * This runnable will have onComplete called after it's parent though. | |
67 | * It is safe to have a parent runnable start a transaction inside of it's onRun() function and | |
68 | * commit it inside of it's onCommit() function and allow that transaction to be used by the | |
69 | * children of that runnable. | |
70 | * | |
71 | * If multiple runnables are run in a parent, they will be prerun, run, and onComplete'd in the | |
72 | * order they were added and they will be committed and/or rolled back in reverse the order they | |
73 | * were added. | |
74 | * | |
75 | * By using the return value of this function, it is possible to sandwich a TransactionRunnable which | |
76 | * with few requirements between 2 runnables with many requirements. Normally you cannot run a | |
77 | * {@code TransactionRunnable<DatabaseTransaction>} of a TransactionRunnable which does not offer database | |
78 | * access. However, when you add a runnable which does not need or offer database access to one which | |
79 | * does, this function returns that runnable casted to a type which does offer database access | |
80 | * (since it is running in one which does). | |
81 | * {@code | |
82 | * StartableTransactionRunnable<DbTransaction> transaction = new DbTransactionRunnable(); | |
83 | * StartableTransactionRunnable<Standalone> standalone = new StandaloneTransactionRunnable(); | |
84 | * TransactionRunnable<DbTransaction> runnableRequiringDb = new DbRequiringTransactionRunnable(); | |
85 | * | |
86 | * // This will not compile: | |
87 | * runnableRequiringDb.runIn(standalone); | |
88 | * // Because if it did, it would allow you to do this: | |
89 | * standalone.start(); | |
90 | * // Ut oh, using the database outside of a transaction! | |
91 | * | |
92 | * // This will work: | |
93 | * TransactionRunnable<DbTransaction> castedStandalone = standalone.runIn(transaction); | |
94 | * runnableRequiringDb.runIn(castedStandalone); | |
95 | * transaction.start(); | |
96 | * } | |
97 | * | |
98 | * @param <U> The type of capabilities provided by the parent runnable. | |
99 | * This defines the state which the state which the storage engine is guaranteed to be in | |
100 | * when this runnable starts. It must extend the type of capabilities required by this | |
101 | * runnable. | |
102 | * @param parentRunnable the TransactionRunnable to run this runnable inside of. | |
103 | * @return this runnable casted to a TransactionRunnable with the capabilities of it's parent. | |
104 | * @throws IllegalStateException if this function has already been called on this runnable because a | |
105 | * TransactionRunnable may only be run once. | |
106 | * @throws IllegalArgumentException if this runnable is an ancestor of the parentRunnable as it would | |
107 | * create an unresolvable loop. | |
108 | */ | |
109 | 81 | ![]() |
110 | { | |
111 | 81 | if (this.parent != null) { |
112 | 1 | throw new IllegalStateException("This TransactionRunnable is already scheduled to run inside " |
113 | + this.parent.toString() + " and cannot be run in " | |
114 | + parentRunnable.toString() + " too."); | |
115 | } | |
116 | 80 | if (parentRunnable.hasPreRun) { |
117 | 0 | throw new IllegalStateException("This TransactionRunnable cannot be runIn() " + parentRunnable |
118 | + " because it has already started 'the ship has set sail'"); | |
119 | } | |
120 | 80 | parentRunnable.assertNoLoop(this); |
121 | ||
122 | // U extends T so this is safe. | |
123 | 79 | this.parent = (TransactionRunnable<T>) parentRunnable; |
124 | ||
125 | 79 | parentRunnable.allRunnables.add(this); |
126 | ||
127 | // Since this runnable runs inside of parentRunnable this cast is safe because all of the | |
128 | // pre-run and post-run operations will be executed by the parent. | |
129 | 79 | return (TransactionRunnable<U>) this; |
130 | } | |
131 | ||
132 | /** | |
133 | * Get whatever is required by this TransactionRunnable. | |
134 | * In the case of a {@link ProvidingTransactionRunnable} this will get what is <i>required</i> | |
135 | * by that runnable, not what is <i>provided</i>. To get the provided context from a | |
136 | * {@link ProvidingTransactionRunnable} use {@link ProvidingTransactionRunnable#getProvidedContext()} | |
137 | * | |
138 | * @return an implementation of T, the context which is required by this TransactionRunnable. | |
139 | */ | |
140 | 9 | ![]() |
141 | { | |
142 | 9 | if (this.parent != null) { |
143 | 9 | if (this.parent instanceof ProvidingTransactionRunnable) { |
144 | // Casting this.parent to ProvidingTransactionRunnable is safe because instanceof says so. | |
145 | // Casting the provided context to T is safe because T be what it provides and to add it | |
146 | // as a ProvidingTR, what it provides (P) must extend T, and adding it as a plain | |
147 | // TransactionRunnable, what it requires must extend T and what it provides must extend | |
148 | // what it requires so this.parent.getProvidedContext() is guarenteed to extend T. | |
149 | 7 | return (T) ((ProvidingTransactionRunnable) this.parent).getProvidedContext(); |
150 | } | |
151 | 2 | return this.parent.getContext(); |
152 | } | |
153 | 0 | return null; |
154 | } | |
155 | ||
156 | /** | |
157 | * This will be run first. | |
158 | * This MUST NOT alter the state of storage engine. | |
159 | * This is intended for acquiring locks and putting temporary storage in order prior to calling run(). | |
160 | * | |
161 | * @throws Exception which will cause the execution of | |
162 | * onRollback then onComplete before being wrapped in a TransactionException and rethrown. | |
163 | */ | |
164 | 43 | ![]() |
165 | { | |
166 | // By default this will do nothing. | |
167 | } | |
168 | ||
169 | /** | |
170 | * This will be run after preRun and before onCommit. | |
171 | * This SHOULD NOT alter the state of storage engine except for it's own temporary storage. | |
172 | * Whatever it does MUST be able to be reverted by calling onRollback(). | |
173 | * | |
174 | * @throws Exception which will cause a rollback of the transaction and then execution of | |
175 | * onRollback then onComplete before being wrapped in a TransactionException and rethrown. | |
176 | */ | |
177 | 23 | ![]() |
178 | { | |
179 | // By default this will do nothing. | |
180 | } | |
181 | ||
182 | /** | |
183 | * This will be run after onRun is complete. | |
184 | * After this function has completed successfully the storage engine MUST be in it's completed state. | |
185 | * No work may be done in onComplete() except for cleanup of temporary files. | |
186 | * | |
187 | * @throws Exception which will stop the committing process and cause onRollback to be invoked on each | |
188 | * of the runnables in the chain before being wrapped in a TransactionException and | |
189 | * rethrown. | |
190 | */ | |
191 | 32 | ![]() |
192 | { | |
193 | // By default this will do nothing. | |
194 | } | |
195 | ||
196 | /** | |
197 | * This will run if the transaction fails. | |
198 | * This function is guaranteed to be run nomatter what happens before in this transaction. | |
199 | * onRollback() will NOT run for a given TransactionRunnable unless onRun() has been called. | |
200 | * onRollback() will be called for each transaction in thr reverse order as they are run, children | |
201 | * first, siblings in oposite order as registered. | |
202 | * | |
203 | * This MUST roll the storage engine back to it's prior state even after onCommit has been invoked. | |
204 | * TransactionRunnables which cannot rollback after a successful onCommit must extend | |
205 | * {@link RootTransactionRunnable} which it cannot be run inside of another runnable. | |
206 | * | |
207 | * @throws Exception which will be reported as the cause of possible storage corruption. | |
208 | * before being wrapped in a TransactionException and rethrown. | |
209 | */ | |
210 | 27 | ![]() |
211 | { | |
212 | // By default this will do nothing. | |
213 | } | |
214 | ||
215 | /** | |
216 | * This will be run when after onCommit or onRollback no matter the outcome. | |
217 | * This function is guaranteed to be run nomatter what happens before in this transaction. | |
218 | * This is intended to do post commit cleanup and it MUST NOT be used to finalize a commit. | |
219 | * onComplete() will NOT run for a given TransactionRunnable unless onPreRun() has been called. | |
220 | * onComplete() will be called for each transaction in thr reverse order as they are run, children | |
221 | * first, siblings in oposite order as registered. | |
222 | * | |
223 | * @throws Exception which will be reported as a failure which cannot cause database corruption. | |
224 | */ | |
225 | 65 | ![]() |
226 | { | |
227 | // By default this will do nothing. | |
228 | } | |
229 | ||
230 | /* -------------------- Internals -------------------- */ | |
231 | ||
232 | /** | |
233 | * Make sure that the given runnable is not this object, not any one of it's parents. | |
234 | * | |
235 | * @param runnable to check against. | |
236 | * @throws IllegalArgumentException if the runnable is the same as this one. | |
237 | */ | |
238 | 89 | ![]() |
239 | { | |
240 | 89 | if (this == runnable) { |
241 | 1 | throw new IllegalArgumentException("A TransactionRunnable cannot be run inside of itself as " |
242 | + "it would create a loop which would never resolve."); | |
243 | } | |
244 | 88 | if (this.parent != null) { |
245 | 9 | this.parent.assertNoLoop(runnable); |
246 | } | |
247 | } | |
248 | ||
249 | /** | |
250 | * PreRun this and all of the chained runnables. | |
251 | * Run in the order as they were registered in a deep first tree walk. | |
252 | * If an exception occures, call onComplete on each runnable in the reverse order preRun was called. | |
253 | * | |
254 | * @throws TransactionException if an exception is thrown by this or one of the chained runnables' | |
255 | * onPreRun() functions, also may contain exceptions thrown by one or more | |
256 | * of the chained runnables' onComplete() functions. | |
257 | */ | |
258 | 27 | ![]() |
259 | { | |
260 | 27 | final ListIterator<TransactionRunnable> runPathIterator = this.getRunPath().listIterator(); |
261 | 27 | try { |
262 | 107 | while (runPathIterator.hasNext()) { |
263 | 83 | final TransactionRunnable tr = runPathIterator.next(); |
264 | 83 | tr.hasPreRun = true; |
265 | 83 | tr.onPreRun(); |
266 | } | |
267 | } catch (Throwable t) { | |
268 | 3 | final List<Throwable> errors = new ArrayList<Throwable>(); |
269 | 3 | errors.add(t); |
270 | 3 | try { |
271 | 3 | completeAll(runPathIterator); |
272 | } catch (TransactionException e) { | |
273 | 0 | errors.add(e); |
274 | } | |
275 | 3 | throw new TransactionException("Failure in onPreRun()", errors, false); |
276 | } | |
277 | } | |
278 | ||
279 | /** | |
280 | * @return all TransactionRunnables under and including this one in the order they need to be run. | |
281 | */ | |
282 | 107 | ![]() |
283 | { | |
284 | 107 | List<TransactionRunnable> runPath = new ArrayList<TransactionRunnable>(); |
285 | 107 | this.addAllToRunPath(runPath); |
286 | 107 | return runPath; |
287 | } | |
288 | ||
289 | /** | |
290 | * Get all TransactionRunnables under and including this one in the order they need to be run. | |
291 | * | |
292 | * @param runPath a list of TransactionRunnable, every transactionRunnable in the chain | |
293 | * under this will be added in the order they should be run. | |
294 | */ | |
295 | 343 | ![]() |
296 | { | |
297 | 343 | runPath.add(this); |
298 | 343 | for (TransactionRunnable run : this.allRunnables) { |
299 | 236 | run.addAllToRunPath(runPath); |
300 | } | |
301 | } | |
302 | ||
303 | /** | |
304 | * Run this and all of the chained runnables. | |
305 | * Run in the same order as they were registered, deep first tree walking. | |
306 | * If an exception is thrown, all runnables so far run will be rollback'd and all runnables will be | |
307 | * completed. Rollback and complete will happen in the reverse order as run. | |
308 | * | |
309 | * @throws TransactionException made by grouping together whatever is thrown by this or one of the | |
310 | * chained runnables' onRun() functions and whatever might be thrown by | |
311 | * onRollback() or onComplete() which are called if somethign goes wrong. | |
312 | */ | |
313 | 27 | ![]() |
314 | { | |
315 | 27 | final ListIterator<TransactionRunnable> runPathIterator = this.getRunPath().listIterator(); |
316 | 27 | try { |
317 | 97 | while (runPathIterator.hasNext()) { |
318 | 81 | runPathIterator.next().onRun(); |
319 | } | |
320 | } catch (Throwable t) { | |
321 | 11 | final List<Throwable> errors = new ArrayList<Throwable>(); |
322 | 11 | errors.add(t); |
323 | 11 | try { |
324 | 11 | rollbackAll(runPathIterator); |
325 | } catch (TransactionException e) { | |
326 | 1 | errors.add(e); |
327 | } | |
328 | 11 | try { |
329 | 11 | this.complete(); |
330 | } catch (TransactionException e) { | |
331 | 0 | errors.add(e); |
332 | } | |
333 | 11 | throw new TransactionException("Failure in onRun()", errors, false); |
334 | } | |
335 | } | |
336 | ||
337 | /** | |
338 | * Commit this and all of the chained runnables. | |
339 | * Committed in the reverse order as they were run(). | |
340 | * If any of the runnables throws an exception while committing, all will be rollback'd in reverse the | |
341 | * order they were run, starting at the last one, not starting at the one which failed. | |
342 | * After all are rolled back, onComplete() will be called on each, also in reverse order. | |
343 | * | |
344 | * @throws TransactionException made from the exception thrown by this or one of the child runnables' | |
345 | * onCommit function or rollback or complete. | |
346 | */ | |
347 | 18 | ![]() |
348 | { | |
349 | 18 | final List<TransactionRunnable> runPath = this.getRunPath(); |
350 | 18 | final ListIterator<TransactionRunnable> runPathReverseIterator = |
351 | runPath.listIterator(runPath.size()); | |
352 | ||
353 | 18 | try { |
354 | 73 | while (runPathReverseIterator.hasPrevious()) { |
355 | 57 | runPathReverseIterator.previous().onCommit(); |
356 | } | |
357 | } catch (Throwable t) { | |
358 | 2 | final List<Throwable> errors = new ArrayList<Throwable>(); |
359 | 2 | errors.add(t); |
360 | 2 | try { |
361 | 2 | this.rollback(); |
362 | } catch (TransactionException e) { | |
363 | 0 | errors.add(e); |
364 | } | |
365 | 2 | try { |
366 | 2 | this.complete(); |
367 | } catch (TransactionException e) { | |
368 | 0 | errors.add(e); |
369 | } | |
370 | 2 | throw new TransactionException("Failure in onCommit()", errors, false); |
371 | } | |
372 | } | |
373 | ||
374 | /** | |
375 | * Rollback this and all of the chained runnables. | |
376 | * Run in the reverse order as they were run so that each runnable | |
377 | * will be rolling back a storage engine which is in as close as possible a state to what it was | |
378 | * when onRun() was called for that runnable. | |
379 | * onRollback() will NOT run for a given TransactionRunnable unless onRun() has been called. | |
380 | * | |
381 | * @throws TransactionException made from gathering whatever exceptions were thrown | |
382 | * running onRollback() on this and the child runnables. | |
383 | */ | |
384 | 4 | ![]() |
385 | { | |
386 | 4 | final List<TransactionRunnable> runPath = this.getRunPath(); |
387 | 4 | final ListIterator<TransactionRunnable> runPathReverseIterator = |
388 | runPath.listIterator(runPath.size()); | |
389 | 4 | rollbackAll(runPathReverseIterator); |
390 | } | |
391 | ||
392 | /** | |
393 | * Run onComplete() on this and each of the chained runnables. | |
394 | * Run in the opposite order as they were registered, child before parent. | |
395 | * | |
396 | * @throws TransactionException if one or more of the runnables throw any exception. | |
397 | */ | |
398 | 31 | ![]() |
399 | { | |
400 | 31 | final List<TransactionRunnable> runPath = this.getRunPath(); |
401 | 31 | final ListIterator<TransactionRunnable> runPathReverseIterator = |
402 | runPath.listIterator(runPath.size()); | |
403 | 31 | completeAll(runPathReverseIterator); |
404 | } | |
405 | ||
406 | /*--------------------Stateless functions--------------------*/ | |
407 | ||
408 | /** | |
409 | * Call onComplete() on each TransactionRunnable in the iterator. | |
410 | * Call them in reverse order until hasPrevious() returns false starting at whatever point the | |
411 | * the iterator is left at. | |
412 | * | |
413 | * @param iterator the iterator of TransactionRunnables to complete. | |
414 | * @throws TransactionException made up any exceptions throws by any of the onComplete calls. | |
415 | * this function does not stop for exceptions. | |
416 | */ | |
417 | 34 | ![]() |
418 | throws TransactionException | |
419 | { | |
420 | 34 | final List<ExceptionThrowingRunnable> list = new ArrayList<ExceptionThrowingRunnable>(); |
421 | ||
422 | 138 | while (iterator.hasPrevious()) { |
423 | 104 | final TransactionRunnable runnable = iterator.previous(); |
424 | 104 | list.add(new ExceptionThrowingRunnable() |
425 | { | |
426 | 104 | ![]() |
427 | { | |
428 | 104 | runnable.onComplete(); |
429 | } | |
430 | }); | |
431 | } | |
432 | 34 | doAllAndCollectThrowables(list, "Failure in onComplete() the storage engine should be " |
433 | + "consistant although it may contain uncollected garbage.", | |
434 | false); | |
435 | } | |
436 | ||
437 | /** | |
438 | * Call onRollback() on each TransactionRunnable in the iterator. | |
439 | * | |
440 | * @param iterator the iterator of TransactionRunnables to rollback. | |
441 | * @throws TransactionException made up any exceptions throws by any of the onRollback calls. | |
442 | * this function does not stop for exceptions. | |
443 | */ | |
444 | 15 | ![]() |
445 | throws TransactionException | |
446 | { | |
447 | 15 | final List<ExceptionThrowingRunnable> list = new ArrayList<ExceptionThrowingRunnable>(); |
448 | ||
449 | 53 | while (iterator.hasPrevious()) { |
450 | 38 | final TransactionRunnable runnable = iterator.previous(); |
451 | 38 | list.add(new ExceptionThrowingRunnable() |
452 | { | |
453 | 38 | ![]() |
454 | { | |
455 | 38 | runnable.onRollback(); |
456 | } | |
457 | }); | |
458 | } | |
459 | ||
460 | 15 | doAllAndCollectThrowables(list, "Failure in onRollback() the storage engine might be " |
461 | + "in an inconsistent state", true); | |
462 | } | |
463 | ||
464 | /** | |
465 | * Execute a list of ExceptionThrowingRunnables and group any error messages together. | |
466 | * This is run when despite an exception thrown by one runnable, the show must go on. | |
467 | * | |
468 | * @param runnables the things to run. | |
469 | * @param message the error message to add to the TransactionException if it is thrown. | |
470 | * @param isNonRecoverable true if this operation is critical and an exception cannot be recovered | |
471 | * from eg: storage corruption. | |
472 | * @throws TransactionException made by grouping all of the exceptions from the runnables together. | |
473 | */ | |
474 | 49 | ![]() |
475 | final String message, | |
476 | final boolean isNonRecoverable) | |
477 | throws TransactionException | |
478 | { | |
479 | 49 | List<Throwable> causes = null; |
480 | 49 | for (ExceptionThrowingRunnable run : runnables) { |
481 | 142 | try { |
482 | 142 | run.run(); |
483 | } catch (Throwable t) { | |
484 | 6 | if (causes == null) { |
485 | 6 | causes = new ArrayList<Throwable>(); |
486 | } | |
487 | 6 | causes.add(t); |
488 | } | |
489 | } | |
490 | 49 | if (causes != null) { |
491 | 6 | throw new TransactionException(message, causes, isNonRecoverable); |
492 | } | |
493 | } | |
494 | ||
495 | /** | |
496 | * A closure which is capable of throwing an exception. | |
497 | */ | |
498 | private interface ExceptionThrowingRunnable | |
499 | { | |
500 | /** | |
501 | * Do something. | |
502 | * | |
503 | * @throws Exception if something goes wrong while doing it. | |
504 | */ | |
505 | void run() throws Exception; | |
506 | } | |
507 | } |