Skip to content

Register Converters for Offset java.time types in Jsr310Converters #2681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>3.2.0-SNAPSHOT</version>
<version>3.2.0-GH-2677-SNAPSHOT</version>

<name>Spring Data Redis</name>
<description>Spring Data module for Redis</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Set;
import java.util.UUID;

import org.springframework.core.convert.converter.Converter;
Expand All @@ -46,6 +48,24 @@ final class BinaryConverters {

private BinaryConverters() {}

static Collection<?> getConvertersToRegister() {

return Set.of(
new BinaryConverters.StringToBytesConverter(),
new BinaryConverters.BytesToStringConverter(),
new BinaryConverters.NumberToBytesConverter(),
new BinaryConverters.BytesToNumberConverterFactory(),
new BinaryConverters.EnumToBytesConverter(),
new BinaryConverters.BytesToEnumConverterFactory(),
new BinaryConverters.BooleanToBytesConverter(),
new BinaryConverters.BytesToBooleanConverter(),
new BinaryConverters.DateToBytesConverter(),
new BinaryConverters.BytesToDateConverter(),
new BinaryConverters.UuidToBytesConverter(),
new BinaryConverters.BytesToUuidConverter()
Comment on lines +54 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop the extra BinaryConverters. part here.

);
}

/**
* @author Christoph Strobl
* @since 1.7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
* 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.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
Expand All @@ -47,9 +48,11 @@ public abstract class Jsr310Converters {
Jsr310Converters.class.getClassLoader());

/**
* Returns the converters to be registered. Will only return converters in case we're running on Java 8.
* Returns the {@link Converter Converters} to be registered.
* <p>
* Will only return {@link Converter Converters} in case we're running on Java 8.
*
* @return
* @return the {@link Converter Converters} to be registered.
*/
public static Collection<Converter<?, ?>> getConvertersToRegister() {

Expand All @@ -58,6 +61,7 @@ public abstract class Jsr310Converters {
}

List<Converter<?, ?>> converters = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be changed to List.of or initialized with an initial size.


converters.add(new LocalDateTimeToBytesConverter());
converters.add(new BytesToLocalDateTimeConverter());
converters.add(new LocalDateToBytesConverter());
Expand All @@ -74,6 +78,10 @@ public abstract class Jsr310Converters {
converters.add(new BytesToPeriodConverter());
converters.add(new DurationToBytesConverter());
converters.add(new BytesToDurationConverter());
converters.add(new OffsetDateTimeToBytesConverter());
converters.add(new BytesToOffsetDateTimeConverter());
converters.add(new OffsetTimeToBytesConverter());
converters.add(new BytesToOffsetTimeConverter());

return converters;
}
Expand Down Expand Up @@ -296,4 +304,51 @@ public Duration convert(byte[] source) {
}
}

/**
* @author John Blum
* @see java.time.OffsetDateTime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though not a public class, having a @since tag can help with code forensic

*/
static class OffsetDateTimeToBytesConverter extends StringBasedConverter implements Converter<OffsetDateTime, byte[]> {

@Override
public byte[] convert(OffsetDateTime source) {
return fromString(source.toString());
}
}

/**
* @author John Blum
* @see java.time.OffsetDateTime
*/
static class BytesToOffsetDateTimeConverter extends StringBasedConverter implements Converter<byte[], OffsetDateTime> {

@Override
public OffsetDateTime convert(byte[] source) {
return OffsetDateTime.parse(toString(source));
}
}

/**
* @author John Blum
* @see java.time.OffsetTime
*/
static class OffsetTimeToBytesConverter extends StringBasedConverter implements Converter<OffsetTime, byte[]> {

@Override
public byte[] convert(OffsetTime source) {
return fromString(source.toString());
}
}

/**
* @author John Blum
* @see java.time.OffsetTime
*/
static class BytesToOffsetTimeConverter extends StringBasedConverter implements Converter<byte[], OffsetTime> {

@Override
public OffsetTime convert(byte[] source) {
return OffsetTime.parse(toString(source));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,7 @@ public class RedisCustomConversions extends org.springframework.data.convert.Cus

List<Object> converters = new ArrayList<>();

converters.add(new BinaryConverters.StringToBytesConverter());
converters.add(new BinaryConverters.BytesToStringConverter());
converters.add(new BinaryConverters.NumberToBytesConverter());
converters.add(new BinaryConverters.BytesToNumberConverterFactory());
converters.add(new BinaryConverters.EnumToBytesConverter());
converters.add(new BinaryConverters.BytesToEnumConverterFactory());
converters.add(new BinaryConverters.BooleanToBytesConverter());
converters.add(new BinaryConverters.BytesToBooleanConverter());
converters.add(new BinaryConverters.DateToBytesConverter());
converters.add(new BinaryConverters.BytesToDateConverter());
converters.add(new BinaryConverters.UuidToBytesConverter());
converters.add(new BinaryConverters.BytesToUuidConverter());
converters.addAll(BinaryConverters.getConvertersToRegister());
converters.addAll(Jsr310Converters.getConvertersToRegister());

STORE_CONVERTERS = Collections.unmodifiableList(converters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.test.condition.EnabledOnRedisClusterAvailable;
import org.springframework.lang.NonNullApi;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

Expand All @@ -52,13 +51,14 @@ class RedisRepositoryClusterIntegrationTests extends RedisRepositoryIntegrationT
@EnableRedisRepositories(considerNestedRepositories = true, indexConfiguration = MyIndexConfiguration.class,
keyspaceConfiguration = MyKeyspaceConfiguration.class,
includeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class }) })
classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class, UserRepository.class }) })
static class Config {

@Bean
RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {

RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();

template.setConnectionFactory(connectionFactory);

return template;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import static org.assertj.core.api.Assertions.assertThat;

import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -49,6 +51,9 @@
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* Base for testing Redis repository support in different configurations.
Expand All @@ -62,6 +67,7 @@ public abstract class RedisRepositoryIntegrationTestBase {
@Autowired PersonRepository repo;
@Autowired CityRepository cityRepo;
@Autowired ImmutableObjectRepository immutableObjectRepo;
@Autowired UserRepository userRepository;
@Autowired KeyValueTemplate kvTemplate;

@BeforeEach
Expand Down Expand Up @@ -495,6 +501,24 @@ void shouldProperlyReadNestedImmutableObject() {
assertThat(loaded.nested).isEqualTo(nested);
}

@Test // GH-2677
void shouldProperlyHandleEntityWithOffsetJavaTimeTypes() {

User jonDoe = User.as("Jon Doe")
.expires(OffsetTime.now().plusMinutes(5))
.lastAccess(OffsetDateTime.now());

this.userRepository.save(jonDoe);

User loadedJonDoe = this.userRepository.findById(jonDoe.getName()).orElse(null);

assertThat(loadedJonDoe).isNotNull();
assertThat(loadedJonDoe).isNotSameAs(jonDoe);
assertThat(loadedJonDoe.getName()).isEqualTo(jonDoe.getName());
assertThat(loadedJonDoe.getLastAccessed()).isEqualTo(jonDoe.getLastAccessed());
assertThat(loadedJonDoe.getExpiration()).isEqualTo(jonDoe.getExpiration());
}

public interface PersonRepository
extends PagingAndSortingRepository<Person, String>, CrudRepository<Person, String>,
QueryByExampleExecutor<Person> {
Expand Down Expand Up @@ -542,6 +566,8 @@ public interface CityRepository extends CrudRepository<City, String> {

public interface ImmutableObjectRepository extends CrudRepository<Immutable, String> {}

public interface UserRepository extends CrudRepository<User, String> { }

/**
* Custom Redis {@link IndexConfiguration} forcing index of {@link Person#lastname}.
*
Expand Down Expand Up @@ -784,4 +810,72 @@ public Immutable withNested(Immutable nested) {
return Objects.equals(getNested(), nested) ? this : new Immutable(this.id, this.name, nested);
}
}

@RedisHash("Users")
static class User {

static User as(@NonNull String name) {
Assert.hasText(name, () -> String.format("Name [%s] of User is required", name));
return new User(name);
}

private OffsetDateTime lastAccessed;

private OffsetTime expiration;

@Id
private final String name;

private User(@NonNull String name) {
this.name = name;
}

@Nullable
public OffsetTime getExpiration() {
return this.expiration;
}

@Nullable
public OffsetDateTime getLastAccessed() {
return this.lastAccessed;
}

public String getName() {
return this.name;
}

public User lastAccess(@Nullable OffsetDateTime dateTime) {
this.lastAccessed = dateTime;
return this;
}

public User expires(@Nullable OffsetTime time) {
this.expiration = time;
return this;
}

@Override
public boolean equals(Object obj) {

if (this == obj) {
return true;
}

if (!(obj instanceof User that)) {
return false;
}

return this.getName().equals(that.getName());
}

@Override
public int hashCode() {
return Objects.hash(getName());
}

@Override
public String toString() {
return getName();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ public class RedisRepositoryIntegrationTests extends RedisRepositoryIntegrationT
@EnableRedisRepositories(considerNestedRepositories = true, indexConfiguration = MyIndexConfiguration.class,
keyspaceConfiguration = MyKeyspaceConfiguration.class,
includeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class }) })

classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class, UserRepository.class }) })
static class Config {

@Bean
Expand All @@ -70,6 +69,7 @@ RedisConnectionFactory connectionFactory() {
RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {

RedisTemplate<String, String> template = new RedisTemplate<>();

template.setDefaultSerializer(StringRedisSerializer.UTF_8);
template.setConnectionFactory(connectionFactory);

Expand Down Expand Up @@ -107,6 +107,7 @@ private RedisTypeMapper customTypeMapper() {
public void shouldConsiderCustomTypeMapper() {

Person rand = new Person();

rand.id = "rand";
rand.firstname = "rand";
rand.lastname = "al'thor";
Expand Down