Skip to content

Commit 3790132

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

File tree

4 files changed

+252
-82
lines changed

4 files changed

+252
-82
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,68 @@
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(String value) {
44+
45+
for (ElasticsearchDateConverter dateConverter : dateConverters) {
46+
try {
47+
return dateConverter.parse(value);
48+
} catch (Exception e) {
49+
LOGGER.trace(e.getMessage(), e);
50+
}
51+
}
52+
53+
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
54+
getProperty().getActualType().getTypeName(), getProperty().getName()));
55+
}
56+
57+
@Override
58+
public String write(Object value) {
59+
60+
try {
61+
return dateConverters.get(0).format((Date) value);
62+
} catch (Exception e) {
63+
throw new ConversionException(
64+
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
65+
}
66+
}
67+
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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(String value) {
45+
46+
Class<?> actualType = getProperty().getActualType();
47+
48+
for (ElasticsearchDateConverter dateConverter : dateConverters) {
49+
try {
50+
return dateConverter.parse(value, (Class<? extends TemporalAccessor>) actualType);
51+
} catch (Exception e) {
52+
LOGGER.trace(e.getMessage(), e);
53+
}
54+
}
55+
56+
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
57+
getProperty().getActualType().getTypeName(), getProperty().getName()));
58+
}
59+
60+
@Override
61+
public String write(Object value) {
62+
63+
try {
64+
return dateConverters.get(0).format((TemporalAccessor) value);
65+
} catch (Exception e) {
66+
throw new ConversionException(
67+
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
68+
}
69+
}
70+
71+
}

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

+73-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,74 @@ 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+
Class<?> actualType = getActualType();
166+
List<ElasticsearchDateConverter> converters = new ArrayList<>();
167+
168+
if (dateFormats.length == 0 && dateFormatPatterns.length == 0) {
169+
LOGGER.warn(
170+
"Property '{}' has @Field type '{}' but has no built-in format or custom date pattern defined. Make sure you have a converter registered for type {}.",
171+
getName(), field.type().name(), actualType.getSimpleName());
172+
return converters;
173+
}
181174

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));
175+
// register converters for built-in formats
176+
for (DateFormat dateFormat : dateFormats) {
177+
switch (dateFormat) {
178+
case none:
179+
case custom:
180+
break;
181+
case weekyear:
182+
case weekyear_week:
183+
case weekyear_week_day:
184+
LOGGER.warn("No default converter available for '{}' and date format '{}'. Use a custom converter instead.",
185+
actualType.getName(), dateFormat.name());
186+
break;
187+
default:
188+
converters.add(ElasticsearchDateConverter.of(dateFormat));
189+
break;
188190
}
191+
}
189192

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-
};
193+
for (String dateFormatPattern : dateFormatPatterns) {
194+
if (!StringUtils.hasText(dateFormatPattern)) {
195+
throw new MappingException(String.format("Date pattern of property '%s' must not be empty", getName()));
225196
}
197+
converters.add(ElasticsearchDateConverter.of(dateFormatPattern));
226198
}
199+
200+
return converters;
227201
}
228202

229203
@SuppressWarnings("ConstantConditions")
@@ -303,4 +277,21 @@ public boolean isJoinFieldProperty() {
303277
public boolean isCompletionProperty() {
304278
return getActualType() == Completion.class;
305279
}
280+
281+
private boolean isDateProperty() {
282+
283+
Class<?> actualType = getActualTypeOrNull();
284+
if (actualType == null
285+
|| !Date.class.isAssignableFrom(actualType) && !TemporalAccessor.class.isAssignableFrom(actualType)) {
286+
return false;
287+
}
288+
289+
Field field = findAnnotation(Field.class);
290+
if (field == null || field.type() == null) {
291+
return false;
292+
}
293+
294+
return field.type() == FieldType.Date || field.type() == FieldType.Date_Nanos;
295+
}
296+
306297
}

0 commit comments

Comments
 (0)