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