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