Project

General

Profile

Download (13.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2009 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.test.integration;
10

    
11
import org.apache.logging.log4j.LogManager;
12
import org.apache.logging.log4j.Logger;
13
import org.junit.After;
14
import org.junit.Before;
15
import org.junit.BeforeClass;
16
import org.springframework.security.core.context.SecurityContextHolder;
17
import org.springframework.transaction.PlatformTransactionManager;
18
import org.springframework.transaction.TransactionDefinition;
19
import org.springframework.transaction.TransactionException;
20
import org.springframework.transaction.TransactionStatus;
21
import org.springframework.transaction.support.DefaultTransactionDefinition;
22
import org.unitils.database.annotations.Transactional;
23
import org.unitils.database.util.TransactionMode;
24
import org.unitils.spring.annotation.SpringBeanByType;
25

    
26
/**
27
 * Abstract base class for integration testing a spring / hibernate application using
28
 * the unitils testing framework and dbunit.
29
 *
30
 * This base class extends the {@link CdmItegrationTest} by transactions management features.
31
 */
32
@Transactional(TransactionMode.DISABLED) // NOTE: we are handling transaction by ourself in this class, thus we prevent unitils from creating transactions
33
public abstract class CdmTransactionalIntegrationTest extends CdmIntegrationTest {
34

    
35
    protected static final Logger logger = LogManager.getLogger();
36

    
37
    /**
38
     * The transaction manager to use
39
     */
40
    @SpringBeanByType
41
    private PlatformTransactionManager transactionManager;
42

    
43
    /**
44
     * Should we roll back by default?
45
     */
46
    private boolean defaultRollback = true;
47

    
48
    /**
49
     * Should we commit the current transaction?
50
     */
51
    private boolean	complete = false;
52

    
53
    /**
54
     * Number of transactions started
55
     */
56
    private int	transactionsStarted = 0;
57

    
58
    /**
59
     * Transaction definition used by this test class: by default, a plain
60
     * DefaultTransactionDefinition. Subclasses can change this to cause
61
     * different behavior.
62
     */
63
    private TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
64

    
65
    /**
66
     * TransactionStatus for this test. Typical subclasses won't need to use it.
67
     */
68
    private TransactionStatus transactionStatus;
69

    
70
    /**
71
     * Get the <em>default rollback</em> flag for this test.
72
     *
73
     * @see #setDefaultRollback(boolean)
74
     * @return The <em>default rollback</em> flag.
75
     */
76
    protected boolean isDefaultRollback() {
77
        return this.defaultRollback;
78
    }
79

    
80
    /**
81
     * Subclasses can set this value in their constructor to change the default,
82
     * which is always to roll the transaction back.
83
     */
84
    public void setDefaultRollback(final boolean defaultRollback) {
85
        this.defaultRollback = defaultRollback;
86
    }
87

    
88
    /**
89
     * <p>
90
     * Determines whether or not to rollback transactions for the current test.
91
     * </p>
92
     * <p>
93
     * The default implementation delegates to {@link #isDefaultRollback()}.
94
     * Subclasses can override as necessary.
95
     * </p>
96
     *
97
     * @return The <em>rollback</em> flag for the current test.
98
     */
99
    protected boolean isRollback() {
100
        return isDefaultRollback();
101
    }
102

    
103
    /**
104
     * Call this method in an overridden {@link #runBare()} method to prevent
105
     * transactional execution.
106
     */
107
    protected void preventTransaction() {
108
        this.transactionDefinition = null;
109
    }
110

    
111
    /**
112
     * Call this method in an overridden {@link #runBare()} method to override
113
     * the transaction attributes that will be used, so that {@link #setUp()}
114
     * and {@link #tearDown()} behavior is modified.
115
     *
116
     * @param customDefinition the custom transaction definition
117
     */
118
    protected void setTransactionDefinition(final TransactionDefinition customDefinition) {
119
        this.transactionDefinition = customDefinition;
120
    }
121

    
122
    @BeforeClass
123
    public static void beforeClass() {
124
        logger.debug("before test class");
125
    }
126

    
127
    /**
128
     * This implementation creates a transaction before test execution.
129
     * <p>
130
     * Override {@link #onSetUpBeforeTransaction()} and/or
131
     * {@link #onSetUpInTransaction()} to add custom set-up behavior for
132
     * transactional execution. Alternatively, override this method for general
133
     * set-up behavior, calling <code>super.onSetUp()</code> as part of your
134
     * method implementation.
135
     *
136
     * @throws Exception simply let any exception propagate
137
     * @see #onTearDown()
138
     */
139
    @Before
140
    public void onSetUp() throws Exception {
141

    
142
        this.complete = !this.isRollback();
143

    
144
        if (this.transactionManager == null) {
145
            logger.info("No transaction manager set: test will NOT run within a transaction");
146
        }
147
        else if (this.transactionDefinition == null) {
148
            logger.info("No transaction definition set: test will NOT run within a transaction");
149
        }
150
        else {
151
            onSetUpBeforeTransaction();
152
            startNewTransaction();
153
            try {
154
                onSetUpInTransaction();
155
            }
156
            catch (final Exception ex) {
157
                endTransaction();
158
                throw ex;
159
            }
160
        }
161
    }
162

    
163
    @After
164
    @Before
165
    public void clearAuthentication() {
166
        SecurityContextHolder.getContext().setAuthentication(null);
167
    }
168

    
169
    /**
170
     * Subclasses can override this method to perform any setup operations, such
171
     * as populating a database table, <i>before</i> the transaction created by
172
     * this class. Only invoked if there <i>is</i> a transaction: that is, if
173
     * {@link #preventTransaction()} has not been invoked in an overridden
174
     * {@link #runTest()} method.
175
     *
176
     * @throws Exception simply let any exception propagate
177
     */
178
    protected void onSetUpBeforeTransaction() throws Exception {
179

    
180
    }
181

    
182
    /**
183
     * Subclasses can override this method to perform any setup operations, such
184
     * as populating a database table, <i>within</i> the transaction created by
185
     * this class.
186
     * <p>
187
     * <b>NB:</b> Not called if there is no transaction management, due to no
188
     * transaction manager being provided in the context.
189
     * <p>
190
     * If any {@link Throwable} is thrown, the transaction that has been started
191
     * prior to the execution of this method will be
192
     * {@link #endTransaction() ended} (or rather an attempt will be made to
193
     * {@link #endTransaction() end it gracefully}); The offending
194
     * {@link Throwable} will then be rethrown.
195
     *
196
     * @throws Exception simply let any exception propagate
197
     */
198
    protected void onSetUpInTransaction() throws Exception {
199

    
200
    }
201

    
202
    /**
203
     * This implementation ends the transaction after test execution.
204
     * <p>
205
     * Override {@link #onTearDownInTransaction()} and/or
206
     * {@link #onTearDownAfterTransaction()} to add custom tear-down behavior
207
     * for transactional execution. Alternatively, override this method for
208
     * general tear-down behavior, calling <code>super.onTearDown()</code> as
209
     * part of your method implementation.
210
     * <p>
211
     * Note that {@link #onTearDownInTransaction()} will only be called if a
212
     * transaction is still active at the time of the test shutdown. In
213
     * particular, it will <i>not</i> be called if the transaction has been
214
     * completed with an explicit {@link #endTransaction()} call before.
215
     *
216
     * @throws Exception simply let any exception propagate
217
     * @see #onSetUp()
218
     */
219
    @After
220
    public void onTearDown() throws Exception {
221

    
222
        // Call onTearDownInTransaction and end transaction if the transaction
223
        // is still active.
224
        if (this.transactionStatus != null && !this.transactionStatus.isCompleted()) {
225
            try {
226
                onTearDownInTransaction();
227
            }
228
            finally {
229
                endTransaction();
230
            }
231
        }
232
        // Call onTearDownAfterTransaction if there was at least one
233
        // transaction, even if it has been completed early through an
234
        // endTransaction() call.
235
        if (this.transactionsStarted > 0) {
236
            onTearDownAfterTransaction();
237
        }
238
    }
239

    
240
    /**
241
     * Subclasses can override this method to run invariant tests here. The
242
     * transaction is <i>still active</i> at this point, so any changes made in
243
     * the transaction will still be visible. However, there is no need to clean
244
     * up the database, as a rollback will follow automatically.
245
     * <p>
246
     * <b>NB:</b> Not called if there is no actual transaction, for example due
247
     * to no transaction manager being provided in the application context.
248
     *
249
     * @throws Exception simply let any exception propagate
250
     */
251
    protected void onTearDownInTransaction() throws Exception {
252

    
253
    }
254

    
255
    /**
256
     * Subclasses can override this method to perform cleanup after a
257
     * transaction here. At this point, the transaction is <i>not active anymore</i>.
258
     *
259
     * @throws Exception simply let any exception propagate
260
     */
261
    protected void onTearDownAfterTransaction() throws Exception {
262

    
263
    }
264

    
265
    /**
266
     * Cause the transaction to commit for this test method, even if the test
267
     * method is configured to {@link #isRollback() rollback}.
268
     *
269
     * @throws IllegalStateException if the operation cannot be set to complete
270
     *         as no transaction manager was provided
271
     */
272
    protected void setComplete() {
273

    
274
        if (this.transactionManager == null) {
275
            throw new IllegalStateException("No transaction manager set");
276
        }
277
        this.complete = true;
278
        logger.debug("set complete = true");
279
    }
280

    
281
    /**
282
     * Immediately force a commit or rollback of the transaction, according to
283
     * the <code>complete</code> and {@link #isRollback() rollback} flags.
284
     * <p>
285
     * Can be used to explicitly let the transaction end early, for example to
286
     * check whether lazy associations of persistent objects work outside of a
287
     * transaction (that is, have been initialized properly).
288
     *
289
     * @see #setComplete()
290
     */
291
    protected void endTransaction() {
292

    
293
        final boolean commit = this.complete || !isRollback();
294

    
295
        if (this.transactionStatus != null) {
296
            try {
297
                logger.debug("Trying to commit or rollback");
298
                if (commit) {
299
                    this.transactionManager.commit(this.transactionStatus);
300
                    logger.debug("Committed transaction after execution of test");
301
                }
302
                else {
303
                    this.transactionManager.rollback(this.transactionStatus);
304
                    logger.debug("Rolled back transaction after execution of test.");
305
                }
306
            }
307
            finally {
308
                logger.debug("Clearing transactionStatus");
309
                this.transactionStatus = null;
310
            }
311
        }
312
    }
313

    
314
    protected void rollback() {
315

    
316
        if (this.transactionStatus != null) {
317
            try {
318
                logger.debug("trying to rollback");
319
                this.transactionManager.rollback(this.transactionStatus);
320
                logger.debug("Rolled back transaction after execution of test.");
321
            }
322
            finally {
323
                logger.debug("Clearing transactionStatus");
324
                this.transactionStatus = null;
325
            }
326
        }
327
    }
328

    
329
    /**
330
     * Start a new transaction. Only call this method if
331
     * {@link #endTransaction()} has been called. {@link #setComplete()} can be
332
     * used again in the new transaction. The fate of the new transaction, by
333
     * default, will be the usual rollback.
334
     *
335
     * @throws TransactionException if starting the transaction failed
336
     */
337
    protected void startNewTransaction() throws TransactionException {
338

    
339
        if (this.transactionStatus != null) {
340
            throw new IllegalStateException("Cannot start new transaction without ending existing transaction: "
341
                    + "Invoke endTransaction() before startNewTransaction()");
342
        }
343
        if (this.transactionManager == null) {
344
            throw new IllegalStateException("No transaction manager set");
345
        }
346

    
347
        this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
348
        ++this.transactionsStarted;
349
        this.complete = !this.isRollback();
350

    
351
        if (logger.isDebugEnabled()) {
352
            logger.debug("Began transaction (" + this.transactionsStarted + "): transaction manager ["
353
                    + this.transactionManager + "]; rollback [" + this.isRollback() + "].");
354
        }
355
    }
356

    
357
    protected void commitAndStartNewTransaction() {
358
        this.commitAndStartNewTransaction(null);
359
    }
360

    
361
    /**
362
     * @param tableNames the tables supplied by this array will be <b>printed after</b> the transaction has committed
363
     * and ended <b>only if the logging level is set to debug</b>, e.g.:
364
     * <pre>
365
     *  log4j.logger.eu.etaxonomy.cdm.test.integration=DEBUG
366
     * </pre>
367
     */
368
    protected void commitAndStartNewTransaction(final String[] tableNames) {
369
        commit();
370
        if(tableNames != null && logger.isDebugEnabled()){
371
            printDataSet(System.out, tableNames);
372
//          careful, the following will overwrite existing files:
373
//          writeDbUnitDataSetFile(tableNames);
374
        }
375
        startNewTransaction();
376
    }
377

    
378
    /**
379
     * Commit and end transaction
380
     */
381
    protected void commit() {
382
        setComplete();
383
        endTransaction();
384
    }
385
}
(2-2/5)