/*
* Copyright 2015-2016 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.springframework.integration.zookeeper.metadata;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.springframework.integration.test.matcher.EqualsResultMatcher.equalsResult;
import static org.springframework.integration.test.matcher.EventuallyMatcher.eventually;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.utils.CloseableUtils;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.metadata.MetadataStoreListener;
import org.springframework.integration.metadata.MetadataStoreListenerAdapter;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.integration.test.matcher.EqualsResultMatcher.Evaluator;
import org.springframework.integration.zookeeper.ZookeeperTestSupport;
/**
* @author Marius Bogoevici
* @since 4.2
*/
public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
private ZookeeperMetadataStore metadataStore;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
this.metadataStore = new ZookeeperMetadataStore(client);
this.metadataStore.start();
}
@Override
@After
public void tearDown() throws Exception {
this.metadataStore.stop();
this.client.delete().deletingChildrenIfNeeded().forPath(this.metadataStore.getRoot());
}
@Test
public void testGetNonExistingKeyValue() {
String retrievedValue = metadataStore.get("does-not-exist");
assertNull(retrievedValue);
}
@Test
public void testPersistKeyValue() throws Exception {
String testKey = "ZookeeperMetadataStoreTests-Persist";
metadataStore.put(testKey, "Integration");
assertNotNull(client.checkExists().forPath(metadataStore.getPath(testKey)));
assertEquals("Integration",
IntegrationUtils.bytesToString(client.getData().forPath(metadataStore.getPath(testKey)), "UTF-8"));
}
@Test
public void testGetValueFromMetadataStore() throws Exception {
String testKey = "ZookeeperMetadataStoreTests-GetValue";
metadataStore.put(testKey, "Hello Zookeeper");
String retrievedValue = metadataStore.get(testKey);
assertEquals("Hello Zookeeper", retrievedValue);
}
@Test
public void testPutIfAbsent() throws Exception {
final String testKey = "ZookeeperMetadataStoreTests-Persist";
final String testKey2 = "ZookeeperMetadataStoreTests-Persist-2";
metadataStore.put(testKey, "Integration");
assertNotNull(client.checkExists().forPath(metadataStore.getPath(testKey)));
assertEquals("Integration",
IntegrationUtils.bytesToString(client.getData().forPath(metadataStore.getPath(testKey)), "UTF-8"));
CuratorFramework otherClient = createNewClient();
final ZookeeperMetadataStore otherMetadataStore = new ZookeeperMetadataStore(otherClient);
otherMetadataStore.start();
otherMetadataStore.putIfAbsent(testKey, "OtherValue");
assertEquals("Integration",
IntegrationUtils.bytesToString(client.getData().forPath(metadataStore.getPath(testKey)), "UTF-8"));
assertEquals("Integration", metadataStore.get(testKey));
assertThat("Integration", eventually(equalsResult(new Evaluator<String>() {
@Override
public String evaluate() {
return otherMetadataStore.get(testKey);
}
})));
otherMetadataStore.putIfAbsent(testKey2, "Integration-2");
assertEquals("Integration-2",
IntegrationUtils.bytesToString(client.getData().forPath(metadataStore.getPath(testKey2)), "UTF-8"));
assertEquals("Integration-2", otherMetadataStore.get(testKey2));
assertThat("Integration-2", eventually(equalsResult(new Evaluator<String>() {
@Override
public String evaluate() {
return metadataStore.get(testKey2);
}
})));
CloseableUtils.closeQuietly(otherClient);
}
@Test
public void testReplace() throws Exception {
final String testKey = "ZookeeperMetadataStoreTests-Replace";
metadataStore.put(testKey, "Integration");
assertNotNull(client.checkExists().forPath(metadataStore.getPath(testKey)));
assertEquals("Integration",
IntegrationUtils.bytesToString(client.getData().forPath(metadataStore.getPath(testKey)), "UTF-8"));
CuratorFramework otherClient = createNewClient();
final ZookeeperMetadataStore otherMetadataStore = new ZookeeperMetadataStore(otherClient);
otherMetadataStore.start();
otherMetadataStore.replace(testKey, "OtherValue", "Integration-2");
assertEquals("Integration",
IntegrationUtils.bytesToString(client.getData().forPath(metadataStore.getPath(testKey)), "UTF-8"));
assertEquals("Integration", metadataStore.get(testKey));
assertThat("Integration", eventually(equalsResult(new Evaluator<String>() {
@Override
public String evaluate() {
return otherMetadataStore.get(testKey);
}
})));
otherMetadataStore.replace(testKey, "Integration", "Integration-2");
assertEquals("Integration-2",
IntegrationUtils.bytesToString(client.getData().forPath(metadataStore.getPath(testKey)), "UTF-8"));
assertThat("Integration-2", eventually(equalsResult(new Evaluator<String>() {
@Override
public String evaluate() {
return metadataStore.get(testKey);
}
})));
assertEquals("Integration-2", otherMetadataStore.get(testKey));
CloseableUtils.closeQuietly(otherClient);
}
@Test
public void testPersistEmptyStringToMetadataStore() {
String testKey = "ZookeeperMetadataStoreTests-PersistEmpty";
metadataStore.put(testKey, "");
assertEquals("", metadataStore.get(testKey));
}
@Test
public void testPersistNullStringToMetadataStore() {
try {
metadataStore.put("ZookeeperMetadataStoreTests-PersistEmpty", null);
}
catch (IllegalArgumentException e) {
assertEquals("'value' must not be null.", e.getMessage());
return;
}
fail("Expected an IllegalArgumentException to be thrown.");
}
@Test
public void testPersistWithEmptyKeyToMetadataStore() {
metadataStore.put("", "PersistWithEmptyKey");
String retrievedValue = metadataStore.get("");
assertEquals("PersistWithEmptyKey", retrievedValue);
}
@Test
public void testPersistWithNullKeyToMetadataStore() {
try {
metadataStore.put(null, "something");
}
catch (IllegalArgumentException e) {
assertEquals("'key' must not be null.", e.getMessage());
return;
}
fail("Expected an IllegalArgumentException to be thrown.");
}
@Test
public void testGetValueWithNullKeyFromMetadataStore() {
try {
metadataStore.get(null);
}
catch (IllegalArgumentException e) {
assertEquals("'key' must not be null.", e.getMessage());
return;
}
fail("Expected an IllegalArgumentException to be thrown.");
}
@Test
public void testRemoveFromMetadataStore() throws Exception {
String testKey = "ZookeeperMetadataStoreTests-Remove";
String testValue = "Integration";
metadataStore.put(testKey, testValue);
assertEquals(testValue, metadataStore.remove(testKey));
Thread.sleep(1000);
assertNull(metadataStore.remove(testKey));
}
@Test
public void testListenerInvokedOnLocalChanges() throws Exception {
String testKey = "ZookeeperMetadataStoreTests";
// register listeners
final List<List<String>> notifiedChanges = new ArrayList<List<String>>();
final Map<String, CyclicBarrier> barriers = new HashMap<String, CyclicBarrier>();
barriers.put("add", new CyclicBarrier(2));
barriers.put("remove", new CyclicBarrier(2));
barriers.put("update", new CyclicBarrier(2));
metadataStore.addListener(new MetadataStoreListenerAdapter() {
@Override
public void onAdd(String key, String value) {
notifiedChanges.add(Arrays.asList("add", key, value));
waitAtBarrier("add", barriers);
}
@Override
public void onRemove(String key, String oldValue) {
notifiedChanges.add(Arrays.asList("remove", key, oldValue));
waitAtBarrier("remove", barriers);
}
@Override
public void onUpdate(String key, String newValue) {
notifiedChanges.add(Arrays.asList("update", key, newValue));
waitAtBarrier("update", barriers);
}
});
// the tests themselves
barriers.get("add").reset();
metadataStore.put(testKey, "Integration");
waitAtBarrier("add", barriers);
assertThat(notifiedChanges, hasSize(1));
assertThat(notifiedChanges.get(0), IsIterableContainingInOrder.contains("add", testKey, "Integration"));
metadataStore.putIfAbsent(testKey, "Integration++");
// there is no update and therefore we expect no changes
assertThat(notifiedChanges, hasSize(1));
barriers.get("update").reset();
metadataStore.put(testKey, "Integration-2");
waitAtBarrier("update", barriers);
assertThat(notifiedChanges, hasSize(2));
assertThat(notifiedChanges.get(1), IsIterableContainingInOrder.contains("update", testKey, "Integration-2"));
barriers.get("update").reset();
metadataStore.replace(testKey, "Integration-2", "Integration-3");
waitAtBarrier("update", barriers);
assertThat(notifiedChanges, hasSize(3));
assertThat(notifiedChanges.get(2), IsIterableContainingInOrder.contains("update", testKey, "Integration-3"));
metadataStore.replace(testKey, "Integration-2", "Integration-none");
assertThat(notifiedChanges, hasSize(3));
barriers.get("remove").reset();
metadataStore.remove(testKey);
waitAtBarrier("remove", barriers);
assertThat(notifiedChanges, hasSize(4));
assertThat(notifiedChanges.get(3), IsIterableContainingInOrder.contains("remove", testKey, "Integration-3"));
// sleep and try to see if there were any other updates
Thread.sleep(1000);
assertThat(notifiedChanges, hasSize(4));
}
@Test
public void testListenerInvokedOnRemoteChanges() throws Exception {
String testKey = "ZookeeperMetadataStoreTests";
CuratorFramework otherClient = createNewClient();
ZookeeperMetadataStore otherMetadataStore = new ZookeeperMetadataStore(otherClient);
// register listeners
final List<List<String>> notifiedChanges = new ArrayList<List<String>>();
final Map<String, CyclicBarrier> barriers = new HashMap<String, CyclicBarrier>();
barriers.put("add", new CyclicBarrier(2));
barriers.put("remove", new CyclicBarrier(2));
barriers.put("update", new CyclicBarrier(2));
metadataStore.addListener(new MetadataStoreListenerAdapter() {
@Override
public void onAdd(String key, String value) {
notifiedChanges.add(Arrays.asList("add", key, value));
waitAtBarrier("add", barriers);
}
@Override
public void onRemove(String key, String oldValue) {
notifiedChanges.add(Arrays.asList("remove", key, oldValue));
waitAtBarrier("remove", barriers);
}
@Override
public void onUpdate(String key, String newValue) {
notifiedChanges.add(Arrays.asList("update", key, newValue));
waitAtBarrier("update", barriers);
}
});
// the tests themselves
barriers.get("add").reset();
otherMetadataStore.put(testKey, "Integration");
waitAtBarrier("add", barriers);
assertThat(notifiedChanges, hasSize(1));
assertThat(notifiedChanges.get(0), IsIterableContainingInOrder.contains("add", testKey, "Integration"));
otherMetadataStore.putIfAbsent(testKey, "Integration++");
// there is no update and therefore we expect no changes
assertThat(notifiedChanges, hasSize(1));
barriers.get("update").reset();
otherMetadataStore.put(testKey, "Integration-2");
waitAtBarrier("update", barriers);
assertThat(notifiedChanges, hasSize(2));
assertThat(notifiedChanges.get(1), IsIterableContainingInOrder.contains("update", testKey, "Integration-2"));
barriers.get("update").reset();
otherMetadataStore.replace(testKey, "Integration-2", "Integration-3");
waitAtBarrier("update", barriers);
assertThat(notifiedChanges, hasSize(3));
assertThat(notifiedChanges.get(2), IsIterableContainingInOrder.contains("update", testKey, "Integration-3"));
otherMetadataStore.replace(testKey, "Integration-2", "Integration-none");
assertThat(notifiedChanges, hasSize(3));
barriers.get("remove").reset();
otherMetadataStore.remove(testKey);
waitAtBarrier("remove", barriers);
assertThat(notifiedChanges, hasSize(4));
assertThat(notifiedChanges.get(3), IsIterableContainingInOrder.contains("remove", testKey, "Integration-3"));
// sleep and try to see if there were any other updates - if there any pending updates, we should catch them by now
Thread.sleep(1000);
assertThat(notifiedChanges, hasSize(4));
}
@Test
public void testAddRemoveListener() throws Exception {
MetadataStoreListener mockListener = Mockito.mock(MetadataStoreListener.class);
DirectFieldAccessor accessor = new DirectFieldAccessor(metadataStore);
@SuppressWarnings("unchecked")
List<MetadataStoreListener> listeners = (List<MetadataStoreListener>) accessor.getPropertyValue("listeners");
assertThat(listeners, hasSize(0));
metadataStore.addListener(mockListener);
assertThat(listeners, hasSize(1));
assertThat(listeners, IsIterableContainingInOrder.contains(mockListener));
metadataStore.removeListener(mockListener);
assertThat(listeners, hasSize(0));
}
private void waitAtBarrier(String barrierName, Map<String, CyclicBarrier> barriers) {
try {
barriers.get(barrierName).await(10, TimeUnit.SECONDS);
}
catch (Exception e) {
throw new AssertionError("Test didn't complete: ", e);
}
}
}