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:
Mark Paluch
2016-02-19 10:02:57 +01:00
committed by Christoph Strobl
parent 4362c58a8a
commit 92274a8450
35 changed files with 1972 additions and 61 deletions

View File

@@ -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;

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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();

View File

@@ -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!");
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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 {};