/*
* #%L
* Nazgul Project: nazgul-core-cache-impl-hazelcast
* %%
* Copyright (C) 2010 - 2017 jGuru Europe AB
* %%
* Licensed under the jGuru Europe AB license (the "License"), based
* on Apache License, Version 2.0; you may not use this file except
* in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt
*
* 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.
* #L%
*
*/
package se.jguru.nazgul.core.cache.impl.hazelcast;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MaxSizeConfig;
import com.hazelcast.core.EntryAdapter;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryListener;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.core.MapEvent;
import com.hazelcast.map.listener.EntryAddedListener;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.jguru.nazgul.core.cache.impl.hazelcast.clients.HazelcastCacheMember;
import se.jguru.nazgul.core.cache.impl.hazelcast.helpers.DebugCacheListener;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB
*/
public class AutonomousActionTest extends AbstractHazelcastCacheTest {
private static final String LOGBACK_CONFIGURATION_PATH = "config/logging/logback-test.xml";
private static final Logger log = LoggerFactory.getLogger(AutonomousActionTest.class);
// Shared state
final String quickEvictionMapId = "quickEvictionMap";
final String key = "key";
final String value = "value";
final String quickEvictConfigFile = "config/hazelcast/QuickEvictionConfig.xml";
@BeforeClass
public static void initialize() {
configureLogging(LOGBACK_CONFIGURATION_PATH);
}
@Test
public void validateQuickEvictionConfigurationFile() {
// Assemble
final int expectedMaxSize = 4;
final Config config = HazelcastCacheMember.readConfigFile(quickEvictConfigFile);
// Act
final MapConfig mapConfig = config.getMapConfig(quickEvictionMapId);
final int maxSize = mapConfig.getMaxSizeConfig().getSize();
// Assert
Assert.assertEquals("The configuration file is corrupt. The map '" + quickEvictionMapId + "' must be defined.",
quickEvictionMapId, mapConfig.getName());
Assert.assertEquals("Corrupt configuration file. The map '" + quickEvictionMapId
+ "' must define max-size as " + expectedMaxSize + ".", expectedMaxSize, maxSize);
}
@Ignore("As of version 3.7, eviction mechanism changed. It uses a probabilistic algorithm based on sampling.")
@Test
public void validateEvictionInLifecycle() throws InterruptedException {
// Assemble
final Config config = HazelcastCacheMember.readConfigFile(quickEvictConfigFile);
final MapConfig mapConfig = config.getMapConfig(quickEvictionMapId);
final MaxSizeConfig maxSizeConfig = mapConfig.getMaxSizeConfig();
Assert.assertEquals(4, maxSizeConfig.getSize());
Assert.assertEquals(20, mapConfig.getEvictionPercentage());
final int expectedRemainingAfterEviction = (int) (mapConfig.getMaxSizeConfig().getSize()
* (100.0d - (double) mapConfig.getEvictionPercentage()) / 100.0d) - 1;
final HazelcastCacheMember cache = new HazelcastCacheMember(config);
final Map<String, String> quickEvictionMap = cache.getDistributedMap(quickEvictionMapId);
final DebugCacheListener unitUnderTest1 = new DebugCacheListener("listener_1");
final boolean success = cache.addListenerFor(quickEvictionMap, unitUnderTest1);
Assert.assertTrue("Could not add DebugCacheListener to the quickEvictionMap.", success);
Thread.sleep(200);
// Act
putValues(mapConfig.getMaxSizeConfig().getSize() + 1, quickEvictionMap);
Thread.sleep(200);
final TreeMap<String, String> result = getValueMap(quickEvictionMap);
System.out.println("" + unitUnderTest1);
System.out.println(" Result: " + result);
/*
######################################
(hz._hzInstance_1_unittest-cache-group.event-1) [2]: put:key_1:value_1
(hz._hzInstance_1_unittest-cache-group.event-2) [3]: put:key_0:value_0
(hz._hzInstance_1_unittest-cache-group.event-4) [4]: autonomousEvict:key_3:null
(hz._hzInstance_1_unittest-cache-group.event-4) [6]: put:key_3:value_3
(hz._hzInstance_1_unittest-cache-group.event-4) [7]: autonomousEvict:key_4:null
(hz._hzInstance_1_unittest-cache-group.event-4) [8]: put:key_4:value_4
(hz._hzInstance_1_unittest-cache-group.event-5) [1]: autonomousEvict:key_2:null
(hz._hzInstance_1_unittest-cache-group.event-5) [5]: put:key_2:value_2
######################################
*/
// Assert
Assert.assertEquals("Expected [" + expectedRemainingAfterEviction
+ "] remaining elements after eviction. Got [" + result.size() + "].",
expectedRemainingAfterEviction, result.size());
for (int i = 0; i < expectedRemainingAfterEviction; i++) {
final String currentKey = key + "_" + i;
final String currentValue = value + "_" + i;
Assert.assertTrue(result.keySet().contains(currentKey));
Assert.assertEquals(currentValue, result.get(currentKey));
}
}
@Test
public void validateEvictionInLifecycleInHazelcastAPI() throws InterruptedException {
// Assemble
final String quickEvictionMapId = "quickEvictionMap";
final String quickEvictConfigFile = "config/hazelcast/QuickEvictionConfig.xml";
final AtomicInteger counter = new AtomicInteger();
final Config config = HazelcastCacheMember.readConfigFile(quickEvictConfigFile);
config.setInstanceName("quickEvictionInstance");
final MapConfig mapConfig = config.getMapConfig(quickEvictionMapId);
final HazelcastInstance cache = Hazelcast.getOrCreateHazelcastInstance(config);
final SortedMap<Integer, String> indexedMessages = new TreeMap<>();
final EntryAddedListener<String, String> el = new EntryAdapter<String, String>() {
final Object lock = new Object();
@Override
public void onEntryEvent(final EntryEvent<String, String> event) {
synchronized (lock) {
final String message = "onEntryEvent: " + event.getEventType().toString() + " - [" + event.getKey()
+ "]: " + event.getOldValue() + " --> " + event.getValue();
indexedMessages.put(counter.getAndIncrement(), message);
}
}
@Override
public void onMapEvent(final MapEvent event) {
synchronized (lock) {
final String message = "onMapEvent: " + event.getEventType().toString() + " - [" + event.getName()
+ "(" + event.getNumberOfEntriesAffected() + "]: " + event.getEventType();
indexedMessages.put(counter.getAndIncrement(), message);
}
}
};
// Act
final IMap<String, String> map = cache.getMap(quickEvictionMapId);
map.addEntryListener(el, true);
Thread.sleep(200);
for (int i = 0; i < mapConfig.getMaxSizeConfig().getSize() + 1; i++) {
map.put(key + "_" + i, value + "_" + i);
}
Thread.sleep(200);
/*
######################################
[0]: onEntryEvent: ADDED - [key_1]: null --> value_1
[1]: onEntryEvent: ADDED - [key_0]: null --> value_0
[2]: onEntryEvent: EVICTED - [key_0]: value_0 --> null
[3]: onEntryEvent: EVICTED - [key_1]: value_1 --> null
[4]: onEntryEvent: ADDED - [key_2]: null --> value_2
[5]: onEntryEvent: EVICTED - [key_2]: value_2 --> null
[6]: onEntryEvent: ADDED - [key_3]: null --> value_3
[7]: onEntryEvent: EVICTED - [key_3]: value_3 --> null
[8]: onEntryEvent: ADDED - [key_4]: null --> value_4
[9]: onEntryEvent: EVICTED - [key_4]: value_4 --> null
######################################
*/
System.out.println("\n\n\n######################################\n");
for (Map.Entry<Integer, String> current : indexedMessages.entrySet()) {
System.out.println("[" + current.getKey() + "]: " + current.getValue());
}
System.out.println("\n######################################\n\n\n");
// Assert
}
//
// Private helpers
//
private void putValues(final int numValues, final Map<String, String> map) {
for (int i = 0; i < numValues; i++) {
map.put(key + "_" + i, value + "_" + i);
}
}
private TreeMap<String, String> getValueMap(final Map<String, String> source) {
TreeMap<String, String> toReturn = new TreeMap<String, String>();
for (String current : source.keySet()) {
toReturn.put(current, source.get(current));
}
return toReturn;
}
}