/* * 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.impl.operation; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import com.hazelcast.map.MapInterceptor; import com.hazelcast.map.impl.MapContainer; import com.hazelcast.map.impl.MapService; import com.hazelcast.query.IndexAwarePredicate; import com.hazelcast.query.impl.ComparisonType; import com.hazelcast.query.impl.Index; import com.hazelcast.query.impl.QueryContext; import com.hazelcast.query.impl.QueryableEntry; import com.hazelcast.test.AssertTask; 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.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import java.io.Serializable; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; /** * Verify that maps created on a member joining the cluster, do actually include index & interceptors * as expected through execution of PostJoinMapOperation on the joining member. */ @RunWith(HazelcastParallelClassRunner.class) @Category({QuickTest.class, ParallelTest.class}) public class PostJoinMapOperationTest extends HazelcastTestSupport { private static class Person implements Serializable { private final int age; private final String name; public Person(String name, int age) { this.age = age; this.name = name; } public int getAge() { return age; } public String getName() { return name; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Person)) { return false; } Person person = (Person) o; if (age != person.age) { return false; } return name != null ? name.equals(person.name) : person.name == null; } @Override public int hashCode() { int result = age; result = 31 * result + (name != null ? name.hashCode() : 0); return result; } } private static final Person RETURNED_FROM_INTERCEPTOR = new Person("THE_PERSON", 100); public static class FixedReturnInterceptor implements MapInterceptor { @Override public Object interceptGet(Object value) { return RETURNED_FROM_INTERCEPTOR; } @Override public void afterGet(Object value) { } @Override public Object interceptPut(Object oldValue, Object newValue) { // allow put operations to proceed return null; } @Override public void afterPut(Object value) { } @Override public Object interceptRemove(Object removedValue) { return RETURNED_FROM_INTERCEPTOR; } @Override public void afterRemove(Object value) { } } // if it locates an index on Person.age, increments isIndexedInvoked public class AgePredicate implements IndexAwarePredicate { private final AtomicInteger isIndexedInvocationCounter; public AgePredicate(AtomicInteger atomicInteger) { this.isIndexedInvocationCounter = atomicInteger; } @Override public Set<QueryableEntry> filter(QueryContext queryContext) { Index ix = queryContext.getIndex("age"); if (ix != null) { return ix.getSubRecords(ComparisonType.GREATER, 50); } else { return null; } } @Override public boolean isIndexed(QueryContext queryContext) { Index ix = queryContext.getIndex("age"); if (ix != null) { isIndexedInvocationCounter.incrementAndGet(); return true; } else { return false; } } @Override public boolean apply(Map.Entry mapEntry) { if (mapEntry.getValue() instanceof Person) { return ((Person) mapEntry.getValue()).getAge() > 50; } else { return false; } } } @Test public void testPostJoinMapOperation_mapWithInterceptor() { TestHazelcastInstanceFactory hzFactory = createHazelcastInstanceFactory(2); // Given: A map with an interceptor on single-node Hazelcast HazelcastInstance hz1 = hzFactory.newHazelcastInstance(); IMap<String, Person> map = hz1.getMap("map"); map.put("foo", new Person("foo", 32)); map.put("bar", new Person("bar", 35)); map.addInterceptor(new FixedReturnInterceptor()); assertEquals(RETURNED_FROM_INTERCEPTOR, map.get("foo")); // when: new member joins cluster HazelcastInstance hz2 = hzFactory.newHazelcastInstance(); waitAllForSafeState(hz1, hz2); // then: values from map reference obtained from node 2 are returned by interceptor IMap<String, Person> mapOnNode2 = hz2.getMap("map"); assertEquals(RETURNED_FROM_INTERCEPTOR, mapOnNode2.get("whatever")); // put a value on node 2, then get it and verify it is the one returned by the interceptor String keyOwnedByNode2 = generateKeyOwnedBy(hz2); map.put(keyOwnedByNode2, new Person("not to be returned", 39)); assertEquals(RETURNED_FROM_INTERCEPTOR, map.get(keyOwnedByNode2)); hzFactory.terminateAll(); } // This test is meant to verify that a query will be executed *with an index* on the joining node // See also QueryIndexMigrationTest, which tests that results are as expected. @Test public void testPostJoinMapOperation_mapWithIndex() throws InterruptedException { TestHazelcastInstanceFactory hzFactory = createHazelcastInstanceFactory(2); // given: a map with index on a single-node HazelcastInstance HazelcastInstance hz1 = hzFactory.newHazelcastInstance(); IMap<String, Person> map = hz1.getMap("map"); map.put("foo", new Person("foo", 32)); map.put("bar", new Person("bar", 70)); map.addIndex("age", true); // when: new node joins and original node is terminated HazelcastInstance hz2 = hzFactory.newHazelcastInstance(); waitAllForSafeState(hz1, hz2); hzFactory.terminate(hz1); waitAllForSafeState(hz2); // then: once all migrations are committed, the query is executed *with* the index and // returns the expected results. final IMap<String, Person> mapOnNode2 = hz2.getMap("map"); final AtomicInteger invocationCounter = new AtomicInteger(0); // eventually index should be created after join assertTrueEventually(new AssertTask() { @Override public void run() throws Exception { Collection<Person> personsWithAgePredicate = mapOnNode2.values(new AgePredicate(invocationCounter)); assertEquals("isIndexed should have located an index", 1, invocationCounter.get()); assertEquals("index should return 1 match", 1, personsWithAgePredicate.size()); } }); } @Test public void testPostJoinMapOperation_whenMapHasNoData() { TestHazelcastInstanceFactory hzFactory = createHazelcastInstanceFactory(2); // given: a single node HazelcastInstance with a map configured with index and interceptor HazelcastInstance hz1 = hzFactory.newHazelcastInstance(); IMap<String, Person> map = hz1.getMap("map"); map.addIndex("age", true); map.addInterceptor(new FixedReturnInterceptor()); assertEquals(RETURNED_FROM_INTERCEPTOR, map.get("foo")); // when: another member joins the cluster HazelcastInstance hz2 = hzFactory.newHazelcastInstance(); waitAllForSafeState(hz1, hz2); // then: index & interceptor exist on internal MapContainer on node that joined the cluster MapService mapService = getNodeEngineImpl(hz2).getService(MapService.SERVICE_NAME); MapContainer mapContainerOnNode2 = mapService.getMapServiceContext().getMapContainer("map"); assertEquals(1, mapContainerOnNode2.getIndexes().getIndexes().length); assertEquals(1, mapContainerOnNode2.getInterceptorRegistry().getInterceptors().size()); assertEquals(Person.class, mapContainerOnNode2.getInterceptorRegistry().getInterceptors().get(0).interceptGet("anything").getClass()); assertEquals(RETURNED_FROM_INTERCEPTOR.getAge(), ((Person) mapContainerOnNode2.getInterceptorRegistry().getInterceptors().get(0).interceptGet("anything")).getAge()); // also verify via user API IMap<String, Person> mapOnNode2 = hz2.getMap("map"); assertEquals(RETURNED_FROM_INTERCEPTOR, mapOnNode2.get("whatever")); hzFactory.terminateAll(); } }