diff --git a/src/main/java/org/springframework/data/redis/core/RedisKeyExpiredEvent.java b/src/main/java/org/springframework/data/redis/core/RedisKeyExpiredEvent.java index 8b2119b36..299f47727 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisKeyExpiredEvent.java +++ b/src/main/java/org/springframework/data/redis/core/RedisKeyExpiredEvent.java @@ -19,7 +19,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.springframework.context.ApplicationEvent; -import org.springframework.data.redis.util.ByteUtils; +import org.springframework.data.redis.core.convert.MappingRedisConverter.BinaryKeyspaceIdentifier; import org.springframework.lang.Nullable; /** @@ -27,6 +27,7 @@ import org.springframework.lang.Nullable; * expires. It might but must not hold the expired value itself next to the key. * * @author Christoph Strobl + * @author Mark Paluch * @since 1.7 */ public class RedisKeyExpiredEvent extends RedisKeyspaceEvent { @@ -36,7 +37,7 @@ public class RedisKeyExpiredEvent extends RedisKeyspaceEvent { */ public static final Charset CHARSET = StandardCharsets.UTF_8; - private final byte[][] args; + private final BinaryKeyspaceIdentifier objectId; private final @Nullable Object value; /** @@ -69,7 +70,12 @@ public class RedisKeyExpiredEvent extends RedisKeyspaceEvent { public RedisKeyExpiredEvent(@Nullable String channel, byte[] key, @Nullable Object value) { super(channel, key); - args = ByteUtils.split(key, ':'); + if (BinaryKeyspaceIdentifier.isValid(key)) { + this.objectId = BinaryKeyspaceIdentifier.of(key); + } else { + this.objectId = null; + } + this.value = value; } @@ -79,12 +85,7 @@ public class RedisKeyExpiredEvent extends RedisKeyspaceEvent { * @return {@literal null} if it could not be determined. */ public String getKeyspace() { - - if (args.length >= 2) { - return new String(args[0], CHARSET); - } - - return null; + return objectId != null ? new String(objectId.getKeyspace(), CHARSET) : null; } /** @@ -93,7 +94,7 @@ public class RedisKeyExpiredEvent extends RedisKeyspaceEvent { * @return */ public byte[] getId() { - return args.length == 2 ? args[1] : args[0]; + return objectId != null ? objectId.getId() : getSource(); } /** diff --git a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java index 07dda1706..bebf53c26 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java +++ b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java @@ -48,6 +48,8 @@ import org.springframework.data.redis.core.convert.CustomConversions; import org.springframework.data.redis.core.convert.GeoIndexedPropertyValue; import org.springframework.data.redis.core.convert.KeyspaceConfiguration; import org.springframework.data.redis.core.convert.MappingRedisConverter; +import org.springframework.data.redis.core.convert.MappingRedisConverter.BinaryKeyspaceIdentifier; +import org.springframework.data.redis.core.convert.MappingRedisConverter.KeyspaceIdentifier; import org.springframework.data.redis.core.convert.PathIndexResolver; import org.springframework.data.redis.core.convert.RedisConverter; import org.springframework.data.redis.core.convert.RedisCustomConversions; @@ -240,7 +242,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter connection.expire(objectKey, rdo.getTimeToLive()); // add phantom key so values can be restored - byte[] phantomKey = ByteUtils.concat(objectKey, toBytes(":phantom")); + byte[] phantomKey = ByteUtils.concat(objectKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX); connection.del(phantomKey); connection.hMSet(phantomKey, rdo.getBucket().rawMap()); connection.expire(phantomKey, rdo.getTimeToLive() + 300); @@ -471,14 +473,14 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter connection.expire(redisKey, rdo.getTimeToLive()); // add phantom key so values can be restored - byte[] phantomKey = ByteUtils.concat(redisKey, toBytes(":phantom")); + byte[] phantomKey = ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX); connection.hMSet(phantomKey, rdo.getBucket().rawMap()); connection.expire(phantomKey, rdo.getTimeToLive() + 300); } else { connection.persist(redisKey); - connection.persist(ByteUtils.concat(redisKey, toBytes(":phantom"))); + connection.persist(ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX)); } } @@ -763,7 +765,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter byte[] key = message.getBody(); - byte[] phantomKey = ByteUtils.concat(key, converter.getConversionService().convert(":phantom", byte[].class)); + byte[] phantomKey = ByteUtils.concat(key, converter.getConversionService().convert(KeyspaceIdentifier.PHANTOM_SUFFIX, byte[].class)); Map hash = ops.execute((RedisCallback>) connection -> { @@ -799,9 +801,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter return false; } - byte[][] args = ByteUtils.split(message.getBody(), ':'); - - return args.length == 2; + return BinaryKeyspaceIdentifier.isValid(message.getBody()); } } diff --git a/src/main/java/org/springframework/data/redis/core/convert/MappingRedisConverter.java b/src/main/java/org/springframework/data/redis/core/convert/MappingRedisConverter.java index 1d4452cab..0b65db051 100644 --- a/src/main/java/org/springframework/data/redis/core/convert/MappingRedisConverter.java +++ b/src/main/java/org/springframework/data/redis/core/convert/MappingRedisConverter.java @@ -16,6 +16,9 @@ package org.springframework.data.redis.core.convert; import lombok.RequiredArgsConstructor; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; import java.lang.reflect.Array; import java.util.*; @@ -52,6 +55,7 @@ import org.springframework.data.redis.core.index.Indexed; import org.springframework.data.redis.core.mapping.RedisMappingContext; import org.springframework.data.redis.core.mapping.RedisPersistentEntity; import org.springframework.data.redis.core.mapping.RedisPersistentProperty; +import org.springframework.data.redis.util.ByteUtils; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; @@ -308,9 +312,14 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { for (Entry entry : bucket.entrySet()) { String referenceKey = fromBytes(entry.getValue(), String.class); - String[] args = referenceKey.split(":"); - Map rawHash = referenceResolver.resolveReference(args[1], args[0]); + if (!KeyspaceIdentifier.isValid(referenceKey)) { + continue; + } + + KeyspaceIdentifier identifier = KeyspaceIdentifier.of(referenceKey); + Map rawHash = referenceResolver.resolveReference(identifier.getId(), + identifier.getKeyspace()); if (!CollectionUtils.isEmpty(rawHash)) { target.add(read(association.getInverse().getActualType(), new RedisData(rawHash))); @@ -326,15 +335,18 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { return; } - String key = fromBytes(binKey, String.class); + String referenceKey = fromBytes(binKey, String.class); + if (KeyspaceIdentifier.isValid(referenceKey)) { - String[] args = key.split(":"); + KeyspaceIdentifier identifier = KeyspaceIdentifier.of(referenceKey); - Map rawHash = referenceResolver.resolveReference(args[1], args[0]); + Map rawHash = referenceResolver.resolveReference(identifier.getId(), + identifier.getKeyspace()); - if (!CollectionUtils.isEmpty(rawHash)) { - accessor.setProperty(association.getInverse(), - read(association.getInverse().getActualType(), new RedisData(rawHash))); + if (!CollectionUtils.isEmpty(rawHash)) { + accessor.setProperty(association.getInverse(), + read(association.getInverse().getActualType(), new RedisData(rawHash))); + } } } }); @@ -434,9 +446,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { targetProperty = getTargetPropertyOrNullForPath(path.replaceAll("\\.\\[.*\\]", ""), update.getTarget()); TypeInformation ti = targetProperty == null ? ClassTypeInformation.OBJECT - : (targetProperty.isMap() ? (targetProperty.getTypeInformation().getMapValueType() != null - ? targetProperty.getTypeInformation().getRequiredMapValueType() - : ClassTypeInformation.OBJECT) : targetProperty.getTypeInformation().getActualType()); + : (targetProperty.isMap() + ? (targetProperty.getTypeInformation().getMapValueType() != null + ? targetProperty.getTypeInformation().getRequiredMapValueType() : ClassTypeInformation.OBJECT) + : targetProperty.getTypeInformation().getActualType()); writeInternal(entity.getKeySpace(), pUpdate.getPropertyPath(), pUpdate.getValue(), ti, sink); return; @@ -1160,4 +1173,177 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { } } + /** + * Value object representing a Redis Hash/Object identifier composed from keyspace and object id in the form of + * {@literal keyspace:id}. + * + * @author Mark Paluch + * @since 1.8.10 + */ + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class KeyspaceIdentifier { + + public static final String PHANTOM = "phantom"; + public static final String DELIMITTER = ":"; + public static final String PHANTOM_SUFFIX = DELIMITTER + PHANTOM; + + private String keyspace; + private String id; + private boolean phantomKey; + + /** + * Parse a {@code key} into {@link KeyspaceIdentifier}. + * + * @param key the key representation. + * @return {@link BinaryKeyspaceIdentifier} for binary key. + */ + public static KeyspaceIdentifier of(String key) { + + Assert.isTrue(isValid(key), String.format("Invalid key %s", key)); + + boolean phantomKey = key.endsWith(PHANTOM_SUFFIX); + int keyspaceEndIndex = key.indexOf(DELIMITTER); + String keyspace = key.substring(0, keyspaceEndIndex); + String id; + + if (phantomKey) { + id = key.substring(keyspaceEndIndex + 1, key.length() - PHANTOM_SUFFIX.length()); + } else { + id = key.substring(keyspaceEndIndex + 1); + } + + return new KeyspaceIdentifier(keyspace, id, phantomKey); + } + + /** + * Check whether the {@code key} is valid, in particular whether the key contains a keyspace and an id part in the + * form of {@literal keyspace:id}. + * + * @param key the key. + * @return {@literal true} if the key is valid. + */ + public static boolean isValid(String key) { + + if (key == null) { + return false; + } + + int keyspaceEndIndex = key.indexOf(DELIMITTER); + + return keyspaceEndIndex > 0 && key.length() > keyspaceEndIndex; + } + } + + /** + * Value object representing a binary Redis Hash/Object identifier composed from keyspace and object id in the form of + * {@literal keyspace:id}. + * + * @author Mark Paluch + * @since 1.8.10 + */ + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class BinaryKeyspaceIdentifier { + + public static final byte[] PHANTOM = KeyspaceIdentifier.PHANTOM.getBytes(); + public static final byte DELIMITTER = ':'; + public static final byte[] PHANTOM_SUFFIX = ByteUtils.concat(new byte[] { DELIMITTER }, PHANTOM); + + private byte[] keyspace; + private byte[] id; + private boolean phantomKey; + + /** + * Parse a binary {@code key} into {@link BinaryKeyspaceIdentifier}. + * + * @param key the binary key representation. + * @return {@link BinaryKeyspaceIdentifier} for binary key. + */ + public static BinaryKeyspaceIdentifier of(byte[] key) { + + Assert.isTrue(isValid(key), String.format("Invalid key %s", new String(key))); + + boolean phantomKey = startsWith(key, PHANTOM_SUFFIX, key.length - PHANTOM_SUFFIX.length); + + int keyspaceEndIndex = find(key, DELIMITTER); + byte[] keyspace = getKeyspace(key, keyspaceEndIndex); + byte[] id = getId(key, phantomKey, keyspaceEndIndex); + + return new BinaryKeyspaceIdentifier(keyspace, id, phantomKey); + } + + /** + * Check whether the {@code key} is valid, in particular whether the key contains a keyspace and an id part in the + * form of {@literal keyspace:id}. + * + * @param key the key. + * @return {@literal true} if the key is valid. + */ + public static boolean isValid(byte[] key) { + + if (key == null) { + return false; + } + + int keyspaceEndIndex = find(key, DELIMITTER); + + return keyspaceEndIndex > 0 && key.length > keyspaceEndIndex; + } + + private static byte[] getId(byte[] key, boolean phantomKey, int keyspaceEndIndex) { + + int idSize; + + if (phantomKey) { + idSize = (key.length - PHANTOM_SUFFIX.length) - (keyspaceEndIndex + 1); + } else { + + idSize = key.length - (keyspaceEndIndex + 1); + } + + byte[] id = new byte[idSize]; + System.arraycopy(key, keyspaceEndIndex + 1, id, 0, idSize); + + return id; + } + + private static byte[] getKeyspace(byte[] key, int keyspaceEndIndex) { + + byte[] keyspace = new byte[keyspaceEndIndex]; + System.arraycopy(key, 0, keyspace, 0, keyspaceEndIndex); + + return keyspace; + } + + private static boolean startsWith(byte[] haystack, byte[] prefix, int offset) { + + int to = offset; + int prefixOffset = 0; + int prefixLength = prefix.length; + + if ((offset < 0) || (offset > haystack.length - prefixLength)) { + return false; + } + + while (--prefixLength >= 0) { + if (haystack[to++] != prefix[prefixOffset++]) { + return false; + } + } + + return true; + } + + private static int find(byte[] haystack, byte needle) { + + for (int i = 0; i < haystack.length; i++) { + if (haystack[i] == needle) { + return i; + } + } + + return -1; + } + } } diff --git a/src/test/java/org/springframework/data/redis/core/RedisKeyExpiredEventUnitTests.java b/src/test/java/org/springframework/data/redis/core/RedisKeyExpiredEventUnitTests.java new file mode 100644 index 000000000..3f4cc27bd --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/RedisKeyExpiredEventUnitTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 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.springframework.data.redis.core; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Unit tests for {@link RedisKeyExpiredEvent}. + * + * @author Mark Paluch + */ +public class RedisKeyExpiredEventUnitTests { + + @Test // DATAREDIS-744 + public void shouldReturnKeyspace() { + + assertThat(new RedisKeyExpiredEvent("foo".getBytes(), "").getKeyspace(), is(nullValue())); + assertThat(new RedisKeyExpiredEvent("foo:bar".getBytes(), "").getKeyspace(), is(equalTo("foo"))); + assertThat(new RedisKeyExpiredEvent("foo:bar:baz".getBytes(), "").getKeyspace(), is(equalTo("foo"))); + } + + @Test // DATAREDIS-744 + public void shouldReturnId() { + + assertThat(new RedisKeyExpiredEvent("foo".getBytes(), "").getId(), is(equalTo("foo".getBytes()))); + assertThat(new RedisKeyExpiredEvent("foo:bar".getBytes(), "").getId(), is(equalTo("bar".getBytes()))); + assertThat(new RedisKeyExpiredEvent("foo:bar:baz".getBytes(), "").getId(), + is(equalTo("bar:baz".getBytes()))); + } +} diff --git a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java index a2fe3ac88..e24699bf1 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java @@ -146,6 +146,20 @@ public class RedisKeyValueAdapterTests { assertThat(template.opsForHash().entries("persons:1").size(), is(2)); } + @Test // DATAREDIS-744 + public void putWritesDataWithColonCorrectly() { + + Person rand = new Person(); + rand.age = 24; + + adapter.put("1:a", rand, "persons"); + + assertThat(template.keys("persons*"), hasItems("persons", "persons:1:a")); + assertThat(template.opsForSet().size("persons"), is(1L)); + assertThat(template.opsForSet().members("persons"), hasItems("1:a")); + assertThat(template.opsForHash().entries("persons:1:a").size(), is(2)); + } + @Test // DATAREDIS-425 public void putWritesSimpleIndexDataCorrectly() { @@ -158,6 +172,18 @@ public class RedisKeyValueAdapterTests { assertThat(template.opsForSet().members("persons:firstname:rand"), hasItems("1")); } + @Test // DATAREDIS-744 + public void putWritesSimpleIndexDataWithColonCorrectly() { + + Person rand = new Person(); + rand.firstname = "rand"; + + adapter.put("1:a", rand, "persons"); + + assertThat(template.keys("persons*"), hasItem("persons:firstname:rand")); + assertThat(template.opsForSet().members("persons:firstname:rand"), hasItems("1:a")); + } + @Test // DATAREDIS-425 public void putWritesNestedDataCorrectly() { @@ -184,15 +210,15 @@ public class RedisKeyValueAdapterTests { assertThat(template.opsForSet().members("persons:address.country:Andor"), hasItems("1")); } - @Test // DATAREDIS-425 + @Test // DATAREDIS-425, DATAREDIS-744 public void getShouldReadSimpleObjectCorrectly() { Map map = new LinkedHashMap<>(); map.put("_class", Person.class.getName()); map.put("age", "24"); - template.opsForHash().putAll("persons:load-1", map); + template.opsForHash().putAll("persons:load-1:a", map); - Object loaded = adapter.get("load-1", "persons"); + Object loaded = adapter.get("load-1:a", "persons"); assertThat(loaded, instanceOf(Person.class)); assertThat(((Person) loaded).age, is(24)); @@ -257,7 +283,7 @@ public class RedisKeyValueAdapterTests { assertThat(template.opsForSet().members("persons:firstname:rand"), not(hasItem("1"))); } - @Test // DATAREDIS-425 + @Test // DATAREDIS-425, DATAREDIS-744 public void keyExpiredEventShouldRemoveHelperStructures() throws Exception { assumeTrue(RedisTestProfileValueSource.matches("runLongTests", "true")); @@ -267,22 +293,22 @@ public class RedisKeyValueAdapterTests { map.put("firstname", "rand"); map.put("address.country", "Andor"); - template.opsForHash().putAll("persons:1", map); + template.opsForHash().putAll("persons:1:b", map); template.opsForSet().add("persons", "1"); - template.opsForSet().add("persons:firstname:rand", "1"); - template.opsForSet().add("persons:1:idx", "persons:firstname:rand"); + template.opsForSet().add("persons:firstname:rand", "1:b"); + template.opsForSet().add("persons:1:b:idx", "persons:firstname:rand"); - template.expire("persons:1", 100, TimeUnit.MILLISECONDS); + template.expire("persons:1:b", 100, TimeUnit.MILLISECONDS); - waitUntilKeyIsGone(template, "persons:1"); - waitUntilKeyIsGone(template, "persons:1:phantom"); + waitUntilKeyIsGone(template, "persons:1:b"); + waitUntilKeyIsGone(template, "persons:1:b:phantom"); waitUntilKeyIsGone(template, "persons:firstname:rand"); assertThat(template.hasKey("persons:1"), is(false)); assertThat(template.hasKey("persons:firstname:rand"), is(false)); - assertThat(template.hasKey("persons:1:idx"), is(false)); - assertThat(template.opsForSet().members("persons"), not(hasItem("1"))); + assertThat(template.hasKey("persons:1:b:idx"), is(false)); + assertThat(template.opsForSet().members("persons"), not(hasItem("1:b"))); } @Test // DATAREDIS-589 @@ -348,17 +374,17 @@ public class RedisKeyValueAdapterTests { assertThat(template.opsForSet().isMember("persons:mat:idx", "persons:firstname:mat"), is(true)); } - @Test // DATAREDIS-471 + @Test // DATAREDIS-471, DATAREDIS-744 public void updateShouldAlterIndexDataCorrectly() { Person rand = new Person(); rand.firstname = "rand"; - adapter.put("1", rand, "persons"); + adapter.put("1:a", rand, "persons"); assertThat(template.hasKey("persons:firstname:rand"), is(true)); - PartialUpdate update = new PartialUpdate<>("1", Person.class) // + PartialUpdate update = new PartialUpdate<>("1:a", Person.class) // .set("firstname", "mat"); adapter.update(update); diff --git a/src/test/java/org/springframework/data/redis/core/convert/BinaryKeyspaceIdentifierUnitTests.java b/src/test/java/org/springframework/data/redis/core/convert/BinaryKeyspaceIdentifierUnitTests.java new file mode 100644 index 000000000..821d93794 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/convert/BinaryKeyspaceIdentifierUnitTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 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.springframework.data.redis.core.convert; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.redis.core.convert.MappingRedisConverter.BinaryKeyspaceIdentifier; + +/** + * Unit tests for {@link BinaryKeyspaceIdentifier}. + * + * @author Mark Paluch + */ +public class BinaryKeyspaceIdentifierUnitTests { + + @Test // DATAREDIS-744 + public void shouldReturnIfKeyIsValid() { + + assertThat(BinaryKeyspaceIdentifier.isValid(null), is(false)); + assertThat(BinaryKeyspaceIdentifier.isValid("foo".getBytes()), is(false)); + assertThat(BinaryKeyspaceIdentifier.isValid("".getBytes()), is(false)); + assertThat(BinaryKeyspaceIdentifier.isValid("foo:bar".getBytes()), is(true)); + assertThat(BinaryKeyspaceIdentifier.isValid("foo:bar:baz".getBytes()), is(true)); + assertThat(BinaryKeyspaceIdentifier.isValid("foo:bar:baz:phantom".getBytes()), is(true)); + } + + @Test // DATAREDIS-744 + public void shouldReturnKeyspace() { + + assertThat(BinaryKeyspaceIdentifier.of("foo:bar".getBytes()).getKeyspace(), is(equalTo("foo".getBytes()))); + assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz".getBytes()).getKeyspace(), is(equalTo("foo".getBytes()))); + assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz:phantom".getBytes()).getKeyspace(), + is(equalTo("foo".getBytes()))); + } + + @Test // DATAREDIS-744 + public void shouldReturnId() { + + assertThat(BinaryKeyspaceIdentifier.of("foo:bar".getBytes()).getId(), is(equalTo("bar".getBytes()))); + assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz".getBytes()).getId(), is(equalTo("bar:baz".getBytes()))); + assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz:phantom".getBytes()).getId(), + is(equalTo("bar:baz".getBytes()))); + } + + @Test // DATAREDIS-744 + public void shouldReturnPhantomKey() { + + assertThat(BinaryKeyspaceIdentifier.of("foo:bar".getBytes()).isPhantomKey(), is(false)); + assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz".getBytes()).isPhantomKey(), is(false)); + assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz:phantom".getBytes()).isPhantomKey(), is(true)); + } +} diff --git a/src/test/java/org/springframework/data/redis/core/convert/KeyspaceIdentifierUnitTests.java b/src/test/java/org/springframework/data/redis/core/convert/KeyspaceIdentifierUnitTests.java new file mode 100644 index 000000000..daac75a57 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/convert/KeyspaceIdentifierUnitTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 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.springframework.data.redis.core.convert; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.redis.core.convert.MappingRedisConverter.KeyspaceIdentifier; + +/** + * Unit tests for {@link KeyspaceIdentifier}. + * + * @author Mark Paluch + */ +public class KeyspaceIdentifierUnitTests { + + @Test // DATAREDIS-744 + public void shouldReturnIfKeyIsValid() { + + assertThat(KeyspaceIdentifier.isValid(null), is(false)); + assertThat(KeyspaceIdentifier.isValid("foo"), is(false)); + assertThat(KeyspaceIdentifier.isValid(""), is(false)); + assertThat(KeyspaceIdentifier.isValid("foo:bar"), is(true)); + assertThat(KeyspaceIdentifier.isValid("foo:bar:baz"), is(true)); + assertThat(KeyspaceIdentifier.isValid("foo:bar:baz:phantom"), is(true)); + } + + @Test // DATAREDIS-744 + public void shouldReturnKeyspace() { + + assertThat(KeyspaceIdentifier.of("foo:bar").getKeyspace(), is("foo")); + assertThat(KeyspaceIdentifier.of("foo:bar:baz").getKeyspace(), is("foo")); + assertThat(KeyspaceIdentifier.of("foo:bar:baz:phantom").getKeyspace(), is("foo")); + } + + @Test // DATAREDIS-744 + public void shouldReturnId() { + + assertThat(KeyspaceIdentifier.of("foo:bar").getId(), is("bar")); + assertThat(KeyspaceIdentifier.of("foo:bar:baz").getId(), is("bar:baz")); + assertThat(KeyspaceIdentifier.of("foo:bar:baz:phantom").getId(), is("bar:baz")); + } + + @Test // DATAREDIS-744 + public void shouldReturnPhantomKey() { + + assertThat(KeyspaceIdentifier.of("foo:bar").isPhantomKey(), is(false)); + assertThat(KeyspaceIdentifier.of("foo:bar:baz").isPhantomKey(), is(false)); + assertThat(KeyspaceIdentifier.of("foo:bar:baz:phantom").isPhantomKey(), is(true)); + } +}