mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-01 09:42:11 +00:00
DATAES-924 - Conversion of properties of collections of Temporal values fails.
Original PR: #519
This commit is contained in:
parent
6034f38d72
commit
0e7791a687
@ -16,18 +16,10 @@
|
|||||||
package org.springframework.data.elasticsearch.core.convert;
|
package org.springframework.data.elasticsearch.core.convert;
|
||||||
|
|
||||||
import java.time.temporal.TemporalAccessor;
|
import java.time.temporal.TemporalAccessor;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -281,8 +273,8 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
Class<R> rawType = targetType.getType();
|
Class<R> rawType = targetType.getType();
|
||||||
|
|
||||||
if (property.hasPropertyConverter() && String.class.isAssignableFrom(source.getClass())) {
|
if (property.hasPropertyConverter()) {
|
||||||
source = property.getPropertyConverter().read((String) source);
|
source = propertyConverterRead(property, source);
|
||||||
} else if (TemporalAccessor.class.isAssignableFrom(property.getType())
|
} else if (TemporalAccessor.class.isAssignableFrom(property.getType())
|
||||||
&& !conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
&& !conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
||||||
|
|
||||||
@ -310,6 +302,32 @@ public class MappingElasticsearchConverter
|
|||||||
return (R) readSimpleValue(source, targetType);
|
return (R) readSimpleValue(source, targetType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
|
||||||
|
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
|
||||||
|
.requireNonNull(property.getPropertyConverter());
|
||||||
|
|
||||||
|
if (source instanceof String[]) {
|
||||||
|
// convert to a List
|
||||||
|
source = Arrays.asList((String[]) source);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source instanceof List) {
|
||||||
|
source = ((List<?>) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toList());
|
||||||
|
} else if (source instanceof Set) {
|
||||||
|
source = ((Set<?>) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toSet());
|
||||||
|
} else {
|
||||||
|
source = convertOnRead(propertyConverter, source);
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object convertOnRead(ElasticsearchPersistentPropertyConverter propertyConverter, Object source) {
|
||||||
|
if (String.class.isAssignableFrom(source.getClass())) {
|
||||||
|
source = propertyConverter.read((String) source);
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Nullable
|
@Nullable
|
||||||
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
|
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
|
||||||
@ -506,9 +524,8 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (property.hasPropertyConverter()) {
|
if (property.hasPropertyConverter()) {
|
||||||
ElasticsearchPersistentPropertyConverter propertyConverter = property.getPropertyConverter();
|
value = propertyConverterWrite(property, value);
|
||||||
value = propertyConverter.write(value);
|
} else if (TemporalAccessor.class.isAssignableFrom(property.getActualType())
|
||||||
} else if (TemporalAccessor.class.isAssignableFrom(property.getType())
|
|
||||||
&& !conversions.hasCustomWriteTarget(value.getClass())) {
|
&& !conversions.hasCustomWriteTarget(value.getClass())) {
|
||||||
|
|
||||||
// log at most 5 times
|
// log at most 5 times
|
||||||
@ -535,6 +552,20 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) {
|
||||||
|
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
|
||||||
|
.requireNonNull(property.getPropertyConverter());
|
||||||
|
|
||||||
|
if (value instanceof List) {
|
||||||
|
value = ((List<?>) value).stream().map(propertyConverter::write).collect(Collectors.toList());
|
||||||
|
} else if (value instanceof Set) {
|
||||||
|
value = ((Set<?>) value).stream().map(propertyConverter::write).collect(Collectors.toSet());
|
||||||
|
} else {
|
||||||
|
value = propertyConverter.write(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) {
|
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) {
|
||||||
|
|
||||||
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
|
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
|
||||||
|
@ -64,7 +64,7 @@ public class SimpleElasticsearchPersistentProperty extends
|
|||||||
private final boolean isSeqNoPrimaryTerm;
|
private final boolean isSeqNoPrimaryTerm;
|
||||||
private final @Nullable String annotatedFieldName;
|
private final @Nullable String annotatedFieldName;
|
||||||
@Nullable private ElasticsearchPersistentPropertyConverter propertyConverter;
|
@Nullable private ElasticsearchPersistentPropertyConverter propertyConverter;
|
||||||
private boolean storeNullValue;
|
private final boolean storeNullValue;
|
||||||
|
|
||||||
public SimpleElasticsearchPersistentProperty(Property property,
|
public SimpleElasticsearchPersistentProperty(Property property,
|
||||||
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
|
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||||
@ -143,8 +143,9 @@ public class SimpleElasticsearchPersistentProperty extends
|
|||||||
*/
|
*/
|
||||||
private void initDateConverter() {
|
private void initDateConverter() {
|
||||||
Field field = findAnnotation(Field.class);
|
Field field = findAnnotation(Field.class);
|
||||||
boolean isTemporalAccessor = TemporalAccessor.class.isAssignableFrom(getType());
|
Class<?> actualType = getActualType();
|
||||||
boolean isDate = Date.class.isAssignableFrom(getType());
|
boolean isTemporalAccessor = TemporalAccessor.class.isAssignableFrom(actualType);
|
||||||
|
boolean isDate = Date.class.isAssignableFrom(actualType);
|
||||||
|
|
||||||
if (field != null && (field.type() == FieldType.Date || field.type() == FieldType.Date_Nanos)
|
if (field != null && (field.type() == FieldType.Date || field.type() == FieldType.Date_Nanos)
|
||||||
&& (isTemporalAccessor || isDate)) {
|
&& (isTemporalAccessor || isDate)) {
|
||||||
@ -156,44 +157,50 @@ public class SimpleElasticsearchPersistentProperty extends
|
|||||||
getOwner().getType().getSimpleName() + "." + getName(), field.type().name()));
|
getOwner().getType().getSimpleName() + "." + getName(), field.type().name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ElasticsearchDateConverter converter = null;
|
ElasticsearchDateConverter converter;
|
||||||
|
|
||||||
if (dateFormat == DateFormat.custom) {
|
if (dateFormat == DateFormat.custom) {
|
||||||
String pattern = field.pattern();
|
String pattern = field.pattern();
|
||||||
|
|
||||||
if (StringUtils.hasLength(pattern)) {
|
if (!StringUtils.hasLength(pattern)) {
|
||||||
converter = ElasticsearchDateConverter.of(pattern);
|
throw new MappingException(
|
||||||
|
String.format("Property %s is annotated with FieldType.%s and a custom format but has no pattern defined",
|
||||||
|
getOwner().getType().getSimpleName() + "." + getName(), field.type().name()));
|
||||||
}
|
}
|
||||||
} else if (dateFormat != DateFormat.none) {
|
|
||||||
|
converter = ElasticsearchDateConverter.of(pattern);
|
||||||
|
} else {
|
||||||
converter = ElasticsearchDateConverter.of(dateFormat);
|
converter = ElasticsearchDateConverter.of(dateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (converter != null) {
|
propertyConverter = new ElasticsearchPersistentPropertyConverter() {
|
||||||
ElasticsearchDateConverter dateConverter = converter;
|
final ElasticsearchDateConverter dateConverter = converter;
|
||||||
propertyConverter = new ElasticsearchPersistentPropertyConverter() {
|
|
||||||
@Override
|
|
||||||
public String write(Object property) {
|
|
||||||
if (isTemporalAccessor) {
|
|
||||||
return dateConverter.format((TemporalAccessor) property);
|
|
||||||
} else { // must be Date
|
|
||||||
return dateConverter.format((Date) property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@Override
|
||||||
@Override
|
public String write(Object property) {
|
||||||
public Object read(String s) {
|
if (isTemporalAccessor && TemporalAccessor.class.isAssignableFrom(property.getClass())) {
|
||||||
if (isTemporalAccessor) {
|
return dateConverter.format((TemporalAccessor) property);
|
||||||
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) getType());
|
} else if (isDate && Date.class.isAssignableFrom(property.getClass())) {
|
||||||
} else { // must be date
|
return dateConverter.format((Date) property);
|
||||||
return dateConverter.parse(s);
|
} else {
|
||||||
}
|
return property.toString();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Object read(String s) {
|
||||||
|
if (isTemporalAccessor) {
|
||||||
|
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) actualType);
|
||||||
|
} else { // must be date
|
||||||
|
return dateConverter.parse(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getAnnotatedFieldName() {
|
private String getAnnotatedFieldName() {
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -617,6 +618,25 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertEquals(expected, json, false);
|
assertEquals(expected, json, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAES-924
|
||||||
|
@DisplayName("should write list of LocalDate")
|
||||||
|
void shouldWriteListOfLocalDate() throws JSONException {
|
||||||
|
|
||||||
|
LocalDatesEntity entity = new LocalDatesEntity();
|
||||||
|
entity.setId("4711");
|
||||||
|
entity.setDates(Arrays.asList(LocalDate.of(2020, 9, 15), LocalDate.of(2019, 5, 1)));
|
||||||
|
String expected = "{\n" + //
|
||||||
|
" \"id\": \"4711\",\n" + //
|
||||||
|
" \"dates\": [\"15.09.2020\", \"01.05.2019\"]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
|
||||||
|
Document document = Document.create();
|
||||||
|
mappingElasticsearchConverter.write(entity, document);
|
||||||
|
String json = document.toJson();
|
||||||
|
|
||||||
|
assertEquals(expected, json, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Test // DATAES-716
|
@Test // DATAES-716
|
||||||
void shouldReadLocalDate() {
|
void shouldReadLocalDate() {
|
||||||
Document document = Document.create();
|
Document document = Document.create();
|
||||||
@ -633,6 +653,20 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(person.getGender()).isEqualTo(Gender.MAN);
|
assertThat(person.getGender()).isEqualTo(Gender.MAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAES-924
|
||||||
|
@DisplayName("should read list of LocalDate")
|
||||||
|
void shouldReadListOfLocalDate() {
|
||||||
|
|
||||||
|
Document document = Document.create();
|
||||||
|
document.put("id", "4711");
|
||||||
|
document.put("dates", new String[] { "15.09.2020", "01.05.2019" });
|
||||||
|
|
||||||
|
LocalDatesEntity entity = mappingElasticsearchConverter.read(LocalDatesEntity.class, document);
|
||||||
|
|
||||||
|
assertThat(entity.getId()).isEqualTo("4711");
|
||||||
|
assertThat(entity.getDates()).hasSize(2).containsExactly(LocalDate.of(2020, 9, 15), LocalDate.of(2019, 5, 1));
|
||||||
|
}
|
||||||
|
|
||||||
@Test // DATAES-763
|
@Test // DATAES-763
|
||||||
void writeEntityWithMapDataType() {
|
void writeEntityWithMapDataType() {
|
||||||
|
|
||||||
@ -870,6 +904,15 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
Map<String, Inventory> inventoryMap;
|
Map<String, Inventory> inventoryMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
static class LocalDatesEntity {
|
||||||
|
@Id private String id;
|
||||||
|
@Field(name = "dates", type = FieldType.Date, format = DateFormat.custom,
|
||||||
|
pattern = "dd.MM.uuuu") private List<LocalDate> dates;
|
||||||
|
}
|
||||||
|
|
||||||
enum Gender {
|
enum Gender {
|
||||||
|
|
||||||
MAN("1"), MACHINE("0");
|
MAN("1"), MACHINE("0");
|
||||||
|
@ -17,13 +17,17 @@ package org.springframework.data.elasticsearch.core.mapping;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||||
import org.springframework.data.elasticsearch.annotations.Field;
|
import org.springframework.data.elasticsearch.annotations.Field;
|
||||||
@ -86,7 +90,7 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
|
|||||||
assertThat(persistentProperty.getFieldName()).isEqualTo("mainfield");
|
assertThat(persistentProperty.getFieldName()).isEqualTo("mainfield");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-716, DATAES-792
|
@Test // DATAES-716, DATAES-792, DATAES-924
|
||||||
void shouldSetPropertyConverters() {
|
void shouldSetPropertyConverters() {
|
||||||
SimpleElasticsearchPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DatesProperty.class);
|
SimpleElasticsearchPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DatesProperty.class);
|
||||||
|
|
||||||
@ -102,6 +106,9 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
|
|||||||
assertThat(persistentProperty.hasPropertyConverter()).isTrue();
|
assertThat(persistentProperty.hasPropertyConverter()).isTrue();
|
||||||
assertThat(persistentProperty.getPropertyConverter()).isNotNull();
|
assertThat(persistentProperty.getPropertyConverter()).isNotNull();
|
||||||
|
|
||||||
|
persistentProperty = persistentEntity.getRequiredPersistentProperty("localDateList");
|
||||||
|
assertThat(persistentProperty.hasPropertyConverter()).isTrue();
|
||||||
|
assertThat(persistentProperty.getPropertyConverter()).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-716
|
@Test // DATAES-716
|
||||||
@ -199,6 +206,14 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
|
|||||||
.withMessageContaining("date");
|
.withMessageContaining("date");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAES-924
|
||||||
|
@DisplayName("should require pattern for custom date format")
|
||||||
|
void shouldRequirePatternForCustomDateFormat() {
|
||||||
|
assertThatExceptionOfType(MappingException.class) //
|
||||||
|
.isThrownBy(() -> context.getRequiredPersistentEntity(DateFieldWithCustomFormatAndNoPattern.class)) //
|
||||||
|
.withMessageContaining("pattern");
|
||||||
|
}
|
||||||
|
|
||||||
static class InvalidScoreProperty {
|
static class InvalidScoreProperty {
|
||||||
@Nullable @Score String scoreProperty;
|
@Nullable @Score String scoreProperty;
|
||||||
}
|
}
|
||||||
@ -220,17 +235,27 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
|
|||||||
@Nullable @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "dd.MM.uuuu") LocalDate localDate;
|
@Nullable @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "dd.MM.uuuu") LocalDate localDate;
|
||||||
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime;
|
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime;
|
||||||
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate;
|
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate;
|
||||||
|
@Nullable @Field(type = FieldType.Date, format = DateFormat.custom,
|
||||||
|
pattern = "dd.MM.uuuu") List<LocalDate> localDateList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
static class SeqNoPrimaryTermProperty {
|
static class SeqNoPrimaryTermProperty {
|
||||||
SeqNoPrimaryTerm seqNoPrimaryTerm;
|
SeqNoPrimaryTerm seqNoPrimaryTerm;
|
||||||
String string;
|
String string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
static class DateFieldWithNoFormat {
|
static class DateFieldWithNoFormat {
|
||||||
@Field(type = FieldType.Date) LocalDateTime datetime;
|
@Field(type = FieldType.Date) LocalDateTime datetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
static class DateFieldWithCustomFormatAndNoPattern {
|
||||||
|
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "") LocalDateTime datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
static class DateNanosFieldWithNoFormat {
|
static class DateNanosFieldWithNoFormat {
|
||||||
@Field(type = FieldType.Date_Nanos) LocalDateTime datetime;
|
@Field(type = FieldType.Date_Nanos) LocalDateTime datetime;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user