/* * Copyright Terracotta, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ehcache.transactions.xa.internal; import org.ehcache.internal.TestTimeSource; import org.ehcache.core.spi.store.AbstractValueHolder; import org.ehcache.core.spi.store.Store; import org.ehcache.core.spi.store.Store.RemoveStatus; import org.ehcache.transactions.xa.internal.commands.StoreEvictCommand; import org.ehcache.transactions.xa.internal.commands.StorePutCommand; import org.ehcache.transactions.xa.internal.commands.StoreRemoveCommand; import org.ehcache.transactions.xa.internal.journal.Journal; import org.ehcache.core.spi.store.Store.ReplaceStatus; import org.ehcache.transactions.xa.utils.TestXid; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyCollection; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; /** * @author Ludovic Orban */ public class XATransactionContextTest { @Mock private Store<Long, SoftLock<String>> underlyingStore; @Mock private Journal<Long> journal; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testSimpleCommands() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), null, null, timeSource, timeSource.getTimeMillis() + 30000); assertThat(xaTransactionContext.touched(1L), is(false)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(false)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L), is(nullValue())); assertThat(xaTransactionContext.oldValueOf(1L), is(nullValue())); assertThat(xaTransactionContext.newValueOf(1L), is(nullValue())); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("old", new XAValueHolder<String>("new", timeSource.getTimeMillis()))); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(true)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L).value(), equalTo("new")); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), equalTo("new")); xaTransactionContext.addCommand(1L, new StoreRemoveCommand<String>("old")); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(true)); assertThat(xaTransactionContext.updated(1L), is(false)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L), is(nullValue())); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), is(nullValue())); xaTransactionContext.addCommand(1L, new StoreEvictCommand<String>("old")); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(false)); assertThat(xaTransactionContext.evicted(1L), is(true)); assertThat(xaTransactionContext.newValueHolderOf(1L), is(nullValue())); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), is(nullValue())); } @Test public void testCommandsOverrideEachOther() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), null, null, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("old", new XAValueHolder<String>("new", timeSource.getTimeMillis()))); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(true)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L).value(), equalTo("new")); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), equalTo("new")); xaTransactionContext.addCommand(1L, new StoreRemoveCommand<String>("old")); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(true)); assertThat(xaTransactionContext.updated(1L), is(false)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L), is(nullValue())); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), is(nullValue())); xaTransactionContext.addCommand(1L, new StoreRemoveCommand<String>("old2")); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(true)); assertThat(xaTransactionContext.updated(1L), is(false)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L), is(nullValue())); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old2")); assertThat(xaTransactionContext.newValueOf(1L), is(nullValue())); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("old2", new XAValueHolder<String>("new2", timeSource.getTimeMillis()))); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(true)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L).value(), equalTo("new2")); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old2")); assertThat(xaTransactionContext.newValueOf(1L), equalTo("new2")); } @Test public void testEvictCommandCannotBeOverridden() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), null, null, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("old", new XAValueHolder<String>("new", timeSource.getTimeMillis()))); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(true)); assertThat(xaTransactionContext.evicted(1L), is(false)); assertThat(xaTransactionContext.newValueHolderOf(1L).value(), equalTo("new")); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), equalTo("new")); xaTransactionContext.addCommand(1L, new StoreEvictCommand<String>("old")); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(false)); assertThat(xaTransactionContext.evicted(1L), is(true)); assertThat(xaTransactionContext.newValueHolderOf(1L), is(nullValue())); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), is(nullValue())); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("old2", new XAValueHolder<String>("new2", timeSource.getTimeMillis()))); assertThat(xaTransactionContext.touched(1L), is(true)); assertThat(xaTransactionContext.removed(1L), is(false)); assertThat(xaTransactionContext.updated(1L), is(false)); assertThat(xaTransactionContext.evicted(1L), is(true)); assertThat(xaTransactionContext.newValueHolderOf(1L), is(nullValue())); assertThat(xaTransactionContext.oldValueOf(1L), equalTo("old")); assertThat(xaTransactionContext.newValueOf(1L), is(nullValue())); } @Test public void testHasTimedOut() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), null, null, timeSource, timeSource.getTimeMillis() + 30000); assertThat(xaTransactionContext.hasTimedOut(), is(false)); timeSource.advanceTime(30000); assertThat(xaTransactionContext.hasTimedOut(), is(true)); } @Test public void testPrepareReadOnly() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); assertThat(xaTransactionContext.prepare(), is(0)); verify(journal, times(1)).saveInDoubt(eq(new TransactionId(new TestXid(0, 0))), eq(Collections.<Long>emptySet())); verify(journal, times(0)).saveCommitted(eq(new TransactionId(new TestXid(0, 0))), anyBoolean()); verify(journal, times(1)).saveRolledBack(eq(new TransactionId(new TestXid(0, 0))), eq(false)); } @Test @SuppressWarnings("unchecked") public void testPrepare() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>(null, new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); xaTransactionContext.addCommand(3L, new StoreEvictCommand<String>("three")); Store.ValueHolder<SoftLock<String>> mockValueHolder = mock(Store.ValueHolder.class); when(mockValueHolder.value()).thenReturn(new SoftLock<String>(null, "two", null)); when(underlyingStore.get(eq(2L))).thenReturn(mockValueHolder); when(underlyingStore.replace(eq(2L), eq(new SoftLock<String>(null, "two", null)), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null)))).thenReturn(ReplaceStatus.HIT); final AtomicReference<Collection<Long>> savedInDoubt = new AtomicReference<Collection<Long>>(); // doAnswer is required to make a copy of the keys collection because xaTransactionContext.prepare() clears it before the verify(journal, times(1)).saveInDoubt(...) assertion can be made. // See: http://stackoverflow.com/questions/17027368/mockito-what-if-argument-passed-to-mock-is-modified doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Collection<Long> o = (Collection<Long>) invocation.getArguments()[1]; savedInDoubt.set(new HashSet<Long>(o)); return null; } }).when(journal).saveInDoubt(eq(new TransactionId(new TestXid(0, 0))), anyCollection()); assertThat(xaTransactionContext.prepare(), is(3)); Assert.assertThat(savedInDoubt.get(), containsInAnyOrder(1L, 2L, 3L)); verify(journal, times(1)).saveInDoubt(eq(new TransactionId(new TestXid(0, 0))), anyCollection()); verify(journal, times(0)).saveCommitted(eq(new TransactionId(new TestXid(0, 0))), anyBoolean()); verify(journal, times(0)).saveRolledBack(eq(new TransactionId(new TestXid(0, 0))), anyBoolean()); verify(underlyingStore, times(0)).get(1L); verify(underlyingStore, times(1)).putIfAbsent(eq(1L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), null, new XAValueHolder<String>("un", timeSource.getTimeMillis())))); verify(underlyingStore, times(0)).get(2L); verify(underlyingStore, times(1)).replace(eq(2L), eq(new SoftLock<String>(null, "two", null)), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null))); verify(underlyingStore, times(0)).get(3L); verify(underlyingStore, times(1)).remove(eq(3L)); } @Test public void testCommitNotPreparedInFlightThrows() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StorePutCommand<String>("two", new XAValueHolder<String>("deux", timeSource.getTimeMillis()))); @SuppressWarnings("unchecked") Store.ValueHolder<SoftLock<String>> mockValueHolder = mock(Store.ValueHolder.class); when(mockValueHolder.value()).thenReturn(new SoftLock<String>(null, "two", null)); when(underlyingStore.get(eq(2L))).thenReturn(mockValueHolder); try { xaTransactionContext.commit(false); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException ise) { // expected } } @Test @SuppressWarnings("unchecked") public void testCommit() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); xaTransactionContext.addCommand(3L, new StoreEvictCommand<String>("three")); Store.ValueHolder<SoftLock<String>> mockValueHolder1 = mock(Store.ValueHolder.class); when(mockValueHolder1.value()).thenReturn(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); when(underlyingStore.get(eq(1L))).thenReturn(mockValueHolder1); Store.ValueHolder<SoftLock<String>> mockValueHolder2 = mock(Store.ValueHolder.class); when(mockValueHolder2.value()).thenReturn(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null)); when(underlyingStore.get(eq(2L))).thenReturn(mockValueHolder2); Store.ValueHolder<SoftLock<String>> mockValueHolder3 = mock(Store.ValueHolder.class); when(mockValueHolder3.value()).thenReturn(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "three", null)); when(underlyingStore.get(eq(3L))).thenReturn(mockValueHolder3); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(true); when(journal.getInDoubtKeys(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(Arrays.asList(1L, 2L, 3L)); when(underlyingStore.replace(any(Long.class), any(SoftLock.class), any(SoftLock.class))).thenReturn(ReplaceStatus.MISS_NOT_PRESENT); when(underlyingStore.remove(any(Long.class), any(SoftLock.class))).thenReturn(RemoveStatus.KEY_MISSING); xaTransactionContext.commit(false); verify(journal, times(1)).saveCommitted(eq(new TransactionId(new TestXid(0, 0))), eq(false)); verify(journal, times(0)).saveRolledBack(eq(new TransactionId(new TestXid(0, 0))), anyBoolean()); verify(journal, times(0)).saveInDoubt(eq(new TransactionId(new TestXid(0, 0))), anyCollection()); verify(underlyingStore, times(1)).get(1L); verify(underlyingStore, times(1)).replace(eq(1L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))), eq(new SoftLock<String>(null, "un", null))); verify(underlyingStore, times(1)).get(2L); verify(underlyingStore, times(1)).remove(eq(2L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null))); verify(underlyingStore, times(1)).get(3L); verify(underlyingStore, times(1)).remove(eq(3L)); } @Test public void testCommitInOnePhasePreparedThrows() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(true); try { xaTransactionContext.commitInOnePhase(); fail("expected IllegalStateException"); } catch (IllegalStateException ise) { // expected } } @Test @SuppressWarnings("unchecked") public void testCommitInOnePhase() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>(null, new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); xaTransactionContext.addCommand(3L, new StoreEvictCommand<String>("three")); Store.ValueHolder<SoftLock<String>> mockValueHolder = mock(Store.ValueHolder.class); when(mockValueHolder.value()).thenReturn(new SoftLock<String>(null, "two", null)); when(underlyingStore.get(eq(2L))).thenReturn(mockValueHolder); final AtomicReference<Collection<Long>> savedInDoubtCollectionRef = new AtomicReference<Collection<Long>>(); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { savedInDoubtCollectionRef.set(new HashSet<Long>((Collection<Long>) invocation.getArguments()[1])); return null; } }).when(journal).saveInDoubt(eq(new TransactionId(new TestXid(0, 0))), anyCollection()); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return savedInDoubtCollectionRef.get() != null; } }); when(journal.getInDoubtKeys(eq(new TransactionId(new TestXid(0, 0))))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return savedInDoubtCollectionRef.get(); } }); final AtomicReference<SoftLock> softLock1Ref = new AtomicReference<SoftLock>(); when(underlyingStore.get(eq(1L))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return softLock1Ref.get() == null ? null : new AbstractValueHolder(-1, -1) { @Override public Object value() { return softLock1Ref.get(); } @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } }; } }); when(underlyingStore.putIfAbsent(eq(1L), isA(SoftLock.class))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { softLock1Ref.set((SoftLock) invocation.getArguments()[1]); return null; } }); when(underlyingStore.replace(eq(1L), isA(SoftLock.class), isA(SoftLock.class))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { if (softLock1Ref.get() != null) { return ReplaceStatus.HIT; } return ReplaceStatus.MISS_PRESENT; } }); final AtomicReference<SoftLock> softLock2Ref = new AtomicReference<SoftLock>(new SoftLock(null, "two", null)); when(underlyingStore.get(eq(2L))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return softLock2Ref.get() == null ? null : new AbstractValueHolder(-1, -1) { @Override public Object value() { return softLock2Ref.get(); } @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } }; } }); when(underlyingStore.replace(eq(2L), isA(SoftLock.class), isA(SoftLock.class))).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { softLock2Ref.set((SoftLock) invocation.getArguments()[2]); return ReplaceStatus.HIT; } }); when(underlyingStore.remove(any(Long.class), any(SoftLock.class))).thenReturn(RemoveStatus.REMOVED); xaTransactionContext.commitInOnePhase(); Assert.assertThat(savedInDoubtCollectionRef.get(), containsInAnyOrder(1L, 2L, 3L)); verify(journal, times(1)).saveCommitted(eq(new TransactionId(new TestXid(0, 0))), eq(false)); verify(journal, times(0)).saveRolledBack(eq(new TransactionId(new TestXid(0, 0))), anyBoolean()); verify(journal, times(1)).saveInDoubt(eq(new TransactionId(new TestXid(0, 0))), anyCollection()); verify(underlyingStore, times(1)).putIfAbsent(eq(1L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), null, new XAValueHolder<String>("un", timeSource.getTimeMillis())))); verify(underlyingStore, times(1)).replace(eq(2L), eq(new SoftLock<String>(null, "two", null)), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null))); verify(underlyingStore, times(1)).remove(eq(3L)); verify(underlyingStore, times(1)).get(1L); verify(underlyingStore, times(1)).replace(eq(1L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), null, new XAValueHolder<String>("un", timeSource.getTimeMillis()))), eq(new SoftLock<String>(null, "un", null))); verify(underlyingStore, times(1)).get(2L); verify(underlyingStore, times(1)).remove(eq(2L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null))); verify(underlyingStore, times(1)).get(3L); verify(underlyingStore, times(1)).remove(eq(3L)); } @Test public void testRollbackPhase1() throws Exception { TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); xaTransactionContext.rollback(false); verifyNoMoreInteractions(underlyingStore); } @Test @SuppressWarnings("unchecked") public void testRollbackPhase2() throws Exception { final TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(true); when(journal.getInDoubtKeys(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(Arrays.asList(1L, 2L)); when(underlyingStore.get(1L)).thenReturn(new AbstractValueHolder<SoftLock<String>>(-1, -1) { @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } @Override public SoftLock<String> value() { return new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "one", new XAValueHolder<String>("un", timeSource.getTimeMillis())); } }); when(underlyingStore.get(2L)).thenReturn(new AbstractValueHolder<SoftLock<String>>(-1, -1) { @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } @Override public SoftLock<String> value() { return new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null); } }); when(underlyingStore.replace(any(Long.class), any(SoftLock.class), any(SoftLock.class))).thenReturn(ReplaceStatus.HIT); xaTransactionContext.rollback(false); verify(underlyingStore, times(1)).get(1L); verify(underlyingStore, times(1)).replace(eq(1L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))), eq(new SoftLock<String>(null, "one", null))); verify(underlyingStore, times(1)).get(2L); verify(underlyingStore, times(1)).replace(eq(2L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null)), eq(new SoftLock<String>(null, "two", null))); } @Test public void testCommitInOnePhaseTimeout() throws Exception { final TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); timeSource.advanceTime(30000); try { xaTransactionContext.commitInOnePhase(); fail("expected TransactionTimeoutException"); } catch (XATransactionContext.TransactionTimeoutException tte) { // expected } } @Test public void testPrepareTimeout() throws Exception { final TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); timeSource.advanceTime(30000); try { xaTransactionContext.prepare(); fail("expected TransactionTimeoutException"); } catch (XATransactionContext.TransactionTimeoutException tte) { // expected } } @Test @SuppressWarnings("unchecked") public void testCommitConflictsEvicts() throws Exception { final TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(true); when(journal.getInDoubtKeys(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(Arrays.asList(1L, 2L)); when(underlyingStore.get(eq(1L))).thenReturn(new AbstractValueHolder<SoftLock<String>>(-1, -1) { @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } @Override public SoftLock<String> value() { return new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old1", new XAValueHolder<String>("new1", timeSource.getTimeMillis())); } }); when(underlyingStore.get(eq(2L))).thenReturn(new AbstractValueHolder<SoftLock<String>>(-1, -1) { @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } @Override public SoftLock<String> value() { return new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old2", null); } }); when(underlyingStore.replace(any(Long.class), any(SoftLock.class), any(SoftLock.class))).thenReturn(ReplaceStatus.MISS_NOT_PRESENT); when(underlyingStore.remove(any(Long.class), any(SoftLock.class))).thenReturn(RemoveStatus.KEY_MISSING); xaTransactionContext.commit(false); verify(underlyingStore, times(1)).replace(eq(1L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old1", new XAValueHolder<String>("new1", timeSource.getTimeMillis()))), eq(new SoftLock<String>(null, "new1", null))); verify(underlyingStore, times(1)).remove(eq(1L)); verify(underlyingStore, times(1)).remove(eq(2L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old2", null))); verify(underlyingStore, times(1)).remove(eq(2L)); } @Test @SuppressWarnings("unchecked") public void testPrepareConflictsEvicts() throws Exception { final TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); xaTransactionContext.addCommand(1L, new StorePutCommand<String>("one", new XAValueHolder<String>("un", timeSource.getTimeMillis()))); xaTransactionContext.addCommand(2L, new StoreRemoveCommand<String>("two")); when(underlyingStore.replace(any(Long.class), any(SoftLock.class), any(SoftLock.class))).thenReturn(ReplaceStatus.MISS_NOT_PRESENT); xaTransactionContext.prepare(); verify(underlyingStore).replace(eq(1L), eq(new SoftLock<String>(null, "one", null)), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "one", new XAValueHolder<String>("un", timeSource.getTimeMillis())))); verify(underlyingStore).remove(1L); verify(underlyingStore).replace(eq(2L), eq(new SoftLock<String>(null, "two", null)), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "two", null))); verify(underlyingStore).remove(2L); } @Test @SuppressWarnings("unchecked") public void testRollbackConflictsEvicts() throws Exception { final TestTimeSource timeSource = new TestTimeSource(); XATransactionContext<Long, String> xaTransactionContext = new XATransactionContext<Long, String>(new TransactionId(new TestXid(0, 0)), underlyingStore, journal, timeSource, timeSource.getTimeMillis() + 30000); when(journal.isInDoubt(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(true); when(journal.getInDoubtKeys(eq(new TransactionId(new TestXid(0, 0))))).thenReturn(Arrays.asList(1L, 2L)); when(underlyingStore.get(eq(1L))).thenReturn(new AbstractValueHolder<SoftLock<String>>(-1, -1) { @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } @Override public SoftLock<String> value() { return new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old1", new XAValueHolder<String>("new1", timeSource.getTimeMillis())); } }); when(underlyingStore.get(eq(2L))).thenReturn(new AbstractValueHolder<SoftLock<String>>(-1, -1) { @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } @Override public SoftLock<String> value() { return new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old2", null); } }); when(underlyingStore.replace(any(Long.class), any(SoftLock.class), any(SoftLock.class))).thenReturn(ReplaceStatus.MISS_NOT_PRESENT); when(underlyingStore.remove(any(Long.class), any(SoftLock.class))).thenReturn(RemoveStatus.KEY_MISSING); xaTransactionContext.rollback(false); verify(underlyingStore, times(1)).replace(eq(1L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old1", new XAValueHolder<String>("new1", timeSource.getTimeMillis()))), eq(new SoftLock<String>(null, "old1", null))); verify(underlyingStore, times(1)).remove(eq(1L)); verify(underlyingStore, times(1)).replace(eq(2L), eq(new SoftLock<String>(new TransactionId(new TestXid(0, 0)), "old2", null)), eq(new SoftLock<String>(null, "old2", null))); verify(underlyingStore, times(1)).remove(eq(2L)); } private static <T> Matcher<Collection<T>> isACollectionThat( final Matcher<Iterable<? extends T>> matcher) { return new BaseMatcher<Collection<T>>() { @Override public boolean matches(Object item) { return matcher.matches(item); } @Override public void describeTo(Description description) { matcher.describeTo(description); } }; } }