From a58251ce84c268e80aa3fabc6ca6e65165d78a66 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 8 May 2015 13:21:18 +0200 Subject: [PATCH 1/2] DATAKV-103 - Add KeyValueStore abstraction. Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2917aeb0..834969c5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-keyvalue - 0.1.0.BUILD-SNAPSHOT + 0.1.0.DATAKV-103-SNAPSHOT Spring Data KeyValue From e6051859fc9d7b68d12739020a8f01cd743b93a2 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 8 May 2015 14:24:32 +0200 Subject: [PATCH 2/2] Polishing. Removed dedicated Entry interface, since Map.Entry is already sufficient. Added missing JavaDoc. --- .../core/AdapterBackedKeyValueStore.java | 144 ++++++++++++++ .../data/keyvalue/core/Entry.java | 27 --- .../core/ForwardingKeyValueIterator.java | 9 +- .../data/keyvalue/core/KeyValueIterator.java | 7 +- .../keyvalue/core/KeyValueOperations.java | 8 + .../data/keyvalue/core/KeyValueStore.java | 91 +++++++++ .../data/keyvalue/core/KeyValueTemplate.java | 9 + .../AdapterBackedKeyValueStoreUnitTests.java | 187 ++++++++++++++++++ .../data/keyvalue/test/util/IsEntry.java | 17 +- 9 files changed, 455 insertions(+), 44 deletions(-) create mode 100644 src/main/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStore.java delete mode 100644 src/main/java/org/springframework/data/keyvalue/core/Entry.java create mode 100644 src/main/java/org/springframework/data/keyvalue/core/KeyValueStore.java create mode 100644 src/test/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStoreUnitTests.java diff --git a/src/main/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStore.java b/src/main/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStore.java new file mode 100644 index 00000000..5944bc9c --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStore.java @@ -0,0 +1,144 @@ +/* + * 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.data.keyvalue.core; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * A {@link KeyValueStore} scoped to a keyspace that stores the values in via a provided {@link KeyValueAdapter}. + * + * @author Christoph Strobl + * @param + * @param + */ +public class AdapterBackedKeyValueStore implements KeyValueStore { + + private final Serializable keyspace; + private final KeyValueAdapter adapter; + + /** + * @param adapter + * @param keyspace + */ + public AdapterBackedKeyValueStore(KeyValueAdapter adapter, Serializable keyspace) { + + Assert.notNull(adapter, "Adapter must not be null!"); + Assert.notNull(keyspace, "Keyspace must not be null!"); + this.adapter = adapter; + this.keyspace = keyspace; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#put(java.io.Serializable, java.lang.Object) + */ + @Override + @SuppressWarnings("unchecked") + public V put(K id, V item) { + return (V) adapter.put(id, item, keyspace); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#contains(java.io.Serializable) + */ + @Override + public boolean contains(K id) { + return adapter.contains(id, keyspace); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#get(java.io.Serializable) + */ + @Override + @SuppressWarnings("unchecked") + public V get(K id) { + return (V) adapter.get(id, keyspace); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#getAll(java.util.Collection) + */ + @Override + public Iterable getAll(Collection keys) { + + if (CollectionUtils.isEmpty(keys)) { + return Collections.emptySet(); + } + + List result = new ArrayList(keys.size()); + for (K key : keys) { + result.add(get(key)); + } + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#delete(java.io.Serializable) + */ + @Override + @SuppressWarnings("unchecked") + public V remove(K id) { + return (V) adapter.delete(id, keyspace); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#removeAll(java.util.Collection) + */ + @Override + public Iterable removeAll(Collection keys) { + + if (CollectionUtils.isEmpty(keys)) { + return Collections.emptySet(); + } + + List result = new ArrayList(keys.size()); + for (K key : keys) { + result.add(remove(key)); + } + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#size() + */ + @Override + public long size() { + return adapter.count(keyspace); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueStore#clear() + */ + @Override + public void clear() { + adapter.deleteAllOf(keyspace); + } + +} diff --git a/src/main/java/org/springframework/data/keyvalue/core/Entry.java b/src/main/java/org/springframework/data/keyvalue/core/Entry.java deleted file mode 100644 index fde586fe..00000000 --- a/src/main/java/org/springframework/data/keyvalue/core/Entry.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.data.keyvalue.core; - -import java.util.Map; - -/** - * @author Christoph Strobl - * @param - * @param - */ -public interface Entry extends Map.Entry { - -} diff --git a/src/main/java/org/springframework/data/keyvalue/core/ForwardingKeyValueIterator.java b/src/main/java/org/springframework/data/keyvalue/core/ForwardingKeyValueIterator.java index ce199798..4830f3b5 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/ForwardingKeyValueIterator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/ForwardingKeyValueIterator.java @@ -15,7 +15,6 @@ */ package org.springframework.data.keyvalue.core; -import java.io.IOException; import java.util.Iterator; import java.util.Map; @@ -38,16 +37,14 @@ public boolean hasNext() { } @Override - public Entry next() { + public Map.Entry next() { return new ForwardingEntry(delegate.next()); } @Override - public void close() throws IOException { + public void close() {} - } - - class ForwardingEntry implements Entry { + class ForwardingEntry implements Map.Entry { private final Map.Entry entry; diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueIterator.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueIterator.java index 9aec1296..48125666 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueIterator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueIterator.java @@ -15,14 +15,15 @@ */ package org.springframework.data.keyvalue.core; -import java.io.Closeable; -import java.util.Iterator; +import java.util.Map; + +import org.springframework.data.util.CloseableIterator; /** * @author Christoph Strobl * @param * @param */ -public interface KeyValueIterator extends Iterator>, Closeable { +public interface KeyValueIterator extends CloseableIterator> { } diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java index 3e17f714..d2098dad 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java @@ -169,6 +169,14 @@ public interface KeyValueOperations extends DisposableBean { */ long count(KeyValueQuery query, Class type); + /** + * Returns a {@link KeyValueStore} scoped to the given {@code keyspace}. + * + * @param keyspace must not be {@literal null}. + * @return + */ + KeyValueStore getKeyValueStore(Serializable keyspace); + /** * @return mapping context in use. */ diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueStore.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueStore.java new file mode 100644 index 00000000..0aaa3b1d --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueStore.java @@ -0,0 +1,91 @@ +/* + * 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.data.keyvalue.core; + +import java.io.Serializable; +import java.util.Collection; + +/** + * A key-value store that supports put, get, delete, and queries. + * + * @author Christoph Strobl + * @author Thomas Darimont + * @param + * @param + */ +public interface KeyValueStore { + + /** + * Add object with given key. + * + * @param key must not be {@literal null}. + * @return the item previously associated with the key. + */ + V put(K key, V item); + + /** + * Check if given key is present in KeyValue store. + * + * @param key must not be {@literal null}. + * @return + */ + boolean contains(K key); + + /** + * Get the Object associated with the given key. + * + * @param key must not be {@literal null}. + * @return {@literal null} if key not available. + */ + V get(K key); + + /** + * Get all Objects associated with the given keys. + * + * @param keys must not be {@literal null}. + * @return an empty {@link Iterable} if no matching key found. + */ + Iterable getAll(Collection keys); + + /** + * Delete and return the element associated with the given key. + * + * @param key must not be {@literal null}. + * @return + */ + V remove(K key); + + /** + * Delete and return all elements associated with the given keys. + * + * @param keys must not be {@literal null}. + * @return + */ + Iterable removeAll(Collection keys); + + /** + * Get the number of total elements contained. + * + * @return + */ + long size(); + + /** + * Removes all elements. The {@link KeyValueStore} will be empty after this call returns. + */ + void clear(); + +} diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java index 72180539..f2934402 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java @@ -428,6 +428,15 @@ public Long doInKeyValue(KeyValueAdapter adapter) { }); } + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.KeyValueOperations#getKeyValueStore(java.io.Serializable) + */ + @Override + public KeyValueStore getKeyValueStore(Serializable keyspace) { + return new AdapterBackedKeyValueStore(this.adapter, keyspace); + } + /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#getMappingContext() diff --git a/src/test/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStoreUnitTests.java b/src/test/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStoreUnitTests.java new file mode 100644 index 00000000..7b9b3507 --- /dev/null +++ b/src/test/java/org/springframework/data/keyvalue/core/AdapterBackedKeyValueStoreUnitTests.java @@ -0,0 +1,187 @@ +/* + * 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.data.keyvalue.core; + +import static org.hamcrest.collection.IsEmptyIterable.*; +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.io.Serializable; +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * @author Christoph Strobl + */ +@RunWith(MockitoJUnitRunner.class) +public class AdapterBackedKeyValueStoreUnitTests { + + @SuppressWarnings("unchecked") K key = (K) "key"; + @SuppressWarnings("unchecked") V value = (V) "value"; + + final String KEYSPACE = "keyspace-1"; + @Mock KeyValueAdapter adapterMock; + AdapterBackedKeyValueStore store; + + @Before + public void setUp() { + store = new AdapterBackedKeyValueStore(adapterMock, KEYSPACE); + } + + /** + * @see DATAKV-103 + */ + @Test(expected = IllegalArgumentException.class) + public void constructorShouldThrowExceptionWhenAdapterIsNull() { + new AdapterBackedKeyValueStore(null, KEYSPACE); + } + + /** + * @see DATAKV-103 + */ + @Test(expected = IllegalArgumentException.class) + public void constructorShouldThrowExceptionWhenKeyspaceIsNull() { + new AdapterBackedKeyValueStore(adapterMock, null); + } + + /** + * @see DATAKV-103 + */ + @Test + public void putShouldDelegateToAdapterCorrectly() { + + store.put(key, value); + + verify(adapterMock, times(1)).put(eq(key), eq(value), eq(KEYSPACE)); + } + + /** + * @see DATAKV-103 + */ + @Test + public void getShouldDelegateToAdapterCorrectly() { + + store.get(key); + + verify(adapterMock, times(1)).get(eq(key), eq(KEYSPACE)); + } + + /** + * @see DATAKV-103 + */ + @Test + public void containsShouldDelegateToAdapterCorrectly() { + + store.contains(key); + + verify(adapterMock, times(1)).contains(eq(key), eq(KEYSPACE)); + } + + /** + * @see DATAKV-103 + */ + @Test + public void getAllShouldDelegateToAdapterCorrectly() { + + store.getAll(Collections.singleton(key)); + + verify(adapterMock, times(1)).get(eq(key), eq(KEYSPACE)); + } + + /** + * @see DATAKV-103 + */ + @Test + public void getAllShouldReturnEmptyCollectionWhenKeysCollectionIsEmpty() { + + assertThat(store.getAll(Collections. emptySet()), emptyIterable()); + + verifyZeroInteractions(adapterMock); + } + + /** + * @see DATAKV-103 + */ + @Test + public void getAllShouldReturnEmptyCollectionWhenKeysCollectionIsNull() { + + assertThat(store.getAll(null), emptyIterable()); + + verifyZeroInteractions(adapterMock); + } + + /** + * @see DATAKV-103 + */ + @Test + public void removeShouldDelegateToAdapterCorrectly() { + + store.remove(key); + + verify(adapterMock, times(1)).delete(eq(key), eq(KEYSPACE)); + } + + /** + * @see DATAKV-103 + */ + @Test + public void removeAllShouldDelegateToAdapterCorrectly() { + + store.removeAll(Collections.singleton(key)); + + verify(adapterMock, times(1)).delete(eq(key), eq(KEYSPACE)); + } + + /** + * @see DATAKV-103 + */ + @Test + public void removeAllShouldReturnEmptyCollectionWhenKeysCollectionIsEmpty() { + + assertThat(store.removeAll(Collections. emptySet()), emptyIterable()); + + verifyZeroInteractions(adapterMock); + } + + /** + * @see DATAKV-103 + */ + @Test + public void removeAllShouldReturnEmptyCollectionWhenKeysCollectionIsNull() { + + assertThat(store.removeAll(null), emptyIterable()); + + verifyZeroInteractions(adapterMock); + } + + /** + * @see DATAKV-103 + */ + @Test + public void clearShouldDelegateToAdapterCorrectly() { + + store.clear(); + + verify(adapterMock, times(1)).deleteAllOf(eq(KEYSPACE)); + } + +} diff --git a/src/test/java/org/springframework/data/keyvalue/test/util/IsEntry.java b/src/test/java/org/springframework/data/keyvalue/test/util/IsEntry.java index 8b3ef19d..ccd40517 100644 --- a/src/test/java/org/springframework/data/keyvalue/test/util/IsEntry.java +++ b/src/test/java/org/springframework/data/keyvalue/test/util/IsEntry.java @@ -15,18 +15,19 @@ */ package org.springframework.data.keyvalue.test.util; +import java.util.Map; + import org.hamcrest.CustomMatcher; import org.hamcrest.core.IsEqual; -import org.springframework.data.keyvalue.core.Entry; /** * @author Christoph Strobl */ -public class IsEntry extends CustomMatcher> { +public class IsEntry extends CustomMatcher> { - private final Entry expected; + private final Map.Entry expected; - private IsEntry(Entry entry) { + private IsEntry(Map.Entry entry) { super(String.format("an entry %s=%s.", entry != null ? entry.getKey() : "null", entry != null ? entry.getValue() : "null")); this.expected = entry; @@ -39,11 +40,11 @@ public boolean matches(Object item) { return true; } - if (!(item instanceof Entry)) { + if (!(item instanceof Map.Entry)) { return false; } - Entry actual = (Entry) item; + Map.Entry actual = (Map.Entry) item; return new IsEqual(expected.getKey()).matches(actual.getKey()) && new IsEqual(expected.getValue()).matches(actual.getValue()); @@ -53,11 +54,11 @@ public static IsEntry isEntry(Object key, Object value) { return isEntry(new EntryImpl(key, value)); } - public static IsEntry isEntry(Entry entry) { + public static IsEntry isEntry(Map.Entry entry) { return new IsEntry(entry); } - private static class EntryImpl implements Entry { + private static class EntryImpl implements Map.Entry { private final Object key; private final Object value;