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.log4j.Level;
12
import org.apache.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
/**
28
 * Abstract base class for integration testing a spring / hibernate application using
29
 * the unitils testing framework and dbunit.
30
 *
31
 * This base class extends the {@link CdmItegrationTest} by transactions management features.
32
 *
33
 */
34
@Transactional(TransactionMode.DISABLED) // NOTE: we are handling transaction by ourself in this class, thus we prevent unitils from creating transactions
35
public abstract class CdmTransactionalIntegrationTest extends CdmIntegrationTest {
36

    
37
    protected static final Logger logger = Logger.getLogger(CdmTransactionalIntegrationTest.class);
38

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

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

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

    
55
    /**
56
     * Number of transactions started
57
     */
58
    private int	transactionsStarted = 0;
59

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

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

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

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

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

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

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

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

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

    
144
        this.complete = !this.isRollback();
145

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

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

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

    
182
    }
183

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

    
202
    }
203

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

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

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

    
255
    }
256

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

    
265
    }
266

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

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

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

    
295
        final boolean commit = this.complete || !isRollback();
296

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

    
316
    protected void rollback() {
317

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

    
331

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

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

    
350
        this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
351
        ++this.transactionsStarted;
352
        this.complete = !this.isRollback();
353

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

    
360
    protected void commitAndStartNewTransaction() {
361
        this.commitAndStartNewTransaction(null);
362
    }
363

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

    
381
    /**
382
     * Commit and end transaction
383
     */
384
    protected void commit() {
385
        setComplete();
386
        endTransaction();
387
    }
388

    
389
}
(2-2/6)