1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.store

File TransactionRunnable.java

 

Coverage histogram

../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

26
89
20
2
507
215
42
0.47
4.45
10
2.1

Classes

Class Line # Actions
TransactionRunnable 43 89 0% 42 9
0.9333333493.3%
TransactionRunnable.ExceptionThrowingRunnable 498 0 - 0 0
-1.0 -
 

Contributing tests

This file is covered by 37 tests. .

Source view

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 toggle public <U extends T> TransactionRunnable<U> runIn(final TransactionRunnable<U> parentRunnable)
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 toggle protected T getContext()
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 toggle protected void onPreRun() throws Exception
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 toggle protected void onRun() throws Exception
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 toggle protected void onCommit() throws Exception
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 toggle protected void onRollback() throws Exception
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 toggle protected void onComplete() throws Exception
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 toggle private void assertNoLoop(TransactionRunnable runnable)
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 toggle protected final void preRun() throws TransactionException
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 toggle private List<TransactionRunnable> getRunPath()
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 toggle private void addAllToRunPath(final List<TransactionRunnable> runPath)
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 toggle protected final void run() throws TransactionException
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 toggle protected final void commit() throws TransactionException
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 toggle protected final void rollback() throws TransactionException
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 toggle protected final void complete() throws TransactionException
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 toggle private static void completeAll(final ListIterator<TransactionRunnable> iterator)
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 toggle public void run() throws Exception
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 toggle private static void rollbackAll(final ListIterator<TransactionRunnable> iterator)
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 toggle public void run() throws Exception
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 toggle private static void doAllAndCollectThrowables(final List<ExceptionThrowingRunnable> runnables,
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    }