Skip to content

Commit 65329f4

Browse files
committed
Add AfterLoad callback.
Closes #2009
1 parent 780f955 commit 65329f4

File tree

7 files changed

+233
-39
lines changed

7 files changed

+233
-39
lines changed

Diff for: src/main/java/org/springframework/data/elasticsearch/backend/elasticsearch7/ReactiveElasticsearchTemplate.java

+26-6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.springframework.data.elasticsearch.core.document.Document;
7070
import org.springframework.data.elasticsearch.core.document.SearchDocument;
7171
import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback;
72+
import org.springframework.data.elasticsearch.core.event.ReactiveAfterLoadCallback;
7273
import org.springframework.data.elasticsearch.core.event.ReactiveAfterSaveCallback;
7374
import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback;
7475
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
@@ -1159,6 +1160,17 @@ protected <T> Mono<T> maybeCallAfterConvert(T entity, Document document, IndexCo
11591160

11601161
return Mono.just(entity);
11611162
}
1163+
1164+
protected <T> Mono<Document> maybeCallbackAfterLoad(Document document, Class<T> type,
1165+
IndexCoordinates indexCoordinates) {
1166+
1167+
if (entityCallbacks != null) {
1168+
return entityCallbacks.callback(ReactiveAfterLoadCallback.class, document, type, indexCoordinates);
1169+
}
1170+
1171+
return Mono.just(document);
1172+
}
1173+
11621174
// endregion
11631175

11641176
// region routing
@@ -1206,12 +1218,20 @@ public Mono<T> toEntity(@Nullable Document document) {
12061218
return Mono.empty();
12071219
}
12081220

1209-
T entity = reader.read(type, document);
1210-
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
1211-
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
1212-
document.getVersion());
1213-
entity = updateIndexedObject(entity, indexedObjectInformation);
1214-
return maybeCallAfterConvert(entity, document, index);
1221+
return maybeCallbackAfterLoad(document, type, index) //
1222+
.flatMap(documentAfterLoad -> {
1223+
1224+
T entity = reader.read(type, documentAfterLoad);
1225+
1226+
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
1227+
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
1228+
documentAfterLoad.getSeqNo(), //
1229+
documentAfterLoad.getPrimaryTerm(), //
1230+
documentAfterLoad.getVersion()); //
1231+
entity = updateIndexedObject(entity, indexedObjectInformation);
1232+
1233+
return maybeCallAfterConvert(entity, documentAfterLoad, index);
1234+
});
12151235
}
12161236
}
12171237

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

+20-5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
3434
import org.springframework.data.elasticsearch.core.document.Document;
3535
import org.springframework.data.elasticsearch.core.event.AfterConvertCallback;
36+
import org.springframework.data.elasticsearch.core.event.AfterLoadCallback;
3637
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
3738
import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback;
3839
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
@@ -690,6 +691,15 @@ protected <T> T maybeCallbackAfterConvert(T entity, Document document, IndexCoor
690691
return entity;
691692
}
692693

694+
protected <T> Document maybeCallbackAfterLoad(Document document, Class<T> type, IndexCoordinates indexCoordinates) {
695+
696+
if (entityCallbacks != null) {
697+
return entityCallbacks.callback(AfterLoadCallback.class, document, type, indexCoordinates);
698+
}
699+
700+
return document;
701+
}
702+
693703
// endregion
694704

