diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 4d5a59ab1..bacba0cc6 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -9,6 +9,7 @@ New and noteworthy in the latest releases. * Unix domain socket connections using <>. * <> support using Lettuce. * <> integration. +* `@TypeAlias` Support for Redis repositories. [[new-in-2.0.0]] == New in Spring Data Redis 2.0 diff --git a/src/main/java/org/springframework/data/redis/core/convert/Bucket.java b/src/main/java/org/springframework/data/redis/core/convert/Bucket.java index 4ea77a4ed..23e8c8a17 100644 --- a/src/main/java/org/springframework/data/redis/core/convert/Bucket.java +++ b/src/main/java/org/springframework/data/redis/core/convert/Bucket.java @@ -15,6 +15,11 @@ */ package org.springframework.data.redis.core.convert; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; + import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -203,6 +208,27 @@ public class Bucket { return raw; } + /** + * Get the {@link BucketPropertyPath} leading to the current {@link Bucket}. + * + * @return new instance of {@link BucketPropertyPath}. + * @since 2.1 + */ + public BucketPropertyPath getPath() { + return BucketPropertyPath.from(this); + } + + /** + * Get the {@link BucketPropertyPath} for a given property within the current {@link Bucket}. + * + * @param property the property to look up. + * @return new instance of {@link BucketPropertyPath}. + * @since 2.1 + */ + public BucketPropertyPath getPropertyPath(String property) { + return BucketPropertyPath.from(this, property); + } + /** * Creates a new Bucket from a given raw map. * @@ -256,7 +282,6 @@ public class Bucket { } } return serialized.toString(); - } @Nullable @@ -270,4 +295,65 @@ public class Bucket { return null; } + /** + * Value object representing a path within a {@link Bucket}. Paths can be either top-level (if the {@code prefix} is + * {@literal null} or empty) or nested with a given {@code prefix}. + * + * @author Mark Paluch + * @since 2.1 + */ + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class BucketPropertyPath { + + private final @NonNull Bucket bucket; + private final @Nullable String prefix; + + /** + * Creates a top-level {@link BucketPropertyPath} given {@link Bucket}. + * + * @param bucket the bucket, must not be {@literal null}. + * @return {@link BucketPropertyPath} within the given {@link Bucket}. + */ + public static BucketPropertyPath from(Bucket bucket) { + return new BucketPropertyPath(bucket, null); + } + + /** + * Creates a {@link BucketPropertyPath} given {@link Bucket} and {@code prefix}. The resulting path is top-level if + * {@code prefix} is empty or nested, if {@code prefix} is not empty. + * + * @param bucket the bucket, must not be {@literal null}. + * @param prefix the prefix. Property path is top-level if {@code prefix} is {@literal null} or empty. + * @return {@link BucketPropertyPath} within the given {@link Bucket} using {@code prefix}. + */ + public static BucketPropertyPath from(Bucket bucket, @Nullable String prefix) { + return new BucketPropertyPath(bucket, prefix); + } + + /** + * Retrieve a value at {@code key} considering top-level/nesting. + * + * @param key must not be {@literal null} or empty. + * @return the resulting value, may be {@literal null}. + */ + @Nullable + public byte[] get(String key) { + return bucket.get(getPath(key)); + } + + /** + * Write a {@code value} at {@code key} considering top-level/nesting. + * + * @param key must not be {@literal null} or empty. + * @param value the value. + */ + public void put(String key, byte[] value) { + bucket.put(getPath(key), value); + } + + private String getPath(String key) { + return StringUtils.hasText(prefix) ? prefix + "." + key : key; + } + } } diff --git a/src/main/java/org/springframework/data/redis/core/convert/BucketPropertyPath.java b/src/main/java/org/springframework/data/redis/core/convert/BucketPropertyPath.java deleted file mode 100644 index 00524ab62..000000000 --- a/src/main/java/org/springframework/data/redis/core/convert/BucketPropertyPath.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NonNull; - -import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; - -/** - * Value object representing a path within a {@link Bucket}. Paths can be either top-level (if the {@code prefix} is - * {@literal null} or empty) or nested with a given {@code prefix}. - * - * @author Mark Paluch - * @since 2.1 - */ -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@Getter -public class BucketPropertyPath { - - private final @NonNull Bucket bucket; - private final @Nullable String prefix; - - /** - * Creates a top-level {@link BucketPropertyPath} given {@link Bucket}. - * - * @param bucket the bucket, must not be {@literal null}. - * @return {@link BucketPropertyPath} within the given {@link Bucket}. - */ - public static BucketPropertyPath from(Bucket bucket) { - return new BucketPropertyPath(bucket, null); - } - - /** - * Creates a {@link BucketPropertyPath} given {@link Bucket} and {@code prefix}. The resulting path is top-level if - * {@code prefix} is empty or nested, if {@code prefix} is not empty. - * - * @param bucket the bucket, must not be {@literal null}. - * @param prefix the prefix. Property path is top-level if {@code prefix} is {@literal null} or empty. - * @return {@link BucketPropertyPath} within the given {@link Bucket} using {@code prefix}. - */ - public static BucketPropertyPath from(Bucket bucket, @Nullable String prefix) { - return new BucketPropertyPath(bucket, prefix); - } - - /** - * Retrieve a value at {@code key} considering top-level/nesting. - * - * @param key must not be {@literal null} or empty. - * @return the resulting value, may be {@literal null}. - */ - @Nullable - public byte[] get(String key) { - return bucket.get(getPath(key)); - } - - /** - * Write a {@code value} at {@code key} considering top-level/nesting. - * - * @param key must not be {@literal null} or empty. - * @param value the value. - */ - public void put(String key, byte[] value) { - bucket.put(getPath(key), value); - } - - private String getPath(String key) { - return StringUtils.hasText(prefix) ? prefix + "." + key : key; - } -} diff --git a/src/main/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapper.java b/src/main/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapper.java index 67af55cf8..752e527d2 100644 --- a/src/main/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapper.java +++ b/src/main/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapper.java @@ -27,6 +27,7 @@ import org.springframework.data.convert.TypeInformationMapper; import org.springframework.data.mapping.Alias; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.redis.core.convert.Bucket.BucketPropertyPath; import org.springframework.lang.Nullable; import org.springframework.util.Assert; 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 d63339686..f3c75ee8f 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 @@ -192,8 +192,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { return null; } - TypeInformation readType = typeMapper.readType(BucketPropertyPath.from(source.getBucket()), - ClassTypeInformation.from(type)); + TypeInformation readType = typeMapper.readType(source.getBucket().getPath(), ClassTypeInformation.from(type)); RedisPersistentEntity entity = mappingContext.getPersistentEntity(readType); @@ -278,7 +277,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { Bucket bucket = source.getBucket().extract(currentPath + "."); RedisData newBucket = new RedisData(bucket); - TypeInformation typeInformation = typeMapper.readType(BucketPropertyPath.from(bucket, currentPath), + TypeInformation typeInformation = typeMapper.readType(bucket.getPropertyPath(currentPath), persistentProperty.getTypeInformation().getActualType()); Class targetType = (Class) typeInformation.getType(); @@ -386,12 +385,12 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { RedisPersistentEntity entity = mappingContext.getPersistentEntity(source.getClass()); if (!customConversions.hasCustomWriteTarget(source.getClass())) { - typeMapper.writeType(ClassUtils.getUserClass(source), BucketPropertyPath.from(sink.getBucket())); + typeMapper.writeType(ClassUtils.getUserClass(source), sink.getBucket().getPath()); } if (entity == null) { - typeMapper.writeType(ClassUtils.getUserClass(source), BucketPropertyPath.from(sink.getBucket())); + typeMapper.writeType(ClassUtils.getUserClass(source), sink.getBucket().getPath()); sink.getBucket().put("_raw", conversionService.convert(source, byte[].class)); return; } @@ -583,7 +582,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { } if (value.getClass() != typeHint.getType()) { - typeMapper.writeType(value.getClass(), BucketPropertyPath.from(sink.getBucket(), path)); + typeMapper.writeType(value.getClass(), sink.getBucket().getPropertyPath(path)); } RedisPersistentEntity entity = mappingContext.getRequiredPersistentEntity(value.getClass()); @@ -743,7 +742,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { if (!targetType.filter(it -> ClassUtils.isAssignable(Map.class, it)).isPresent() && customConversions.isSimpleType(value.getClass()) && value.getClass() != propertyType) { - typeMapper.writeType(value.getClass(), BucketPropertyPath.from(sink.getBucket(), path)); + typeMapper.writeType(value.getClass(), sink.getBucket().getPropertyPath(path)); } if (targetType.filter(it -> ClassUtils.isAssignable(Map.class, it)).isPresent()) { @@ -779,7 +778,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { Bucket elementData = bucket.extract(key); - TypeInformation typeInformation = typeMapper.readType(BucketPropertyPath.from(elementData, key), + TypeInformation typeInformation = typeMapper.readType(elementData.getPropertyPath(key), ClassTypeInformation.from(valueType)); Class typeToUse = typeInformation.getType(); @@ -885,10 +884,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { for (String key : keys) { Bucket partial = source.getBucket().extract(key); - + Object mapKey = extractMapKeyForPath(path, key, keyType); - TypeInformation typeInformation = typeMapper.readType(BucketPropertyPath.from(source.getBucket(), key), + TypeInformation typeInformation = typeMapper.readType(source.getBucket().getPropertyPath(key), ClassTypeInformation.from(valueType)); Object o = readInternal(key, typeInformation.getType(), new RedisData(partial)); @@ -920,7 +919,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean { private Class getTypeHint(String path, Bucket bucket, Class fallback) { - TypeInformation typeInformation = typeMapper.readType(BucketPropertyPath.from(bucket, path), + TypeInformation typeInformation = typeMapper.readType(bucket.getPropertyPath(path), ClassTypeInformation.from(fallback)); return typeInformation.getType(); } diff --git a/src/main/java/org/springframework/data/redis/core/convert/RedisTypeMapper.java b/src/main/java/org/springframework/data/redis/core/convert/RedisTypeMapper.java index 92dd47f99..1ad97c746 100644 --- a/src/main/java/org/springframework/data/redis/core/convert/RedisTypeMapper.java +++ b/src/main/java/org/springframework/data/redis/core/convert/RedisTypeMapper.java @@ -16,13 +16,14 @@ package org.springframework.data.redis.core.convert; import org.springframework.data.convert.TypeMapper; +import org.springframework.data.redis.core.convert.Bucket.BucketPropertyPath; /** * Redis-specific {@link TypeMapper} exposing that {@link BucketPropertyPath}s might contain a type key. * * @author Mark Paluch * @since 2.1 - * @see BucketPropertyPath + * @see Bucket.BucketPropertyPath */ public interface RedisTypeMapper extends TypeMapper { diff --git a/src/test/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapperUnitTests.java b/src/test/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapperUnitTests.java index b96a5329f..cfde869ae 100644 --- a/src/test/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapperUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/convert/DefaultRedisTypeMapperUnitTests.java @@ -185,7 +185,7 @@ public class DefaultRedisTypeMapperUnitTests { private void readsTypeFromField(Bucket bucket, @Nullable Class type) { - TypeInformation typeInfo = typeMapper.readType(BucketPropertyPath.from(bucket)); + TypeInformation typeInfo = typeMapper.readType(bucket.getPath()); if (type != null) { assertThat(typeInfo).isNotNull(); @@ -197,7 +197,7 @@ public class DefaultRedisTypeMapperUnitTests { private void writesTypeToField(@Nullable String field, Bucket bucket, Class type) { - typeMapper.writeType(type, BucketPropertyPath.from(bucket)); + typeMapper.writeType(type, bucket.getPath()); if (field == null) { assertThat(bucket.keySet()).isEmpty(); @@ -209,7 +209,7 @@ public class DefaultRedisTypeMapperUnitTests { private void writesTypeToField(Bucket bucket, Class type, @Nullable Object value) { - typeMapper.writeType(type, BucketPropertyPath.from(bucket)); + typeMapper.writeType(type, bucket.getPath()); if (value == null) { assertThat(bucket.keySet()).isEmpty();