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}