001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.kernel.impl;
007
008import static org.junit.Assert.assertEquals;
009import static org.junit.Assert.assertTrue;
010import static org.junit.Assert.fail;
011import static org.mockito.Mockito.doThrow;
012import static org.mockito.Mockito.never;
013import static org.mockito.Mockito.times;
014import static org.mockito.Mockito.verify;
015import static org.mockito.Mockito.when;
016
017import java.time.Duration;
018import java.time.Instant;
019import java.util.concurrent.Executors;
020import java.util.concurrent.Phaser;
021import java.util.concurrent.TimeUnit;
022
023import com.google.common.base.Stopwatch;
024import org.fcrepo.common.db.DbTransactionExecutor;
025import org.fcrepo.kernel.api.ContainmentIndex;
026import org.fcrepo.kernel.api.cache.UserTypesCache;
027import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
028import org.fcrepo.kernel.api.exception.TransactionClosedException;
029import org.fcrepo.kernel.api.lock.ResourceLockManager;
030import org.fcrepo.kernel.api.observer.EventAccumulator;
031import org.fcrepo.kernel.api.services.MembershipService;
032import org.fcrepo.kernel.api.services.ReferenceService;
033import org.fcrepo.persistence.api.PersistentStorageSession;
034import org.fcrepo.persistence.api.PersistentStorageSessionManager;
035import org.fcrepo.persistence.api.exceptions.PersistentStorageException;
036import org.fcrepo.search.api.SearchIndex;
037import org.junit.Before;
038import org.junit.Test;
039import org.junit.runner.RunWith;
040import org.mockito.Mock;
041import org.mockito.junit.MockitoJUnitRunner;
042
043/**
044 * <p>
045 * TransactionTest class.
046 * </p>
047 *
048 * @author mohideen
049 */
050@RunWith(MockitoJUnitRunner.Silent.class)
051public class TransactionImplTest {
052
053    private TransactionImpl testTx;
054
055    @Mock
056    private TransactionManagerImpl txManager;
057
058    @Mock
059    private PersistentStorageSessionManager pssManager;
060
061    @Mock
062    private PersistentStorageSession psSession;
063
064    @Mock
065    private ContainmentIndex containmentIndex;
066
067    @Mock
068    private SearchIndex searchIndex;
069
070    @Mock
071    private EventAccumulator eventAccumulator;
072
073    @Mock
074    private ReferenceService referenceService;
075
076    @Mock
077    private MembershipService membershipService;
078
079    @Mock
080    private ResourceLockManager resourceLockManager;
081
082    @Mock
083    private UserTypesCache userTypesCache;
084
085    @Before
086    public void setUp() {
087        testTx = new TransactionImpl("123", txManager, Duration.ofMillis(180000));
088        when(pssManager.getSession(testTx)).thenReturn(psSession);
089        when(txManager.getPersistentStorageSessionManager()).thenReturn(pssManager);
090        when(txManager.getContainmentIndex()).thenReturn(containmentIndex);
091        when(txManager.getEventAccumulator()).thenReturn(eventAccumulator);
092        when(txManager.getReferenceService()).thenReturn(referenceService);
093        when(txManager.getMembershipService()).thenReturn(membershipService);
094        when(txManager.getSearchIndex()).thenReturn(this.searchIndex);
095        when(txManager.getDbTransactionExecutor()).thenReturn(new DbTransactionExecutor());
096        when(txManager.getResourceLockManager()).thenReturn(resourceLockManager);
097        when(txManager.getUserTypesCache()).thenReturn(userTypesCache);
098    }
099
100    @Test
101    public void testGetId() {
102        assertEquals("123", testTx.getId());
103    }
104
105    @Test
106    public void testDefaultShortLived() {
107        assertEquals(true, testTx.isShortLived());
108    }
109
110    @Test
111    public void testSetShortLived() {
112        testTx.setShortLived(false);
113        assertEquals(false, testTx.isShortLived());
114    }
115
116    @Test
117    public void testCommit() throws Exception {
118        testTx.commit();
119        verify(psSession).commit();
120    }
121
122    @Test
123    public void testCommitIfShortLived() throws Exception {
124        testTx.setShortLived(true);
125        testTx.commitIfShortLived();
126        verify(psSession).commit();
127    }
128
129    @Test
130    public void testCommitIfShortLivedOnNonShortLived() throws Exception {
131        testTx.setShortLived(false);
132        testTx.commitIfShortLived();
133        verify(psSession, never()).commit();
134    }
135
136    @Test(expected = TransactionClosedException.class)
137    public void testCommitExpired() throws Exception {
138        testTx.expire();
139        try {
140            testTx.commit();
141        } finally {
142            verify(psSession, never()).commit();
143        }
144    }
145
146    @Test(expected = TransactionClosedException.class)
147    public void testCommitRolledbackTx() throws Exception {
148        testTx.rollback();
149        try {
150            testTx.commit();
151        } finally {
152            verify(psSession, never()).commit();
153        }
154    }
155
156    @Test(expected = RepositoryRuntimeException.class)
157    public void testEnsureRollbackOnFailedCommit() throws Exception {
158        doThrow(new PersistentStorageException("Failed")).when(psSession).commit();
159        try {
160            testTx.commit();
161        } finally {
162            verify(psSession).commit();
163            verify(psSession).rollback();
164        }
165    }
166
167    @Test
168    public void testCommitAlreadyCommittedTx() throws Exception {
169        testTx.commit();
170        testTx.commit();
171        verify(psSession, times(1)).commit();
172    }
173
174    @Test
175    public void testRollback() throws Exception {
176        testTx.rollback();
177        verify(psSession).rollback();
178    }
179
180    @Test
181    public void shouldRollbackAllWhenStorageThrowsException() throws Exception {
182        doThrow(new PersistentStorageException("storage")).when(psSession).rollback();
183        testTx.rollback();
184        verifyRollback();
185    }
186
187    @Test
188    public void shouldRollbackAllWhenContainmentThrowsException() throws Exception {
189        doThrow(new RuntimeException()).when(containmentIndex).rollbackTransaction(testTx);
190        testTx.rollback();
191        verifyRollback();
192    }
193
194    @Test
195    public void shouldRollbackAllWhenEventsThrowsException() throws Exception {
196        doThrow(new RuntimeException()).when(eventAccumulator).clearEvents(testTx);
197        testTx.rollback();
198        verifyRollback();
199    }
200
201    @Test(expected = TransactionClosedException.class)
202    public void testRollbackCommited() throws Exception {
203        testTx.commit();
204        try {
205            testTx.rollback();
206        } finally {
207            verify(psSession, never()).rollback();
208        }
209    }
210
211    @Test
212    public void testRollbackAlreadyRolledbackTx() throws Exception {
213        testTx.rollback();
214        testTx.rollback();
215        verify(psSession, times(1)).rollback();
216    }
217
218    @Test
219    public void testExpire() {
220        testTx.expire();
221        assertTrue(testTx.hasExpired());
222    }
223
224    @Test
225    public void testUpdateExpiry() {
226        final Instant previousExpiry = testTx.getExpires();
227        testTx.updateExpiry(Duration.ofSeconds(1));
228        assertTrue(testTx.getExpires().isAfter(previousExpiry));
229    }
230
231    @Test(expected = TransactionClosedException.class)
232    public void testUpdateExpiryOnExpired() {
233        testTx.expire();
234        final Instant previousExpiry = testTx.getExpires();
235        try {
236            testTx.updateExpiry(Duration.ofSeconds(1));
237        } finally {
238            assertEquals(testTx.getExpires(), previousExpiry);
239        }
240    }
241
242    @Test
243    public void testRefresh() {
244        final Instant previousExpiry = testTx.getExpires();
245        testTx.refresh();
246        assertTrue(testTx.getExpires().isAfter(previousExpiry));
247    }
248
249    @Test(expected = TransactionClosedException.class)
250    public void testRefreshOnExpired() {
251        testTx.expire();
252        final Instant previousExpiry = testTx.getExpires();
253        try {
254            testTx.refresh();
255        } finally {
256            assertEquals(testTx.getExpires(), previousExpiry);
257        }
258    }
259
260    @Test
261    public void testNewTransactionNotExpired() {
262        assertTrue(testTx.getExpires().isAfter(Instant.now()));
263    }
264
265    @Test(expected = TransactionClosedException.class)
266    public void operationsShouldFailWhenTxNotOpen() {
267        testTx.commit();
268        testTx.doInTx(() -> {
269            fail("This code should not be executed");
270        });
271    }
272
273    @Test
274    public void commitShouldWaitTillAllOperationsComplete() {
275        final var executor = Executors.newCachedThreadPool();
276        final var phaser = new Phaser(2);
277
278        executor.submit(() -> {
279            testTx.doInTx(() -> {
280                phaser.arriveAndAwaitAdvance();
281                try {
282                    TimeUnit.SECONDS.sleep(2);
283                } catch (InterruptedException e) {
284                    throw new RuntimeException(e);
285                }
286            });
287        });
288
289        phaser.arriveAndAwaitAdvance();
290        final var stopwatch = Stopwatch.createStarted();
291        testTx.commit();
292        final var duration = stopwatch.stop().elapsed().toMillis();
293
294        assertTrue(duration < 3000 && duration > 1000);
295    }
296
297    private void verifyRollback() throws PersistentStorageException {
298        verify(psSession).rollback();
299        verify(containmentIndex).rollbackTransaction(testTx);
300        verify(eventAccumulator).clearEvents(testTx);
301    }
302
303    @Test
304    public void testSuppressEvents() {
305        testTx.suppressEvents();
306        testTx.commit();
307        verify(eventAccumulator, times(0))
308                .emitEvents(testTx, null, null);
309    }
310
311    @Test
312    public void testNoEventSuppression() {
313        testTx.commit();
314        verify(eventAccumulator, times(1))
315                .emitEvents(testTx, null, null);
316    }
317}