DATAREDIS-425 - Add JSR-310 support, CDI extension and Update reference documentation.
We ship converters for JSR-310 types (LocalDate/Time, ZonedDateTime, Period, Duration and ZoneId) to map between UTF-8-encoded byte[] and JDK 8 date/time types. We also export Redis Repositories in a CDI environment. Repositories can be injected using @Inject. The CDI extension requires at least RedisOperations to be provided. Other beans like RedisKeyValueAdapter and RedisKeyValueTemplate can be provided by the user. If no RedisKeyValueAdapter/RedisKeyValueTemplate beans are found, the CDI extension creates own managed instances. Original Pull Request: #156
This commit is contained in:
committed by
Christoph Strobl
parent
4362c58a8a
commit
92274a8450
@@ -29,6 +29,11 @@ import org.springframework.data.redis.util.ByteUtils;
|
||||
*/
|
||||
public class RedisKeyExpiredEvent<T> extends RedisKeyspaceEvent {
|
||||
|
||||
/**
|
||||
* Use {@literal UTF-8} as default charset.
|
||||
*/
|
||||
public static final Charset CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
private final byte[][] args;
|
||||
private final Object value;
|
||||
|
||||
@@ -62,7 +67,7 @@ public class RedisKeyExpiredEvent<T> extends RedisKeyspaceEvent {
|
||||
public String getKeyspace() {
|
||||
|
||||
if (args.length >= 2) {
|
||||
return new String(args[0], Charset.forName("UTF-8"));
|
||||
return new String(args[0], CHARSET);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -160,6 +160,12 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
initKeyExpirationListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
protected RedisKeyValueAdapter() {
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#put(java.io.Serializable, java.lang.Object, java.io.Serializable)
|
||||
|
||||
@@ -66,6 +66,7 @@ class RedisQueryEngine extends QueryEngine<RedisKeyValueAdapter, RedisOperationC
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.keyvalue.core.QueryEngine#execute(java.lang.Object, java.lang.Object, int, int, java.io.Serializable, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> Collection<T> execute(final RedisOperationChain criteria, final Comparator<?> sort, final int offset,
|
||||
final int rows, final Serializable keyspace, Class<T> type) {
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ final class BinaryConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
private static class StringBasedConverter {
|
||||
static class StringBasedConverter {
|
||||
|
||||
byte[] fromString(String source) {
|
||||
|
||||
@@ -166,7 +166,7 @@ final class BinaryConverters {
|
||||
if (value == null || value.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
return (T) Enum.valueOf(this.enumType, value.trim());
|
||||
return Enum.valueOf(this.enumType, value.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,8 +183,8 @@ final class BinaryConverters {
|
||||
return new BytesToNumberConverter<T>(targetType);
|
||||
}
|
||||
|
||||
private static final class BytesToNumberConverter<T extends Number> extends StringBasedConverter implements
|
||||
Converter<byte[], T> {
|
||||
private static final class BytesToNumberConverter<T extends Number> extends StringBasedConverter
|
||||
implements Converter<byte[], T> {
|
||||
|
||||
private final Class<T> targetType;
|
||||
|
||||
|
||||
@@ -98,6 +98,13 @@ public class Bucket {
|
||||
return data.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of key-value mappings of the {@link Bucket}.
|
||||
*/
|
||||
public int size() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@@ -121,6 +128,12 @@ public class Bucket {
|
||||
return Collections.unmodifiableMap(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a bucket containing key/value pairs with the {@code prefix}.
|
||||
*
|
||||
* @param prefix
|
||||
* @return
|
||||
*/
|
||||
public Bucket extract(String prefix) {
|
||||
|
||||
Bucket partial = new Bucket();
|
||||
|
||||
@@ -103,6 +103,8 @@ public class CustomConversions {
|
||||
toRegister.add(new BinaryConverters.DateToBytesConverter());
|
||||
toRegister.add(new BinaryConverters.BytesToDateConverter());
|
||||
|
||||
toRegister.addAll(Jsr310Converters.getConvertersToRegister());
|
||||
|
||||
for (Object c : toRegister) {
|
||||
registerConversion(c);
|
||||
}
|
||||
@@ -161,7 +163,8 @@ public class CustomConversions {
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
throw new IllegalArgumentException("Given set contains element that is neither Converter nor ConverterFactory!");
|
||||
throw new IllegalArgumentException(
|
||||
"Given set contains element that is neither Converter nor ConverterFactory!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 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.data.redis.core.convert;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.redis.core.convert.BinaryConverters.StringBasedConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Helper class to register JSR-310 specific {@link Converter} implementations in case the we're running on Java 8.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public abstract class Jsr310Converters {
|
||||
|
||||
private static final boolean JAVA_8_IS_PRESENT = ClassUtils.isPresent("java.time.LocalDateTime",
|
||||
Jsr310Converters.class.getClassLoader());
|
||||
|
||||
/**
|
||||
* Returns the converters to be registered. Will only return converters in case we're running on Java 8.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Collection<Converter<?, ?>> getConvertersToRegister() {
|
||||
|
||||
if (!JAVA_8_IS_PRESENT) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
|
||||
converters.add(new LocalDateTimeToBytesConverter());
|
||||
converters.add(new BytesToLocalDateTimeConverter());
|
||||
converters.add(new LocalDateToBytesConverter());
|
||||
converters.add(new BytesToLocalDateConverter());
|
||||
converters.add(new LocalTimeToBytesConverter());
|
||||
converters.add(new BytesToLocalTimeConverter());
|
||||
converters.add(new ZonedDateTimeToBytesConverter());
|
||||
converters.add(new BytesToZonedDateTimeConverter());
|
||||
converters.add(new InstantToBytesConverter());
|
||||
converters.add(new BytesToInstantConverter());
|
||||
converters.add(new ZoneIdToBytesConverter());
|
||||
converters.add(new BytesToZoneIdConverter());
|
||||
converters.add(new PeriodToBytesConverter());
|
||||
converters.add(new BytesToPeriodConverter());
|
||||
converters.add(new DurationToBytesConverter());
|
||||
converters.add(new BytesToDurationConverter());
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
public static boolean supports(Class<?> type) {
|
||||
|
||||
if (!JAVA_8_IS_PRESENT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.<Class<?>> asList(LocalDateTime.class, LocalDate.class, LocalTime.class, Instant.class,
|
||||
ZonedDateTime.class, ZoneId.class, Period.class, Duration.class).contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class LocalDateTimeToBytesConverter extends StringBasedConverter implements Converter<LocalDateTime, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(LocalDateTime source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToLocalDateTimeConverter extends StringBasedConverter implements Converter<byte[], LocalDateTime> {
|
||||
|
||||
@Override
|
||||
public LocalDateTime convert(byte[] source) {
|
||||
return LocalDateTime.parse(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class LocalDateToBytesConverter extends StringBasedConverter implements Converter<LocalDate, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(LocalDate source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToLocalDateConverter extends StringBasedConverter implements Converter<byte[], LocalDate> {
|
||||
|
||||
@Override
|
||||
public LocalDate convert(byte[] source) {
|
||||
return LocalDate.parse(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class LocalTimeToBytesConverter extends StringBasedConverter implements Converter<LocalTime, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(LocalTime source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToLocalTimeConverter extends StringBasedConverter implements Converter<byte[], LocalTime> {
|
||||
|
||||
@Override
|
||||
public LocalTime convert(byte[] source) {
|
||||
return LocalTime.parse(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class ZonedDateTimeToBytesConverter extends StringBasedConverter implements Converter<ZonedDateTime, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(ZonedDateTime source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToZonedDateTimeConverter extends StringBasedConverter implements Converter<byte[], ZonedDateTime> {
|
||||
|
||||
@Override
|
||||
public ZonedDateTime convert(byte[] source) {
|
||||
return ZonedDateTime.parse(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class InstantToBytesConverter extends StringBasedConverter implements Converter<Instant, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(Instant source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToInstantConverter extends StringBasedConverter implements Converter<byte[], Instant> {
|
||||
|
||||
@Override
|
||||
public Instant convert(byte[] source) {
|
||||
return Instant.parse(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class ZoneIdToBytesConverter extends StringBasedConverter implements Converter<ZoneId, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(ZoneId source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToZoneIdConverter extends StringBasedConverter implements Converter<byte[], ZoneId> {
|
||||
|
||||
@Override
|
||||
public ZoneId convert(byte[] source) {
|
||||
return ZoneId.of(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class PeriodToBytesConverter extends StringBasedConverter implements Converter<Period, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(Period source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToPeriodConverter extends StringBasedConverter implements Converter<byte[], Period> {
|
||||
|
||||
@Override
|
||||
public Period convert(byte[] source) {
|
||||
return Period.parse(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@WritingConverter
|
||||
static class DurationToBytesConverter extends StringBasedConverter implements Converter<Duration, byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] convert(Duration source) {
|
||||
return fromString(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
@ReadingConverter
|
||||
static class BytesToDurationConverter extends StringBasedConverter implements Converter<byte[], Duration> {
|
||||
|
||||
@Override
|
||||
public Duration convert(byte[] source) {
|
||||
return Duration.parse(toString(source));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -144,6 +144,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityReader#read(java.lang.Class, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public <R> R read(Class<R> type, final RedisData source) {
|
||||
return readInternal("", type, source);
|
||||
}
|
||||
@@ -282,11 +283,11 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
|
||||
if (association.getInverse().isCollectionLike()) {
|
||||
|
||||
Collection<Object> target = CollectionFactory.createCollection(association.getInverse().getType(),
|
||||
association.getInverse().getComponentType(), 10);
|
||||
|
||||
Bucket bucket = source.getBucket().extract(currentPath + ".[");
|
||||
|
||||
Collection<Object> target = CollectionFactory.createCollection(association.getInverse().getType(),
|
||||
association.getInverse().getComponentType(), bucket.size());
|
||||
|
||||
for (Entry<String, byte[]> entry : bucket.entrySet()) {
|
||||
|
||||
String referenceKey = fromBytes(entry.getValue(), String.class);
|
||||
@@ -327,6 +328,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
public void write(Object source, final RedisData sink) {
|
||||
|
||||
@@ -402,8 +404,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
writeCollection(keyspace, propertyStringPath, (Collection<?>) accessor.getProperty(persistentProperty),
|
||||
persistentProperty.getTypeInformation().getComponentType(), sink);
|
||||
} else if (persistentProperty.isEntity()) {
|
||||
writeInternal(keyspace, propertyStringPath, accessor.getProperty(persistentProperty), persistentProperty
|
||||
.getTypeInformation().getActualType(), sink);
|
||||
writeInternal(keyspace, propertyStringPath, accessor.getProperty(persistentProperty),
|
||||
persistentProperty.getTypeInformation().getActualType(), sink);
|
||||
} else {
|
||||
|
||||
Object propertyValue = accessor.getProperty(persistentProperty);
|
||||
@@ -436,8 +438,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
|
||||
if (association.getInverse().isCollectionLike()) {
|
||||
|
||||
KeyValuePersistentEntity<?> ref = mappingContext.getPersistentEntity(association.getInverse()
|
||||
.getTypeInformation().getComponentType().getActualType());
|
||||
KeyValuePersistentEntity<?> ref = mappingContext
|
||||
.getPersistentEntity(association.getInverse().getTypeInformation().getComponentType().getActualType());
|
||||
|
||||
String keyspace = ref.getKeySpace();
|
||||
String propertyStringPath = (!path.isEmpty() ? path + "." : "") + association.getInverse().getName();
|
||||
@@ -452,8 +454,8 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
|
||||
} else {
|
||||
|
||||
KeyValuePersistentEntity<?> ref = mappingContext.getPersistentEntity(association.getInverse()
|
||||
.getTypeInformation());
|
||||
KeyValuePersistentEntity<?> ref = mappingContext
|
||||
.getPersistentEntity(association.getInverse().getTypeInformation());
|
||||
String keyspace = ref.getKeySpace();
|
||||
|
||||
Object refId = ref.getPropertyAccessor(refObject).getProperty(ref.getIdProperty());
|
||||
@@ -529,10 +531,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
private Collection<?> readCollectionOfSimpleTypes(String path, Class<?> collectionType, Class<?> valueType,
|
||||
RedisData source) {
|
||||
|
||||
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, 10);
|
||||
|
||||
Bucket partial = source.getBucket().extract(path + ".[");
|
||||
|
||||
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, partial.size());
|
||||
|
||||
for (byte[] value : partial.values()) {
|
||||
target.add(fromBytes(value, valueType));
|
||||
}
|
||||
@@ -549,10 +551,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
private Collection<?> readCollectionOfComplexTypes(String path, Class<?> collectionType, Class<?> valueType,
|
||||
Bucket source) {
|
||||
|
||||
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, 10);
|
||||
|
||||
Set<String> keys = source.extractAllKeysFor(path);
|
||||
|
||||
Collection<Object> target = CollectionFactory.createCollection(collectionType, valueType, keys.size());
|
||||
|
||||
for (String key : keys) {
|
||||
|
||||
Bucket elementData = source.extract(key);
|
||||
@@ -577,6 +579,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
* @param sink
|
||||
*/
|
||||
private void writeMap(String keyspace, String path, Class<?> mapValueType, Map<?, ?> source, RedisData sink) {
|
||||
|
||||
if (CollectionUtils.isEmpty(source)) {
|
||||
return;
|
||||
}
|
||||
@@ -608,10 +611,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
private Map<?, ?> readMapOfSimpleTypes(String path, Class<?> mapType, Class<?> keyType, Class<?> valueType,
|
||||
RedisData source) {
|
||||
|
||||
Map<Object, Object> target = CollectionFactory.createMap(mapType, 10);
|
||||
|
||||
Bucket partial = source.getBucket().extract(path + ".[");
|
||||
|
||||
Map<Object, Object> target = CollectionFactory.createMap(mapType, partial.size());
|
||||
|
||||
for (Entry<String, byte[]> entry : partial.entrySet()) {
|
||||
|
||||
String regex = "^(" + Pattern.quote(path) + "\\.\\[)(.*?)(\\])";
|
||||
@@ -639,10 +642,10 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
private Map<?, ?> readMapOfComplexTypes(String path, Class<?> mapType, Class<?> keyType, Class<?> valueType,
|
||||
RedisData source) {
|
||||
|
||||
Map<Object, Object> target = CollectionFactory.createMap(mapType, 10);
|
||||
|
||||
Set<String> keys = source.getBucket().extractAllKeysFor(path);
|
||||
|
||||
Map<Object, Object> target = CollectionFactory.createMap(mapType, keys.size());
|
||||
|
||||
for (String key : keys) {
|
||||
|
||||
String regex = "^(" + Pattern.quote(path) + "\\.\\[)(.*?)(\\])";
|
||||
@@ -717,6 +720,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityConverter#getMappingContext()
|
||||
*/
|
||||
@Override
|
||||
public RedisMappingContext getMappingContext() {
|
||||
return this.mappingContext;
|
||||
}
|
||||
@@ -725,6 +729,7 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityConverter#getConversionService()
|
||||
*/
|
||||
@Override
|
||||
public ConversionService getConversionService() {
|
||||
return this.conversionService;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
|
||||
|
||||
/**
|
||||
* Redis sepcific {@link EntityConverter}.
|
||||
* Redis specific {@link EntityConverter}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.data.util.TypeInformation;
|
||||
/**
|
||||
* {@link IndexDefinition} allow to set up a blueprint for creating secondary index structures in Redis. Setting up
|
||||
* conditions allows to define {@link Condition} that have to be passed in order to add a value to the index. This
|
||||
* allows to fine grained tune the I index structure. {@link IndexValueTransformer} gets applied to the raw value for
|
||||
* allows to fine grained tune the index structure. {@link IndexValueTransformer} gets applied to the raw value for
|
||||
* creating the actual index entry.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright 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.data.redis.repository.cdi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.context.spi.CreationalContext;
|
||||
import javax.enterprise.inject.Alternative;
|
||||
import javax.enterprise.inject.Default;
|
||||
import javax.enterprise.inject.Stereotype;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.enterprise.inject.spi.InjectionPoint;
|
||||
import javax.enterprise.inject.spi.PassivationCapable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base class for {@link Bean} wrappers.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public abstract class CdiBean<T> implements Bean<T>, PassivationCapable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CdiBean.class);
|
||||
|
||||
protected final BeanManager beanManager;
|
||||
|
||||
private final Set<Annotation> qualifiers;
|
||||
private final Set<Type> types;
|
||||
private final Class<T> beanClass;
|
||||
private final String passivationId;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CdiBean}.
|
||||
*
|
||||
* @param qualifiers must not be {@literal null}.
|
||||
* @param beanClass has to be an interface must not be {@literal null}.
|
||||
* @param beanManager the CDI {@link BeanManager}, must not be {@literal null}.
|
||||
*/
|
||||
public CdiBean(Set<Annotation> qualifiers, Class<T> beanClass, BeanManager beanManager) {
|
||||
this(qualifiers, Collections.<Type> emptySet(), beanClass, beanManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CdiBean}.
|
||||
*
|
||||
* @param qualifiers must not be {@literal null}.
|
||||
* @param types additional bean types, must not be {@literal null}.
|
||||
* @param beanClass must not be {@literal null}.
|
||||
* @param beanManager the CDI {@link BeanManager}, must not be {@literal null}.
|
||||
*/
|
||||
public CdiBean(Set<Annotation> qualifiers, Set<Type> types, Class<T> beanClass, BeanManager beanManager) {
|
||||
|
||||
Assert.notNull(qualifiers);
|
||||
Assert.notNull(beanManager);
|
||||
Assert.notNull(types);
|
||||
Assert.notNull(beanClass);
|
||||
|
||||
this.qualifiers = qualifiers;
|
||||
this.types = types;
|
||||
this.beanClass = beanClass;
|
||||
this.beanManager = beanManager;
|
||||
this.passivationId = createPassivationId(qualifiers, beanClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique identifier for the given repository type and the given annotations.
|
||||
*
|
||||
* @param qualifiers must not be {@literal null} or contain {@literal null} values.
|
||||
* @param repositoryType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private final String createPassivationId(Set<Annotation> qualifiers, Class<?> repositoryType) {
|
||||
|
||||
List<String> qualifierNames = new ArrayList<String>(qualifiers.size());
|
||||
|
||||
for (Annotation qualifier : qualifiers) {
|
||||
qualifierNames.add(qualifier.annotationType().getName());
|
||||
}
|
||||
|
||||
Collections.sort(qualifierNames);
|
||||
|
||||
StringBuilder builder = new StringBuilder(StringUtils.collectionToDelimitedString(qualifierNames, ":"));
|
||||
builder.append(":").append(repositoryType.getName());
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#getTypes()
|
||||
*/
|
||||
public Set<Type> getTypes() {
|
||||
|
||||
Set<Type> types = new HashSet<Type>();
|
||||
types.add(beanClass);
|
||||
types.addAll(Arrays.asList(beanClass.getInterfaces()));
|
||||
types.addAll(this.types);
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the given {@link Bean} from the container.
|
||||
*
|
||||
* @param <S> the actual class type of the {@link Bean}.
|
||||
* @param bean the {@link Bean} defining the instance to create.
|
||||
* @param type the expected component type of the instance created from the {@link Bean}.
|
||||
* @return an instance of the given {@link Bean}.
|
||||
* @see javax.enterprise.inject.spi.BeanManager#getReference(Bean, Type, CreationalContext)
|
||||
* @see javax.enterprise.inject.spi.Bean
|
||||
* @see java.lang.reflect.Type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <S> S getDependencyInstance(Bean<S> bean, Type type) {
|
||||
return (S) beanManager.getReference(bean, type, beanManager.createCreationalContext(bean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the initialization of bean target.
|
||||
*/
|
||||
public final void initialize() {
|
||||
create(beanManager.createCreationalContext(this));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.context.spi.Contextual#destroy(java.lang.Object, javax.enterprise.context.spi.CreationalContext)
|
||||
*/
|
||||
public void destroy(T instance, CreationalContext<T> creationalContext) {
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("Destroying bean instance %s for repository type '%s'.", instance.toString(),
|
||||
beanClass.getName()));
|
||||
}
|
||||
|
||||
creationalContext.release();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#getQualifiers()
|
||||
*/
|
||||
public Set<Annotation> getQualifiers() {
|
||||
return qualifiers;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#getName()
|
||||
*/
|
||||
public String getName() {
|
||||
|
||||
return getQualifiers().contains(Default.class) ? beanClass.getName()
|
||||
: beanClass.getName() + "-" + getQualifiers().toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#getStereotypes()
|
||||
*/
|
||||
public Set<Class<? extends Annotation>> getStereotypes() {
|
||||
|
||||
Set<Class<? extends Annotation>> stereotypes = new HashSet<Class<? extends Annotation>>();
|
||||
|
||||
for (Annotation annotation : beanClass.getAnnotations()) {
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
if (annotationType.isAnnotationPresent(Stereotype.class)) {
|
||||
stereotypes.add(annotationType);
|
||||
}
|
||||
}
|
||||
|
||||
return stereotypes;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#getBeanClass()
|
||||
*/
|
||||
public Class<?> getBeanClass() {
|
||||
return beanClass;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#isAlternative()
|
||||
*/
|
||||
public boolean isAlternative() {
|
||||
return beanClass.isAnnotationPresent(Alternative.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#isNullable()
|
||||
*/
|
||||
public boolean isNullable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#getInjectionPoints()
|
||||
*/
|
||||
public Set<InjectionPoint> getInjectionPoints() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.Bean#getScope()
|
||||
*/
|
||||
public Class<? extends Annotation> getScope() {
|
||||
return ApplicationScoped.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see javax.enterprise.inject.spi.PassivationCapable#getId()
|
||||
*/
|
||||
public String getId() {
|
||||
return passivationId;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("CdiBean: type='%s', qualifiers=%s", beanClass.getName(), qualifiers.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 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.data.redis.repository.cdi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.spi.CreationalContext;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.data.redis.core.RedisKeyValueAdapter;
|
||||
import org.springframework.data.redis.core.RedisKeyValueTemplate;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link CdiBean} to create {@link RedisKeyValueAdapter} instances.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class RedisKeyValueAdapterBean extends CdiBean<RedisKeyValueAdapter> {
|
||||
|
||||
private final Bean<RedisOperations<?, ?>> redisOperations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RedisKeyValueAdapterBean}.
|
||||
*
|
||||
* @param redisOperations must not be {@literal null}.
|
||||
* @param qualifiers must not be {@literal null}.
|
||||
* @param repositoryType must not be {@literal null}.
|
||||
* @param beanManager must not be {@literal null}.
|
||||
*/
|
||||
public RedisKeyValueAdapterBean(Bean<RedisOperations<?, ?>> redisOperations, Set<Annotation> qualifiers,
|
||||
BeanManager beanManager) {
|
||||
|
||||
super(qualifiers, RedisKeyValueAdapter.class, beanManager);
|
||||
Assert.notNull(redisOperations);
|
||||
this.redisOperations = redisOperations;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public RedisKeyValueAdapter create(CreationalContext<RedisKeyValueAdapter> creationalContext) {
|
||||
|
||||
Type beanType = getBeanType();
|
||||
|
||||
RedisOperations<?, ?> redisOperations = getDependencyInstance(this.redisOperations, beanType);
|
||||
RedisKeyValueAdapter redisKeyValueAdapter = new RedisKeyValueAdapter(redisOperations);
|
||||
|
||||
return redisKeyValueAdapter;
|
||||
}
|
||||
|
||||
private Type getBeanType() {
|
||||
|
||||
for (Type type : this.redisOperations.getTypes()) {
|
||||
if (type instanceof Class<?> && RedisOperations.class.isAssignableFrom((Class<?>) type)) {
|
||||
return type;
|
||||
}
|
||||
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
if (parameterizedType.getRawType() instanceof Class<?>
|
||||
&& RedisOperations.class.isAssignableFrom((Class<?>) parameterizedType.getRawType())) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Cannot resolve bean type for class " + RedisOperations.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(RedisKeyValueAdapter instance, CreationalContext<RedisKeyValueAdapter> creationalContext) {
|
||||
|
||||
if (instance instanceof DisposableBean) {
|
||||
try {
|
||||
instance.destroy();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
super.destroy(instance, creationalContext);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 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.data.redis.repository.cdi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.spi.CreationalContext;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.redis.core.RedisKeyValueAdapter;
|
||||
import org.springframework.data.redis.core.RedisKeyValueTemplate;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link CdiBean} to create {@link RedisKeyValueTemplate} instances.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class RedisKeyValueTemplateBean extends CdiBean<KeyValueOperations> {
|
||||
|
||||
private final Bean<RedisKeyValueAdapter> keyValueAdapter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RedisKeyValueTemplateBean}.
|
||||
*
|
||||
* @param keyValueAdapter must not be {@literal null}.
|
||||
* @param qualifiers must not be {@literal null}.
|
||||
* @param beanManager must not be {@literal null}.
|
||||
*/
|
||||
public RedisKeyValueTemplateBean(Bean<RedisKeyValueAdapter> keyValueAdapter, Set<Annotation> qualifiers,
|
||||
BeanManager beanManager) {
|
||||
|
||||
super(qualifiers, KeyValueOperations.class, beanManager);
|
||||
Assert.notNull(keyValueAdapter);
|
||||
this.keyValueAdapter = keyValueAdapter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public KeyValueOperations create(CreationalContext<KeyValueOperations> creationalContext) {
|
||||
|
||||
RedisKeyValueAdapter keyValueAdapter = getDependencyInstance(this.keyValueAdapter, RedisKeyValueAdapter.class);
|
||||
|
||||
RedisMappingContext redisMappingContext = new RedisMappingContext();
|
||||
redisMappingContext.afterPropertiesSet();
|
||||
|
||||
RedisKeyValueTemplate redisKeyValueTemplate = new RedisKeyValueTemplate(keyValueAdapter, redisMappingContext);
|
||||
return redisKeyValueTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(KeyValueOperations instance, CreationalContext<KeyValueOperations> creationalContext) {
|
||||
|
||||
if (instance.getMappingContext() instanceof DisposableBean) {
|
||||
try {
|
||||
((DisposableBean) instance.getMappingContext()).destroy();
|
||||
instance.destroy();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
super.destroy(instance, creationalContext);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 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.data.redis.repository.cdi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.spi.CreationalContext;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.redis.repository.query.RedisQueryCreator;
|
||||
import org.springframework.data.redis.repository.support.RedisRepositoryFactory;
|
||||
import org.springframework.data.repository.cdi.CdiRepositoryBean;
|
||||
import org.springframework.data.repository.config.CustomRepositoryImplementationDetector;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link CdiRepositoryBean} to create Redis repository instances.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class RedisRepositoryBean<T> extends CdiRepositoryBean<T> {
|
||||
|
||||
private final Bean<KeyValueOperations> keyValueTemplate;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CdiRepositoryBean}.
|
||||
*
|
||||
* @param keyValueTemplate must not be {@literal null}.
|
||||
* @param qualifiers must not be {@literal null}.
|
||||
* @param repositoryType must not be {@literal null}.
|
||||
* @param beanManager must not be {@literal null}.
|
||||
* @param detector detector for the custom {@link org.springframework.data.repository.Repository} implementations
|
||||
* {@link CustomRepositoryImplementationDetector}, can be {@literal null}.
|
||||
*/
|
||||
public RedisRepositoryBean(Bean<KeyValueOperations> keyValueTemplate, Set<Annotation> qualifiers,
|
||||
Class<T> repositoryType, BeanManager beanManager, CustomRepositoryImplementationDetector detector) {
|
||||
|
||||
super(qualifiers, repositoryType, beanManager, detector);
|
||||
Assert.notNull(keyValueTemplate);
|
||||
this.keyValueTemplate = keyValueTemplate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType, Object customImplementation) {
|
||||
|
||||
KeyValueOperations keyValueTemplate = getDependencyInstance(this.keyValueTemplate, KeyValueOperations.class);
|
||||
RedisRepositoryFactory factory = new RedisRepositoryFactory(keyValueTemplate, RedisQueryCreator.class);
|
||||
|
||||
return factory.getRepository(repositoryType, customImplementation);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright 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.data.redis.repository.cdi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.event.Observes;
|
||||
import javax.enterprise.inject.UnsatisfiedResolutionException;
|
||||
import javax.enterprise.inject.spi.AfterBeanDiscovery;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.enterprise.inject.spi.ProcessBean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.redis.core.RedisKeyValueAdapter;
|
||||
import org.springframework.data.redis.core.RedisKeyValueTemplate;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.repository.cdi.CdiRepositoryBean;
|
||||
import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport;
|
||||
|
||||
/**
|
||||
* CDI extension to export Redis repositories. This extension enables Redis
|
||||
* {@link org.springframework.data.repository.Repository} support. It requires either a {@link RedisKeyValueTemplate} or a
|
||||
* {@link RedisOperations} bean. If no {@link RedisKeyValueTemplate} or {@link RedisKeyValueAdapter} are provided by the
|
||||
* user, the extension creates own managed beans.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class RedisRepositoryExtension extends CdiRepositoryExtensionSupport {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RedisRepositoryExtension.class);
|
||||
|
||||
private final Map<Set<Annotation>, Bean<RedisKeyValueAdapter>> redisKeyValueAdapters = new HashMap<Set<Annotation>, Bean<RedisKeyValueAdapter>>();
|
||||
private final Map<Set<Annotation>, Bean<KeyValueOperations>> redisKeyValueTemplates = new HashMap<Set<Annotation>, Bean<KeyValueOperations>>();
|
||||
private final Map<Set<Annotation>, Bean<RedisOperations<?, ?>>> redisOperations = new HashMap<Set<Annotation>, Bean<RedisOperations<?, ?>>>();
|
||||
|
||||
public RedisRepositoryExtension() {
|
||||
LOG.info("Activating CDI extension for Spring Data Redis repositories.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick up existing bean definitions that are required for a Repository to work.
|
||||
*
|
||||
* @param processBean
|
||||
* @param <X>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
<X> void processBean(@Observes ProcessBean<X> processBean) {
|
||||
|
||||
Bean<X> bean = processBean.getBean();
|
||||
|
||||
for (Type type : bean.getTypes()) {
|
||||
Type beanType = type;
|
||||
|
||||
if (beanType instanceof ParameterizedType) {
|
||||
beanType = ((ParameterizedType) beanType).getRawType();
|
||||
}
|
||||
|
||||
if (beanType instanceof Class<?> && RedisKeyValueTemplate.class.isAssignableFrom((Class<?>) beanType)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Discovered %s with qualifiers %s.", RedisKeyValueTemplate.class.getName(),
|
||||
bean.getQualifiers()));
|
||||
}
|
||||
|
||||
// Store the Key-Value Templates bean using its qualifiers.
|
||||
redisKeyValueTemplates.put(new HashSet<Annotation>(bean.getQualifiers()), (Bean<KeyValueOperations>) bean);
|
||||
}
|
||||
|
||||
if (beanType instanceof Class<?> && RedisKeyValueAdapter.class.isAssignableFrom((Class<?>) beanType)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Discovered %s with qualifiers %s.", RedisKeyValueAdapter.class.getName(),
|
||||
bean.getQualifiers()));
|
||||
}
|
||||
|
||||
// Store the RedisKeyValueAdapter bean using its qualifiers.
|
||||
redisKeyValueAdapters.put(new HashSet<Annotation>(bean.getQualifiers()), (Bean<RedisKeyValueAdapter>) bean);
|
||||
}
|
||||
|
||||
if (beanType instanceof Class<?> && RedisOperations.class.isAssignableFrom((Class<?>) beanType)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Discovered %s with qualifiers %s.", RedisOperations.class.getName(),
|
||||
bean.getQualifiers()));
|
||||
}
|
||||
|
||||
// Store the RedisOperations bean using its qualifiers.
|
||||
redisOperations.put(new HashSet<Annotation>(bean.getQualifiers()), (Bean<RedisOperations<?, ?>>) bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
|
||||
|
||||
registerDependenciesIfNecessary(afterBeanDiscovery, beanManager);
|
||||
|
||||
for (Entry<Class<?>, Set<Annotation>> entry : getRepositoryTypes()) {
|
||||
|
||||
Class<?> repositoryType = entry.getKey();
|
||||
Set<Annotation> qualifiers = entry.getValue();
|
||||
|
||||
// Create the bean representing the repository.
|
||||
CdiRepositoryBean<?> repositoryBean = createRepositoryBean(repositoryType, qualifiers, beanManager);
|
||||
|
||||
if (LOG.isInfoEnabled()) {
|
||||
LOG.info(String.format("Registering bean for %s with qualifiers %s.", repositoryType.getName(), qualifiers));
|
||||
}
|
||||
|
||||
// Register the bean to the container.
|
||||
registerBean(repositoryBean);
|
||||
afterBeanDiscovery.addBean(repositoryBean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register {@link RedisKeyValueAdapter} and {@link RedisKeyValueTemplate} if these beans are not provided by the CDI
|
||||
* application.
|
||||
*
|
||||
* @param afterBeanDiscovery
|
||||
* @param beanManager
|
||||
*/
|
||||
private void registerDependenciesIfNecessary(@Observes AfterBeanDiscovery afterBeanDiscovery,
|
||||
BeanManager beanManager) {
|
||||
|
||||
for (Entry<Class<?>, Set<Annotation>> entry : getRepositoryTypes()) {
|
||||
|
||||
Set<Annotation> qualifiers = entry.getValue();
|
||||
|
||||
if (!redisKeyValueAdapters.containsKey(qualifiers)) {
|
||||
if (LOG.isInfoEnabled()) {
|
||||
LOG.info(String.format("Registering bean for %s with qualifiers %s.", RedisKeyValueAdapter.class.getName(),
|
||||
qualifiers));
|
||||
}
|
||||
RedisKeyValueAdapterBean redisKeyValueAdapterBean = createRedisKeyValueAdapterBean(qualifiers, beanManager);
|
||||
redisKeyValueAdapters.put(qualifiers, redisKeyValueAdapterBean);
|
||||
afterBeanDiscovery.addBean(redisKeyValueAdapterBean);
|
||||
}
|
||||
|
||||
if (!redisKeyValueTemplates.containsKey(qualifiers)) {
|
||||
if (LOG.isInfoEnabled()) {
|
||||
LOG.info(String.format("Registering bean for %s with qualifiers %s.", RedisKeyValueTemplate.class.getName(),
|
||||
qualifiers));
|
||||
}
|
||||
|
||||
RedisKeyValueTemplateBean redisKeyValueTemplateBean = createRedisKeyValueTemplateBean(qualifiers, beanManager);
|
||||
redisKeyValueTemplates.put(qualifiers, redisKeyValueTemplateBean);
|
||||
afterBeanDiscovery.addBean(redisKeyValueTemplateBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CdiRepositoryBean} for the repository of the given type, requires a {@link KeyValueOperations}
|
||||
* bean with the same qualifiers.
|
||||
*
|
||||
* @param <T> the type of the repository.
|
||||
* @param repositoryType the class representing the repository.
|
||||
* @param qualifiers the qualifiers to be applied to the bean.
|
||||
* @param beanManager the BeanManager instance.
|
||||
* @return
|
||||
*/
|
||||
private <T> CdiRepositoryBean<T> createRepositoryBean(Class<T> repositoryType, Set<Annotation> qualifiers,
|
||||
BeanManager beanManager) {
|
||||
|
||||
// Determine the MongoOperations bean which matches the qualifiers of the repository.
|
||||
Bean<KeyValueOperations> redisKeyValueTemplate = this.redisKeyValueTemplates.get(qualifiers);
|
||||
|
||||
if (redisKeyValueTemplate == null) {
|
||||
throw new UnsatisfiedResolutionException(String.format("Unable to resolve a bean for '%s' with qualifiers %s.",
|
||||
RedisKeyValueTemplate.class.getName(), qualifiers));
|
||||
}
|
||||
|
||||
// Construct and return the repository bean.
|
||||
return new RedisRepositoryBean<T>(redisKeyValueTemplate, qualifiers, repositoryType, beanManager,
|
||||
getCustomImplementationDetector());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RedisKeyValueAdapterBean}, requires a {@link RedisOperations} bean with the same qualifiers.
|
||||
*
|
||||
* @param qualifiers the qualifiers to be applied to the bean.
|
||||
* @param beanManager the BeanManager instance.
|
||||
* @return
|
||||
*/
|
||||
private RedisKeyValueAdapterBean createRedisKeyValueAdapterBean(Set<Annotation> qualifiers, BeanManager beanManager) {
|
||||
|
||||
// Determine the MongoOperations bean which matches the qualifiers of the repository.
|
||||
Bean<RedisOperations<?, ?>> redisOperationsBean = this.redisOperations.get(qualifiers);
|
||||
|
||||
if (redisOperationsBean == null) {
|
||||
throw new UnsatisfiedResolutionException(String.format("Unable to resolve a bean for '%s' with qualifiers %s.",
|
||||
RedisOperations.class.getName(), qualifiers));
|
||||
}
|
||||
|
||||
// Construct and return the repository bean.
|
||||
return new RedisKeyValueAdapterBean(redisOperationsBean, qualifiers, beanManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RedisKeyValueTemplateBean}, requires a {@link RedisKeyValueAdapter} bean with the same qualifiers.
|
||||
*
|
||||
* @param qualifiers the qualifiers to be applied to the bean.
|
||||
* @param beanManager the BeanManager instance.
|
||||
* @return
|
||||
*/
|
||||
private RedisKeyValueTemplateBean createRedisKeyValueTemplateBean(Set<Annotation> qualifiers,
|
||||
BeanManager beanManager) {
|
||||
|
||||
// Determine the MongoOperations bean which matches the qualifiers of the repository.
|
||||
Bean<RedisKeyValueAdapter> redisKeyValueAdapterBean = this.redisKeyValueAdapters.get(qualifiers);
|
||||
|
||||
if (redisKeyValueAdapterBean == null) {
|
||||
throw new UnsatisfiedResolutionException(String.format("Unable to resolve a bean for '%s' with qualifiers %s.",
|
||||
RedisKeyValueAdapter.class.getName(), qualifiers));
|
||||
}
|
||||
|
||||
// Construct and return the repository bean.
|
||||
return new RedisKeyValueTemplateBean(redisKeyValueAdapterBean, qualifiers, beanManager);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* CDI support for Redis specific repository implementation.
|
||||
*/
|
||||
package org.springframework.data.redis.repository.cdi;
|
||||
@@ -53,7 +53,7 @@ public @interface EnableRedisRepositories {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
|
||||
* {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}.
|
||||
* {@code @EnableRedisRepositories("org.my.pkg")} instead of {@code @EnableRedisRepositories(basePackages="org.my.pkg")}.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user