Skip to content

Commit 5b3cf9a

Browse files
committed
Fix reading response runtime field by mapping.
Original Pull Request #2432 Closes #2431 (cherry picked from commit d9bf76f)
1 parent 85a7ed7 commit 5b3cf9a

10 files changed

+179
-101
lines changed

src/main/asciidoc/reference/elasticsearch-object-mapping.adoc

+20-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ See <<elasticsearch.repositories.autocreation>>
2929

3030

3131
* `@Id`: Applied at the field level to mark the field used for identity purpose.
32-
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
32+
* `@Transient`, `@ReadOnlyProperty`, `@WriteOnlyProperty`: see the following section <<elasticsearch.mapping.meta-model.annotations.read-write>> for detailed information.
3333
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database.
3434
Constructor arguments are mapped by name to the key values in the retrieved Document.
3535
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
@@ -49,6 +49,25 @@ In difference to a registered Spring `Converter` this only converts the annotate
4949

5050
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
5151

52+
[[elasticsearch.mapping.meta-model.annotations.read-write]]
53+
==== Controlling which properties are written to and read from Elasticsearch
54+
55+
This section details the annotations that define if the value of a property is written to or
56+
read from Elasticsearch.
57+
58+
`@Transient`: A property annotated with this annotation will not be written to the mapping, it's value will not be
59+
sent to Elasticsearch and when documents are returned from Elasticsearch, this property will not be set in the
60+
resulting entity.
61+
62+
`@ReadOnlyProperty`: A property with this annotaiton will not have its value written to Elasticsearch, but when
63+
returning data, the proeprty will be filled with the value returned in the document from Elasticsearch. One use case
64+
for this are runtime fields defined in the index mapping.
65+
66+
`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set
67+
with any value when reading document. This can be used for example for synthesized fields which should go into the
68+
Elasticsearch index but are not used elsewhere.
69+
70+
5271
[[elasticsearch.mapping.meta-model.annotations.date-formats]]
5372
==== Date format mapping
5473

src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java

+61-58
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,67 @@ public List<IndexedObjectInformation> bulkOperation(List<?> queries, BulkOptions
362362
public abstract List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
363363
IndexCoordinates index);
364364

365+
@Override
366+
public <T> UpdateResponse update(T entity) {
367+
368+
Assert.notNull(entity, "entity must not be null");
369+
370+
return update(buildUpdateQueryByEntity(entity), getIndexCoordinatesFor(entity.getClass()));
371+
}
372+
373+
protected <T> UpdateQuery buildUpdateQueryByEntity(T entity) {
374+
375+
Assert.notNull(entity, "entity must not be null");
376+
377+
String id = getEntityId(entity);
378+
Assert.notNull(id, "entity must have an id that is notnull");
379+
380+
UpdateQuery.Builder updateQueryBuilder = UpdateQuery.builder(id)
381+
.withDocument(elasticsearchConverter.mapObject(entity));
382+
383+
String routing = getEntityRouting(entity);
384+
if (StringUtils.hasText(routing)) {
385+
updateQueryBuilder.withRouting(routing);
386+
}
387+
388+
return updateQueryBuilder.build();
389+
}
390+
391+
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
392+
393+
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
394+
.getPersistentEntity(entity.getClass());
395+
396+
if (persistentEntity != null) {
397+
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
398+
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
399+
400+
// Only deal with text because ES generated Ids are strings!
401+
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable()
402+
&& idProperty.getType().isAssignableFrom(String.class)) {
403+
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
404+
}
405+
406+
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
407+
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
408+
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
409+
// noinspection ConstantConditions
410+
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
411+
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
412+
}
413+
414+
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
415+
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
416+
// noinspection ConstantConditions
417+
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
418+
}
419+
420+
// noinspection unchecked
421+
return (T) propertyAccessor.getBean();
422+
}
423+
return entity;
424+
}
425+
365426
// endregion
366427

367428
// region SearchOperations
@@ -463,64 +524,6 @@ public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
463524
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
464525
}
465526

466-
@Override
467-
public <T> UpdateResponse update(T entity) {
468-
return update(buildUpdateQueryByEntity(entity), getIndexCoordinatesFor(entity.getClass()));
469-
}
470-
471-
protected <T> UpdateQuery buildUpdateQueryByEntity(T entity) {
472-
473-
Assert.notNull(entity, "entity must not be null");
474-
475-
String id = getEntityId(entity);
476-
Assert.notNull(id, "entity must have an id that is notnull");
477-
478-
UpdateQuery.Builder updateQueryBuilder = UpdateQuery.builder(id)
479-
.withDocument(elasticsearchConverter.mapObject(entity));
480-
481-
String routing = getEntityRouting(entity);
482-
if (StringUtils.hasText(routing)) {
483-
updateQueryBuilder.withRouting(routing);
484-
}
485-
486-
return updateQueryBuilder.build();
487-
}
488-
489-
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
490-
491-
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
492-
.getPersistentEntity(entity.getClass());
493-
494-
if (persistentEntity != null) {
495-
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
496-
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
497-
498-
// Only deal with text because ES generated Ids are strings!
499-
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
500-
&& idProperty.getType().isAssignableFrom(String.class)) {
501-
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
502-
}
503-
504-
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
505-
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
506-
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
507-
// noinspection ConstantConditions
508-
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
509-
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
510-
}
511-
512-
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
513-
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
514-
// noinspection ConstantConditions
515-
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
516-
}
517-
518-
// noinspection unchecked
519-
return (T) propertyAccessor.getBean();
520-
}
521-
return entity;
522-
}
523-
524527
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
525528
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
526529
}

src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedOb
260260
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
261261

262262
// Only deal with text because ES generated Ids are strings!
263-
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
263+
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable()
264264
&& idProperty.getType().isAssignableFrom(String.class)) {
265265
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
266266
}

src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ private <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Ob
349349
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
350350
targetEntity.getPropertyAccessor(result), conversionService);
351351
// Only deal with String because ES generated Ids are strings !
352-
if (idProperty != null && idProperty.isWritable() && idProperty.getType().isAssignableFrom(String.class)) {
352+
if (idProperty != null && idProperty.isReadable() && idProperty.getType().isAssignableFrom(String.class)) {
353353
propertyAccessor.setProperty(idProperty, document.getId());
354354
}
355355
}
@@ -411,7 +411,7 @@ protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instan
411411

412412
for (ElasticsearchPersistentProperty prop : entity) {
413413

414-
if (entity.isCreatorArgument(prop) || !prop.isReadable() || !prop.isWritable()) {
414+
if (entity.isCreatorArgument(prop) || !prop.isReadable()) {
415415
continue;
416416
}
417417

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
6161
PropertyValueConverter getPropertyValueConverter();
6262

6363
/**
64-
* Returns true if the property may be read.
64+
* Returns true if the property may be read from the store into the entity.
6565
*
6666
* @return true if readable, false otherwise
6767
* @since 4.0

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.commons.logging.Log;
2525
import org.apache.commons.logging.LogFactory;
2626
import org.springframework.beans.BeanUtils;
27+
import org.springframework.data.annotation.ReadOnlyProperty;
2728
import org.springframework.data.domain.Range;
2829
import org.springframework.data.elasticsearch.annotations.DateFormat;
2930
import org.springframework.data.elasticsearch.annotations.Field;
@@ -119,7 +120,7 @@ public PropertyValueConverter getPropertyValueConverter() {
119120

120121
@Override
121122
public boolean isWritable() {
122-
return super.isWritable() && !isSeqNoPrimaryTermProperty();
123+
return !isTransient() && !isSeqNoPrimaryTermProperty() && !isAnnotationPresent(ReadOnlyProperty.class);
123124
}
124125

125126
@Override

src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java

+15-30
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,20 @@
1515
*/
1616
package org.springframework.data.elasticsearch.core;
1717

18-
import static java.util.Collections.singletonList;
19-
import static org.assertj.core.api.Assertions.assertThat;
20-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
21-
import static org.assertj.core.api.Assertions.fail;
22-
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
23-
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
24-
import static org.springframework.data.elasticsearch.annotations.Document.VersionType.EXTERNAL_GTE;
18+
import static java.util.Collections.*;
19+
import static org.assertj.core.api.Assertions.*;
20+
import static org.elasticsearch.index.query.QueryBuilders.*;
21+
import static org.springframework.data.elasticsearch.annotations.Document.VersionType.*;
22+
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
2523
import static org.springframework.data.elasticsearch.annotations.FieldType.Integer;
26-
import static org.springframework.data.elasticsearch.annotations.FieldType.Keyword;
27-
import static org.springframework.data.elasticsearch.annotations.FieldType.Text;
28-
import static org.springframework.data.elasticsearch.core.document.Document.create;
29-
import static org.springframework.data.elasticsearch.core.document.Document.parse;
30-
import static org.springframework.data.elasticsearch.utils.IdGenerator.nextIdAsString;
31-
import static org.springframework.data.elasticsearch.utils.IndexBuilder.buildIndex;
32-
24+
import static org.springframework.data.elasticsearch.core.document.Document.*;
25+
import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
26+
import static org.springframework.data.elasticsearch.utils.IndexBuilder.*;
27+
28+
import java.lang.Double;
29+
import java.lang.Integer;
30+
import java.lang.Long;
31+
import java.lang.Object;
3332
import java.util.ArrayList;
3433
import java.util.Arrays;
3534
import java.util.Collection;
@@ -55,7 +54,6 @@
5554
import org.springframework.dao.OptimisticLockingFailureException;
5655
import org.springframework.data.annotation.AccessType;
5756
import org.springframework.data.annotation.Id;
58-
import org.springframework.data.annotation.ReadOnlyProperty;
5957
import org.springframework.data.annotation.Version;
6058
import org.springframework.data.domain.PageRequest;
6159
import org.springframework.data.domain.Pageable;
@@ -79,20 +77,7 @@
7977
import org.springframework.data.elasticsearch.core.index.Settings;
8078
import org.springframework.data.elasticsearch.core.join.JoinField;
8179
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
82-
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
83-
import org.springframework.data.elasticsearch.core.query.Criteria;
84-
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
85-
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
86-
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
87-
import org.springframework.data.elasticsearch.core.query.IndexQuery;
88-
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
89-
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
90-
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
91-
import org.springframework.data.elasticsearch.core.query.Query;
92-
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
93-
import org.springframework.data.elasticsearch.core.query.SourceFilter;
94-
import org.springframework.data.elasticsearch.core.query.StringQuery;
95-
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
80+
import org.springframework.data.elasticsearch.core.query.*;
9681
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
9782
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
9883
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
@@ -4582,7 +4567,7 @@ static class ReadonlyIdEntity {
45824567
@Field(type = FieldType.Keyword) private String part2;
45834568

45844569
@Id
4585-
@ReadOnlyProperty
4570+
@WriteOnlyProperty
45864571
@AccessType(AccessType.Type.PROPERTY)
45874572
public String getId() {
45884573
return part1 + '-' + part2;

src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchIntegrationTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
import org.springframework.dao.OptimisticLockingFailureException;
5353
import org.springframework.data.annotation.AccessType;
5454
import org.springframework.data.annotation.Id;
55-
import org.springframework.data.annotation.ReadOnlyProperty;
5655
import org.springframework.data.annotation.Version;
5756
import org.springframework.data.domain.PageRequest;
5857
import org.springframework.data.domain.Pageable;
@@ -63,6 +62,7 @@
6362
import org.springframework.data.elasticsearch.annotations.FieldType;
6463
import org.springframework.data.elasticsearch.annotations.Mapping;
6564
import org.springframework.data.elasticsearch.annotations.Setting;
65+
import org.springframework.data.elasticsearch.annotations.WriteOnlyProperty;
6666
import org.springframework.data.elasticsearch.client.erhlc.NativeSearchQuery;
6767
import org.springframework.data.elasticsearch.client.erhlc.NativeSearchQueryBuilder;
6868
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchTemplate;
@@ -1503,7 +1503,7 @@ static class ReadonlyIdEntity {
15031503
@Field(type = FieldType.Keyword) private String part2;
15041504

15051505
@Id
1506-
@ReadOnlyProperty
1506+
@WriteOnlyProperty
15071507
@AccessType(AccessType.Type.PROPERTY)
15081508
public String getId() {
15091509
return part1 + '-' + part2;

0 commit comments

Comments
 (0)