Skip to content

Commit eadd3f0

Browse files
jxblummp911de
authored andcommitted
Register Converters for Offset java.time types in JSR310Converters.
We now appropriately handle OffsetDateTime and OffsetTime the same as all other java.time types, supported as simple types on Spring application (persistent) entity classes. Closes #2677
1 parent 3d6dacc commit eadd3f0

File tree

6 files changed

+178
-18
lines changed

6 files changed

+178
-18
lines changed

src/main/java/org/springframework/data/redis/core/convert/BinaryConverters.java

+20
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.text.DateFormat;
2121
import java.text.ParseException;
2222
import java.util.Arrays;
23+
import java.util.Collection;
2324
import java.util.Date;
25+
import java.util.Set;
2426
import java.util.UUID;
2527

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

4749
private BinaryConverters() {}
4850

51+
static Collection<?> getConvertersToRegister() {
52+
53+
return Set.of(
54+
new BinaryConverters.StringToBytesConverter(),
55+
new BinaryConverters.BytesToStringConverter(),
56+
new BinaryConverters.NumberToBytesConverter(),
57+
new BinaryConverters.BytesToNumberConverterFactory(),
58+
new BinaryConverters.EnumToBytesConverter(),
59+
new BinaryConverters.BytesToEnumConverterFactory(),
60+
new BinaryConverters.BooleanToBytesConverter(),
61+
new BinaryConverters.BytesToBooleanConverter(),
62+
new BinaryConverters.DateToBytesConverter(),
63+
new BinaryConverters.BytesToDateConverter(),
64+
new BinaryConverters.UuidToBytesConverter(),
65+
new BinaryConverters.BytesToUuidConverter()
66+
);
67+
}
68+
4969
/**
5070
* @author Christoph Strobl
5171
* @since 1.7

src/main/java/org/springframework/data/redis/core/convert/Jsr310Converters.java

+58-3
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package org.springframework.data.redis.core.convert;
1817

1918
import java.time.Duration;
2019
import java.time.Instant;
2120
import java.time.LocalDate;
2221
import java.time.LocalDateTime;
2322
import java.time.LocalTime;
23+
import java.time.OffsetDateTime;
24+
import java.time.OffsetTime;
2425
import java.time.Period;
2526
import java.time.ZoneId;
2627
import java.time.ZonedDateTime;
@@ -47,9 +48,11 @@ public abstract class Jsr310Converters {
4748
Jsr310Converters.class.getClassLoader());
4849

4950
/**
50-
* Returns the converters to be registered. Will only return converters in case we're running on Java 8.
51+
* Returns the {@link Converter Converters} to be registered.
52+
* <p>
53+
* Will only return {@link Converter Converters} in case we're running on Java 8.
5154
*
52-
* @return
55+
* @return the {@link Converter Converters} to be registered.
5356
*/
5457
public static Collection<Converter<?, ?>> getConvertersToRegister() {
5558

@@ -58,6 +61,7 @@ public abstract class Jsr310Converters {
5861
}
5962

6063
List<Converter<?, ?>> converters = new ArrayList<>();
64+
6165
converters.add(new LocalDateTimeToBytesConverter());
6266
converters.add(new BytesToLocalDateTimeConverter());
6367
converters.add(new LocalDateToBytesConverter());
@@ -74,6 +78,10 @@ public abstract class Jsr310Converters {
7478
converters.add(new BytesToPeriodConverter());
7579
converters.add(new DurationToBytesConverter());
7680
converters.add(new BytesToDurationConverter());
81+
converters.add(new OffsetDateTimeToBytesConverter());
82+
converters.add(new BytesToOffsetDateTimeConverter());
83+
converters.add(new OffsetTimeToBytesConverter());
84+
converters.add(new BytesToOffsetTimeConverter());
7785

7886
return converters;
7987
}
@@ -296,4 +304,51 @@ public Duration convert(byte[] source) {
296304
}
297305
}
298306

