From b2ffc236bf582b5e5d7a0bcd01e1438e72e6adc1 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 10 Feb 2020 22:09:49 +0100 Subject: [PATCH] DATAES-738 - Add entity related save methods to DocumentOperations. Original PR: #389 --- .../core/AbstractElasticsearchTemplate.java | 110 +++++++++++++++++- .../core/DocumentOperations.java | 69 ++++++++++- .../core/ElasticsearchOperations.java | 16 +++ .../core/ElasticsearchRestTemplate.java | 11 +- .../core/ElasticsearchTemplate.java | 9 +- .../AbstractElasticsearchRepository.java | 34 ++---- .../SimpleElasticsearchRepository.java | 4 +- .../core/ElasticsearchTemplateTests.java | 81 ++++++++++++- 8 files changed, 291 insertions(+), 43 deletions(-) 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 985e388ff..f2c6b46ca 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -1,10 +1,13 @@ package org.springframework.data.elasticsearch.core; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkResponse; @@ -27,10 +30,13 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.DeleteQuery; +import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder; import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.util.CloseableIterator; +import org.springframework.data.util.Streamable; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -83,6 +89,68 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper // endregion // region DocumentOperations + + @Override + public T save(T entity) { + + Assert.notNull(entity, "entity must not be null"); + + return save(entity, getIndexCoordinatesFor(entity.getClass())); + } + + @Override + public T save(T entity, IndexCoordinates index) { + + Assert.notNull(entity, "entity must not be null"); + Assert.notNull(index, "index must not be null"); + + index(getIndexQuery(entity), index); + return entity; + } + + @Override + public Iterable save(Iterable entities) { + + Assert.notNull(entities, "entities must not be null"); + + Iterator iterator = entities.iterator(); + if (iterator.hasNext()) { + return save(entities, getIndexCoordinatesFor(iterator.next().getClass())); + } + + return entities; + } + + @Override + public Iterable save(Iterable entities, IndexCoordinates index) { + + Assert.notNull(entities, "entities must not be null"); + Assert.notNull(index, "index must not be null"); + + List indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery) + .collect(Collectors.toList()); + + if (!indexQueries.isEmpty()) { + List ids = bulkIndex(indexQueries, index); + Iterator idIterator = ids.iterator(); + entities.forEach(entity -> { + setPersistentEntityId(entity, idIterator.next()); + }); + } + + return entities; + } + + @Override + public Iterable save(T... entities) { + return save(Arrays.asList(entities)); + } + + @Override + public Iterable save(IndexCoordinates index, T... entities) { + return save(Arrays.asList(entities), index); + } + @Override public void delete(Query query, Class clazz, IndexCoordinates index) { @@ -197,7 +265,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper return getRequiredPersistentEntity(clazz).getIndexCoordinates(); } - protected void checkForBulkOperationFailure(BulkResponse bulkResponse) { + /** + * @param bulkResponse + * @return the list of the item id's + */ + protected List checkForBulkOperationFailure(BulkResponse bulkResponse) { if (bulkResponse.hasFailures()) { Map failedDocuments = new HashMap<>(); @@ -211,6 +283,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper + failedDocuments + ']', failedDocuments); } + + return Stream.of(bulkResponse.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList()); } protected void setPersistentEntityId(Object entity, String id) { @@ -227,5 +301,39 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper ElasticsearchPersistentEntity getRequiredPersistentEntity(Class clazz) { return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz); } + + @Nullable + private String getEntityId(Object entity) { + ElasticsearchPersistentEntity persistentEntity = getRequiredPersistentEntity(entity.getClass()); + ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); + + if (idProperty != null) { + return stringIdRepresentation(persistentEntity.getPropertyAccessor(entity).getProperty(idProperty)); + } + + return null; + } + + @Nullable + private Long getEntityVersion(Object entity) { + ElasticsearchPersistentEntity persistentEntity = getRequiredPersistentEntity(entity.getClass()); + ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); + + if (versionProperty != null) { + Object version = persistentEntity.getPropertyAccessor(entity).getProperty(versionProperty); + + if (version != null && Long.class.isAssignableFrom(version.getClass())) { + return ((Long) version); + } + } + + return null; + } + + private IndexQuery getIndexQuery(T entity) { + return new IndexQueryBuilder().withObject(entity).withId(getEntityId(entity)).withVersion(getEntityVersion(entity)) + .build(); + } + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java index a38d4afc0..72285ab83 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java @@ -25,6 +25,7 @@ import org.springframework.data.elasticsearch.core.query.GetQuery; import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.UpdateQuery; +import org.springframework.lang.Nullable; /** * The operations for the @@ -35,6 +36,63 @@ import org.springframework.data.elasticsearch.core.query.UpdateQuery; */ public interface DocumentOperations { + /** + * Saves an entity to the index specified in the entity's Document annotation + * + * @param entity the entity to save, must not be {@literal null} + * @param the entity type + * @return the saved entity + */ + T save(T entity); + + /** + * Saves an entity to the index specified in the entity's Document annotation + * + * @param entity the entity to save, must not be {@literal null} + * @param index the index to save the entity in, must not be {@literal null} + * @param the entity type + * @return the saved entity + */ + T save(T entity, IndexCoordinates index); + + /** + * saves the given entities to the index retrieved from the entities' Document annotation + * + * @param entities must not be {@literal null} + * @param the entity type + * @return the saved entites + */ + Iterable save(Iterable entities); + + /** + * saves the given entities to the given index + * + * @param entities must not be {@literal null} + * @param index the idnex to save the entities in, must not be {@literal null} + * @param the entity type + * @return the saved entites + */ + Iterable save(Iterable entities, IndexCoordinates index); + + /** + * saves the given entities to the index retrieved from the entities' Document annotation + * + * @param entities must not be {@literal null} + * @param the entity type + * @return the saved entites as Iterable + */ + Iterable save(T... entities); + + /** + * saves the given entities to the given index. + * + * @param index the idnex to save the entities in, must not be {@literal null} + * @param entities must not be {@literal null} + * @param the entity type + * @return the saved entites as Iterable + */ + Iterable save(IndexCoordinates index, T... entities); + /** * Index an object. Will do save or update. * @@ -52,6 +110,7 @@ public interface DocumentOperations { * @param index the index from which the object is read. * @return the found object */ + @Nullable T get(GetQuery query, Class clazz, IndexCoordinates index); /** @@ -68,18 +127,20 @@ public interface DocumentOperations { * Bulk index all objects. Will do save or update. * * @param queries the queries to execute in bulk + * @return the ids of the indexed objects */ - default void bulkIndex(List queries, IndexCoordinates index) { - bulkIndex(queries, BulkOptions.defaultOptions(), index); + default List bulkIndex(List queries, IndexCoordinates index) { + return bulkIndex(queries, BulkOptions.defaultOptions(), index); } /** * Bulk index all objects. Will do save or update. - * + * * @param queries the queries to execute in bulk * @param bulkOptions options to be added to the bulk request + * @return the ids of the indexed objects */ - void bulkIndex(List queries, BulkOptions bulkOptions, IndexCoordinates index); + List bulkIndex(List queries, BulkOptions bulkOptions, IndexCoordinates index); /** * Bulk update all objects. Will do update. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java index 456c91def..f82529548 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java @@ -17,11 +17,13 @@ package org.springframework.data.elasticsearch.core; import java.util.List; import java.util.Map; +import java.util.Objects; import org.elasticsearch.cluster.metadata.AliasMetaData; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.AliasQuery; +import org.springframework.lang.Nullable; /** * ElasticsearchOperations. Since 4.0 this interface only contains common helper functions, the other methods have been @@ -312,4 +314,18 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera getIndexOperations().refresh(clazz); } // endregion + + // region helper + /** + * gets the String representation for an id. + * + * @param id + * @return + * @since 4.0 + */ + @Nullable + default String stringIdRepresentation(@Nullable Object id) { + return Objects.toString(id, null); + } + // endregion } 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 ad2f94c3b..2c20807f7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -127,6 +127,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { } @Override + @Nullable public T get(GetQuery query, Class clazz, IndexCoordinates index) { GetRequest request = requestFactory.getRequest(query, index); try { @@ -153,12 +154,12 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { } @Override - public void bulkIndex(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + public List bulkIndex(List queries, BulkOptions bulkOptions, IndexCoordinates index) { Assert.notNull(queries, "List of IndexQuery must not be null"); Assert.notNull(bulkOptions, "BulkOptions must not be null"); - doBulkOperation(queries, bulkOptions, index); + return doBulkOperation(queries, bulkOptions, index); } @Override @@ -200,10 +201,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { } } - private void doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + private List doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index) { BulkRequest bulkRequest = requestFactory.bulkRequest(queries, bulkOptions, index); try { - checkForBulkOperationFailure(client.bulk(bulkRequest, RequestOptions.DEFAULT)); + return checkForBulkOperationFailure(client.bulk(bulkRequest, RequestOptions.DEFAULT)); } catch (IOException e) { throw new ElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e); } @@ -212,7 +213,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { // region SearchOperations @Override - public long count(Query query,@Nullable Class clazz, IndexCoordinates index) { + public long count(Query query, @Nullable Class clazz, IndexCoordinates index) { Assert.notNull(query, "query must not be null"); Assert.notNull(index, "index must not be null"); 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 e8c1c8c93..7ba860ee9 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -127,6 +127,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { } @Override + @Nullable public T get(GetQuery query, Class clazz, IndexCoordinates index) { GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, query, index); GetResponse response = getRequestBuilder.execute().actionGet(); @@ -145,12 +146,12 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { } @Override - public void bulkIndex(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + public List bulkIndex(List queries, BulkOptions bulkOptions, IndexCoordinates index) { Assert.notNull(queries, "List of IndexQuery must not be null"); Assert.notNull(bulkOptions, "BulkOptions must not be null"); - doBulkOperation(queries, bulkOptions, index); + return doBulkOperation(queries, bulkOptions, index); } @Override @@ -178,9 +179,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { return updateRequestBuilder.execute().actionGet(); } - private void doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + private List doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index) { BulkRequestBuilder bulkRequest = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index); - checkForBulkOperationFailure(bulkRequest.execute().actionGet()); + return checkForBulkOperationFailure(bulkRequest.execute().actionGet()); } // endregion diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java index 544b7fe57..c3ca2a834 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java @@ -176,34 +176,35 @@ public abstract class AbstractElasticsearchRepository implements Elastics @Override public S save(S entity) { + Assert.notNull(entity, "Cannot save 'null' entity."); - operations.index(createIndexQuery(entity), getIndexCoordinates()); + + operations.save(entity, getIndexCoordinates()); indexOperations.refresh(getIndexCoordinates()); return entity; } public List save(List entities) { + Assert.notNull(entities, "Cannot insert 'null' as a List."); + return Streamable.of(saveAll(entities)).stream().collect(Collectors.toList()); } @Override public S indexWithoutRefresh(S entity) { Assert.notNull(entity, "Cannot save 'null' entity."); - operations.index(createIndexQuery(entity), getIndexCoordinates()); + operations.save(entity); return entity; } @Override public Iterable saveAll(Iterable entities) { - Assert.notNull(entities, "Cannot insert 'null' as a List."); - List queries = Streamable.of(entities).stream().map(this::createIndexQuery) - .collect(Collectors.toList()); - if (!queries.isEmpty()) { - operations.bulkIndex(queries, getIndexCoordinates()); - indexOperations.refresh(getIndexCoordinates()); - } + Assert.notNull(entities, "Cannot insert 'null' as a List."); + + operations.save(entities, getIndexCoordinates()); + indexOperations.refresh(getIndexCoordinates()); return entities; } @@ -329,14 +330,6 @@ public abstract class AbstractElasticsearchRepository implements Elastics indexOperations.refresh(getEntityClass()); } - private IndexQuery createIndexQuery(T entity) { - IndexQuery query = new IndexQuery(); - query.setObject(entity); - query.setId(stringIdRepresentation(extractIdFromBean(entity))); - query.setVersion(extractVersionFromBean(entity)); - query.setParentId(extractParentIdFromBean(entity)); - return query; - } @SuppressWarnings("unchecked") private Class resolveReturnedClassFromGenericType() { @@ -396,13 +389,6 @@ public abstract class AbstractElasticsearchRepository implements Elastics protected abstract @Nullable String stringIdRepresentation(@Nullable ID id); - private Long extractVersionFromBean(T entity) { - return entityInformation.getVersion(entity); - } - - private String extractParentIdFromBean(T entity) { - return entityInformation.getParentId(entity); - } private IndexCoordinates getIndexCoordinates() { return operations.getIndexCoordinatesFor(getEntityClass()); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java index 73414212b..2a4e51863 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java @@ -15,8 +15,6 @@ */ package org.springframework.data.elasticsearch.repository.support; -import java.util.Objects; - import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.lang.Nullable; @@ -43,6 +41,6 @@ public class SimpleElasticsearchRepository extends AbstractElasticsearchR @Override protected @Nullable String stringIdRepresentation(@Nullable ID id) { - return Objects.toString(id, null); + return operations.stringIdRepresentation(id); } } 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 f513ed4d0..f1bc0ad99 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -2907,6 +2907,84 @@ public abstract class ElasticsearchTemplateTests { assertThat(highlightField.get(1)).contains("message"); } + @Test // DATAES-738 + void shouldSaveEntityWithIndexCoordinates() { + String id = "42"; + SampleEntity entity = new SampleEntity(); + entity.setId(id); + entity.setVersion(42L); + entity.setMessage("message"); + + operations.save(entity, index); + indexOperations.refresh(index); + + SampleEntity result = operations.get(new GetQuery(id), SampleEntity.class, index); + + assertThat(result).isEqualTo(entity); + } + + @Test // DATAES-738 + void shouldSaveEntityWithOutIndexCoordinates() { + String id = "42"; + SampleEntity entity = new SampleEntity(); + entity.setId(id); + entity.setVersion(42L); + entity.setMessage("message"); + + operations.save(entity); + indexOperations.refresh(index); + + SampleEntity result = operations.get(new GetQuery(id), SampleEntity.class, index); + + assertThat(result).isEqualTo(entity); + } + + @Test // DATAES-738 + void shouldSaveEntityIterableWithIndexCoordinates() { + String id1 = "42"; + SampleEntity entity1 = new SampleEntity(); + entity1.setId(id1); + entity1.setVersion(42L); + entity1.setMessage("message"); + String id2 = "43"; + SampleEntity entity2 = new SampleEntity(); + entity2.setId(id2); + entity2.setVersion(43L); + entity2.setMessage("message"); + + operations.save(Arrays.asList(entity1, entity2), index); + indexOperations.refresh(index); + + SampleEntity result1 = operations.get(new GetQuery(id1), SampleEntity.class, index); + SampleEntity result2 = operations.get(new GetQuery(id2), SampleEntity.class, index); + + assertThat(result1).isEqualTo(entity1); + assertThat(result2).isEqualTo(entity2); + } + + @Test // DATAES-738 + void shouldSaveEntityIterableWithoutIndexCoordinates() { + String id1 = "42"; + SampleEntity entity1 = new SampleEntity(); + entity1.setId(id1); + entity1.setVersion(42L); + entity1.setMessage("message"); + String id2 = "43"; + SampleEntity entity2 = new SampleEntity(); + entity2.setId(id2); + entity2.setVersion(43L); + entity2.setMessage("message"); + + operations.save(Arrays.asList(entity1, entity2)); + indexOperations.refresh(index); + + SampleEntity result1 = operations.get(new GetQuery(id1), SampleEntity.class, index); + SampleEntity result2 = operations.get(new GetQuery(id2), SampleEntity.class, index); + + assertThat(result1).isEqualTo(entity1); + assertThat(result2).isEqualTo(entity2); + } + protected RequestFactory getRequestFactory() { return ((AbstractElasticsearchTemplate) operations).getRequestFactory(); } @@ -3039,8 +3117,7 @@ public abstract class ElasticsearchTemplateTests { static class NestedEntity { - @Nullable - @Field(type = Text) private String someField; + @Nullable @Field(type = Text) private String someField; @Nullable public String getSomeField() {