/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.cache;
import com.hazelcast.cache.impl.HazelcastServerCachingProvider;
import com.hazelcast.config.CacheConfig;
import com.hazelcast.config.Config;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.test.HazelcastParallelClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.TestHazelcastInstanceFactory;
import com.hazelcast.test.annotation.ParallelTest;
import com.hazelcast.test.annotation.QuickTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.configuration.FactoryBuilder;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriter;
import javax.cache.integration.CacheWriterException;
import javax.cache.integration.CompletionListener;
import javax.cache.spi.CachingProvider;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelTest.class})
public class CacheReadWriteThroughTest extends HazelcastTestSupport {
protected TestHazelcastInstanceFactory factory;
protected HazelcastInstance hz;
protected CachingProvider cachingProvider;
protected Config createConfig() {
return new Config();
}
protected CachingProvider createCachingProvider(HazelcastInstance instance) {
return HazelcastServerCachingProvider.createCachingProvider(instance);
}
protected TestHazelcastInstanceFactory createInstanceFactory(int instanceCount) {
return createHazelcastInstanceFactory(instanceCount);
}
protected HazelcastInstance getInstance() {
// Create master instance
HazelcastInstance instance = factory.newHazelcastInstance(createConfig());
// Create second instance
factory.newHazelcastInstance(createConfig());
return instance;
}
@Before
public void setup() {
factory = createInstanceFactory(2);
hz = getInstance();
cachingProvider = createCachingProvider(hz);
}
protected void onTearDown() {
}
@After
public void tearDown() {
onTearDown();
cachingProvider.close();
factory.shutdownAll();
}
protected CacheConfig<Integer, Integer> createCacheConfig() {
CacheConfig cacheConfig = new CacheConfig<Integer, Integer>();
cacheConfig.setTypes(Integer.class, Integer.class);
return cacheConfig;
}
@Test
public void test_getAll_readThrough() throws Exception {
final String cacheName = randomName();
CacheManager cacheManager = cachingProvider.getCacheManager();
assertNotNull(cacheManager);
assertNull(cacheManager.getCache(cacheName));
CacheConfig<Integer, Integer> config = createCacheConfig();
config.setReadThrough(true);
config.setCacheLoaderFactory(FactoryBuilder.factoryOf(new GetAllAsyncCacheLoader(false)));
Cache<Integer, Integer> cache = cacheManager.createCache(cacheName, config);
assertNotNull(cache);
Set<Integer> keys = new HashSet<Integer>();
for (int i = 0; i < 150; i++) {
keys.add(i);
}
Map<Integer, Integer> loaded = cache.getAll(keys);
assertEquals(100, loaded.size());
}
private void loadAll_readThrough(boolean throwError) throws Exception {
final String cacheName = randomName();
CacheManager cacheManager = cachingProvider.getCacheManager();
assertNotNull(cacheManager);
assertNull(cacheManager.getCache(cacheName));
CacheConfig<Integer, Integer> config = createCacheConfig();
config.setReadThrough(true);
config.setCacheLoaderFactory(FactoryBuilder.factoryOf(new GetAllAsyncCacheLoader(throwError)));
Cache<Integer, Integer> cache = cacheManager.createCache(cacheName, config);
assertNotNull(cache);
Set<Integer> keys = new HashSet<Integer>();
for (int i = 0; i < 150; i++) {
keys.add(i);
}
final CountDownLatch latch = new CountDownLatch(1);
cache.loadAll(keys, false, new CompletionListener() {
@Override
public void onCompletion() {
latch.countDown();
}
@Override
public void onException(Exception e) {
e.printStackTrace();
latch.countDown();
}
});
assertOpenEventually(latch);
if (!throwError) {
assertEquals(100, cache.unwrap(ICache.class).size());
}
}
@Test
public void test_loadAll_readThrough() throws Exception {
loadAll_readThrough(false);
}
@Test
public void test_loadAll_readThrough_whenThereIsAnThrowableButNotAnException() throws Exception {
loadAll_readThrough(true);
}
public static class GetAllAsyncCacheLoader implements CacheLoader<Integer, Integer>, Serializable {
private final boolean throwError;
private GetAllAsyncCacheLoader(boolean throwError) {
this.throwError = throwError;
}
@Override
public Integer load(Integer key) {
return key != null && key < 100 ? key : null;
}
@Override
public Map<Integer, Integer> loadAll(Iterable<? extends Integer> keys) throws CacheLoaderException {
if (throwError) {
return new HashMap<Integer, Integer>() {
@Override
public Integer get(Object key) {
throw new IllegalAccessError("Bazinga !!!");
}
};
}
Map<Integer, Integer> result = new HashMap<Integer, Integer>();
for (Integer key : keys) {
Integer value = load(key);
if (value != null) {
result.put(key, value);
}
}
return result;
}
}
private void do_putAsAdd_writeThrough(boolean acceptAll) {
final int ENTRY_COUNT = 100;
final String cacheName = randomName();
CacheManager cacheManager = cachingProvider.getCacheManager();
assertNotNull(cacheManager);
assertNull(cacheManager.getCache(cacheName));
PutCacheWriter putCacheWriter;
if (acceptAll) {
putCacheWriter = new PutCacheWriter();
} else {
putCacheWriter = new PutCacheWriter(new ModValueChecker(ENTRY_COUNT / 10));
}
CacheConfig<Integer, Integer> config = createCacheConfig();
config.setWriteThrough(true);
config.setCacheWriterFactory(FactoryBuilder.factoryOf(putCacheWriter));
ICache<Integer, Integer> cache = cacheManager.createCache(cacheName, config).unwrap(ICache.class);
assertNotNull(cache);
List<Integer> bannedKeys = new ArrayList<Integer>();
for (int i = 0; i < ENTRY_COUNT; i++) {
try {
cache.put(i, i);
} catch (CacheWriterException e) {
bannedKeys.add(i);
}
}
assertEquals(ENTRY_COUNT - bannedKeys.size(), cache.size());
for (Integer bannedKey : bannedKeys) {
assertNull(cache.get(bannedKey));
}
}
@Test
public void test_putAsAdd_writeThrough_allKeysAccepted() {
do_putAsAdd_writeThrough(true);
}
@Test
public void test_putAsAdd_writeThrough_someKeysBanned() {
do_putAsAdd_writeThrough(false);
}
private void do_putAsUpdate_writeThrough(boolean acceptAll) {
final int ENTRY_COUNT = 100;
final String cacheName = randomName();
CacheManager cacheManager = cachingProvider.getCacheManager();
assertNotNull(cacheManager);
assertNull(cacheManager.getCache(cacheName));
PutCacheWriter putCacheWriter;
if (acceptAll) {
putCacheWriter = new PutCacheWriter();
} else {
putCacheWriter = new PutCacheWriter(new MaxValueChecker(ENTRY_COUNT));
}
CacheConfig<Integer, Integer> config = createCacheConfig();
config.setWriteThrough(true);
config.setCacheWriterFactory(FactoryBuilder.factoryOf(putCacheWriter));
ICache<Integer, Integer> cache = cacheManager.createCache(cacheName, config).unwrap(ICache.class);
assertNotNull(cache);
for (int i = 0; i < ENTRY_COUNT; i++) {
cache.put(i, i);
}
assertEquals(ENTRY_COUNT, cache.size());
Map<Integer, Integer> keysAndValues = new HashMap<Integer, Integer>();
for (int i = 0; i < ENTRY_COUNT; i++) {
int oldValue = cache.get(i);
int newValue = ENTRY_COUNT + i;
try {
cache.put(i, newValue);
keysAndValues.put(i, newValue);
} catch (CacheWriterException e) {
keysAndValues.put(i, oldValue);
}
}
assertEquals(ENTRY_COUNT, cache.size());
for (int i = 0; i < ENTRY_COUNT; i++) {
Integer expectedValue = keysAndValues.get(i);
assertNotNull(expectedValue);
Integer actualValue = cache.get(i);
assertNotNull(actualValue);
assertEquals(expectedValue, actualValue);
}
}
@Test
public void test_putAsUpdate_writeThrough_allKeysAccepted() {
do_putAsUpdate_writeThrough(true);
}
@Test
public void test_putAsUpdate_writeThrough_someKeysBanned() {
do_putAsUpdate_writeThrough(false);
}
public static class PutCacheWriter implements CacheWriter<Integer, Integer>, Serializable {
private final ValueChecker valueChecker;
private PutCacheWriter() {
this.valueChecker = null;
}
private PutCacheWriter(ValueChecker valueChecker) {
this.valueChecker = valueChecker;
}
private boolean isAcceptableValue(int value) {
if (valueChecker == null) {
return true;
}
return valueChecker.isAcceptableValue(value);
}
@Override
public void write(Cache.Entry<? extends Integer, ? extends Integer> entry) throws CacheWriterException {
Integer value = entry.getValue();
if (!isAcceptableValue(value)) {
throw new CacheWriterException("Value is invalid: " + value);
}
}
@Override
public void writeAll(Collection<Cache.Entry<? extends Integer, ? extends Integer>> entries) throws CacheWriterException {
}
@Override
public void delete(Object key) throws CacheWriterException {
}
@Override
public void deleteAll(Collection<?> keys) throws CacheWriterException {
}
}
public interface ValueChecker extends Serializable {
boolean isAcceptableValue(int value);
}
public static class ModValueChecker implements ValueChecker {
private final int mod;
private ModValueChecker(int mod) {
this.mod = mod;
}
@Override
public boolean isAcceptableValue(int value) {
return value % mod != 0;
}
}
public static class MaxValueChecker implements ValueChecker {
private final int maxValue;
private MaxValueChecker(int maxValue) {
this.maxValue = maxValue;
}
@Override
public boolean isAcceptableValue(int value) {
return value < maxValue;
}
}
}