307+
/**
308+
* @author John Blum
309+
* @see java.time.OffsetDateTime
310+
*/
311+
static class OffsetDateTimeToBytesConverter extends StringBasedConverter implements Converter<OffsetDateTime, byte[]> {
312+
313+
@Override
314+
public byte[] convert(OffsetDateTime source) {
315+
return fromString(source.toString());
316+
}
317+
}
318+
319+
/**
320+
* @author John Blum
321+
* @see java.time.OffsetDateTime
322+
*/
323+
static class BytesToOffsetDateTimeConverter extends StringBasedConverter implements Converter<byte[], OffsetDateTime> {
324+
325+
@Override
326+
public OffsetDateTime convert(byte[] source) {
327+
return OffsetDateTime.parse(toString(source));
328+
}
329+
}
330+
331+
/**
332+
* @author John Blum
333+
* @see java.time.OffsetTime
334+
*/
335+
static class OffsetTimeToBytesConverter extends StringBasedConverter implements Converter<OffsetTime, byte[]> {
336+
337+
@Override
338+
public byte[] convert(OffsetTime source) {
339+
return fromString(source.toString());
340+
}
341+
}
342+
343+
/**
344+
* @author John Blum
345+
* @see java.time.OffsetTime
346+
*/
347+
static class BytesToOffsetTimeConverter extends StringBasedConverter implements Converter<byte[], OffsetTime> {
348+
349+
@Override
350+
public OffsetTime convert(byte[] source) {
351+
return OffsetTime.parse(toString(source));
352+
}
353+
}
299354
}

src/main/java/org/springframework/data/redis/core/convert/RedisCustomConversions.java

+1-12
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,7 @@ public class RedisCustomConversions extends org.springframework.data.convert.Cus
3939

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

42-
converters.add(new BinaryConverters.StringToBytesConverter());
43-
converters.add(new BinaryConverters.BytesToStringConverter());
44-
converters.add(new BinaryConverters.NumberToBytesConverter());
45-
converters.add(new BinaryConverters.BytesToNumberConverterFactory());
46-
converters.add(new BinaryConverters.EnumToBytesConverter());
47-
converters.add(new BinaryConverters.BytesToEnumConverterFactory());
48-
converters.add(new BinaryConverters.BooleanToBytesConverter());
49-
converters.add(new BinaryConverters.BytesToBooleanConverter());
50-
converters.add(new BinaryConverters.DateToBytesConverter());
51-
converters.add(new BinaryConverters.BytesToDateConverter());
52-
converters.add(new BinaryConverters.UuidToBytesConverter());
53-
converters.add(new BinaryConverters.BytesToUuidConverter());
42+
converters.addAll(BinaryConverters.getConvertersToRegister());
5443
converters.addAll(Jsr310Converters.getConvertersToRegister());
5544

5645
STORE_CONVERTERS = Collections.unmodifiableList(converters);

