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

@@ -1,6 +1,5 @@
language: java
jdk:
- oraclejdk7
- oraclejdk8
env:
matrix:

30
pom.xml
View File

@@ -152,6 +152,36 @@
<optional>true</optional>
</dependency>
<!-- CDI -->
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>${cdi}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>${cdi}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>test</scope>
</dependency>
<!-- Test -->
<dependency>

View File

@@ -14,7 +14,7 @@ To access domain entities stored in a Redis you can leverage repository support
====
[source,java]
----
@RedisHash("persons");
@RedisHash("persons")
public class Person {
@Id String id;
@@ -158,7 +158,7 @@ of Complex Type
addresses.[work].city = "...
|===
Mapping behavior can be customized by registering the according `Converter` in `CustomConversions`. Those converters can take care of converting from/to a single `byte[]` as well as `Map<String,byte[]>` whereas the first one is suitable for eg. converting one complex type to eg. a binary JSON representation that still uses the default mappings hash structure, whereas the second options offers full control over the resulting hash.
Mapping behavior can be customized by registering the according `Converter` in `CustomConversions`. Those converters can take care of converting from/to a single `byte[]` as well as `Map<String,byte[]>` whereas the first one is suitable for eg. converting one complex type to eg. a binary JSON representation that still uses the default mappings hash structure. The second option offers full control over the resulting hash. Writing objects to a Redis hash will delete the content from the hash and re-create the whole hash, so not mapped data will be lost.
.Sample byte[] Converters
====
@@ -308,7 +308,7 @@ public class ApplicationConfig {
[[redis.repositories.indexes]]
== Secondary Indexes
http://redis.io/topics/indexes[Secondary indexes] are used to enable lookup operations based on native redis structures. Values are written to the according indexes on every save and are removed when objects are deleted or <<redis.repositories.expirations,expire>>.
http://redis.io/topics/indexes[Secondary indexes] are used to enable lookup operations based on native Redis structures. Values are written to the according indexes on every save and are removed when objects are deleted or <<redis.repositories.expirations,expire>>.
Given the sample `Person` entity we can create an index for _firstname_ by annotating the property with `@Indexed`.
@@ -316,7 +316,7 @@ Given the sample `Person` entity we can create an index for _firstname_ by annot
====
[source,java]
----
@RedisHash("persons");
@RedisHash("persons")
public class Person {
@Id String id;
@@ -351,7 +351,7 @@ Further more the programmatic setup allows to define indexes on map keys and lis
====
[source,java]
----
@RedisHash("persons");
@RedisHash("persons")
public class Person {
// ... other properties omitted
@@ -457,16 +457,19 @@ public class TimeToLiveOnMethod {
The repository implementation ensures subscription to http://redis.io/topics/notifications[Redis keyspace notifications] via `RedisMessageListenerContainer`.
When the expiration is set to a positive value the according `EXPIRE` command is executed.
Additionally to persisting the original, a _phantom_ copy is persisted in Redis and set to expire 5 minutes after the original one. This done to enable the Repository support to publish `RedisKeyExpiredEvent` holding the expired value via Springs `ApplicationEventPublisher` whenever a key expires even though the original values have already been gone.
Additionally to persisting the original, a _phantom_ copy is persisted in Redis and set to expire 5 minutes after the original one. This is done to enable the Repository support to publish `RedisKeyExpiredEvent` holding the expired value via Springs `ApplicationEventPublisher` whenever a key expires even though the original values have already been gone. Expiry events
will be received on all connected applications using Spring Data Redis repositories.
The `RedisKeyExpiredEvent` will hold a copy of the actually expired domain object as well as the key.
NOTE: The keyspace notification message listener will alter `notify-keyspace-events` settings in Redis if those are not already set. Existing settings will not be overridden so it is left to the user to set those up correctly when not leaving them empty.
NOTE: The keyspace notification message listener will alter `notify-keyspace-events` settings in Redis if those are not already set. Existing settings will not be overridden, so it is left to the user to set those up correctly when not leaving them empty.
NOTE: Redis Pub/Sub messages are not persistent. If a key expires while the application is down the expiry event will not be processed which may lead to secondary indexes containing still references to the expired object.
[[redis.repositories.references]]
== Persisting References
Marking properties with `@Reference` allows to store a simple key reference instead of copying the all values into the hash itself.
On loading from Redis references are resolved automatically and mapped back into the object.
Marking properties with `@Reference` allows storing a simple key reference instead of copying values into the hash itself.
On loading from Redis, references are resolved automatically and mapped back into the object.
.Sample Property Reference
====
@@ -499,9 +502,12 @@ public interface PersonRepository extends CrudRepository<Person, String> {
----
====
NOTE: Please make sure properties used in finder methods are set up for indexing.
Using derived finder methods might not always be sufficient to model the queries to execute. `RedisCallback` offers more control over the actual matching of index structures or even customly added ones. All it takes is providing a `RedisCallback` that returns a single or `Iterable` set of _id_ values.
NOTE: Query methods for Redis repositories support only queries for entities and collections of entities with paging.
Using derived query methods might not always be sufficient to model the queries to execute. `RedisCallback` offers more control over the actual matching of index structures or even custom added ones. All it takes is providing a `RedisCallback` that returns a single or `Iterable` set of _id_ values.
.Sample finder using RedisCallback
====
@@ -518,6 +524,81 @@ List<RedisSession> sessionsByUser = template.find(new RedisCallback<Set<byte[]>>
----
====
Here's an overview of the keywords supported for Redis and what a method containing that keyword essentially translates to.
====
.Supported keywords inside method names
[options = "header, autowidth"]
|===============
|Keyword|Sample|Redis snippet
|`And`|`findByLastnameAndFirstname`|`SINTER …:firstname:rand …:lastname:althor`
|`Or`|`findByLastnameOrFirstname`|`SUNION …:firstname:rand …:lastname:althor`
|`Is,Equals`|`findByFirstname`,`findByFirstnameIs`,`findByFirstnameEquals`|`SINTER …:firstname:rand`
|===============
====
[[redis.misc.cdi-integration]]
== CDI integration
Instances of the repository interfaces are usually created by a container, which Spring is the most natural choice when working with Spring Data. There's sophisticated support to easily set up Spring to create bean instances. Spring Data Redis ships with a custom CDI extension that allows using the repository abstraction in CDI environments. The extension is part of the JAR so all you need to do to activate it is dropping the Spring Data Redis JAR into your classpath.
You can now set up the infrastructure by implementing a CDI Producer for the `RedisConnectionFactory` and `RedisOperations`:
[source, java]
----
class RedisOperationsProducer {
@Produces
RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setHostName("localhost");
jedisConnectionFactory.setPort(6379);
jedisConnectionFactory.afterPropertiesSet();
return jedisConnectionFactory;
}
void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {
if (redisConnectionFactory instanceof DisposableBean) {
((DisposableBean) redisConnectionFactory).destroy();
}
}
@Produces
@ApplicationScoped
RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
}
----
The necessary setup can vary depending on the JavaEE environment you run in.
The Spring Data Redis CDI extension will pick up all Repositories available as CDI beans and create a proxy for a Spring Data repository whenever a bean of a repository type is requested by the container. Thus obtaining an instance of a Spring Data repository is a matter of declaring an `@Injected` property:
[source, java]
----
class RepositoryClient {
@Inject
PersonRepository repository;
public void businessMethod() {
List<Person> people = repository.findAll();
}
}
----
A Redis Repository requires `RedisKeyValueAdapter` and `RedisKeyValueTemplate` instances. These beans are created and managed by the Spring Data CDI extension if no provided beans are found. You can however supply your own beans to configure the specific properties of `RedisKeyValueAdapter` and `RedisKeyValueTemplate`.

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

View File

@@ -0,0 +1 @@
org.springframework.data.redis.repository.cdi.RedisRepositoryExtension

View File

@@ -15,6 +15,14 @@
*/
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.Date;
import java.util.List;
import java.util.Map;
@@ -50,6 +58,15 @@ public class ConversionTestEntities {
Boolean alive;
Date birthdate;
LocalDate localDate;
LocalDateTime localDateTime;
LocalTime localTime;
Instant instant;
ZonedDateTime zonedDateTime;
ZoneId zoneId;
Duration duration;
Period period;
Address address;
Map<String, String> physicalAttributes;

View File

@@ -27,6 +27,14 @@ import static org.springframework.data.redis.test.util.IsBucketMatcher.*;
import java.io.Serializable;
import java.nio.charset.Charset;
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.Calendar;
@@ -182,8 +190,8 @@ public class MappingRedisConverterUnitTests {
RedisData target = write(rand);
assertThat(target.getBucket(),
isBucket().containingUtf8String("address.city", "two rivers").containingUtf8String("address.country", "andora"));
assertThat(target.getBucket(), isBucket().containingUtf8String("address.city", "two rivers")
.containingUtf8String("address.country", "andora"));
}
/**
@@ -207,10 +215,11 @@ public class MappingRedisConverterUnitTests {
RedisData target = write(rand);
assertThat(target.getBucket(), isBucket().containingUtf8String("coworkers.[0].firstname", "mat") //
.containingUtf8String("coworkers.[0].nicknames.[0]", "prince of the ravens") //
.containingUtf8String("coworkers.[1].firstname", "perrin") //
.containingUtf8String("coworkers.[1].address.city", "two rivers"));
assertThat(target.getBucket(),
isBucket().containingUtf8String("coworkers.[0].firstname", "mat") //
.containingUtf8String("coworkers.[0].nicknames.[0]", "prince of the ravens") //
.containingUtf8String("coworkers.[1].firstname", "perrin") //
.containingUtf8String("coworkers.[1].address.city", "two rivers"));
}
/**
@@ -509,6 +518,191 @@ public class MappingRedisConverterUnitTests {
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("age", "20"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesLocalDateTimeValuesCorrectly() {
rand.localDateTime = LocalDateTime.parse("2016-02-19T10:18:01");
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("localDateTime", "2016-02-19T10:18:01"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsLocalDateTimeValuesCorrectly() {
Person target = converter.read(Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("localDateTime", "2016-02-19T10:18:01"))));
assertThat(target.localDateTime, is(LocalDateTime.parse("2016-02-19T10:18:01")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesLocalDateValuesCorrectly() {
rand.localDate = LocalDate.parse("2016-02-19");
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("localDate", "2016-02-19"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsLocalDateValuesCorrectly() {
Person target = converter.read(Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("localDate", "2016-02-19"))));
assertThat(target.localDate, is(LocalDate.parse("2016-02-19")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesLocalTimeValuesCorrectly() {
rand.localTime = LocalTime.parse("11:12:13");
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("localTime", "11:12:13"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsLocalTimeValuesCorrectly() {
Person target = converter.read(Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("localTime", "11:12"))));
assertThat(target.localTime, is(LocalTime.parse("11:12:00")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesZonedDateTimeValuesCorrectly() {
rand.zonedDateTime = ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");
assertThat(write(rand).getBucket(),
isBucket().containingUtf8String("zonedDateTime", "2007-12-03T10:15:30+01:00[Europe/Paris]"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsZonedDateTimeValuesCorrectly() {
Person target = converter.read(Person.class, new RedisData(Bucket
.newBucketFromStringMap(Collections.singletonMap("zonedDateTime", "2007-12-03T10:15:30+01:00[Europe/Paris]"))));
assertThat(target.zonedDateTime, is(ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesInstantValuesCorrectly() {
rand.instant = Instant.parse("2007-12-03T10:15:30.01Z");
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("instant", "2007-12-03T10:15:30.010Z"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsInstantValuesCorrectly() {
Person target = converter.read(Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("instant", "2007-12-03T10:15:30.01Z"))));
assertThat(target.instant, is(Instant.parse("2007-12-03T10:15:30.01Z")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesZoneIdValuesCorrectly() {
rand.zoneId = ZoneId.of("Europe/Paris");
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("zoneId", "Europe/Paris"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsZoneIdValuesCorrectly() {
Person target = converter.read(Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("zoneId", "Europe/Paris"))));
assertThat(target.zoneId, is(ZoneId.of("Europe/Paris")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesDurationValuesCorrectly() {
rand.duration = Duration.parse("P2DT3H4M");
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("duration", "PT51H4M"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsDurationValuesCorrectly() {
Person target = converter.read(Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("duration", "PT51H4M"))));
assertThat(target.duration, is(Duration.parse("P2DT3H4M")));
}
/**
* @see DATAREDIS-425
*/
@Test
public void writesPeriodValuesCorrectly() {
rand.period = Period.parse("P1Y2M25D");
assertThat(write(rand).getBucket(), isBucket().containingUtf8String("period", "P1Y2M25D"));
}
/**
* @see DATAREDIS-425
*/
@Test
public void readsPeriodValuesCorrectly() {
Person target = converter.read(Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("period", "P1Y2M25D"))));
assertThat(target.period, is(Period.parse("P1Y2M25D")));
}
/**
* @see DATAREDIS-425
*/
@@ -592,10 +786,8 @@ public class MappingRedisConverterUnitTests {
Date date = cal.getTime();
Person target = converter.read(
Person.class,
new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("birthdate", Long.valueOf(date.getTime())
.toString()))));
Person target = converter.read(Person.class, new RedisData(
Bucket.newBucketFromStringMap(Collections.singletonMap("birthdate", Long.valueOf(date.getTime()).toString()))));
assertThat(target.birthdate, is(date));
}
@@ -614,9 +806,10 @@ public class MappingRedisConverterUnitTests {
RedisData target = write(rand);
assertThat(target.getBucket(), isBucket().containingUtf8String("location", "locations:1") //
.without("location.id") //
.without("location.name"));
assertThat(target.getBucket(),
isBucket().containingUtf8String("location", "locations:1") //
.without("location.id") //
.without("location.name"));
}
/**
@@ -661,9 +854,10 @@ public class MappingRedisConverterUnitTests {
RedisData target = write(rand);
assertThat(target.getBucket(), isBucket().containingUtf8String("coworkers.[0].location", "locations:1") //
.without("coworkers.[0].location.id") //
.without("coworkers.[0].location.name"));
assertThat(target.getBucket(),
isBucket().containingUtf8String("coworkers.[0].location", "locations:1") //
.without("coworkers.[0].location.id") //
.without("coworkers.[0].location.name"));
}
/**
@@ -713,9 +907,10 @@ public class MappingRedisConverterUnitTests {
RedisData target = write(rand);
assertThat(target.getBucket(), isBucket().containingUtf8String("visited.[0]", "locations:1") //
.containingUtf8String("visited.[1]", "locations:2") //
.containingUtf8String("visited.[2]", "locations:3"));
assertThat(target.getBucket(),
isBucket().containingUtf8String("visited.[0]", "locations:1") //
.containingUtf8String("visited.[1]", "locations:2") //
.containingUtf8String("visited.[2]", "locations:3"));
}
/**
@@ -882,8 +1077,8 @@ public class MappingRedisConverterUnitTests {
address.country = "andor";
rand.address = address;
assertThat(write(rand).getIndexedData(), hasItem(new SimpleIndexedPropertyValue(KEYSPACE_PERSON, "address.country",
"andor")));
assertThat(write(rand).getIndexedData(),
hasItem(new SimpleIndexedPropertyValue(KEYSPACE_PERSON, "address.country", "andor")));
}
/**
@@ -1151,8 +1346,8 @@ public class MappingRedisConverterUnitTests {
species.name = new String(source.get("species-name"), Charset.forName("UTF-8"));
}
if (source.containsKey("species-nicknames")) {
species.alsoKnownAs = Arrays.asList(StringUtils.commaDelimitedListToStringArray(new String(source
.get("species-nicknames"), Charset.forName("UTF-8"))));
species.alsoKnownAs = Arrays.asList(StringUtils
.commaDelimitedListToStringArray(new String(source.get("species-nicknames"), Charset.forName("UTF-8"))));
}
return species;
}

View File

@@ -96,7 +96,7 @@ public class RedisRepositoryIntegrationTests {
* @see DATAREDIS-425
*/
@Test
public void simpleFindSouldReturnEntitiesCorrectly() {
public void simpleFindShouldReturnEntitiesCorrectly() {
Person rand = new Person();
rand.firstname = "rand";

View File

@@ -0,0 +1,114 @@
/*
* 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 static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.Bean;
import org.apache.webbeans.cditest.CdiTestContainer;
import org.apache.webbeans.cditest.CdiTestContainerLoader;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Integration tests for Spring Data Redis CDI extension.
*
* @author Mark Paluch
*/
public class CdiExtensionIntegrationTests {
private static Logger LOGGER = LoggerFactory.getLogger(CdiExtensionIntegrationTests.class);
static CdiTestContainer container;
@BeforeClass
public static void setUp() throws Exception {
container = CdiTestContainerLoader.getCdiContainer();
container.bootContainer();
LOGGER.debug("CDI container bootstrapped!");
}
/**
* @see DATAREDIS-425
*/
@Test
@SuppressWarnings("rawtypes")
public void beanShouldBeRegistered() {
Set<Bean<?>> beans = container.getBeanManager().getBeans(PersonRepository.class);
assertThat(beans, hasSize(1));
assertThat(beans.iterator().next().getScope(), is(equalTo((Class) ApplicationScoped.class)));
}
/**
* @see DATAREDIS-425
*/
@Test
public void saveAndFindUnqualified() {
RepositoryConsumer repositoryConsumer = container.getInstance(RepositoryConsumer.class);
repositoryConsumer.deleteAll();
Person person = new Person();
person.setName("foo");
repositoryConsumer.getUnqualifiedRepo().save(person);
List<Person> result = repositoryConsumer.getUnqualifiedRepo().findByName("foo");
assertThat(result, contains(person));
}
/**
* @see DATAREDIS-425
*/
@Test
public void saveAndFindQualified() {
RepositoryConsumer repositoryConsumer = container.getInstance(RepositoryConsumer.class);
repositoryConsumer.deleteAll();
Person person = new Person();
person.setName("foo");
repositoryConsumer.getUnqualifiedRepo().save(person);
List<Person> result = repositoryConsumer.getQualifiedRepo().findByName("foo");
assertThat(result, contains(person));
}
/**
* @see DATAREDIS-425
*/
@Test
public void callMethodOnCustomRepositoryShouldSuceed() {
RepositoryConsumer repositoryConsumer = container.getInstance(RepositoryConsumer.class);
int result = repositoryConsumer.getUnqualifiedRepo().returnOne();
assertThat(result, is(1));
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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 org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
/**
* @author Mark Paluch
*/
@RedisHash
class Person {
@Id private String id;
@Indexed private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Person))
return false;
Person person = (Person) o;
if (id != null ? !id.equals(person.id) : person.id != null)
return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
* @author Mark Paluch
*/
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@interface PersonDB {
}

View File

@@ -0,0 +1,33 @@
/*
* 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.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.cdi.Eager;
/**
* @author Mark Paluch
*/
@Eager
public interface PersonRepository extends CrudRepository<Person, String>, PersonRepositoryCustom {
List<Person> findAll();
List<Person> findByName(String name);
}

View File

@@ -0,0 +1,25 @@
/*
* 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;
/**
* @author Mark Paluch
*/
interface PersonRepositoryCustom {
int returnOne();
}

View File

@@ -0,0 +1,28 @@
/*
* 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;
/**
* @author Mark Paluch
*/
public class PersonRepositoryImpl implements PersonRepositoryCustom {
@Override
public int returnOne() {
return 1;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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;
/**
* @author Mark Paluch
*/
@PersonDB
public interface QualifiedPersonRepository extends PersonRepository {
}

View File

@@ -0,0 +1,94 @@
/*
* 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 javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.data.redis.SettingsUtils;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
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.redis.core.RedisTemplate;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
/**
* @author Mark Paluch
*/
public class RedisCdiDependenciesProducer {
/**
* Provides a producer method for {@link RedisConnectionFactory}.
*/
@Produces
public RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setHostName(SettingsUtils.getHost());
jedisConnectionFactory.setPort(SettingsUtils.getPort());
jedisConnectionFactory.afterPropertiesSet();
return jedisConnectionFactory;
}
public void closeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {
if (redisConnectionFactory instanceof DisposableBean) {
((DisposableBean) redisConnectionFactory).destroy();
}
}
/**
* Provides a producer method for {@link RedisOperations}.
*/
@Produces
public RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
// shortcut for managed KeyValueAdapter/Template.
@Produces
@PersonDB
public RedisOperations<byte[], byte[]> redisOperationsProducerQualified(RedisOperations<byte[], byte[]> instance) {
return instance;
}
public void closeRedisOperations(@Disposes RedisOperations<byte[], byte[]> redisOperations) throws Exception {
if (redisOperations instanceof DisposableBean) {
((DisposableBean) redisOperations).destroy();
}
}
/**
* Provides a producer method for {@link RedisKeyValueTemplate}.
*/
@Produces
public RedisKeyValueTemplate redisKeyValueAdapterDefault(RedisOperations<?, ?> redisOperations) {
RedisKeyValueAdapter redisKeyValueAdapter = new RedisKeyValueAdapter(redisOperations);
RedisKeyValueTemplate keyValueTemplate = new RedisKeyValueTemplate(redisKeyValueAdapter, new RedisMappingContext());
return keyValueTemplate;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 javax.inject.Inject;
/**
* @author Mark Paluch
*/
class RepositoryConsumer {
@Inject PersonRepository unqualifiedRepo;
@Inject @PersonDB PersonRepository qualifiedRepo;
public PersonRepository getUnqualifiedRepo() {
return unqualifiedRepo;
}
public PersonRepository getQualifiedRepo() {
return qualifiedRepo;
}
public void deleteAll() {
unqualifiedRepo.deleteAll();
qualifiedRepo.deleteAll();
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

View File

@@ -28,6 +28,7 @@ Import-Template:
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
org.w3c.dom.*;version="0",
javax.xml.transform.*;resolution:="optional";version="0",
javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional,
org.jredis.*;resolution:="optional";version="[1.0.0, 2.0.0)",
redis.clients.*;resolution:="optional";version="${jedis}",
org.apache.commons.pool2.*;resolution:="optional";version="${pool}",