Skip to content

Commit e4c6455

Browse files
committed
Rewrite persistent property converter for date and java.time
1 parent 66d1344 commit e4c6455

File tree

5 files changed

+257
-83
lines changed

5 files changed

+257
-83
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2019-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.convert;
17+
18+
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter;
19+
import org.springframework.data.mapping.PersistentProperty;
20+
import org.springframework.util.Assert;
21+
22+
/**
23+
* @author Sascha Woo
24+
* @since 4.3
25+
*/
26+
public abstract class AbstractPersistentPropertyConverter implements ElasticsearchPersistentPropertyConverter {
27+
28+
private final PersistentProperty<?> property;
29+
30+
public AbstractPersistentPropertyConverter(PersistentProperty<?> property) {
31+
32+
Assert.notNull(property, "property must not be null.");
33+
this.property = property;
34+
}
35+
36+
protected PersistentProperty<?> getProperty() {
37+
return property;
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2019-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.convert;
17+
18+
import java.util.Date;
19+
import java.util.List;
20+
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.springframework.data.mapping.PersistentProperty;
24+
25+
/**
26+
* @author Sascha Woo
27+
* @since 4.3
28+
*/
29+
public class DatePersistentPropertyConverter extends AbstractPersistentPropertyConverter {
30+
31+
private static final Logger LOGGER = LoggerFactory.getLogger(DatePersistentPropertyConverter.class);
32+
33+
private final List<ElasticsearchDateConverter> dateConverters;
34+
35+
public DatePersistentPropertyConverter(PersistentProperty<?> property,
36+
List<ElasticsearchDateConverter> dateConverters) {
37+
38+
super(property);
39+
this.dateConverters = dateConverters;
40+
}
41+
42+
@Override
43+
public Object read(Object value) {
44+
45+
String s = value.toString();
46+
for (ElasticsearchDateConverter dateConverter : dateConverters) {
47+
try {
48+
return dateConverter.parse(s);
49+
} catch (Exception e) {
50+
LOGGER.trace(e.getMessage(), e);
51+
}
52+
}
53+
54+
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s,
55+
getProperty().getActualType().getTypeName(), getProperty().getName()));
56+
}
57+
58+
@Override
59+
public Object write(Object value) {
60+
61+
try {
62+
return dateConverters.get(0).format((Date) value);
63+
} catch (Exception e) {
64+
throw new ConversionException(
65+
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
66+
}
67+
}
68+
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2019-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.convert;
17+
18+
import java.time.temporal.TemporalAccessor;
19+
import java.util.List;
20+
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.springframework.data.mapping.PersistentProperty;
24+
25+
/**
26+
* @author Sascha Woo
27+
* @since 4.3
28+
*/
29+
public class TemporalPersistentPropertyConverter extends AbstractPersistentPropertyConverter {
30+
31+
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalPersistentPropertyConverter.class);
32+
33+
private final List<ElasticsearchDateConverter> dateConverters;
34+
35+
public TemporalPersistentPropertyConverter(PersistentProperty<?> property,
36+
List<ElasticsearchDateConverter> dateConverters) {
37+
38+
super(property);
39+
this.dateConverters = dateConverters;
40+
}
41+
42+
@SuppressWarnings("unchecked")
43+
@Override
44+
public Object read(Object value) {
45+
46+
String s = value.toString();
47+
Class<?> actualType = getProperty().getActualType();
48+
49+
for (ElasticsearchDateConverter dateConverter : dateConverters) {
50+
try {
51+
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) actualType);
52+
} catch (Exception e) {
53+
LOGGER.trace(e.getMessage(), e);
54+
}
55+
}
56+
57+
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s,
58+
getProperty().getActualType().getTypeName(), getProperty().getName()));
59+
}
60+
61+
@Override
62+
public Object write(Object value) {
63+
64+
try {
65+
return dateConverters.get(0).format((TemporalAccessor) value);
66+
} catch (Exception e) {
67+
throw new ConversionException(
68+
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
69+
}
70+
}
71+
72+
}

Diff for: src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java

