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 6703696fd..c5e48fb1c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -188,7 +188,7 @@ public T save(T entity, IndexCoordinates index) { IndexQuery query = getIndexQuery(entityAfterBeforeConvert); doIndex(query, index); - T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index); + T entityAfterAfterSave = (T) maybeCallbackAfterSave(query.getObject(), index); return entityAfterAfterSave; } @@ -215,13 +215,18 @@ public Iterable save(Iterable entities, IndexCoordinates index) { List indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery) .collect(Collectors.toList()); - if (!indexQueries.isEmpty()) { - List indexedObjectInformations = bulkIndex(indexQueries, index); - Iterator iterator = indexedObjectInformations.iterator(); - entities.forEach(entity -> updateIndexedObject(entity, iterator.next())); + if (indexQueries.isEmpty()) { + return Collections.emptyList(); } - return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList()); + List indexedObjectInformations = bulkIndex(indexQueries, index); + Iterator iterator = indexedObjectInformations.iterator(); + + // noinspection unchecked + return indexQueries.stream() // + .map(IndexQuery::getObject) // + .map(entity -> (T) updateIndexedObject(entity, iterator.next())) // + .collect(Collectors.toList()); // } @Override @@ -419,7 +424,9 @@ public SearchHits search(MoreLikeThisQuery query, Class clazz, IndexCo Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery"); MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index); - return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index); + return search( + new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), + clazz, index); } @Override @@ -611,7 +618,7 @@ protected List checkForBulkOperationFailure(BulkRespon }).collect(Collectors.toList()); } - protected void updateIndexedObject(Object entity, IndexedObjectInformation indexedObjectInformation) { + protected T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { ElasticsearchPersistentEntity persistentEntity = elasticsearchConverter.getMappingContext() .getPersistentEntity(entity.getClass()); @@ -621,22 +628,30 @@ protected void updateIndexedObject(Object entity, IndexedObjectInformation index ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); // Only deal with text because ES generated Ids are strings! - if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { + if (indexedObjectInformation.getId() != null && idProperty != null + && 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 + T updatedEntity = (T) propertyAccessor.getBean(); + return updatedEntity; } + return entity; } ElasticsearchPersistentEntity getRequiredPersistentEntity(Class clazz) { @@ -807,13 +822,16 @@ protected T maybeCallbackAfterConvert(T entity, Document document, IndexCoor protected void updateIndexedObjectsWithQueries(List queries, List indexedObjectInformations) { + for (int i = 0; i < queries.size(); i++) { Object query = queries.get(i); + if (query instanceof IndexQuery) { IndexQuery indexQuery = (IndexQuery) query; Object queryObject = indexQuery.getObject(); + if (queryObject != null) { - updateIndexedObject(queryObject, indexedObjectInformations.get(i)); + indexQuery.setObject(updateIndexedObject(queryObject, indexedObjectInformations.get(i))); } } } @@ -848,6 +866,10 @@ public T doWith(@Nullable Document document) { } T entity = reader.read(type, document); + IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( + document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(), + document.getVersion()); + entity = updateIndexedObject(entity, indexedObjectInformation); return maybeCallbackAfterConvert(entity, document, index); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index f4826db23..d65cb1045 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -157,9 +157,10 @@ public String doIndex(IndexQuery query, IndexCoordinates index) { IndexResponse indexResponse = execute(client -> client.index(request, RequestOptions.DEFAULT)); Object queryObject = query.getObject(); + if (queryObject != null) { - updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), indexResponse.getSeqNo(), - indexResponse.getPrimaryTerm(), indexResponse.getVersion())); + query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), + indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion()))); } return indexResponse.getId(); @@ -168,6 +169,7 @@ public String doIndex(IndexQuery query, IndexCoordinates index) { @Override @Nullable public T get(String id, Class clazz, IndexCoordinates index) { + GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(), index); GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT)); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 70911a12e..6d3489fbe 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -177,8 +177,8 @@ public String doIndex(IndexQuery query, IndexCoordinates index) { Object queryObject = query.getObject(); if (queryObject != null) { - updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(), - response.getPrimaryTerm(), response.getVersion())); + query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(), + response.getPrimaryTerm(), response.getVersion()))); } return documentId; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java index 5a52c1ad9..56e5da428 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java @@ -477,7 +477,6 @@ public ElasticsearchPersistentEntity getPersistentEntity() { */ private static class AdaptibleMappedEntity extends MappedEntity implements AdaptibleEntity { - private final T bean; private final ElasticsearchPersistentEntity entity; private final ConvertingPropertyAccessor propertyAccessor; private final IdentifierAccessor identifierAccessor; @@ -490,7 +489,6 @@ private AdaptibleMappedEntity(T bean, ElasticsearchPersistentEntity entity, super(entity, identifierAccessor, propertyAccessor); - this.bean = bean; this.entity = entity; this.propertyAccessor = propertyAccessor; this.identifierAccessor = identifierAccessor; @@ -510,6 +508,11 @@ static AdaptibleEntity of(T bean, new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver); } + @Override + public T getBean() { + return propertyAccessor.getBean(); + } + @Nullable @Override public T populateIdIfNecessary(@Nullable Object id) { @@ -584,7 +587,7 @@ public T incrementVersion() { @Override public String getRouting() { - String routing = routingResolver.getRouting(bean); + String routing = routingResolver.getRouting(propertyAccessor.getBean()); if (routing != null) { return routing; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java b/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java index 18c239949..0ae1fd061 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java @@ -25,12 +25,12 @@ * @since 4.1 */ public class IndexedObjectInformation { - private final String id; + @Nullable private final String id; @Nullable private final Long seqNo; @Nullable private final Long primaryTerm; @Nullable private final Long version; - private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long primaryTerm, + private IndexedObjectInformation(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm, @Nullable Long version) { this.id = id; this.seqNo = seqNo; @@ -38,11 +38,12 @@ private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long this.version = version; } - public static IndexedObjectInformation of(String id, @Nullable Long seqNo, @Nullable Long primaryTerm, + public static IndexedObjectInformation of(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm, @Nullable Long version) { return new IndexedObjectInformation(id, seqNo, primaryTerm, version); } + @Nullable public String getId() { return id; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 552bd4544..67ebf03b8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -275,27 +275,42 @@ public Flux saveAll(Mono> entitiesPubli } private T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { - AdaptibleEntity adaptibleEntity = operations.forEntity(entity, converter.getConversionService(), - routingResolver); - adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId()); - ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(entity.getClass()); + ElasticsearchPersistentEntity persistentEntity = converter.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.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 + T updatedEntity = (T) propertyAccessor.getBean(); + return updatedEntity; + } else { + AdaptibleEntity adaptibleEntity = operations.forEntity(entity, converter.getConversionService(), + routingResolver); + adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId()); + } return entity; } @@ -457,7 +472,7 @@ public Mono get(String id, Class entityType, IndexCoordinates index) { DocumentCallback callback = new ReadDocumentCallback<>(converter, entityType, index); - return doGet(id, index).flatMap(it -> callback.toEntity(DocumentAdapters.from(it))); + return doGet(id, index).flatMap(response -> callback.toEntity(DocumentAdapters.from(response))); } private Mono doGet(String id, IndexCoordinates index) { @@ -1097,6 +1112,10 @@ public Mono toEntity(@Nullable Document document) { } T entity = reader.read(type, document); + IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( + document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(), + document.getVersion()); + entity = updateIndexedObject(entity, indexedObjectInformation); return maybeCallAfterConvert(entity, document, index); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java b/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java index c3538ef5e..034905be0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java @@ -15,6 +15,7 @@ */ package org.springframework.data.elasticsearch.core.join; +import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.lang.Nullable; /** @@ -35,6 +36,7 @@ public JoinField(String name) { this(name, null); } + @PersistenceConstructor public JoinField(String name, @Nullable ID parent) { this.name = name; this.parent = parent; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index 74bb804c2..9b4822a6e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -35,7 +35,6 @@ import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.FieldNamingStrategy; -import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; @@ -238,19 +237,6 @@ private void warnAboutBothSeqNoPrimaryTermAndVersionProperties() { getType()); } - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.model.BasicPersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory) - */ - @SuppressWarnings("SpellCheckingInspection") - @Override - public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) { - - // Do nothing to avoid the usage of ClassGeneratingPropertyAccessorFactory for now - // DATACMNS-1322 switches to proper immutability behavior which Spring Data Elasticsearch - // cannot yet implement - } - @Nullable @Override public ElasticsearchPersistentProperty getPersistentPropertyWithFieldName(String fieldName) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 1d171715c..1ecb9dcb1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -279,11 +279,6 @@ protected Association createAssociation() { throw new UnsupportedOperationException(); } - @Override - public boolean isImmutable() { - return false; - } - @Override public boolean isSeqNoPrimaryTermProperty() { return isSeqNoPrimaryTerm; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 48fedcada..64bab266a 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -60,7 +60,6 @@ import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -137,7 +136,8 @@ public abstract class ElasticsearchTemplateTests { @BeforeEach public void before() { indexOperations = operations.indexOps(SampleEntity.class); - deleteIndices(); + + operations.indexOps(IndexCoordinates.of("*")).delete(); indexOperations.create(); indexOperations.putMapping(SampleEntity.class); @@ -155,29 +155,6 @@ public void before() { indexOpsJoinEntity.putMapping(SampleJoinEntity.class); } - @AfterEach - public void after() { - - deleteIndices(); - } - - private void deleteIndices() { - - indexOperations.delete(); - operations.indexOps(SampleEntityUUIDKeyed.class).delete(); - operations.indexOps(UseServerConfigurationEntity.class).delete(); - operations.indexOps(SampleMappingEntity.class).delete(); - operations.indexOps(Book.class).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)).delete(); - operations.indexOps(SearchHitsEntity.class).delete(); - operations.indexOps(HighlightEntity.class).delete(); - operations.indexOps(OptimisticEntity.class).delete(); - operations.indexOps(OptimisticAndVersionedEntity.class).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).delete(); - } - @Test // DATAES-106 public void shouldReturnCountForGivenCriteriaQuery() { @@ -1126,7 +1103,8 @@ void shouldUsePageableOnMoreLikeThisQueries() { Collection ids = IntStream.rangeClosed(1, 10).mapToObj(i -> nextIdAsString()).collect(Collectors.toList()); ids.add(referenceId); ids.stream() - .map(id -> getIndexQuery(SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build())) + .map(id -> getIndexQuery( + SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build())) .forEach(indexQuery -> operations.index(indexQuery, index)); indexOperations.refresh(); @@ -1141,7 +1119,8 @@ void shouldUsePageableOnMoreLikeThisQueries() { assertThat(searchHits.getTotalHits()).isEqualTo(10); assertThat(searchHits.getSearchHits()).hasSize(5); - Collection returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId).collect(Collectors.toList()); + Collection returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId) + .collect(Collectors.toList()); moreLikeThisQuery.setPageable(PageRequest.of(1, 5)); @@ -3588,6 +3567,24 @@ void shouldReturnExplanationWhenRequested() { assertThat(explanation).isNotNull(); } + @Test // #1800 + @DisplayName("should work with immutable classes") + void shouldWorkWithImmutableClasses() { + + ImmutableEntity entity = new ImmutableEntity(null, "some text", null); + + ImmutableEntity saved = operations.save(entity); + + assertThat(saved).isNotNull(); + assertThat(saved.getId()).isNotEmpty(); + SeqNoPrimaryTerm seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm(); + assertThat(seqNoPrimaryTerm).isNotNull(); + + ImmutableEntity retrieved = operations.get(saved.getId(), ImmutableEntity.class); + + assertThat(retrieved).isEqualTo(saved); + } + // region entities @Document(indexName = INDEX_NAME_SAMPLE_ENTITY) @Setting(shards = 1, replicas = 0, refreshInterval = "-1") @@ -4366,5 +4363,61 @@ public void setText(@Nullable String text) { this.text = text; } } - //endregion + + @Document(indexName = "immutable-class") + private static final class ImmutableEntity { + @Id private final String id; + @Field(type = FieldType.Text) private final String text; + @Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm; + + public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.id = id; + this.text = text; + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } + + public String getId() { + return id; + } + + public String getText() { + return text; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ImmutableEntity that = (ImmutableEntity) o; + + if (!id.equals(that.id)) + return false; + if (!text.equals(that.text)) + return false; + return seqNoPrimaryTerm != null ? seqNoPrimaryTerm.equals(that.seqNoPrimaryTerm) : that.seqNoPrimaryTerm == null; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + text.hashCode(); + result = 31 * result + (seqNoPrimaryTerm != null ? seqNoPrimaryTerm.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ImmutableEntity{" + "id='" + id + '\'' + ", text='" + text + '\'' + ", seqNoPrimaryTerm=" + + seqNoPrimaryTerm + '}'; + } + } + // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index 90755add8..0fb532bf1 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -64,6 +65,7 @@ import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.Mapping; import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; @@ -1149,6 +1151,26 @@ void shouldReturnInformationListOfAllIndices() { }).verifyComplete(); } + @Test // #1800 + @DisplayName("should work with immutable classes") + void shouldWorkWithImmutableClasses() { + + ImmutableEntity entity = new ImmutableEntity(null, "some text", null); + AtomicReference savedEntity = new AtomicReference<>(); + + template.save(entity).as(StepVerifier::create).consumeNextWith(saved -> { + assertThat(saved).isNotNull(); + savedEntity.set(saved); + assertThat(saved.getId()).isNotEmpty(); + SeqNoPrimaryTerm seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm(); + assertThat(seqNoPrimaryTerm).isNotNull(); + }).verifyComplete(); + + template.get(savedEntity.get().getId(), ImmutableEntity.class).as(StepVerifier::create) + .consumeNextWith(retrieved -> { + assertThat(retrieved).isEqualTo(savedEntity.get()); + }).verifyComplete(); + } // endregion // region Helper functions @@ -1243,8 +1265,10 @@ public void setMessage(@Nullable String message) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Message message1 = (Message) o; @@ -1301,14 +1325,19 @@ public void setVersion(@Nullable java.lang.Long version) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; SampleEntity that = (SampleEntity) o; - if (rate != that.rate) return false; - if (id != null ? !id.equals(that.id) : that.id != null) return false; - if (message != null ? !message.equals(that.message) : that.message != null) return false; + if (rate != that.rate) + return false; + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + if (message != null ? !message.equals(that.message) : that.message != null) + return false; return version != null ? version.equals(that.version) : that.version == null; } @@ -1440,5 +1469,60 @@ public void setId(@Nullable String id) { } } + @Document(indexName = "immutable-class") + private static final class ImmutableEntity { + @Id private final String id; + @Field(type = FieldType.Text) private final String text; + @Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm; + + public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.id = id; + this.text = text; + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } + + public String getId() { + return id; + } + + public String getText() { + return text; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ImmutableEntity that = (ImmutableEntity) o; + + if (!id.equals(that.id)) + return false; + if (!text.equals(that.text)) + return false; + return seqNoPrimaryTerm != null ? seqNoPrimaryTerm.equals(that.seqNoPrimaryTerm) : that.seqNoPrimaryTerm == null; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + text.hashCode(); + result = 31 * result + (seqNoPrimaryTerm != null ? seqNoPrimaryTerm.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ImmutableEntity{" + "id='" + id + '\'' + ", text='" + text + '\'' + ", seqNoPrimaryTerm=" + + seqNoPrimaryTerm + '}'; + } + } // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java index 1f1b15666..7cb305172 100644 --- a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; @@ -95,11 +96,16 @@ public void shouldSaveAndFindImmutableDocument() { static class ImmutableEntity { private final String id, name; - public ImmutableEntity(String name) { - this.id = null; + @PersistenceConstructor + public ImmutableEntity(String id, String name) { + this.id = id; this.name = name; } + public ImmutableEntity(String name) { + this(null, name); + } + public String getId() { return id; }