/*
* 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.map;
import com.hazelcast.config.Config;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.map.listener.EntryAddedListener;
import com.hazelcast.map.listener.EntryRemovedListener;
import com.hazelcast.map.listener.EntryUpdatedListener;
import com.hazelcast.map.listener.MapListener;
import com.hazelcast.query.SqlPredicate;
import org.junit.Ignore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A simple test that performs random puts, updates & removes on a map and counts the number of entries
* and exits (with regards to the value space defined by the predicate) as performed by the map randomizer
* and as observed on the listener side.
*/
@Ignore("Not a JUnit test")
public class MapListenerTest {
private static final AtomicInteger ENTRIES, EXITS, ENTRIES_OBSERVED, EXITS_OBSERVED;
private static final Logger LOGGER = LoggerFactory.getLogger(MapListenerTest.class);
private static final int AGE_THRESHOLD = 50;
static {
System.setProperty("hazelcast.map.entry.filtering.natural.event.types", "true");
ENTRIES = new AtomicInteger();
EXITS = new AtomicInteger();
ENTRIES_OBSERVED = new AtomicInteger();
EXITS_OBSERVED = new AtomicInteger();
}
static class AllListener implements EntryAddedListener<String, Person>, EntryRemovedListener<String, Person>,
EntryUpdatedListener<String, Person> {
private static final Logger LOGGER = LoggerFactory.getLogger(AllListener.class);
@Override
public void entryAdded(EntryEvent<String, Person> event) {
ENTRIES_OBSERVED.incrementAndGet();
}
@Override
public void entryRemoved(EntryEvent<String, Person> event) {
if (event.getValue() != null && event.getOldValue() != null) {
dumpEvent("exit from removed", event);
}
EXITS_OBSERVED.incrementAndGet();
}
@Override
public void entryUpdated(EntryEvent<String, Person> event) {
if (event.getOldValue().getAge() > AGE_THRESHOLD &&
event.getValue().getAge() <= AGE_THRESHOLD) {
EXITS_OBSERVED.incrementAndGet();
dumpEvent("exit", event);
} else if (event.getOldValue().getAge() <= AGE_THRESHOLD &&
event.getValue().getAge() > AGE_THRESHOLD) {
ENTRIES_OBSERVED.incrementAndGet();
dumpEvent("entry", event);
}
}
private static void dumpEvent(String qualifier, EntryEvent event) {
LOGGER.info(qualifier + " " + event);
}
}
static class Person implements Serializable {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Person(Person p) {
this.name = p.name;
this.age = p.age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
static class MapRandomizer implements Runnable {
private static final int ACTION_ADD = 0;
private static final int ACTION_UPDATE_AGE = 1;
private static final int ACTION_REMOVE = 2;
final Random random = new Random();
final IMap<String, Person> map;
volatile boolean running;
MapRandomizer(IMap<String, Person> map) {
this.map = map;
this.running = true;
}
private void act() {
int action = random.nextInt(10) < 6 ? ACTION_ADD :
random.nextInt(10) < 8 ? ACTION_UPDATE_AGE : ACTION_REMOVE;
switch (action) {
case ACTION_ADD:
addPerson();
break;
case ACTION_UPDATE_AGE:
updatePersonAge();
break;
case ACTION_REMOVE:
removePerson();
break;
}
}
private void addPerson() {
Person p = new Person(random.nextInt(100), UUID.randomUUID().toString());
map.put(p.getName(), p);
if (p.getAge() > AGE_THRESHOLD) {
ENTRIES.incrementAndGet();
}
}
private void updatePersonAge() {
if (map.size() > 0) {
Collection<String> allKeys = map.keySet();
String key = allKeys.toArray(new String[0])[random.nextInt(map.size())];
Person p = map.get(key);
int oldAge = p.getAge();
p.setAge(p.getAge() + random.nextInt(10) * ((int) Math.pow(-1, random.nextInt(2))));
if (oldAge > AGE_THRESHOLD && p.getAge() <= AGE_THRESHOLD) {
EXITS.incrementAndGet();
LOGGER.info("updatePersonAge exit from " + oldAge + " to " + p.getAge());
} else if (oldAge <= AGE_THRESHOLD && p.getAge() > AGE_THRESHOLD) {
ENTRIES.incrementAndGet();
LOGGER.info("updatePersonAge entry from " + oldAge + " to " + p.getAge());
}
map.put(key, p);
}
}
private void removePerson() {
if (map.size() > 0) {
Collection<String> allKeys = map.keySet();
String key = allKeys.toArray(new String[0])[random.nextInt(map.size())];
if (map.get(key).getAge() > AGE_THRESHOLD) {
EXITS.incrementAndGet();
}
map.remove(key);
}
}
@Override
public void run() {
while (running) {
act();
}
}
public void setRunning(boolean running) {
this.running = running;
}
}
public static void main(String[] args) throws InterruptedException {
// create Hazelcast instance
Config config = new Config();
config.setInstanceName("hz-maplistener");
config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
config.getNetworkConfig().getInterfaces().setInterfaces(Arrays.asList(new String[]{"127.0.0.1"}));
HazelcastInstance hz = Hazelcast.newHazelcastInstance(config);
IMap<String, Person> map = hz.getMap("map");
MapListener listener = new AllListener();
map.addEntryListener(listener, new SqlPredicate("age > " + AGE_THRESHOLD), true);
MapRandomizer mapRandomizer = new MapRandomizer(map);
Thread t = new Thread(mapRandomizer);
t.start();
// let it run for 1 minute
Thread.sleep(60000);
mapRandomizer.setRunning(false);
// assertions
assertCount(ENTRIES, ENTRIES_OBSERVED, "entries");
assertCount(EXITS, EXITS_OBSERVED, "exits");
// dumpMap(map);
hz.shutdown();
}
private static void assertCount(AtomicInteger expected, AtomicInteger observed, String unit) {
if (expected.get() != observed.get()) {
LOGGER.error("Actually performed " + expected.get() + " " + unit + ", but observed " + observed.get());
} else {
LOGGER.info("Correctly observed " + expected.get() + " " + unit);
}
}
private static void dumpMap(IMap<String, Person> map) {
LOGGER.info("Map dump follows");
for (Map.Entry<String, Person> e : map.entrySet()) {
LOGGER.info(e.getKey() + " > " + e.getValue());
}
}
}