695705
protected void updateIndexedObjectsWithQueries(List<?> queries,
@@ -736,13 +746,18 @@ public T doWith(@Nullable Document document) {
736746
if (document == null) {
737747
return null;
738748
}
749+
Document documentAfterLoad = maybeCallbackAfterLoad(document, type, index);
739750

740-
T entity = reader.read(type, document);
741-
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
742-
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
743-
document.getVersion());
751+
T entity = reader.read(type, documentAfterLoad);
752+
753+
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
754+
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
755+
documentAfterLoad.getSeqNo(), //
756+
documentAfterLoad.getPrimaryTerm(), //
757+
documentAfterLoad.getVersion()); //
744758
entity = updateIndexedObject(entity, indexedObjectInformation);
745-
return maybeCallbackAfterConvert(entity, document, index);
759+
760+
return maybeCallbackAfterConvert(entity, documentAfterLoad, index);
746761
}
747762
}
748763

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 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.event;
17+
18+
import org.springframework.data.elasticsearch.core.document.Document;
19+
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
20+
import org.springframework.data.mapping.callback.EntityCallback;
21+
22+
/**
23+
* Callback being invoked after a {@link Document} is read from Elasticsearch and before it is converted into a domain
24+
* object.
25+
*
26+
* @author Peter-Josef Meisch
27+
* @since 4.4
28+
* @see org.springframework.data.mapping.callback.EntityCallbacks
29+
*/
30+
@FunctionalInterface
31+
public interface AfterLoadCallback<T> extends EntityCallback<Document> {
32+
33+
/**
34+
* Entity callback method invoked after a domain object is materialized from a {@link Document}. Can return either the
35+
* same or a modified instance of the {@link Document} object.
36+
*
37+
* @param document the document.
38+
* @param indexCoordinates of the index the document was read from.
39+
* @return a possible modified or new {@link Document}.
40+
*/
41+
Document onAfterLoad(Document document, Class<T> type, IndexCoordinates indexCoordinates);
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 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.event;
17+
18+
import org.reactivestreams.Publisher;
19+
import org.springframework.data.elasticsearch.core.document.Document;
20+
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
21+
import org.springframework.data.mapping.callback.EntityCallback;
22+
23+
/**
24+
* Callback being invoked after a {@link Document} is read from Elasticsearch and before it is converted into a domain
25+
* object.
26+
*
27+
* @author Peter-Josef Meisch
28+
* @since 4.4
29+
* @see org.springframework.data.mapping.callback.EntityCallbacks
30+
*/
31+
@FunctionalInterface
32+
public interface ReactiveAfterLoadCallback<T> extends EntityCallback<Document> {
33+
34+
/**
35+
* Entity callback method invoked after a domain object is materialized from a {@link Document}. Can return either the
36+
* same or a modified instance of the {@link Document} object.
37+
*
38+
* @param document the document.
39+
* @param indexCoordinates of the index the document was read from.
40+
* @return a possible modified or new {@link Document}.
41+
*/
42+
Publisher<Document> onAfterLoad(Document document, Class<T> type, IndexCoordinates indexCoordinates);
43+
}

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

+34-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.beans.factory.annotation.Autowired;
3030
import org.springframework.context.annotation.Configuration;
3131
import org.springframework.data.annotation.Id;
32+
import org.springframework.data.annotation.ReadOnlyProperty;
3233
import org.springframework.data.elasticsearch.annotations.Document;
3334
import org.springframework.data.elasticsearch.annotations.JoinTypeRelation;
3435
import org.springframework.data.elasticsearch.annotations.JoinTypeRelations;
@@ -73,6 +74,19 @@ public SampleEntity onBeforeConvert(SampleEntity entity, IndexCoordinates index)
7374
return entity;
7475
}
7576
}
77+
78+
@Component
79+
static class SampleEntityAfterLoadCallback implements AfterLoadCallback<SampleEntity> {
80+
81+
@Override
82+
public org.springframework.data.elasticsearch.core.document.Document onAfterLoad(
83+
org.springframework.data.elasticsearch.core.document.Document document, Class<SampleEntity> type,
84+
IndexCoordinates indexCoordinates) {
85+
86+
document.put("className", document.get("_class"));
87+
return document;
88+
}
89+
}
7690
}
7791

7892
@BeforeEach
@@ -214,12 +228,30 @@ void shouldApplyConversionResultToIndexQueryInBulkIndex() {
214228
assertThat(capturedIndexQuery.getPrimaryTerm()).isEqualTo(seqNoPrimaryTerm.getPrimaryTerm());
215229
}
216230

