/*
* 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.entryprocessor;
import com.hazelcast.cache.BackupAwareEntryProcessor;
import com.hazelcast.cache.ICache;
import com.hazelcast.cache.impl.CacheService;
import com.hazelcast.cache.impl.HazelcastServerCachingProvider;
import com.hazelcast.cache.impl.ICacheRecordStore;
import com.hazelcast.cache.impl.record.CacheRecord;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.Partition;
import com.hazelcast.core.PartitionService;
import com.hazelcast.instance.HazelcastInstanceImpl;
import com.hazelcast.instance.HazelcastInstanceProxy;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.spi.serialization.SerializationService;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastSerialClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.TestHazelcastInstanceFactory;
import com.hazelcast.test.annotation.QuickTest;
import org.junit.AfterClass;
import org.junit.BeforeClass;
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.CompleteConfiguration;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.cache.processor.MutableEntry;
import javax.cache.spi.CachingProvider;
import java.io.Serializable;
import java.lang.reflect.Field;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@RunWith(HazelcastSerialClassRunner.class)
@Category(QuickTest.class)
public class CacheEntryProcessorTest extends HazelcastTestSupport {
private static final int ASSERTION_TIMEOUT_SECONDS = 300;
private static TestHazelcastInstanceFactory factory;
private static HazelcastInstance node1;
private static HazelcastInstance node2;
private static CacheService cacheServiceOnNode1;
private static CacheService cacheServiceOnNode2;
private static SerializationService serializationService;
@BeforeClass
public static void setUp() throws Exception {
setUpInternal();
}
@AfterClass
public static void tearDown() {
factory.shutdownAll();
}
/**
* If there is not any implemented backup entry processor,
* we are only sending the result of execution to the backup node.
*/
@Test
public void whenBackupEntryProcessor_isNotImplemented() throws Exception {
EntryProcessor<Integer, String, Void> entryProcessor = new SimpleEntryProcessor();
executeTestInternal(entryProcessor);
}
@Test
public void whenBackupEntryProcessor_isImplemented() throws Exception {
EntryProcessor<Integer, String, Void> entryProcessor = new CustomBackupAwareEntryProcessor();
executeTestInternal(entryProcessor);
}
@Test
public void whenBackupEntryProcessor_isSame_withPrimaryEntryProcessor() throws Exception {
EntryProcessor<Integer, String, Void> entryProcessor = new SimpleBackupAwareEntryProcessor();
executeTestInternal(entryProcessor);
}
@Test
public void whenBackupEntryProcessor_isNull() throws Exception {
EntryProcessor<Integer, String, Void> entryProcessor = new NullBackupAwareEntryProcessor();
executeTestInternal(entryProcessor);
}
@Test
public void removeRecordWithEntryProcessor() {
final int ENTRY_COUNT = 10;
CachingProvider cachingProvider = HazelcastServerCachingProvider.createCachingProvider(node1);
CacheManager cacheManager = cachingProvider.getCacheManager();
CompleteConfiguration<Integer, String> cacheConfig =
new MutableConfiguration<Integer, String>()
.setTypes(Integer.class, String.class);
ICache<Integer, String> cache = cacheManager.createCache("MyCache", cacheConfig).unwrap(ICache.class);
for (int i = 0; i < ENTRY_COUNT; i++) {
cache.put(i * 1000, "Value-" + (i * 1000));
}
assertEquals(ENTRY_COUNT, cache.size());
for (int i = 0; i < ENTRY_COUNT; i++) {
if (i % 2 == 0) {
cache.invoke(i * 1000, new RemoveRecordEntryProcessor());
}
}
assertEquals(ENTRY_COUNT / 2, cache.size());
}
private void executeTestInternal(EntryProcessor<Integer, String, Void> entryProcessor) {
final String cacheName = randomString();
final Integer key = 1;
executeEntryProcessor(key, entryProcessor, cacheName);
assertKeyExistsInCache("Foo", key, cacheName, cacheServiceOnNode1);
assertKeyExistsInCache("Foo", key, cacheName, cacheServiceOnNode2);
}
private void assertKeyExistsInCache(final String expectedValue, final Integer key,
final String cacheName, final CacheService cacheService) {
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
final Data dataKey = serializationService.toData(key);
final PartitionService partitionService = node1.getPartitionService();
final Partition partition = partitionService.getPartition(key);
final int partitionId = partition.getPartitionId();
final ICacheRecordStore recordStore = getRecordStore(cacheService, cacheName, partitionId);
final CacheRecord record = recordStore.getRecord(dataKey);
assertNotNull("Backups are not done yet!!!", record);
final Object value = serializationService.toObject(record.getValue());
assertEquals(expectedValue, value);
}
}, ASSERTION_TIMEOUT_SECONDS);
}
public static class SimpleEntryProcessor
implements EntryProcessor<Integer, String, Void>, Serializable {
private static final long serialVersionUID = -396575576353368113L;
@Override
public Void process(MutableEntry<Integer, String> entry, Object... arguments)
throws EntryProcessorException {
entry.setValue("Foo");
return null;
}
}
public static class SimpleBackupAwareEntryProcessor
implements BackupAwareEntryProcessor<Integer, String, Void>, Serializable {
private static final long serialVersionUID = -5274605583423489718L;
@Override
public Void process(MutableEntry<Integer, String> entry, Object... arguments)
throws EntryProcessorException {
entry.setValue("Foo");
return null;
}
@Override
public EntryProcessor<Integer, String, Void> createBackupEntryProcessor() {
return this;
}
}
public static class NullBackupAwareEntryProcessor
implements BackupAwareEntryProcessor<Integer, String, Void>, Serializable {
private static final long serialVersionUID = -8423196656316041614L;
@Override
public Void process(MutableEntry<Integer, String> entry, Object... arguments)
throws EntryProcessorException {
entry.setValue("Foo");
return null;
}
@Override
public EntryProcessor<Integer, String, Void> createBackupEntryProcessor() {
return null;
}
}
public static class CustomBackupAwareEntryProcessor
implements BackupAwareEntryProcessor<Integer, String, Void>, Serializable {
private static final long serialVersionUID = 3409663318028125754L;
@Override
public Void process(MutableEntry<Integer, String> entry, Object... arguments)
throws EntryProcessorException {
entry.setValue("Foo");
return null;
}
@Override
public EntryProcessor<Integer, String, Void> createBackupEntryProcessor() {
return new BackupEntryProcessor();
}
}
public static class BackupEntryProcessor
implements EntryProcessor<Integer, String, Void>, Serializable {
private static final long serialVersionUID = -6376894786246368848L;
@Override
public Void process(MutableEntry<Integer, String> entry, Object... arguments)
throws EntryProcessorException {
entry.setValue("Foo");
return null;
}
}
public static class RemoveRecordEntryProcessor
implements EntryProcessor<Integer, String, Void>, Serializable {
@Override
public Void process(MutableEntry<Integer, String> entry, Object... arguments)
throws EntryProcessorException {
entry.remove();
return null;
}
}
private void executeEntryProcessor(Integer key, EntryProcessor<Integer, String, Void> entryProcessor, String cacheName) {
CachingProvider cachingProvider = HazelcastServerCachingProvider.createCachingProvider(node1);
CacheManager cacheManager = cachingProvider.getCacheManager();
CompleteConfiguration<Integer, String> config =
new MutableConfiguration<Integer, String>()
.setTypes(Integer.class, String.class);
Cache<Integer, String> cache = cacheManager.createCache(cacheName, config);
cache.invoke(key, entryProcessor);
}
private ICacheRecordStore getRecordStore(CacheService cacheService, String cacheName, int partitionId) {
try {
return cacheService.getOrCreateRecordStore("/hz/" + cacheName, partitionId);
} catch (Exception e) {
fail("CacheRecordStore not yet initialized!!!");
}
return null;
}
private static void setUpInternal() throws NoSuchFieldException, IllegalAccessException {
factory = new TestHazelcastInstanceFactory(2);
node1 = factory.newHazelcastInstance();
node2 = factory.newHazelcastInstance();
Field original = HazelcastInstanceProxy.class.getDeclaredField("original");
original.setAccessible(true);
HazelcastInstanceImpl impl1 = (HazelcastInstanceImpl) original.get(node1);
HazelcastInstanceImpl impl2 = (HazelcastInstanceImpl) original.get(node2);
cacheServiceOnNode1 = impl1.node.getNodeEngine().getService(CacheService.SERVICE_NAME);
cacheServiceOnNode2 = impl2.node.getNodeEngine().getService(CacheService.SERVICE_NAME);
serializationService = impl1.node.getNodeEngine().getSerializationService();
}
}