+75-82
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@
3030
import org.springframework.data.elasticsearch.annotations.GeoShapeField;
3131
import org.springframework.data.elasticsearch.annotations.MultiField;
3232
import org.springframework.data.elasticsearch.core.completion.Completion;
33-
import org.springframework.data.elasticsearch.core.convert.ConversionException;
33+
import org.springframework.data.elasticsearch.core.convert.DatePersistentPropertyConverter;
3434
import org.springframework.data.elasticsearch.core.convert.ElasticsearchDateConverter;
35+
import org.springframework.data.elasticsearch.core.convert.TemporalPersistentPropertyConverter;
3536
import org.springframework.data.elasticsearch.core.geo.GeoJson;
3637
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
3738
import org.springframework.data.elasticsearch.core.join.JoinField;
3839
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
3940
import org.springframework.data.mapping.Association;
4041
import org.springframework.data.mapping.MappingException;
4142
import org.springframework.data.mapping.PersistentEntity;
43+
import org.springframework.data.mapping.PersistentProperty;
4244
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
4345
import org.springframework.data.mapping.model.FieldNamingStrategy;
4446
import org.springframework.data.mapping.model.Property;
@@ -128,102 +130,76 @@ protected boolean hasExplicitFieldName() {
128130
}
129131

130132
/**
131-
* Initializes an {@link ElasticsearchPersistentPropertyConverter} if this property is annotated as a Field with type
132-
* {@link FieldType#Date}, has a {@link DateFormat} set and if the type of the property is one of the Java8 temporal
133-
* classes or java.util.Date.
133+
* Set the property converter for this {@link PersistentProperty} to {@link TemporalPersistentPropertyConverter} or
134+
* {@link DatePersistentPropertyConverter} depending on the property type and if the property is annotated with
135+
* {@code @Field} of type {@link FieldType#Date} or {@link FieldType#Date_Nanos}.
134136
*/
135137
private void initDateConverter() {
136-
Field field = findAnnotation(Field.class);
137-
138-
Class<?> actualType = getActualTypeOrNull();
139138

140-
if (actualType == null) {
139+
if (!isDateProperty()) {
141140
return;
142141
}
143142

144-
boolean isTemporalAccessor = TemporalAccessor.class.isAssignableFrom(actualType);
145-
boolean isDate = Date.class.isAssignableFrom(actualType);
143+
Class<?> actualType = getActualType();
144+
List<ElasticsearchDateConverter> dateConverters = getDateConverters();
146145

147-
if (field != null && (field.type() == FieldType.Date || field.type() == FieldType.Date_Nanos)
148-
&& (isTemporalAccessor || isDate)) {
149-
150-
DateFormat[] dateFormats = field.format();
151-
String[] dateFormatPatterns = field.pattern();
146+
if (dateConverters.isEmpty()) {
147+
LOGGER.warn("No date formatters configured for property '{}'.", getName());
148+
return;
149+
}
152150

153-
String property = getOwner().getType().getSimpleName() + "." + getName();
151+
if (TemporalAccessor.class.isAssignableFrom(actualType)) {
152+
propertyConverter = new TemporalPersistentPropertyConverter(this, dateConverters);
153+
} else if (Date.class.isAssignableFrom(actualType)) {
154+
propertyConverter = new DatePersistentPropertyConverter(this, dateConverters);
155+
} else {
156+
LOGGER.warn("Unsupported type '{}' for date property '{}'.", actualType, getName());
157+
}
158+
}
154159

155-
if (dateFormats.length == 0 && dateFormatPatterns.length == 0) {
156-
LOGGER.warn(
157-
"Property '{}' has @Field type '{}' but has no built-in format or custom date pattern defined. Make sure you have a converter registered for type {}.",
158-
property, field.type().name(), actualType.getSimpleName());
159-
return;
160-
}
160+
private List<ElasticsearchDateConverter> getDateConverters() {
161161

162-
List<ElasticsearchDateConverter> converters = new ArrayList<>();
163-
164-
// register converters for built-in formats
165-
for (DateFormat dateFormat : dateFormats) {
166-
switch (dateFormat) {
167-
case none:
168-
case custom:
169-
break;
170-
case weekyear:
171-
case weekyear_week:
172-
case weekyear_week_day:
173-
LOGGER.warn("No default converter available for '{}' and date format '{}'. Use a custom converter instead.",
174-
actualType.getName(), dateFormat.name());
175-
break;
176-
default:
177-
converters.add(ElasticsearchDateConverter.of(dateFormat));
178-
break;
179-
}
180-
}
162+
Field field = findAnnotation(Field.class);
163+
DateFormat[] dateFormats = field.format();
164+
String[] dateFormatPatterns = field.pattern();
165+
String property = getOwner().getType().getSimpleName() + "." + getName();
166+
Class<?> actualType = getActualType();
167+
List<ElasticsearchDateConverter> converters = new ArrayList<>();
168+
169+
if (dateFormats.length == 0 && dateFormatPatterns.length == 0) {
170+
LOGGER.warn(
171+
"Property '{}' has @Field type '{}' but has no built-in format or custom date pattern defined. Make sure you have a converter registered for type {}.",
172+
property, field.type().name(), actualType.getSimpleName());
173+
return converters;
174+
}
181175

182-
// register converters for custom formats
183-
for (String dateFormatPattern : dateFormatPatterns) {
184-
if (!StringUtils.hasText(dateFormatPattern)) {
185-
throw new MappingException(String.format("Date pattern of property '%s' must not be empty", property));
186-
}
187-
converters.add(ElasticsearchDateConverter.of(dateFormatPattern));
176+
// register converters for built-in formats
177+
for (DateFormat dateFormat : dateFormats) {
178+
switch (dateFormat) {
179+
case none:
180+
case custom:
181+
break;
182+
case weekyear:
183+
case weekyear_week:
184+
case weekyear_week_day:
185+
LOGGER.warn("No default converter available for '{}' and date format '{}'. Use a custom converter instead.",
186+
actualType.getName(), dateFormat.name());
187+
break;
188+
default:
189+
converters.add(ElasticsearchDateConverter.of(dateFormat));
190+
break;
188191
}
192+
}
189193

190-
if (!converters.isEmpty()) {
191-
propertyConverter = new ElasticsearchPersistentPropertyConverter() {
192-
final List<ElasticsearchDateConverter> dateConverters = converters;
193-
194-
@SuppressWarnings("unchecked")
195-
@Override
196-
public Object read(String s) {
197-
for (ElasticsearchDateConverter dateConverter : dateConverters) {
198-
try {
199-
if (isTemporalAccessor) {
200-
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) actualType);
201-
} else { // must be date
202-
return dateConverter.parse(s);
203-
}
204-
} catch (Exception e) {
205-
LOGGER.trace(e.getMessage(), e);
206-
}
207-
}
208-
209-
throw new ConversionException(String
210-
.format("Unable to parse date value '%s' of property '%s' with configured converters", s, property));
211-
}
212-
213-
@Override
214-
public String write(Object property) {
215-
ElasticsearchDateConverter dateConverter = dateConverters.get(0);
216-
if (isTemporalAccessor && TemporalAccessor.class.isAssignableFrom(property.getClass())) {
217-
return dateConverter.format((TemporalAccessor) property);
218-
} else if (isDate && Date.class.isAssignableFrom(property.getClass())) {
219-
return dateConverter.format((Date) property);
220-
} else {
221-
return property.toString();
222-
}
223-
}
224-
};
194+
// register converters for custom formats
195+
for (String dateFormatPattern : dateFormatPatterns) {
196+
if (!StringUtils.hasText(dateFormatPattern)) {
197+
throw new MappingException(String.format("Date pattern of property '%s' must not be empty", property));
225198
}
199+
converters.add(ElasticsearchDateConverter.of(dateFormatPattern));
226200
}
201+
202+
return converters;
227203
}
228204

229205
@SuppressWarnings("ConstantConditions")
@@ -303,4 +279,21 @@ public boolean isJoinFieldProperty() {
303279
public boolean isCompletionProperty() {
304280
return getActualType() == Completion.class;
305281
}
282+
283+
private boolean isDateProperty() {
284+
285+
Class<?> actualType = getActualTypeOrNull();
286+
if (actualType == null
287+
|| !Date.class.isAssignableFrom(actualType) && !TemporalAccessor.class.isAssignableFrom(actualType)) {
288+
return false;
289+
}
290+
291+
Field field = findAnnotation(Field.class);
292+
if (field == null || field.type() == null) {
293+
return false;
294+
}
295+
296+
return field.type() == FieldType.Date || field.type() == FieldType.Date_Nanos;
297+
}
298+
306299
}

Diff for: src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ void shouldMapNamesAndConvertValuesInCriteriaQueryForSubCriteriaWithDate() throw
196196
CriteriaQuery criteriaQuery = new CriteriaQuery( //
197197
Criteria.or().subCriteria(Criteria.where("birthDate") //
198198
.between(LocalDate.of(1989, 11, 9), LocalDate.of(1990, 11, 9))) //
199-
.subCriteria(Criteria.where("createdDate").is(383745721653L)) //
199+
.subCriteria(Criteria.where("createdDate").is(new Date(383745721653L))) //
200200
);
201201

202202
// mapped field name and converted parameter

0 commit comments

Comments
 (0)