src/test/java/org/springframework/data/redis/repository/RedisRepositoryClusterIntegrationTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class RedisRepositoryClusterIntegrationTests extends RedisRepositoryIntegrationT
5050
@EnableRedisRepositories(considerNestedRepositories = true, indexConfiguration = MyIndexConfiguration.class,
5151
keyspaceConfiguration = MyKeyspaceConfiguration.class,
5252
includeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
53-
classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class }) })
53+
classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class, UserRepository.class }) })
5454
static class Config {
5555

5656
@Bean
@@ -62,6 +62,7 @@ static class Config {
6262
connectionFactory.afterPropertiesSet();
6363

6464
RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
65+
6566
template.setConnectionFactory(connectionFactory);
6667

6768
return template;

src/test/java/org/springframework/data/redis/repository/RedisRepositoryIntegrationTestBase.java

+94
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import lombok.Value;
2222
import lombok.With;
2323

24+
import java.time.OffsetDateTime;
25+
import java.time.OffsetTime;
2426
import java.util.Arrays;
2527
import java.util.Collections;
2628
import java.util.List;
@@ -51,6 +53,9 @@
5153
import org.springframework.data.repository.CrudRepository;
5254
import org.springframework.data.repository.PagingAndSortingRepository;
5355
import org.springframework.data.repository.query.QueryByExampleExecutor;
56+
import org.springframework.lang.NonNull;
57+
import org.springframework.lang.Nullable;
58+
import org.springframework.util.Assert;
5459

5560
/**
5661
* Base for testing Redis repository support in different configurations.
@@ -63,6 +68,7 @@ public abstract class RedisRepositoryIntegrationTestBase {
6368
@Autowired PersonRepository repo;
6469
@Autowired CityRepository cityRepo;
6570
@Autowired ImmutableObjectRepository immutableObjectRepo;
71+
@Autowired UserRepository userRepository;
6672
@Autowired KeyValueTemplate kvTemplate;
6773

6874
@BeforeEach
@@ -474,6 +480,24 @@ void shouldProperlyReadNestedImmutableObject() {
474480
assertThat(loaded.nested).isEqualTo(nested);
475481
}
476482

483+
@Test // GH-2677
484+
void shouldProperlyHandleEntityWithOffsetJavaTimeTypes() {
485+
486+
User jonDoe = User.as("Jon Doe")
487+
.expires(OffsetTime.now().plusMinutes(5))
488+
.lastAccess(OffsetDateTime.now());
489+
490+
this.userRepository.save(jonDoe);
491+
492+
User loadedJonDoe = this.userRepository.findById(jonDoe.getName()).orElse(null);
493+
494+
assertThat(loadedJonDoe).isNotNull();
495+
assertThat(loadedJonDoe).isNotSameAs(jonDoe);
496+
assertThat(loadedJonDoe.getName()).isEqualTo(jonDoe.getName());
497+
assertThat(loadedJonDoe.getLastAccessed()).isEqualTo(jonDoe.getLastAccessed());
498+
assertThat(loadedJonDoe.getExpiration()).isEqualTo(jonDoe.getExpiration());
499+
}
500+
477501
public static interface PersonRepository
478502
extends PagingAndSortingRepository<Person, String>, CrudRepository<Person, String>,
479503
QueryByExampleExecutor<Person> {
@@ -519,6 +543,8 @@ public interface CityRepository extends CrudRepository<City, String> {
519543

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

546+
public interface UserRepository extends CrudRepository<User, String> { }
547+
522548
/**
523549
* Custom Redis {@link IndexConfiguration} forcing index of {@link Person#lastname}.
524550
*
@@ -583,4 +609,72 @@ static class Immutable {
583609

584610
Immutable nested;
585611
}
612+
613+
@RedisHash("Users")
614+
static class User {
615+
616+
static User as(@NonNull String name) {
617+
Assert.hasText(name, () -> String.format("Name [%s] of User is required", name));
618+
return new User(name);
619+
}
620+
621+
private OffsetDateTime lastAccessed;
622+
623+
private OffsetTime expiration;
624+
625+
@Id
626+
private final String name;
627+
628+
private User(@NonNull String name) {
629+
this.name = name;
630+
}
631+
632+
@Nullable
633+
public OffsetTime getExpiration() {
634+
return this.expiration;
635+
}
636+
637+
@Nullable
638+
public OffsetDateTime getLastAccessed() {
639+
return this.lastAccessed;
640+
}
641+
642+
public String getName() {
643+
return this.name;
644+
}
645+
646+
public User lastAccess(@Nullable OffsetDateTime dateTime) {
647+
this.lastAccessed = dateTime;
648+
return this;
649+
}
650+
651+
public User expires(@Nullable OffsetTime time) {
652+
this.expiration = time;
653+
return this;
654+
}
655+
656+
@Override
657+
public boolean equals(Object obj) {
658+
659+
if (this == obj) {
660+
return true;
661+
}
662+
663+
if (!(obj instanceof User that)) {
664+
return false;
665+
}
666+
667+
return this.getName().equals(that.getName());
668+
}
669+
670+
@Override
671+
public int hashCode() {
672+
return Objects.hash(getName());
673+
}
674+
675+
@Override
676+
public String toString() {
677+
return getName();
678+
}
679+
}
586680
}

src/test/java/org/springframework/data/redis/repository/RedisRepositoryIntegrationTests.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ public class RedisRepositoryIntegrationTests extends RedisRepositoryIntegrationT
5656
@EnableRedisRepositories(considerNestedRepositories = true, indexConfiguration = MyIndexConfiguration.class,
5757
keyspaceConfiguration = MyKeyspaceConfiguration.class,
5858
includeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
59-
classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class }) })
60-
59+
classes = { PersonRepository.class, CityRepository.class, ImmutableObjectRepository.class, UserRepository.class }) })
6160
static class Config {
6261

6362
@Bean
@@ -67,6 +66,7 @@ static class Config {
6766
connectionFactory.afterPropertiesSet();
6867

6968
RedisTemplate<String, String> template = new RedisTemplate<>();
69+
7070
template.setDefaultSerializer(StringRedisSerializer.UTF_8);
7171
template.setConnectionFactory(connectionFactory);
7272

@@ -104,6 +104,7 @@ private RedisTypeMapper customTypeMapper() {
104104
public void shouldConsiderCustomTypeMapper() {
105105

106106
Person rand = new Person();
107+
107108
rand.id = "rand";
108109
rand.firstname = "rand";
109110
rand.lastname = "al'thor";

0 commit comments

Comments
 (0)