Skip to content

Commit 0836411

Browse files
authored
Add runtime fields to index mapping.
Original Pull Request: #1820 Closes: #1816
1 parent 25b323c commit 0836411

File tree

7 files changed

+108
-4
lines changed

7 files changed

+108
-4
lines changed

Diff for: src/main/asciidoc/reference/elasticsearch-misc.adoc

+15-1
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,24 @@ class Entity {
5151

5252
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation. In addition to that it is possible to add the `@Mapping` annotation to a class. This annotation has the following properties:
5353

54-
* `mappingPath` a classpath resource in JSON format which is used as the mapping, no other mapping processing is done.
54+
* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done.
5555
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
5656
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
5757
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
58+
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
59+
====
60+
[source,json]
61+
----
62+
{
63+
"day_of_week": {
64+
"type": "keyword",
65+
"script": {
66+
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
67+
}
68+
}
69+
}
70+
----
71+
====
5872

5973
[[elasticsearch.misc.filter]]
6074
== Filter Builder

Diff for: src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java

+12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* Elasticsearch Mapping
2828
*
2929
* @author Mohsin Husen
30+
* @author Peter-Josef Meisch
3031
*/
3132
@Persistent
3233
@Inherited
@@ -42,6 +43,7 @@
4243
* @since 4.2
4344
*/
4445
boolean enabled() default true;
46+
4547
/**
4648
* whether date_detection is enabled
4749
*
@@ -58,10 +60,20 @@
5860

5961
/**
6062
* custom dynamic date formats
63+
*
6164
* @since 4.3
6265
*/
6366
String[] dynamicDateFormats() default {};
6467

68+
/**
69+
* classpath to a JSON file containing the values for a runtime mapping definition. The file must contain the JSON
70+
* object that is written as the value of the runtime property. {@see <a href=
71+
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html">elasticsearch doc</a>}
72+
*
73+
* @since 4.3
74+
*/
75+
String runtimeFieldsPath() default "";
76+
6577
enum Detection {
6678
DEFAULT, TRUE, FALSE;
6779
}

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

-2
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,6 @@ protected Document buildMapping(Class<?> clazz) {
219219
if (hasText(mappings)) {
220220
return Document.parse(mappings);
221221
}
222-
} else {
223-
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
224222
}
225223
}
226224

Diff for: src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java

+14
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public class MappingBuilder {
9595
private static final String DATE_DETECTION = "date_detection";
9696
private static final String NUMERIC_DETECTION = "numeric_detection";
9797
private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats";
98+
private static final String RUNTIME = "runtime";
9899

99100
private final ElasticsearchConverter elasticsearchConverter;
100101

@@ -168,6 +169,10 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten
168169
if (mappingAnnotation.dynamicDateFormats().length > 0) {
169170
builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats());
170171
}
172+
173+
if (StringUtils.hasText(mappingAnnotation.runtimeFieldsPath())) {
174+
addRuntimeFields(builder, mappingAnnotation.runtimeFieldsPath());
175+
}
171176
}
172177

173178
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
@@ -222,6 +227,15 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten
222227

223228
}
224229

230+
private void addRuntimeFields(XContentBuilder builder, String runtimeFieldsPath) throws IOException {
231+
232+
ClassPathResource runtimeFields = new ClassPathResource(runtimeFieldsPath);
233+
234+
if (runtimeFields.exists()) {
235+
builder.rawField(RUNTIME, runtimeFields.getInputStream(), XContentType.JSON);
236+
}
237+
}
238+
225239
private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject,
226240
ElasticsearchPersistentProperty property) throws IOException {
227241

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

+19
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.lang.Integer;
2626
import java.lang.Object;
2727
import java.math.BigDecimal;
28+
import java.time.Instant;
2829
import java.util.Collection;
2930
import java.util.Collections;
3031
import java.util.Date;
@@ -316,6 +317,16 @@ void shouldWriteDynamicDetectionValues() {
316317
indexOps.delete();
317318
}
318319

320+
@Test // #1816
321+
@DisplayName("should write runtime fields")
322+
void shouldWriteRuntimeFields() {
323+
324+
IndexOperations indexOps = operations.indexOps(RuntimeFieldEntity.class);
325+
indexOps.create();
326+
indexOps.putMapping();
327+
indexOps.delete();
328+
}
329+
319330
// region entities
320331
@Document(indexName = "ignore-above-index")
321332
static class IgnoreAboveEntity {
@@ -1130,6 +1141,14 @@ public void setAuthor(Author author) {
11301141
private static class DynamicDetectionMapping {
11311142
@Id @Nullable private String id;
11321143
}
1144+
1145+
@Document(indexName = "runtime-fields")
1146+
@Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json")
1147+
private static class RuntimeFieldEntity {
1148+
@Id @Nullable private String id;
1149+
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp;
1150+
}
1151+
11331152
// endregion
11341153

11351154
}

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

+40-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.lang.Integer;
2727
import java.lang.Object;
2828
import java.math.BigDecimal;
29+
import java.time.Instant;
2930
import java.time.LocalDate;
3031
import java.time.LocalDateTime;
3132
import java.util.Collection;
@@ -855,7 +856,38 @@ void shouldWriteDynamicDateFormats() throws JSONException {
855856
assertEquals(expected, mapping, true);
856857
}
857858

859+
@Test // #1816
860+
@DisplayName("should write runtime fields")
861+
void shouldWriteRuntimeFields() throws JSONException {
862+
863+
String expected = "{\n" + //
864+
" \"runtime\": {\n" + //
865+
" \"day_of_week\": {\n" + //
866+
" \"type\": \"keyword\",\n" + //
867+
" \"script\": {\n" + //
868+
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + //
869+
" }\n" + //
870+
" }\n" + //
871+
" },\n" + //
872+
" \"properties\": {\n" + //
873+
" \"_class\": {\n" + //
874+
" \"type\": \"keyword\",\n" + //
875+
" \"index\": false,\n" + //
876+
" \"doc_values\": false\n" + //
877+
" },\n" + //
878+
" \"@timestamp\": {\n" + //
879+
" \"type\": \"date\",\n" + //
880+
" \"format\": \"epoch_millis\"\n" + //
881+
" }\n" + //
882+
" }\n" + //
883+
"}\n"; //
884+
885+
String mapping = getMappingBuilder().buildPropertyMapping(RuntimeFieldEntity.class);
886+
887+
assertEquals(expected, mapping, true);
888+
}
858889
// region entities
890+
859891
@Document(indexName = "ignore-above-index")
860892
static class IgnoreAboveEntity {
861893
@Nullable @Id private String id;
@@ -1778,7 +1810,7 @@ private static class DynamicDetectionMappingDefault {
17781810
}
17791811

17801812
@Document(indexName = "dynamic-dateformats-mapping")
1781-
@Mapping(dynamicDateFormats = {"date1", "date2"})
1813+
@Mapping(dynamicDateFormats = { "date1", "date2" })
17821814
private static class DynamicDateFormatsMapping {
17831815
@Id @Nullable private String id;
17841816
}
@@ -1794,5 +1826,12 @@ private static class DynamicDetectionMappingTrue {
17941826
private static class DynamicDetectionMappingFalse {
17951827
@Id @Nullable private String id;
17961828
}
1829+
1830+
@Document(indexName = "runtime-fields")
1831+
@Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json")
1832+
private static class RuntimeFieldEntity {
1833+
@Id @Nullable private String id;
1834+
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp;
1835+
}
17971836
// endregion
17981837
}

Diff for: src/test/resources/mappings/runtime-fields.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"day_of_week": {
3+
"type": "keyword",
4+
"script": {
5+
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)