231+
@Test // #2009
232+
@DisplayName("should invoke after load callback")
233+
void shouldInvokeAfterLoadCallback() {
234+
235+
SampleEntity entity = new SampleEntity("1", "test");
236+
operations.save(entity);
237+
238+
SampleEntity loaded = operations.get(entity.getId(), SampleEntity.class);
239+
240+
assertThat(loaded).isNotNull();
241+
assertThat(loaded.className).isEqualTo(SampleEntity.class.getName());
242+
}
243+
217244
@Document(indexName = INDEX)
218245
static class SampleEntity {
219-
@Nullable @Id private String id;
246+
@Nullable
247+
@Id private String id;
220248
@Nullable private String text;
221249

222-
@Nullable @JoinTypeRelations(relations = {
250+
@ReadOnlyProperty
251+
@Nullable private String className;
252+
253+
@Nullable
254+
@JoinTypeRelations(relations = {
223255
@JoinTypeRelation(parent = "question", children = { "answer" }) }) private JoinField<String> joinField;
224256

225257
@Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm;

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

+36
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222

2323
import org.junit.jupiter.api.AfterEach;
2424
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.DisplayName;
2526
import org.junit.jupiter.api.Test;
2627
import org.springframework.beans.factory.annotation.Autowired;
2728
import org.springframework.context.annotation.Configuration;
2829
import org.springframework.context.annotation.Import;
2930
import org.springframework.data.annotation.Id;
31+
import org.springframework.data.annotation.ReadOnlyProperty;
3032
import org.springframework.data.elasticsearch.annotations.Document;
3133
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
3234
import org.springframework.data.elasticsearch.core.IndexOperations;
@@ -35,6 +37,7 @@
3537
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
3638
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
3739
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
40+
import org.springframework.lang.Nullable;
3841
import org.springframework.stereotype.Component;
3942
import org.springframework.test.context.ContextConfiguration;
4043

@@ -49,6 +52,7 @@ public class ReactiveElasticsearchOperationsCallbackTest {
4952
@Configuration
5053
@Import({ ReactiveElasticsearchRestTemplateConfiguration.class, ElasticsearchRestTemplateConfiguration.class })
5154
static class Config {
55+
5256
@Component
5357
static class SampleEntityBeforeConvertCallback implements ReactiveBeforeConvertCallback<SampleEntity> {
5458
@Override
@@ -58,6 +62,20 @@ public Mono<SampleEntity> onBeforeConvert(SampleEntity entity, IndexCoordinates
5862
}
5963
}
6064

65+
@Component
66+
static class SampleEntityAfterLoadCallback
67+
implements ReactiveAfterLoadCallback<ElasticsearchOperationsCallbackIntegrationTests.SampleEntity> {
68+
69+
@Override
70+
public Mono<org.springframework.data.elasticsearch.core.document.Document> onAfterLoad(
71+
org.springframework.data.elasticsearch.core.document.Document document,
72+
Class<ElasticsearchOperationsCallbackIntegrationTests.SampleEntity> type, IndexCoordinates indexCoordinates) {
73+
74+
document.put("className", document.get("_class"));
75+
return Mono.just(document);
76+
}
77+
}
78+
6179
}
6280

6381
@Autowired private ReactiveElasticsearchOperations operations;
@@ -88,11 +106,29 @@ void shouldCallCallbackOnSave() {
88106
.verifyComplete();
89107
}
90108

109+
@Test // #2009
110+
@DisplayName("should invoke after load callback")
111+
void shouldInvokeAfterLoadCallback() {
112+
113+
SampleEntity entity = new SampleEntity("1", "test");
114+
115+
operations.save(entity) //
116+
.then(operations.get(entity.getId(), SampleEntity.class)) //
117+
.as(StepVerifier::create) //
118+
.consumeNextWith(loaded -> { //
119+
assertThat(loaded).isNotNull(); //
120+
assertThat(loaded.className).isEqualTo(SampleEntity.class.getName()); //
121+
}).verifyComplete(); //
122+
}
123+
91124
@Document(indexName = "test-operations-reactive-callback")
92125
static class SampleEntity {
93126
@Id private String id;
94127
private String text;
95128

129+
@ReadOnlyProperty
130+
@Nullable private String className;
131+
96132
public SampleEntity(String id, String text) {
97133
this.id = id;
98134
this.text = text;

0 commit comments

Comments
 (0)