From 5b3cf9af4c47b7b1b8423be2a7b8280a31264ef2 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 22 Jan 2023 19:15:12 +0100 Subject: [PATCH] Fix reading response runtime field by mapping. Original Pull Request #2432 Closes #2431 (cherry picked from commit d9bf76fb31c1574225a43c68ea0069d88f5df9b1) --- .../elasticsearch-object-mapping.adoc | 21 +++- .../core/AbstractElasticsearchTemplate.java | 119 +++++++++--------- ...AbstractReactiveElasticsearchTemplate.java | 2 +- .../MappingElasticsearchConverter.java | 4 +- .../ElasticsearchPersistentProperty.java | 2 +- ...SimpleElasticsearchPersistentProperty.java | 3 +- .../core/ElasticsearchIntegrationTests.java | 43 +++---- ...ReactiveElasticsearchIntegrationTests.java | 4 +- .../core/RuntimeFieldsIntegrationTests.java | 71 ++++++++++- src/test/resources/runtime-fields-person.json | 9 ++ 10 files changed, 178 insertions(+), 100 deletions(-) create mode 100644 src/test/resources/runtime-fields-person.json diff --git a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc index bc119f108..95babfbd2 100644 --- a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc +++ b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc @@ -29,7 +29,7 @@ See <> * `@Id`: Applied at the field level to mark the field used for identity purpose. -* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field. +* `@Transient`, `@ReadOnlyProperty`, `@WriteOnlyProperty`: see the following section <> for detailed information. * `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document. * `@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 The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic. +[[elasticsearch.mapping.meta-model.annotations.read-write]] +==== Controlling which properties are written to and read from Elasticsearch + +This section details the annotations that define if the value of a property is written to or +read from Elasticsearch. + +`@Transient`: A property annotated with this annotation will not be written to the mapping, it's value will not be +sent to Elasticsearch and when documents are returned from Elasticsearch, this property will not be set in the +resulting entity. + +`@ReadOnlyProperty`: A property with this annotaiton will not have its value written to Elasticsearch, but when +returning data, the proeprty will be filled with the value returned in the document from Elasticsearch. One use case +for this are runtime fields defined in the index mapping. + +`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set +with any value when reading document. This can be used for example for synthesized fields which should go into the +Elasticsearch index but are not used elsewhere. + + [[elasticsearch.mapping.meta-model.annotations.date-formats]] ==== Date format mapping diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index 00571662a..192458601 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -362,6 +362,67 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper public abstract List doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index); + @Override + public UpdateResponse update(T entity) { + + Assert.notNull(entity, "entity must not be null"); + + return update(buildUpdateQueryByEntity(entity), getIndexCoordinatesFor(entity.getClass())); + } + + protected UpdateQuery buildUpdateQueryByEntity(T entity) { + + Assert.notNull(entity, "entity must not be null"); + + String id = getEntityId(entity); + Assert.notNull(id, "entity must have an id that is notnull"); + + UpdateQuery.Builder updateQueryBuilder = UpdateQuery.builder(id) + .withDocument(elasticsearchConverter.mapObject(entity)); + + String routing = getEntityRouting(entity); + if (StringUtils.hasText(routing)) { + updateQueryBuilder.withRouting(routing); + } + + return updateQueryBuilder.build(); + } + + protected T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { + + ElasticsearchPersistentEntity persistentEntity = elasticsearchConverter.getMappingContext() + .getPersistentEntity(entity.getClass()); + + if (persistentEntity != null) { + PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(entity); + ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); + + // Only deal with text because ES generated Ids are strings! + if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable() + && idProperty.getType().isAssignableFrom(String.class)) { + propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId()); + } + + if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null + && persistentEntity.hasSeqNoPrimaryTermProperty()) { + ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty(); + // noinspection ConstantConditions + propertyAccessor.setProperty(seqNoPrimaryTermProperty, + new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm())); + } + + if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) { + ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); + // noinspection ConstantConditions + propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion()); + } + + // noinspection unchecked + return (T) propertyAccessor.getBean(); + } + return entity; + } + // endregion // region SearchOperations @@ -463,64 +524,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper return getRequiredPersistentEntity(clazz).getIndexCoordinates(); } - @Override - public UpdateResponse update(T entity) { - return update(buildUpdateQueryByEntity(entity), getIndexCoordinatesFor(entity.getClass())); - } - - protected UpdateQuery buildUpdateQueryByEntity(T entity) { - - Assert.notNull(entity, "entity must not be null"); - - String id = getEntityId(entity); - Assert.notNull(id, "entity must have an id that is notnull"); - - UpdateQuery.Builder updateQueryBuilder = UpdateQuery.builder(id) - .withDocument(elasticsearchConverter.mapObject(entity)); - - String routing = getEntityRouting(entity); - if (StringUtils.hasText(routing)) { - updateQueryBuilder.withRouting(routing); - } - - return updateQueryBuilder.build(); - } - - protected T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { - - ElasticsearchPersistentEntity persistentEntity = elasticsearchConverter.getMappingContext() - .getPersistentEntity(entity.getClass()); - - if (persistentEntity != null) { - PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(entity); - ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); - - // Only deal with text because ES generated Ids are strings! - if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable() - && idProperty.getType().isAssignableFrom(String.class)) { - propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId()); - } - - if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null - && persistentEntity.hasSeqNoPrimaryTermProperty()) { - ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty(); - // noinspection ConstantConditions - propertyAccessor.setProperty(seqNoPrimaryTermProperty, - new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm())); - } - - if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) { - ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); - // noinspection ConstantConditions - propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion()); - } - - // noinspection unchecked - return (T) propertyAccessor.getBean(); - } - return entity; - } - ElasticsearchPersistentEntity getRequiredPersistentEntity(Class clazz) { return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java index 04f018644..b9e191305 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java @@ -260,7 +260,7 @@ abstract public class AbstractReactiveElasticsearchTemplate ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); // Only deal with text because ES generated Ids are strings! - if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable() + if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable() && idProperty.getType().isAssignableFrom(String.class)) { propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index fc4aefab4..e1ebd09b3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -349,7 +349,7 @@ public class MappingElasticsearchConverter PersistentPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor<>( targetEntity.getPropertyAccessor(result), conversionService); // Only deal with String because ES generated Ids are strings ! - if (idProperty != null && idProperty.isWritable() && idProperty.getType().isAssignableFrom(String.class)) { + if (idProperty != null && idProperty.isReadable() && idProperty.getType().isAssignableFrom(String.class)) { propertyAccessor.setProperty(idProperty, document.getId()); } } @@ -411,7 +411,7 @@ public class MappingElasticsearchConverter for (ElasticsearchPersistentProperty prop : entity) { - if (entity.isCreatorArgument(prop) || !prop.isReadable() || !prop.isWritable()) { + if (entity.isCreatorArgument(prop) || !prop.isReadable()) { continue; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java index 4afa06be2..6ba751476 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java @@ -61,7 +61,7 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty