INT-3691 Add ZookeeperMetadataStore
JIRA: https://jira.spring.io/browse/INT-3691 - create new core interfaces `ListenableMetadataStore` and `MessageStoreListener`; - create new module `spring-integration-zookeeper`; - add `ZookeeperMetadataStore` implementation Polishing Polishing - moved classes into core/test where necessary, including conversion methods - properly renamed `MetadataStoreListenerAdapter`
This commit is contained in:
committed by
Gary Russell
parent
51b52ea3a6
commit
8782d097cf
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* Copyright 2015 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.io.IOException;
|
||||
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.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
|
||||
import org.apache.curator.test.TestingServer;
|
||||
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.MetadataStoreListenerAdapter;
|
||||
import org.springframework.integration.metadata.MetadataStoreListener;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.integration.test.matcher.EqualsResultMatcher.Evaluator;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class ZookeeperMetadataStoreTests {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ZookeeperMetadataStore.class);
|
||||
|
||||
private TestingServer testingServer;
|
||||
|
||||
private CuratorFramework client;
|
||||
|
||||
private ZookeeperMetadataStore metadataStore;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception{
|
||||
testingServer = new TestingServer(true);
|
||||
client = createNewClient();
|
||||
metadataStore = new ZookeeperMetadataStore(client);
|
||||
metadataStore.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
this.metadataStore.stop();
|
||||
closeClient(this.client);
|
||||
try {
|
||||
testingServer.stop();
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.warn("Exception thrown while shutting down ZooKeeper: ", e);
|
||||
}
|
||||
testingServer.getTempDirectory().delete();
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
})));
|
||||
closeClient(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));
|
||||
closeClient(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);
|
||||
}
|
||||
}
|
||||
|
||||
private CuratorFramework createNewClient() throws InterruptedException {
|
||||
CuratorFramework client = CuratorFrameworkFactory.newClient(testingServer.getConnectString(),
|
||||
new BoundedExponentialBackoffRetry(100, 1000, 3));
|
||||
client.start();
|
||||
client.blockUntilConnected(10000, TimeUnit.SECONDS);
|
||||
return client;
|
||||
}
|
||||
|
||||
private void closeClient(CuratorFramework client) {
|
||||
try {
|
||||
CloseableUtils.closeQuietly(client);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception thrown while closing client: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
log4j.rootCategory=WARN, stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss.SSS} %-5p [%t][%c] %m%n
|
||||
|
||||
log4j.category.org.springframework.integration=WARN
|
||||
log4j.category.org.springframework.integration.zookeeper=INFO
|
||||
Reference in New Issue
Block a user