/*
* 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.clustered.client.internal.store.operations;
import org.ehcache.ValueSupplier;
import org.ehcache.clustered.client.TestTimeSource;
import org.ehcache.clustered.client.internal.store.ChainBuilder;
import org.ehcache.clustered.client.internal.store.ResolvedChain;
import org.ehcache.clustered.client.internal.store.operations.codecs.OperationsCodec;
import org.ehcache.clustered.common.internal.store.Chain;
import org.ehcache.clustered.common.internal.store.Element;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expiry;
import org.ehcache.impl.serialization.LongSerializer;
import org.ehcache.impl.serialization.StringSerializer;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.hamcrest.Matchers.is;
public class ChainResolverExpiryTest {
private static final OperationsCodec<Long, String> codec = new OperationsCodec<Long, String>(new LongSerializer(), new StringSerializer());
private static TestTimeSource timeSource = null;
@Before
public void initialSetup() {
timeSource = new TestTimeSource();
}
@Test
@SuppressWarnings("unchecked")
public void testGetExpiryForAccessIsIgnored() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(Duration.INFINITE);
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "One", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "Second", timeSource.getTimeMillis()));
Chain chain = getChainFromOperations(list);
ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
verify(expiry, times(0)).getExpiryForAccess(anyLong(), any(ValueSupplier.class));
verify(expiry, times(1)).getExpiryForCreation(anyLong(), anyString());
verify(expiry, times(1)).getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString());
assertThat(resolvedChain.isCompacted(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testGetExpiryForCreationIsInvokedOnlyOnce() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(Duration.INFINITE);
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "One", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "Second", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "Three", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "Four", timeSource.getTimeMillis()));
Chain chain = getChainFromOperations(list);
ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
InOrder inOrder = inOrder(expiry);
inOrder.verify(expiry, times(1)).getExpiryForCreation(anyLong(), anyString());
inOrder.verify(expiry, times(3)).getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString());
assertThat(resolvedChain.isCompacted(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testGetExpiryForCreationIsNotInvokedForReplacedChains() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(Duration.INFINITE);
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "Replaced", -10L));
list.add(new PutOperation<Long, String>(1L, "SecondAfterReplace", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "ThirdAfterReplace", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "FourthAfterReplace", timeSource.getTimeMillis()));
Chain chain = getChainFromOperations(list);
ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
verify(expiry, times(0)).getExpiryForCreation(anyLong(), anyString());
verify(expiry, times(3)).getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString());
assertThat(resolvedChain.isCompacted(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testGetExpiryForCreationIsInvokedAfterRemoveOperations() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(Duration.INFINITE);
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "Replaced", 10L));
list.add(new PutOperation<Long, String>(1L, "SecondAfterReplace", 3L));
list.add(new RemoveOperation<Long, String>(1L, 4L));
list.add(new PutOperation<Long, String>(1L, "FourthAfterReplace", 5L));
Chain replacedChain = getChainFromOperations(list);
ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(replacedChain, 1L, timeSource.getTimeMillis());
InOrder inOrder = inOrder(expiry);
verify(expiry, times(0)).getExpiryForAccess(anyLong(), any(ValueSupplier.class));
inOrder.verify(expiry, times(1)).getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString());
inOrder.verify(expiry, times(1)).getExpiryForCreation(anyLong(), anyString());
assertThat(resolvedChain.isCompacted(), is(true));
reset(expiry);
when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(Duration.INFINITE);
list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "One", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "Second", timeSource.getTimeMillis()));
list.add(new RemoveOperation<Long, String>(1L, timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "Four", timeSource.getTimeMillis()));
Chain chain = getChainFromOperations(list);
chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
inOrder = inOrder(expiry);
verify(expiry, times(0)).getExpiryForAccess(anyLong(), any(ValueSupplier.class));
inOrder.verify(expiry, times(1)).getExpiryForCreation(anyLong(), anyString());
inOrder.verify(expiry, times(1)).getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString());
inOrder.verify(expiry, times(1)).getExpiryForCreation(anyLong(), anyString());
assertThat(resolvedChain.isCompacted(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testNullGetExpiryForCreation() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForCreation(anyLong(), anyString())).thenReturn(null);
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "Replaced", 10L));
Chain chain = getChainFromOperations(list);
ResolvedChain resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
assertTrue(resolvedChain.getCompactedChain().isEmpty());
assertThat(resolvedChain.isCompacted(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testNullGetExpiryForUpdate() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString())).thenReturn(null);
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "Replaced", -10L));
list.add(new PutOperation<Long, String>(1L, "New", timeSource.getTimeMillis()));
Chain chain = getChainFromOperations(list);
ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
assertThat(resolvedChain.getResolvedResult(1L).getValue(), is("New"));
assertTrue(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).isExpiryAvailable());
assertThat(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).expirationTime(), is(10L));
assertThat(resolvedChain.isCompacted(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testGetExpiryForUpdateUpdatesExpirationTimeStamp() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString())).thenReturn(new Duration(2L, TimeUnit.MILLISECONDS));
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "Replaced", -10L));
list.add(new PutOperation<Long, String>(1L, "New", timeSource.getTimeMillis()));
Chain chain = getChainFromOperations(list);
ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
assertThat(resolvedChain.getResolvedResult(1L).getValue(), is("New"));
assertTrue(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).isExpiryAvailable());
assertThat(getOperationsListFromChain(resolvedChain.getCompactedChain()).get(0).expirationTime(), is(2L));
assertThat(resolvedChain.isCompacted(), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testExpiryThrowsException() {
Expiry<Long, String> expiry = mock(Expiry.class);
ChainResolver<Long, String> chainResolver = new ChainResolver<Long, String>(codec, expiry);
when(expiry.getExpiryForUpdate(anyLong(), any(ValueSupplier.class), anyString())).thenThrow(new RuntimeException("Test Update Expiry"));
when(expiry.getExpiryForCreation(anyLong(), anyString())).thenThrow(new RuntimeException("Test Create Expiry"));
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "One", -10L));
list.add(new PutOperation<Long, String>(1L, "Two", timeSource.getTimeMillis()));
Chain chain = getChainFromOperations(list);
ResolvedChain<Long, String> resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
assertThat(resolvedChain.getResolvedResult(1L), nullValue());
list = new ArrayList<Operation<Long, String>>();
list.add(new PutOperation<Long, String>(1L, "One", timeSource.getTimeMillis()));
list.add(new PutOperation<Long, String>(1L, "Two", timeSource.getTimeMillis()));
chain = getChainFromOperations(list);
resolvedChain = chainResolver.resolve(chain, 1L, timeSource.getTimeMillis());
assertThat(resolvedChain.getResolvedResult(1L), nullValue());
assertThat(resolvedChain.isCompacted(), is(true));
}
private Chain getChainFromOperations(List<Operation<Long, String>> operations) {
ChainBuilder chainBuilder = new ChainBuilder();
for(Operation<Long, String> operation: operations) {
chainBuilder = chainBuilder.add(codec.encode(operation));
}
return chainBuilder.build();
}
private List<Operation<Long, String>> getOperationsListFromChain(Chain chain) {
List<Operation<Long, String>> list = new ArrayList<Operation<Long, String>>();
for (Element element : chain) {
Operation<Long, String> operation = codec.decode(element.getPayload());
list.add(operation);
}
return list;
}
}