/*
* Copyright (c) 2008-2017 the original author or authors.
*
* 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 org.cometd.oort;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Test;
public class OortLongMapTest extends AbstractOortObjectTest {
public OortLongMapTest(String serverTransport) {
super(serverTransport);
}
@Test
public void testShare() throws Exception {
String name = "test";
OortObject.Factory<ConcurrentMap<Long, Object>> factory = OortObjectFactories.forConcurrentMap();
OortLongMap<Object> oortMap1 = new OortLongMap<>(oort1, name, factory);
OortLongMap<Object> oortMap2 = new OortLongMap<>(oort2, name, factory);
startOortObjects(oortMap1, oortMap2);
final long key1 = 13L;
final String value1 = "value1";
final CountDownLatch objectLatch1 = new CountDownLatch(1);
oortMap1.addListener(new OortObject.Listener.Adapter<ConcurrentMap<Long, Object>>() {
@Override
public void onUpdated(OortObject.Info<ConcurrentMap<Long, Object>> oldInfo, OortObject.Info<ConcurrentMap<Long, Object>> newInfo) {
Assert.assertTrue(newInfo.isLocal());
Assert.assertNotNull(oldInfo);
Assert.assertTrue(oldInfo.getObject().isEmpty());
Assert.assertNotSame(oldInfo, newInfo);
Assert.assertEquals(value1, newInfo.getObject().get(key1));
objectLatch1.countDown();
}
});
// The other OortObject listens to receive the object
final CountDownLatch objectLatch2 = new CountDownLatch(1);
oortMap2.addListener(new OortObject.Listener.Adapter<ConcurrentMap<Long, Object>>() {
@Override
public void onUpdated(OortObject.Info<ConcurrentMap<Long, Object>> oldInfo, OortObject.Info<ConcurrentMap<Long, Object>> newInfo) {
Assert.assertFalse(newInfo.isLocal());
Assert.assertNotNull(oldInfo);
Assert.assertTrue(oldInfo.getObject().isEmpty());
Assert.assertNotSame(oldInfo, newInfo);
Assert.assertEquals(value1, newInfo.getObject().get(key1));
objectLatch2.countDown();
}
});
// Change the object and share the change
ConcurrentMap<Long, Object> object1 = factory.newObject(null);
object1.put(key1, value1);
oortMap1.setAndShare(object1, null);
Assert.assertTrue(objectLatch1.await(5, TimeUnit.SECONDS));
Assert.assertTrue(objectLatch2.await(5, TimeUnit.SECONDS));
Assert.assertEquals(value1, oortMap1.getInfo(oort1.getURL()).getObject().get(key1));
Assert.assertTrue(oortMap1.getInfo(oort2.getURL()).getObject().isEmpty());
Assert.assertTrue(oortMap2.getInfo(oort2.getURL()).getObject().isEmpty());
Assert.assertEquals(object1, oortMap2.getInfo(oort1.getURL()).getObject());
ConcurrentMap<Long, Object> objectAtOort2 = oortMap2.merge(OortObjectMergers.<Long, Object>concurrentMapUnion());
Assert.assertEquals(object1, objectAtOort2);
}
@Test
public void testHowToDealWitMutableValues() throws Exception {
// We are using a Map as mutable value because it serializes easily in JSON.
// Any other mutable data structure would require a serializer/deserializer.
String name = "test";
OortObject.Factory<ConcurrentMap<Long, Map<String, Boolean>>> factory = OortObjectFactories.forConcurrentMap();
OortLongMap<Map<String, Boolean>> oortMap1 = new OortLongMap<>(oort1, name, factory);
OortLongMap<Map<String, Boolean>> oortMap2 = new OortLongMap<>(oort2, name, factory);
startOortObjects(oortMap1, oortMap2);
long key = 13L;
Map<String, Boolean> node1Value = new HashMap<>();
final CountDownLatch putLatch1 = new CountDownLatch(2);
OortMap.EntryListener.Adapter<Long, Map<String, Boolean>> listener1 = new OortMap.EntryListener.Adapter<Long, Map<String, Boolean>>() {
@Override
public void onPut(OortObject.Info<ConcurrentMap<Long, Map<String, Boolean>>> info, OortMap.Entry<Long, Map<String, Boolean>> entry) {
putLatch1.countDown();
}
};
oortMap1.addEntryListener(listener1);
oortMap2.addEntryListener(listener1);
// First problem is how concurrent threads may insert the initial value for a certain key:
// solution is to use putIfAbsentAndShare().
OortObject.Result.Deferred<Map<String, Boolean>> result = new OortObject.Result.Deferred<>();
oortMap1.putIfAbsentAndShare(key, node1Value, result);
Map<String, Boolean> existing = result.get(5, TimeUnit.SECONDS);
if (existing != null) {
node1Value = existing;
}
Assert.assertTrue(putLatch1.await(5, TimeUnit.SECONDS));
oortMap1.removeEntryListener(listener1);
oortMap2.removeEntryListener(listener1);
// Now we have a reference to the value object for that key.
// We mutate the value object.
synchronized (node1Value) {
node1Value.put("1", true);
}
// Another thread may just get the value and modify it
node1Value = oortMap1.get(key);
synchronized (node1Value) {
node1Value.put("2", true);
}
final CountDownLatch putLatch2 = new CountDownLatch(2);
OortMap.EntryListener.Adapter<Long, Map<String, Boolean>> listener2 = new OortMap.EntryListener.Adapter<Long, Map<String, Boolean>>() {
@Override
public void onPut(OortObject.Info<ConcurrentMap<Long, Map<String, Boolean>>> info, OortMap.Entry<Long, Map<String, Boolean>> entry) {
putLatch2.countDown();
}
};
oortMap1.addEntryListener(listener2);
oortMap2.addEntryListener(listener2);
// Share the value notifying EntryListeners
oortMap1.putAndShare(key, node1Value, null);
Assert.assertTrue(putLatch2.await(5, TimeUnit.SECONDS));
oortMap1.removeEntryListener(listener2);
oortMap2.removeEntryListener(listener2);
Map<String, Boolean> node2Value = oortMap2.find(key);
Assert.assertEquals(2, node2Value.size());
}
}