From 4eb8f08ad859b3d92003fa0d0002ca0addd1b0c0 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 19 Mar 2022 18:51:01 +0100 Subject: [PATCH] Add new Elasticsearch client integration. Original Pull Request #2115 --- .gitignore | 3 + .mvn/wrapper/MavenWrapperDownloader.java | 170 ++- CONTRIBUTING.adoc | 4 + mvnw | 2 +- pom.xml | 28 +- ...elasticsearch-migration-guide-4.3-4.4.adoc | 149 +- .../asciidoc/reference/elasticsearch-new.adoc | 1 + .../asciidoc/reference/migration-guides.adoc | 2 + .../ElasticsearchErrorCause.java | 78 + .../UncategorizedElasticsearchException.java | 42 +- .../elasticsearch/annotations/Document.java | 18 +- .../elasticsearch/client/ClientLogger.java | 78 +- .../client/UnsupportedBackendOperation.java | 43 + .../elc/AutoCloseableElasticsearchClient.java | 48 + .../client/elc/ChildTemplate.java | 75 + .../client/elc/ClusterTemplate.java | 46 + .../client/elc/CriteriaFilterProcessor.java | 321 +++++ .../client/elc/CriteriaQueryException.java} | 15 +- .../client/elc/CriteriaQueryProcessor.java | 368 +++++ .../client/elc/DocumentAdapters.java | 212 +++ .../client/elc/ElasticsearchAggregations.java | 42 + .../client/elc/ElasticsearchClients.java | 381 +++++ .../elc/ElasticsearchConfiguration.java | 98 ++ .../elc/ElasticsearchExceptionTranslator.java | 108 ++ .../client/elc/ElasticsearchTemplate.java | 496 +++++++ .../elasticsearch/client/elc/EntityAsMap.java | 27 + .../client/elc/HighlightQueryBuilder.java | 236 +++ .../client/elc/IndicesTemplate.java | 354 +++++ .../elasticsearch/client/elc/JsonUtils.java | 53 + .../elasticsearch/client/elc/NativeQuery.java | 90 ++ .../client/elc/NativeQueryBuilder.java | 85 ++ .../client/elc/QueryBuilders.java | 154 ++ .../client/elc/ReactiveChildTemplate.java | 71 + .../client/elc/ReactiveClusterTemplate.java | 46 + .../elc/ReactiveElasticsearchClient.java | 226 +++ .../ReactiveElasticsearchClusterClient.java | 56 + .../ReactiveElasticsearchConfiguration.java | 97 ++ .../ReactiveElasticsearchIndicesClient.java | 629 ++++++++ .../elc/ReactiveElasticsearchTemplate.java | 480 +++++++ .../client/elc/ReactiveIndicesTemplate.java | 337 +++++ .../client/elc/RequestConverter.java | 1261 +++++++++++++++++ .../client/elc/ResponseConverter.java | 389 +++++ .../elc/SearchDocumentResponseBuilder.java | 123 ++ .../elasticsearch/client/elc/TypeUtils.java | 298 ++++ .../client/elc/package-info.java | 23 + .../DefaultReactiveElasticsearchClient.java | 94 +- .../client/reactive/HostProvider.java | 25 +- .../client/reactive/WebClientProvider.java | 110 ++ .../core/AbstractElasticsearchTemplate.java | 23 +- .../core/AbstractIndexTemplate.java | 9 +- ...AbstractReactiveElasticsearchTemplate.java | 685 +++++++++ .../core/CriteriaQueryProcessor.java | 1 - .../core/ElasticsearchRestTemplate.java | 62 +- .../elasticsearch/core/EntityOperations.java | 28 +- .../elasticsearch/core/IndexOperations.java | 12 +- .../data/elasticsearch/core/MultiGetItem.java | 18 +- .../core/ReactiveElasticsearchOperations.java | 4 +- .../core/ReactiveElasticsearchTemplate.java | 597 +------- .../core/ReactiveIndexOperations.java | 6 +- .../core/ReactiveIndexTemplate.java | 22 +- .../core/ReactiveResourceUtil.java | 34 + .../elasticsearch/core/RequestFactory.java | 37 +- .../elasticsearch/core/ResponseConverter.java | 6 +- .../elasticsearch/core/RestIndexTemplate.java | 5 +- .../data/elasticsearch/core/RuntimeField.java | 14 + .../elasticsearch/core/SearchHitMapping.java | 2 +- .../elasticsearch/core/SearchOperations.java | 11 +- .../core/cluster/ClusterHealth.java | 26 +- .../core/document/DocumentAdapters.java | 5 +- .../core/document/Explanation.java | 8 +- .../core/document/SearchDocumentAdapter.java | 296 ++++ .../core/document/SearchDocumentResponse.java | 203 +-- .../SearchDocumentResponseBuilder.java | 224 +++ .../core/index/MappingBuilder.java | 2 +- .../elasticsearch/core/index/Settings.java | 82 +- .../elasticsearch/core/query/BaseQuery.java | 17 + .../core/query/BaseQueryBuilder.java | 165 +++ .../core/query/ByQueryResponse.java | 14 +- .../core/query/CriteriaQuery.java | 14 +- .../core/query/CriteriaQueryBuilder.java | 43 + .../core/query/HighlightQueryBuilder.java | 2 +- .../elasticsearch/core/query/IndexQuery.java | 12 +- .../core/query/NativeSearchQuery.java | 12 +- .../core/query/NativeSearchQueryBuilder.java | 101 +- .../data/elasticsearch/core/query/Query.java | 2 +- .../elasticsearch/core/query/StringQuery.java | 18 +- .../core/query/StringQueryBuilder.java | 43 + .../core/reindex/ReindexRequest.java | 30 +- .../core/reindex/ReindexResponse.java | 52 +- .../SimpleElasticsearchRepository.java | 29 +- .../support/DefaultStringObjectMap.java | 38 + .../elasticsearch/support/ExceptionUtils.java | 48 + ...Unit5SampleElasticsearchTemplateTests.java | 48 + .../JUnit5SampleReactiveELCTests.java | 48 + ...va => JUnit5SampleReactiveERHLCTests.java} | 2 +- ...ava => JUnit5SampleRestTemplateTests.java} | 2 +- .../NewElasticsearchClientDevelopment.java | 34 + .../BlockHoundIntegrationCustomizer.java | 5 +- .../elasticsearch/client/RestClientsTest.java | 153 +- .../AutoCloseableElasticsearchClientTest.java | 51 + .../elasticsearch/client/elc/DevTests.java | 363 +++++ .../client/elc/DocumentAdaptersUnitTests.java | 109 ++ ...ticsearchPartQueryELCIntegrationTests.java | 49 + .../config/AuditingELCIntegrationTests.java | 31 + .../AuditingERHLCIntegrationTests.java} | 11 +- .../config/AuditingIntegrationTests.java | 13 + .../AuditingReactiveELCIntegrationTests.java | 32 + ...AuditingReactiveERHLCIntegrationTests.java | 33 + ...a => AuditingReactiveIntegrationTest.java} | 26 +- .../AuditingRestTemplateIntegrationTests.java | 41 - .../package-info.java | 3 - .../ElasticsearchConfigurationELCTests.java | 88 ++ ...ElasticsearchConfigurationERHLCTests.java} | 9 +- ...iveElasticsearchConfigurationELCTests.java | 88 ++ ...eElasticsearchConfigurationERHLCTests.java | 78 + ...NestedRepositoriesELCIntegrationTests.java | 45 + ...tedRepositoriesERHLCIntegrationTests.java} | 16 +- ...leNestedRepositoriesIntegrationTests.java} | 17 +- ...EnableRepositoriesELCIntegrationTests.java | 40 + ...bleRepositoriesERHLCIntegrationTests.java} | 13 +- ...> EnableRepositoriesIntegrationTests.java} | 26 +- .../SampleElasticsearchRepository.java | 6 +- ...ampleUUIDKeyedElasticsearchRepository.java | 6 +- .../ElasticsearchELCIntegrationTests.java | 96 ++ ...> ElasticsearchERHLCIntegrationTests.java} | 61 +- ...ElasticsearchExceptionTranslatorTests.java | 1 - ...ava => ElasticsearchIntegrationTests.java} | 631 ++++----- ...csearchPartQueryERHLCIntegrationTests.java | 47 + ...st.java => EntityOperationsUnitTests.java} | 20 +- ...st.java => IndexCoordinatesUnitTests.java} | 2 +- .../core/InnerHitsELCIntegrationTests.java | 64 + .../core/InnerHitsERHLCIntegrationTests.java | 60 + .../core/InnerHitsIntegrationTests.java | 49 +- .../core/LogEntityELCIntegrationTests.java | 65 + .../core/LogEntityERHLCIntegrationTests.java | 53 + .../core/LogEntityIntegrationTests.java | 81 +- ...ctiveElasticsearchELCIntegrationTests.java | 45 + ...iveElasticsearchERHLCIntegrationTests.java | 40 + ...eactiveElasticsearchIntegrationTests.java} | 103 +- .../ReactiveReindexELCIntegrationTests.java | 40 + .../ReactiveReindexERHLCIntegrationTests.java | 39 + .../core/ReactiveReindexIntegrationTests.java | 159 +++ .../core/ReindexELCIntegrationTests.java | 49 + .../core/ReindexERHLCIntegrationTests.java | 47 + .../core/ReindexIntegrationTests.java | 158 +++ .../core/RequestFactoryTests.java | 56 +- .../RuntimeFieldsELCIntegrationTests.java | 40 + ...> RuntimeFieldsERHLCIntegrationTests.java} | 20 +- .../SearchAsYouTypeELCIntegrationTests.java | 55 + .../SearchAsYouTypeERHLCIntegrationTests.java | 51 + ...a => SearchAsYouTypeIntegrationTests.java} | 84 +- .../core/SourceFilterELCIntegrationTests.java | 41 + ...=> SourceFilterERHLCIntegrationTests.java} | 7 +- .../core/SourceFilterIntegrationTests.java | 20 +- .../AggregationELCIntegrationTests.java | 98 ++ .../AggregationERHLCIntegrationTests.java | 98 ++ .../AggregationIntegrationTests.java | 127 +- ...gregationRestTemplateIntegrationTests.java | 33 - ...ClusterOperationsELCIntegrationTests.java} | 9 +- ...usterOperationsERHLCIntegrationTests.java} | 2 +- ...OperationsReactiveELCIntegrationTests.java | 27 + ...erationsReactiveERHLCIntegrationTests.java | 25 + ...erOperationsReactiveIntegrationTests.java} | 5 +- ...sticsearchCustomConversionsUnitTests.java} | 3 +- .../DocumentAdaptersUnitTests.java | 6 +- .../event/CallbackELCIntegrationTests.java | 41 + .../event/CallbackERHLCIntegrationTests.java | 41 + ...sts.java => CallbackIntegrationTests.java} | 34 +- .../ReactiveCallbackELCIntegrationTests.java | 40 + ...ReactiveCallbackERHLCIntegrationTests.java | 40 + ... => ReactiveCallbackIntegrationTests.java} | 39 +- .../core/geo/GeoELCIntegrationTests.java | 68 + .../core/geo/GeoERHLCIntegrationTests.java | 56 + .../core/geo/GeoIntegrationTests.java | 151 +- .../GeoJsonELCIntegrationTests.java} | 14 +- ...java => GeoJsonERHLCIntegrationTests.java} | 4 +- .../geo/GeoRestTemplateIntegrationTests.java | 33 - ...OperationRestTemplateIntegrationTests.java | 10 - .../IndexTemplateELCIntegrationTests.java} | 11 +- ...> IndexTemplateERHLCIntegrationTests.java} | 2 +- .../MappingBuilderELCIntegrationTests.java | 45 + .../MappingBuilderERHLCIntegrationTests.java | 40 + .../index/MappingBuilderIntegrationTests.java | 552 +------- ...a => ReactiveMappingBuilderUnitTests.java} | 2 +- .../core/index/SettingsUnitTests.java | 110 ++ .../IndexOperationsELCIntegrationTests.java | 44 + .../IndexOperationsERHLCIntegrationTests.java | 39 + .../IndexOperationsIntegrationTests.java} | 72 +- ...iveIndexOperationsELCIntegrationTests.java | 39 + ...eIndexOperationsERHLCIntegrationTests.java | 39 + ...ctiveIndexOperationsIntegrationTests.java} | 195 +-- ...tyCustomConversionELCIntegrationTests.java | 51 + ...CustomConversionERHLCIntegrationTests.java | 51 + ...ntityCustomConversionIntegrationTests.java | 31 +- ...ieldNamingStrategyELCIntegrationTests.java | 55 + ...dNamingStrategyERHLCIntegrationTests.java} | 23 +- .../FieldNamingStrategyIntegrationTests.java | 24 +- ...ieldNamingStrategyELCIntegrationTests.java | 52 + ...ldNamingStrategyERHLCIntegrationTests.java | 52 + ...eFieldNamingStrategyIntegrationTests.java} | 40 +- ...eactiveSearchAfterELCIntegrationTests.java | 41 + ...ctiveSearchAfterERHLCIntegrationTests.java | 41 + .../ReactiveSearchAfterIntegrationTests.java | 25 +- .../SearchAfterELCIntegrationTests.java | 42 + .../SearchAfterERHLCIntegrationTests.java | 42 + .../SearchAfterIntegrationTests.java | 20 +- .../CriteriaQueryELCIntegrationTests.java | 40 + ...> CriteriaQueryERHLCIntegrationTests.java} | 4 +- .../query/CriteriaQueryIntegrationTests.java | 46 +- ...asticsearchPartQueryIntegrationTests.java} | 310 ++-- .../ReactiveRoutingELCIntegrationTests.java | 40 + .../ReactiveRoutingERHLCIntegrationTests.java | 39 + ...ngTests.java => ReactiveRoutingTests.java} | 36 +- .../routing/RoutingELCIntegrationTests.java | 41 + .../routing/RoutingERHLCIntegrationTests.java | 40 + .../core/routing/RoutingIntegrationTests.java | 36 +- .../CompletionELCIntegrationTests.java | 73 + .../CompletionERHLCIntegrationTests.java | 57 + .../suggest/CompletionIntegrationTests.java | 65 +- ...ompletionRestTemplateIntegrationTests.java | 33 - .../ReactiveSuggestELCIntegrationTests.java | 71 + .../ReactiveSuggestERHLCIntegrationTests.java | 55 + ...a => ReactiveSuggestIntegrationTests.java} | 44 +- ...mmutableRepositoryELCIntegrationTests.java | 43 + ...utableRepositoryERHLCIntegrationTests.java | 43 + ... ImmutableRepositoryIntegrationTests.java} | 48 +- .../ElasticsearchTemplateConfiguration.java | 79 ++ ...lasticsearchRestTemplateConfiguration.java | 4 +- ...iveElasticsearchTemplateConfiguration.java | 78 + .../repositories/cdi/CdiRepositoryTests.java | 17 +- .../cdi/ElasticsearchOperationsProducer.java | 1 + ...omMethodRepositoryELCIntegrationTests.java | 43 + ...ethodRepositoryERHLCIntegrationTests.java} | 7 +- ...sitoryManualWiringELCIntegrationTests.java | 44 + ...oryManualWiringERHLCIntegrationTests.java} | 6 +- ...omMethodRepositoryELCIntegrationTests.java | 44 + ...ethodRepositoryERHLCIntegrationTests.java} | 6 +- ...DoubleIDRepositoryELCIntegrationTests.java | 42 + ...bleIDRepositoryERHLCIntegrationTests.java} | 6 +- ...ynamicIndexEntityELCIntegrationTests.java} | 22 +- ...amicIndexEntityERHLCIntegrationTests.java} | 5 +- .../DynamicIndexEntityIntegrationTests.java | 4 +- .../geo/GeoRepositoryELCIntegrationTests.java | 41 + .../GeoRepositoryERHLCIntegrationTests.java | 40 + .../geo/GeoRepositoryIntegrationTests.java | 19 +- ...ntegerIDRepositoryELCIntegrationTests.java | 43 + ...egerIDRepositoryERHLCIntegrationTests.java | 41 + .../IntegerIDRepositoryIntegrationTests.java | 27 +- ...epositoryRestTemplateIntegrationTests.java | 34 - .../InnerObjectELCIntegrationTests.java | 42 + .../InnerObjectERHLCIntegrationTests.java | 41 + .../InnerObjectIntegrationTests.java | 22 +- ...nerObjectRestTemplateIntegrationTests.java | 34 - ...ngEntityRepositoryELCIntegrationTests.java | 44 + ...ntityRepositoryERHLCIntegrationTests.java} | 6 +- ...ngEntityRepositoryELCIntegrationTests.java | 44 + ...ntityRepositoryERHLCIntegrationTests.java} | 14 +- ...ppingEntityRepositoryIntegrationTests.java | 24 +- .../spel/SpELEntityELCIntegrationTests.java | 41 + ...a => SpELEntityERHLCIntegrationTests.java} | 13 +- .../spel/SpELEntityIntegrationTests.java | 25 +- .../SynonymRepositoryELCIntegrationTests.java | 41 + ...ynonymRepositoryERHLCIntegrationTests.java | 40 + ...=> SynonymRepositoryIntegrationTests.java} | 26 +- .../SynonymRepositoryRestTemplateTests.java | 33 - ...icsearchRepositoryELCIntegrationTests.java | 41 + ...searchRepositoryERHLCIntegrationTests.java | 41 + ...asticsearchRepositoryIntegrationTests.java | 22 +- ...epositoryRestTemplateIntegrationTests.java | 35 - .../QueryKeywordsELCIntegrationTests.java | 44 + .../QueryKeywordsERHLCIntegrationTests.java | 43 + ...ava => QueryKeywordsIntegrationTests.java} | 24 +- ...ryKeywordsRepositoryRestTemplateTests.java | 36 - ...ctiveQueryKeywordsELCIntegrationTests.java | 43 + ...iveQueryKeywordsERHLCIntegrationTests.java | 43 + ...ReactiveQueryKeywordsIntegrationTests.java | 20 +- .../DefaultStringObjectMapUnitTests.java | 72 + .../elasticsearch/utils/geohash/BitUtil.java | 71 + .../elasticsearch/utils/geohash/Geohash.java | 379 +++++ .../elasticsearch/utils/geohash/Geometry.java | 40 + .../utils/geohash/GeometryValidator.java | 32 + .../utils/geohash/GeometryVisitor.java | 68 + .../elasticsearch/utils/geohash/Point.java | 129 ++ .../utils/geohash/Rectangle.java | 204 +++ .../utils/geohash/ShapeType.java | 42 + .../utils/geohash/StandardValidator.java | 60 + .../utils/geohash/WellKnownText.java | 311 ++++ .../utils/geohash/package-info.java | 24 + src/test/resources/log4j2.xml | 30 + src/test/resources/synonyms/settings.json | 3 +- .../testcontainers-elasticsearch.properties | 3 +- 291 files changed, 18267 insertions(+), 3608 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/ElasticsearchErrorCause.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/UnsupportedBackendOperation.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClient.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ChildTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ClusterTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaFilterProcessor.java rename src/{test/java/org/springframework/data/elasticsearch/core/routing/RoutingRestTemplateIntegrationTests.java => main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryException.java} (57%) create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryProcessor.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/DocumentAdapters.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchAggregations.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/EntityAsMap.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/JsonUtils.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQuery.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQueryBuilder.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/QueryBuilders.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveChildTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveClusterTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClient.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClusterClient.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchIndicesClient.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/SearchDocumentResponseBuilder.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/client/elc/package-info.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentAdapter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponseBuilder.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/BaseQueryBuilder.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryBuilder.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/StringQueryBuilder.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/support/ExceptionUtils.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/JUnit5SampleElasticsearchTemplateTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveELCTests.java rename src/test/java/org/springframework/data/elasticsearch/{JUnit5SampleReactiveRestClientBasedTests.java => JUnit5SampleReactiveERHLCTests.java} (97%) rename src/test/java/org/springframework/data/elasticsearch/{JUnit5SampleRestTemplateBasedTests.java => JUnit5SampleRestTemplateTests.java} (97%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/NewElasticsearchClientDevelopment.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClientTest.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/client/elc/DocumentAdaptersUnitTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/AuditingELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/{core/InnerHitsRestTemplateIntegrationTests.java => config/AuditingERHLCIntegrationTests.java} (71%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/config/{ReactiveAuditingIntegrationTest.java => AuditingReactiveIntegrationTest.java} (91%) delete mode 100644 src/test/java/org/springframework/data/elasticsearch/config/AuditingRestTemplateIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/config/abstractelasticsearchconfiguration/package-info.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/configuration/ElasticsearchConfigurationELCTests.java rename src/test/java/org/springframework/data/elasticsearch/config/{abstractelasticsearchconfiguration/ElasticsearchConfigurationTests.java => configuration/ElasticsearchConfigurationERHLCTests.java} (86%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationELCTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationERHLCTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/config/nested/{EnableNestedElasticsearchRepositoriesRestTemplateIntegrationTests.java => EnableNestedRepositoriesERHLCIntegrationTests.java} (73%) rename src/test/java/org/springframework/data/elasticsearch/config/nested/{EnableNestedElasticsearchRepositoriesIntegrationTests.java => EnableNestedRepositoriesIntegrationTests.java} (82%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/config/notnested/{EnableElasticsearchRepositoriesRestTemplateTests.java => EnableRepositoriesERHLCIntegrationTests.java} (76%) rename src/test/java/org/springframework/data/elasticsearch/config/notnested/{EnableElasticsearchRepositoriesTests.java => EnableRepositoriesIntegrationTests.java} (90%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{ElasticsearchRestTemplateTests.java => ElasticsearchERHLCIntegrationTests.java} (76%) rename src/test/java/org/springframework/data/elasticsearch/core/{ElasticsearchTemplateTests.java => ElasticsearchIntegrationTests.java} (88%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchPartQueryERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{EntityOperationsTest.java => EntityOperationsUnitTests.java} (89%) rename src/test/java/org/springframework/data/elasticsearch/core/{IndexCoordinatesTest.java => IndexCoordinatesUnitTests.java} (97%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/InnerHitsELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/InnerHitsERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/LogEntityELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/LogEntityERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{ReactiveElasticsearchTemplateIntegrationTests.java => ReactiveElasticsearchIntegrationTests.java} (94%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReindexELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReindexERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/ReindexIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{LogEntityRestTemplateIntegrationTests.java => RuntimeFieldsERHLCIntegrationTests.java} (67%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{SearchAsYouTypeTests.java => SearchAsYouTypeIntegrationTests.java} (66%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/SourceFilterELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{RuntimeFieldsRestTemplateIntegrationTests.java => SourceFilterERHLCIntegrationTests.java} (83%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationERHLCIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationRestTemplateIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{paginating/SearchAfterRestTemplateIntegrationTests.java => cluster/ClusterOperationsELCIntegrationTests.java} (73%) rename src/test/java/org/springframework/data/elasticsearch/core/cluster/{ClusterOperationsRestTemplateIntegrationTests.java => ClusterOperationsERHLCIntegrationTests.java} (90%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/cluster/{ClusterOperationsReactiveTemplateIntegrationTests.java => ClusterOperationsReactiveIntegrationTests.java} (84%) rename src/test/java/org/springframework/data/elasticsearch/core/convert/{ElasticsearchCustomConversionsTest.java => ElasticsearchCustomConversionsUnitTests.java} (96%) rename src/test/java/org/springframework/data/elasticsearch/core/{ => document}/DocumentAdaptersUnitTests.java (96%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/event/CallbackELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/event/CallbackERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/event/{ElasticsearchOperationsCallbackIntegrationTests.java => CallbackIntegrationTests.java} (92%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/event/{ReactiveElasticsearchOperationsCallbackTest.java => ReactiveCallbackIntegrationTests.java} (71%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/geo/GeoELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/geo/GeoERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{event/ElasticsearchRestTemplateCallbackIntegrationTests.java => geo/GeoJsonELCIntegrationTests.java} (64%) rename src/test/java/org/springframework/data/elasticsearch/core/geo/{GeoJsonRestTemplateIntegrationTests.java => GeoJsonERHLCIntegrationTests.java} (87%) delete mode 100644 src/test/java/org/springframework/data/elasticsearch/core/geo/GeoRestTemplateIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationRestTemplateIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{SourceFilterRestTemplateIntegrationTests.java => index/IndexTemplateELCIntegrationTests.java} (69%) rename src/test/java/org/springframework/data/elasticsearch/core/index/{IndexTemplateRestTemplateIntegrationTests.java => IndexTemplateERHLCIntegrationTests.java} (90%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/index/{ReactiveMappingBuilderTests.java => ReactiveMappingBuilderUnitTests.java} (97%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/SettingsUnitTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{index/IndexOperationIntegrationTests.java => indices/IndexOperationsIntegrationTests.java} (51%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/{ReactiveIndexOperationsTest.java => indices/ReactiveIndexOperationsIntegrationTests.java} (71%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/mapping/{FieldNamingStrategyRestTemplateIntegrationTests.java => FieldNamingStrategyERHLCIntegrationTests.java} (64%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/mapping/{FieldNamingStrategyReactiveTemplateIntegrationTests.java => ReactiveFieldNamingStrategyIntegrationTests.java} (69%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/query/{CriteriaQueryRestTemplateIntegrationTests.java => CriteriaQueryERHLCIntegrationTests.java} (87%) rename src/test/java/org/springframework/data/elasticsearch/core/{ElasticsearchPartQueryTests.java => query/ElasticsearchPartQueryIntegrationTests.java} (65%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/routing/{ReactiveElasticsearchOperationsRoutingTests.java => ReactiveRoutingTests.java} (83%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionERHLCIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionRestTemplateIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/core/suggest/{SuggestReactiveTemplateIntegrationTests.java => ReactiveSuggestIntegrationTests.java} (81%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/immutable/{ImmutableElasticsearchRepositoryTests.java => ImmutableRepositoryIntegrationTests.java} (63%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchTemplateConfiguration.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchTemplateConfiguration.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/{ComplexCustomMethodRepositoryRestTemplateIntegrationTests.java => ComplexCustomMethodRepositoryERHLCIntegrationTests.java} (86%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/{ComplexCustomMethodRepositoryManualWiringRestTemplateIntegrationTests.java => ComplexCustomMethodRepositoryManualWiringERHLCIntegrationTests.java} (89%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/{CustomMethodRepositoryRestTemplateIntegrationTests.java => CustomMethodRepositoryERHLCIntegrationTests.java} (85%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/{DoubleIDRepositoryRestTemplateIntegrationTests.java => DoubleIDRepositoryERHLCIntegrationTests.java} (84%) rename src/test/java/org/springframework/data/elasticsearch/repositories/{geo/GeoRepositoryRestTemplateIntegrationTests.java => dynamicindex/DynamicIndexEntityELCIntegrationTests.java} (63%) rename src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/{DynamicIndexEntityRestTemplateIntegrationTests.java => DynamicIndexEntityERHLCIntegrationTests.java} (87%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryERHLCIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryRestTemplateIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectERHLCIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectRestTemplateIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/{DynamicSettingAndMappingEntityRepositoryRestTemplateIntegrationTests.java => DynamicSettingAndMappingEntityRepositoryERHLCIntegrationTests.java} (88%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/{FieldDynamicMappingEntityRepositoryRestTemplateIntegrationTests.java => FieldDynamicMappingEntityRepositoryERHLCIntegrationTests.java} (78%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityELCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/spel/{SpELEntityRestTemplateIntegrationTests.java => SpELEntityERHLCIntegrationTests.java} (74%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repositories/synonym/{SynonymRepositoryTests.java => SynonymRepositoryIntegrationTests.java} (82%) delete mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryRestTemplateTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryERHLCIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryRestTemplateIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsERHLCIntegrationTests.java rename src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/{QueryKeywordsTests.java => QueryKeywordsIntegrationTests.java} (95%) delete mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsRepositoryRestTemplateTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsELCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsERHLCIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMapUnitTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/BitUtil.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geohash.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geometry.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryValidator.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryVisitor.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/Point.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/Rectangle.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/ShapeType.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/StandardValidator.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/WellKnownText.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/geohash/package-info.java create mode 100644 src/test/resources/log4j2.xml diff --git a/.gitignore b/.gitignore index 6d39e3ec3..ef50ee720 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ target *.iws .idea /.env + + +/zap.env diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index e76d1f324..64a46202a 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -1,3 +1,4 @@ + /* * Copyright 2007-present the original author or authors. * @@ -20,98 +21,95 @@ import java.util.Properties; public class MavenWrapperDownloader { - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the default + * one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using transport directory: " + baseDirectory.getAbsolutePath()); - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println("- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } } diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index b98684b8b..18c1924f9 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -5,3 +5,7 @@ You find the contribution guidelines for Spring Data projects https://github.com == Running the test locally In order to run the tests locally with `./mvnw test` you need to have docker running because Spring Data Elasticsearch uses https://www.testcontainers.org/[Testcontainers] to start a local running Elasticsearch instance. + +== Class names of the test classes + +Tset classes that do depend on the client have either `ERHLC` (when using the deprecated Elasticsearch `RestHighLevelClient`) or `ELC` (the new `ElasticsearchClient`) in their name. diff --git a/mvnw b/mvnw index 41c0f0c23..9091adf18 100755 --- a/mvnw +++ b/mvnw @@ -162,7 +162,7 @@ fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory +# first directory with .mvn subdirectory is considered project transport directory find_maven_basedir() { if [ -z "$1" ] diff --git a/pom.xml b/pom.xml index d238b71dd..d088c282b 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 7.17.1 + 7.17.1 2.17.1 4.1.65.Final 2.7.0-SNAPSHOT @@ -134,7 +135,7 @@ test - + org.elasticsearch.client elasticsearch-rest-high-level-client @@ -147,6 +148,30 @@ + + + co.elastic.clients + elasticsearch-java + ${elasticsearch-java} + + + commons-logging + commons-logging + + + + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch} + + + commons-logging + commons-logging + + + + com.fasterxml.jackson.core @@ -480,6 +505,7 @@ local-maven-repo file:///${project.basedir}/src/test/resources/local-maven-repo + diff --git a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.3-4.4.adoc b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.3-4.4.adoc index 325e9c845..72138e2ce 100644 --- a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.3-4.4.adoc +++ b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.3-4.4.adoc @@ -6,14 +6,159 @@ This section describes breaking changes from version 4.3.x to 4.4.x and how remo [[elasticsearch-migration-guide-4.3-4.4.deprecations]] == Deprecations +=== org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations + +The method ` Publisher execute(ClientCallback> callback)` has been deprecated. +As there now are multiple implementations using different client libraries the `execute` method is still available in the different implementations, but there is no more method in the interface, because there is no common callback interface for the different clients. + [[elasticsearch-migration-guide-4.3-4.4.breaking-changes]] == Breaking Changes === Removal of deprecated classes -==== `ElasticsearchTemplate` has been removed +==== `org.springframework.data.elasticsearch.core.ElasticsearchTemplate` has been removed As of version 4.4 Spring Data Elasticsearch does not use the `TransportClient` from Elasticsearch anymore (which itself is deprecated since Elasticsearch 7.0). -This means that the `ElasticsearchTemplate` class which was deprecated since Spring Data Elasticsearch 4.0 has been removed. +This means that the `org.springframework.data.elasticsearch.core.ElasticsearchTemplate` class which was deprecated since Spring Data Elasticsearch 4.0 has been removed. This was the implementation of the `ElasticsearchOperations` interface that was using the `TransportClient`. Connections to Elasticsearch must be made using either the imperative `ElasticsearchRestTemplate` or the reactive `ReactiveElasticsearchTemplate`. + +=== Package changes + +In 4.3 two classes (`ElasticsearchAggregations` and `ElasticsearchAggregation`) had been moved to the `org.springframework.data.elasticsearch.core.clients.elasticsearch7` package in preparation for the integration of the new Elasticsearch client. +The were moved back to the `org.springframework.data.elasticsearch.core` package as we keep the classes use the old Elasticsearch client where they were. + +[[elasticsearch-migration-guide-4.3-4.4.new-clients]] +== New Elasticsearch client + +Elasticsearch has introduced it's new `ElasticsearchClient` and has deprecated the previous `RestHighLevelClient`. +Spring Data Elasticsearch 4.4 still uses the old client as the default client for the following reasons: + +* The new client forces applications to use the `jakarta.json.spi.JsonProvider` package whereas Spring Boot will stick to `javax.json.spi.JsonProvider` until version 3. So switching the default implementaiton in Spring Data Elasticsearch can only come with Spring Data Elasticsearch 5 (Spring Data 3, Spring 6). +* There are still some bugs in the Elasticsearch client which need to be resolved +* The implementation using the new client in Spring Data Elasticsearch is not yet complete, due to limited resources working on that - remember Spring Data Elasticsearch is a community driven project that lives from public contributions. + +=== How to use the new client + +CAUTION: The implementation using the new client is not complete, some operations will throw a `java.lang.UnsupportedOperationException` or might throw NPE (for example when the Elasticsearch cannot parse a response from the server, this still happens sometimes) + +Use the new client to test the implementations but do not use it in productive code yet! + +In order to try and use the new client the following steps are necessary: + +==== Make sure not to configure the existing default client + +If using Spring Boot, exclude Spring Data Elasticsearch from the autoconfiguration + +==== +[source,java] +---- +@SpringBootApplication(exclude = ElasticsearchDataAutoConfiguration.class) +public class SpringdataElasticTestApplication { + // ... +} + +---- +==== + +Remove Spring Data Elasticsearch related properties from your application configuration. +If Spring Data Elasticsearch was configured using a programmatic configuration (see <>), remove these beans from the Spring application context. + +==== Add dependencies + +The dependencies for the new Elasticsearch client are still optional in Spring Data Elasticsearch so they need to be added explicitly: + +==== +[source,xml] +---- + + + co.elastic.clients + elasticsearch-java + 7.17.1 + + + commons-logging + commons-logging + + + + + org.elasticsearch.client + elasticsearch-rest-client + 7.17.1 + + + commons-logging + commons-logging + + + + +---- +==== + +When using Spring Boot, it is necessary to set the following property in the _pom.xml_. + +==== +[source,xml] +---- + + 2.0.1 + +---- +==== + +==== New configuration classes + +===== Imperative style + +In order configure Spring Data Elasticsearch to use the new client, it is necessary to create a configuration bean that derives from `org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration`: + +==== +[source,java] +---- +@Configuration +public class NewRestClientConfig extends ElasticsearchConfiguration { + + @Override + public ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() // + .connectedTo("localhost:9200") // + .build(); + } +} +---- +==== + +The configuration is done in the same way as with the old client, but it is not necessary anymore to create more than the configuration bean. +With this configuration, the following beans will be available in the Spring application context: + +* a `RestClient` bean, that is the configured low level `RestClient` that is used by the Elasticsearch client +* an `ElasticsearchClient` bean, this is the new client that uses the `RestClient` +* an `ElasticsearchOperations` bean, available with the bean names _elasticsearchOperations_ and _elasticsearchTemplate_, this uses the `ElasticsearchClient` + +===== Reactive style + +To use the new client in a reactive environment the only difference is the class from which to derive the configuration: + +==== +[source,java] +---- +@Configuration +public class NewRestClientConfig extends ReactiveElasticsearchConfiguration { + + @Override + public ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() // + .connectedTo("localhost:9200") // + .build(); + } +} +---- +==== + +With this configuration, the following beans will be available in the Spring application context: + +* a `RestClient` bean, that is the configured low level `RestClient` that is used by the Elasticsearch client +* an `ReactiveElasticsearchClient` bean, this is the new reactive client that uses the `RestClient` +* an `ReactiveElasticsearchOperations` bean, available with the bean names _reactiveElasticsearchOperations_ and _reactiveElasticsearchTemplate_, this uses the `ReactiveElasticsearchClient` diff --git a/src/main/asciidoc/reference/elasticsearch-new.adoc b/src/main/asciidoc/reference/elasticsearch-new.adoc index 3385e95ba..4c1b683f6 100644 --- a/src/main/asciidoc/reference/elasticsearch-new.adoc +++ b/src/main/asciidoc/reference/elasticsearch-new.adoc @@ -4,6 +4,7 @@ [[new-features.4-4-0]] == New in Spring Data Elasticsearch 4.4 +* Introduction of new imperative and reactive clients using the classes from the new Elasticsearch Java client * Upgrade to Elasticsearch 7.17.1. [[new-features.4-3-0]] diff --git a/src/main/asciidoc/reference/migration-guides.adoc b/src/main/asciidoc/reference/migration-guides.adoc index 1fe24a41a..fcba27ff1 100644 --- a/src/main/asciidoc/reference/migration-guides.adoc +++ b/src/main/asciidoc/reference/migration-guides.adoc @@ -10,4 +10,6 @@ include::elasticsearch-migration-guide-4.0-4.1.adoc[] include::elasticsearch-migration-guide-4.1-4.2.adoc[] include::elasticsearch-migration-guide-4.2-4.3.adoc[] + +include::elasticsearch-migration-guide-4.3-4.4.adoc[] :leveloffset: -1 diff --git a/src/main/java/org/springframework/data/elasticsearch/ElasticsearchErrorCause.java b/src/main/java/org/springframework/data/elasticsearch/ElasticsearchErrorCause.java new file mode 100644 index 000000000..8860aae66 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/ElasticsearchErrorCause.java @@ -0,0 +1,78 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch; + +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Object describing an Elasticsearch error + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ElasticsearchErrorCause { + @Nullable private final String type; + + private final String reason; + + @Nullable private final String stackTrace; + + @Nullable private final ElasticsearchErrorCause causedBy; + + private final List rootCause; + + private final List suppressed; + + public ElasticsearchErrorCause(@Nullable String type, String reason, @Nullable String stackTrace, + @Nullable ElasticsearchErrorCause causedBy, List rootCause, + List suppressed) { + this.type = type; + this.reason = reason; + this.stackTrace = stackTrace; + this.causedBy = causedBy; + this.rootCause = rootCause; + this.suppressed = suppressed; + } + + @Nullable + public String getType() { + return type; + } + + public String getReason() { + return reason; + } + + @Nullable + public String getStackTrace() { + return stackTrace; + } + + @Nullable + public ElasticsearchErrorCause getCausedBy() { + return causedBy; + } + + public List getRootCause() { + return rootCause; + } + + public List getSuppressed() { + return suppressed; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/UncategorizedElasticsearchException.java b/src/main/java/org/springframework/data/elasticsearch/UncategorizedElasticsearchException.java index 708bee180..cf190ee77 100644 --- a/src/main/java/org/springframework/data/elasticsearch/UncategorizedElasticsearchException.java +++ b/src/main/java/org/springframework/data/elasticsearch/UncategorizedElasticsearchException.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch; import org.springframework.dao.UncategorizedDataAccessException; +import org.springframework.lang.Nullable; /** * @author Peter-Josef Meisch @@ -23,11 +24,48 @@ import org.springframework.dao.UncategorizedDataAccessException; */ public class UncategorizedElasticsearchException extends UncategorizedDataAccessException { + /** + * the response status code from Elasticsearch if available + * + * @since 4.4 + */ + @Nullable private final Integer statusCode; + + /** + * The response body from Elasticsearch if available + * + * @since 4.4 + */ + @Nullable final String responseBody; + public UncategorizedElasticsearchException(String msg) { - super(msg, null); + this(msg, null); } - public UncategorizedElasticsearchException(String msg, Throwable cause) { + public UncategorizedElasticsearchException(String msg, @Nullable Throwable cause) { + this(msg, null, null, cause); + } + + public UncategorizedElasticsearchException(String msg, @Nullable Integer statusCode, @Nullable String responseBody, + @Nullable Throwable cause) { super(msg, cause); + this.statusCode = statusCode; + this.responseBody = responseBody; + } + + /** + * @since 4.4 + */ + @Nullable + public Integer getStatusCode() { + return statusCode; + } + + /** + * @since 4.4 + */ + @Nullable + public String getResponseBody() { + return responseBody; } } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java index 45af98c92..64d79fb94 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java @@ -124,6 +124,22 @@ public @interface Document { * @since 4.3 */ enum VersionType { - INTERNAL, EXTERNAL, EXTERNAL_GTE + INTERNAL("internal"), // + EXTERNAL("external"), // + EXTERNAL_GTE("external_gte"), // + /** + * @since 4.4 + */ + FORCE("force"); + + private final String esName; + + VersionType(String esName) { + this.esName = esName; + } + + public String getEsName() { + return esName; + } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java index aa4d868b7..7dcc11e4d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java @@ -29,11 +29,11 @@ import org.springframework.util.ObjectUtils; * * @author Mark Paluch * @author Christoph Strobl + * @author Peter-Josef Meisch * @since 3.2 */ public abstract class ClientLogger { - private static final String lineSeparator = System.getProperty("line.separator"); private static final Log WIRE_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.client.WIRE"); private ClientLogger() {} @@ -63,6 +63,24 @@ public abstract class ClientLogger { } } + /** + * Log an outgoing HTTP request. + * + * @param logId the correlation id, see {@link #newLogId()}. + * @param method HTTP method + * @param endpoint URI + * @param parameters optional parameters. + * @param headers a String containing the headers + * @since 4.4 + */ + public static void logRequest(String logId, String method, String endpoint, Object parameters, String headers) { + + if (isEnabled()) { + WIRE_LOGGER.trace(String.format("[%s] Sending request%n%s %s%nParameters: %s%nHeaders: %s", logId, + method.toUpperCase(), endpoint, parameters, headers)); + } + } + /** * Log an outgoing HTTP request with a request body. * @@ -76,8 +94,28 @@ public abstract class ClientLogger { Supplier body) { if (isEnabled()) { - WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%sRequest body: %s", logId, - method.toUpperCase(), endpoint, parameters, lineSeparator, body.get())); + WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%nRequest body: %s", logId, + method.toUpperCase(), endpoint, parameters, body.get())); + } + } + + /** + * Log an outgoing HTTP request with a request body. + * + * @param logId the correlation id, see {@link #newLogId()}. + * @param method HTTP method + * @param endpoint URI + * @param parameters optional parameters. + * @param headers a String containing the headers + * @param body body content supplier. + * @since 4.4 + */ + public static void logRequest(String logId, String method, String endpoint, Object parameters, String headers, + Supplier body) { + + if (isEnabled()) { + WIRE_LOGGER.trace(String.format("[%s] Sending request%n%s %s%nParameters: %s%nHeaders: %s%nRequest body: %s", + logId, method.toUpperCase(), endpoint, parameters, headers, body.get())); } } @@ -94,6 +132,20 @@ public abstract class ClientLogger { } } + /** + * Log a raw HTTP response without logging the body. + * + * @param logId the correlation id, see {@link #newLogId()}. + * @param statusCode the HTTP status code. + * @param headers a String containing the headers + */ + public static void logRawResponse(String logId, @Nullable HttpStatus statusCode, String headers) { + + if (isEnabled()) { + WIRE_LOGGER.trace(String.format("[%s] Received response: %s%n%s", logId, statusCode, headers)); + } + } + /** * Log a raw HTTP response along with the body. * @@ -104,8 +156,24 @@ public abstract class ClientLogger { public static void logResponse(String logId, HttpStatus statusCode, String body) { if (isEnabled()) { - WIRE_LOGGER.trace( - String.format("[%s] Received response: %s%sResponse body: %s", logId, statusCode, lineSeparator, body)); + WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nResponse body: %s", logId, statusCode, body)); + } + } + + /** + * Log a raw HTTP response along with the body. + * + * @param logId the correlation id, see {@link #newLogId()}. + * @param statusCode the HTTP status code. + * @param headers a String containing the headers + * @param body body content. + * @since 4.4 + */ + public static void logResponse(String logId, @Nullable HttpStatus statusCode, String headers, String body) { + + if (isEnabled()) { + WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nHeaders: %s%nResponse body: %s", logId, statusCode, + headers, body)); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/UnsupportedBackendOperation.java b/src/main/java/org/springframework/data/elasticsearch/client/UnsupportedBackendOperation.java new file mode 100644 index 000000000..1c55f3538 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/UnsupportedBackendOperation.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client; + +/** + * Exception to be thrown by a backend implementation on operations that are not supported for that backend. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class UnsupportedBackendOperation extends RuntimeException { + public UnsupportedBackendOperation() {} + + public UnsupportedBackendOperation(String message) { + super(message); + } + + public UnsupportedBackendOperation(String message, Throwable cause) { + super(message, cause); + } + + public UnsupportedBackendOperation(Throwable cause) { + super(cause); + } + + public UnsupportedBackendOperation(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClient.java new file mode 100644 index 000000000..129e99a16 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClient.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient; +import co.elastic.clients.transport.ElasticsearchTransport; + +import org.elasticsearch.client.RestClient; +import org.springframework.util.Assert; + +/** + * Extension of the {@link ElasticsearchClient} class that implements {@link AutoCloseable}. As the underlying + * {@link RestClient} must be closed properly this is handled in the {@link #close()} method. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class AutoCloseableElasticsearchClient extends ElasticsearchClient implements AutoCloseable { + + public AutoCloseableElasticsearchClient(ElasticsearchTransport transport) { + super(transport); + Assert.notNull(transport, "transport must not be null"); + } + + @Override + public void close() throws Exception { + transport.close(); + } + + @Override + public ElasticsearchClusterClient cluster() { + return super.cluster(); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ChildTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ChildTemplate.java new file mode 100644 index 000000000..1433cfe38 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ChildTemplate.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.ApiClient; +import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient; +import co.elastic.clients.json.JsonpMapper; + +import java.io.IOException; + +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.util.Assert; + +/** + * base class for a template that uses one of the {@link co.elastic.clients.elasticsearch.ElasticsearchClient}'s child + * clients like {@link ElasticsearchClusterClient} or + * {@link co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public abstract class ChildTemplate { + + protected final CLIENT client; + protected final RequestConverter requestConverter; + protected final ResponseConverter responseConverter; + protected final ElasticsearchExceptionTranslator exceptionTranslator; + + public ChildTemplate(CLIENT client, ElasticsearchConverter elasticsearchConverter) { + this.client = client; + JsonpMapper jsonpMapper = client._transport().jsonpMapper(); + requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper); + responseConverter = new ResponseConverter(jsonpMapper); + exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper); + } + + /** + * Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on the client. + */ + @FunctionalInterface + public interface ClientCallback { + RESULT doWithClient(CLIENT client) throws IOException; + } + + /** + * Execute a callback with the client and provide exception translation. + * + * @param callback the callback to execute, must not be {@literal null} + * @param the type returned from the callback + * @return the callback result + */ + public RESULT execute(ClientCallback callback) { + + Assert.notNull(callback, "callback must not be null"); + + try { + return callback.doWithClient(client); + } catch (IOException | RuntimeException e) { + throw exceptionTranslator.translateException(e); + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ClusterTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ClusterTemplate.java new file mode 100644 index 000000000..34a073dbf --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ClusterTemplate.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient; +import co.elastic.clients.elasticsearch.cluster.HealthRequest; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; + +import org.springframework.data.elasticsearch.core.cluster.ClusterHealth; +import org.springframework.data.elasticsearch.core.cluster.ClusterOperations; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; + +/** + * Implementation of the {@link ClusterOperations} interface using en {@link ElasticsearchClusterClient}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ClusterTemplate extends ChildTemplate implements ClusterOperations { + + public ClusterTemplate(ElasticsearchClusterClient client, ElasticsearchConverter elasticsearchConverter) { + super(client, elasticsearchConverter); + } + + @Override + public ClusterHealth health() { + + HealthRequest healthRequest = requestConverter.clusterHealthRequest(); + HealthResponse healthResponse = execute(client -> client.health(healthRequest)); + return responseConverter.clusterHealth(healthResponse); + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaFilterProcessor.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaFilterProcessor.java new file mode 100644 index 000000000..11c2ee5c6 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaFilterProcessor.java @@ -0,0 +1,321 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.GeoDistanceType; +import co.elastic.clients.elasticsearch._types.GeoShapeRelation; +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.GeoBoundingBoxQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.GeoDistanceQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.GeoShapeQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryVariant; +import co.elastic.clients.json.JsonData; +import co.elastic.clients.util.ObjectBuilder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.data.elasticsearch.core.convert.GeoConverters; +import org.springframework.data.elasticsearch.core.geo.GeoBox; +import org.springframework.data.elasticsearch.core.geo.GeoJson; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.data.elasticsearch.core.query.Criteria; +import org.springframework.data.geo.Box; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; +import org.springframework.util.Assert; + +/** + * Class to convert a {@link org.springframework.data.elasticsearch.core.query.CriteriaQuery} into an Elasticsearch + * filter. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +class CriteriaFilterProcessor { + /** + * Creates a filter query from the given criteria. + * + * @param criteria the criteria to process + * @return the optional query, empty if the criteria did not contain filter relevant elements + */ + public static Optional createQuery(Criteria criteria) { + + Assert.notNull(criteria, "criteria must not be null"); + + List filterQueries = new ArrayList<>(); + + for (Criteria chainedCriteria : criteria.getCriteriaChain()) { + + if (chainedCriteria.isOr()) { + // todo #1973 + } else if (chainedCriteria.isNegating()) { + // todo #1973 + } else { + filterQueries.addAll(queriesForEntries(chainedCriteria)); + } + } + + if (filterQueries.isEmpty()) { + return Optional.empty(); + } else { + + if (filterQueries.size() == 1) { + return Optional.of(filterQueries.get(0)); + } else { + BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool(); + filterQueries.forEach(boolQueryBuilder::must); + BoolQuery boolQuery = boolQueryBuilder.build(); + return Optional.of(boolQuery._toQuery()); + } + } + } + + private static Collection queriesForEntries(Criteria criteria) { + + Assert.notNull(criteria.getField(), "criteria must have a field"); + String fieldName = criteria.getField().getName(); + Assert.notNull(fieldName, "Unknown field"); + + return criteria.getFilterCriteriaEntries().stream() + .map(entry -> queryFor(entry.getKey(), entry.getValue(), fieldName)) // + .filter(Optional::isPresent) // + .map(Optional::get) // + .collect(Collectors.toList()); + } + + private static Optional queryFor(Criteria.OperationKey key, Object value, String fieldName) { + + ObjectBuilder queryBuilder = null; + + switch (key) { + case WITHIN: + Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values."); + queryBuilder = withinQuery(fieldName, (Object[]) value); + break; + case BBOX: + Assert.isTrue(value instanceof Object[], + "Value of a boundedBy filter should be an array of one or two values."); + queryBuilder = boundingBoxQuery(fieldName, (Object[]) value); + break; + case GEO_INTERSECTS: + Assert.isTrue(value instanceof GeoJson, "value of a GEO_INTERSECTS filter must be a GeoJson object"); + queryBuilder = geoJsonQuery(fieldName, (GeoJson) value, "intersects"); + break; + case GEO_IS_DISJOINT: + Assert.isTrue(value instanceof GeoJson, "value of a GEO_IS_DISJOINT filter must be a GeoJson object"); + queryBuilder = geoJsonQuery(fieldName, (GeoJson) value, "disjoint"); + break; + case GEO_WITHIN: + Assert.isTrue(value instanceof GeoJson, "value of a GEO_WITHIN filter must be a GeoJson object"); + queryBuilder = geoJsonQuery(fieldName, (GeoJson) value, "within"); + break; + case GEO_CONTAINS: + Assert.isTrue(value instanceof GeoJson, "value of a GEO_CONTAINS filter must be a GeoJson object"); + queryBuilder = geoJsonQuery(fieldName, (GeoJson) value, "contains"); + break; + } + + return Optional.ofNullable(queryBuilder != null ? queryBuilder.build()._toQuery() : null); + } + + private static ObjectBuilder withinQuery(String fieldName, Object[] values) { + + Assert.noNullElements(values, "Geo distance filter takes 2 not null elements array as parameter."); + Assert.isTrue(values.length == 2, "Geo distance filter takes a 2-elements array as parameter."); + Assert.isTrue(values[0] instanceof GeoPoint || values[0] instanceof String || values[0] instanceof Point, + "First element of a geo distance filter must be a GeoPoint, a Point or a text"); + Assert.isTrue(values[1] instanceof String || values[1] instanceof Distance, + "Second element of a geo distance filter must be a text or a Distance"); + + String dist = (values[1] instanceof Distance) ? extractDistanceString((Distance) values[1]) : (String) values[1]; + + return QueryBuilders.geoDistance() // + .field(fieldName) // + .distance(dist) // + .distanceType(GeoDistanceType.Plane) // + .location(location -> { + if (values[0] instanceof GeoPoint) { + GeoPoint loc = (GeoPoint) values[0]; + location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon())); + } else if (values[0] instanceof Point) { + GeoPoint loc = GeoPoint.fromPoint((Point) values[0]); + location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon())); + } else { + String loc = (String) values[0]; + if (loc.contains(",")) { + String[] c = loc.split(","); + location.latlon(latlon -> latlon.lat(Double.parseDouble(c[0])).lon(Double.parseDouble(c[1]))); + } else { + location.geohash(geohash -> geohash.geohash(loc)); + } + } + return location; + }); + } + + private static ObjectBuilder boundingBoxQuery(String fieldName, Object[] values) { + + Assert.noNullElements(values, "Geo boundedBy filter takes a not null element array as parameter."); + + GeoBoundingBoxQuery.Builder queryBuilder = QueryBuilders.geoBoundingBox() // + .field(fieldName); + + if (values.length == 1) { + // GeoEnvelop + oneParameterBBox(queryBuilder, values[0]); + } else if (values.length == 2) { + // 2x GeoPoint + // 2x text + twoParameterBBox(queryBuilder, values); + } else { + throw new IllegalArgumentException( + "Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash))."); + } + return queryBuilder; + } + + private static void oneParameterBBox(GeoBoundingBoxQuery.Builder queryBuilder, Object value) { + Assert.isTrue(value instanceof GeoBox || value instanceof Box, + "single-element of boundedBy filter must be type of GeoBox or Box"); + + GeoBox geoBBox; + if (value instanceof Box) { + geoBBox = GeoBox.fromBox((Box) value); + } else { + geoBBox = (GeoBox) value; + } + + queryBuilder.boundingBox(bb -> bb // + .tlbr(tlbr -> tlbr // + .topLeft(glb -> glb // + .latlon(latlon -> latlon // + .lat(geoBBox.getTopLeft().getLat()) // + .lon(geoBBox.getTopLeft().getLon()))) // + .bottomRight(glb -> glb // + .latlon(latlon -> latlon // + .lat(geoBBox.getBottomRight().getLat())// + .lon(geoBBox.getBottomRight().getLon()// ) + ))))); + } + + private static void twoParameterBBox(GeoBoundingBoxQuery.Builder queryBuilder, Object[] values) { + + Assert.isTrue(allElementsAreOfType(values, GeoPoint.class) || allElementsAreOfType(values, String.class), + " both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)"); + + if (values[0] instanceof GeoPoint) { + GeoPoint topLeft = (GeoPoint) values[0]; + GeoPoint bottomRight = (GeoPoint) values[1]; + queryBuilder.boundingBox(bb -> bb // + .tlbr(tlbr -> tlbr // + .topLeft(glb -> glb // + .latlon(latlon -> latlon // + .lat(topLeft.getLat()) // + .lon(topLeft.getLon()))) // + .bottomRight(glb -> glb // + .latlon(latlon -> latlon // + .lat(bottomRight.getLat()) // + .lon(bottomRight.getLon()))) // + ) // + ); + } else { + String topLeft = (String) values[0]; + String bottomRight = (String) values[1]; + boolean isGeoHash = !topLeft.contains(","); + queryBuilder.boundingBox(bb -> bb // + .tlbr(tlbr -> tlbr // + .topLeft(glb -> { + if (isGeoHash) { + glb.geohash(gh -> gh.geohash(topLeft)); + } else { + glb.text(topLeft); + } + return glb; + }) // + .bottomRight(glb -> { + if (isGeoHash) { + glb.geohash(gh -> gh.geohash(bottomRight)); + } else { + glb.text(bottomRight); + } + return glb; + }) // + )); + } + } + + private static boolean allElementsAreOfType(Object[] array, Class clazz) { + for (Object o : array) { + if (!clazz.isInstance(o)) { + return false; + } + } + return true; + } + + private static ObjectBuilder geoJsonQuery(String fieldName, GeoJson geoJson, + String relation) { + return buildGeoShapeQuery(fieldName, geoJson, relation); + } + + private static ObjectBuilder buildGeoShapeQuery(String fieldName, GeoJson geoJson, + String relation) { + return QueryBuilders.geoShape().field(fieldName) // + .shape(gsf -> gsf // + .shape(JsonData.of(GeoConverters.GeoJsonToMapConverter.INSTANCE.convert(geoJson))) // + .relation(toRelation(relation))); // + } + + private static GeoShapeRelation toRelation(String relation) { + + for (GeoShapeRelation geoShapeRelation : GeoShapeRelation.values()) { + + if (geoShapeRelation.name().equalsIgnoreCase(relation)) { + return geoShapeRelation; + } + } + throw new IllegalArgumentException("Unknown geo_shape relation: " + relation); + } + + /** + * extract the distance string from a {@link org.springframework.data.geo.Distance} object. + * + * @param distance distance object to extract string from + */ + private static String extractDistanceString(Distance distance) { + + StringBuilder sb = new StringBuilder(); + sb.append((int) distance.getValue()); + switch ((Metrics) distance.getMetric()) { + case KILOMETERS: + sb.append("km"); + break; + case MILES: + sb.append("mi"); + break; + } + + return sb.toString(); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingRestTemplateIntegrationTests.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryException.java similarity index 57% rename from src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingRestTemplateIntegrationTests.java rename to src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryException.java index 726f108e6..98c2de8ec 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingRestTemplateIntegrationTests.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryException.java @@ -1,5 +1,5 @@ /* - * Copyright2020-2021 the original author or authors. + * Copyright 2021-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core.routing; +package org.springframework.data.elasticsearch.client.elc; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.dao.UncategorizedDataAccessException; /** * @author Peter-Josef Meisch + * @since 4.4 */ -@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) -public class RoutingRestTemplateIntegrationTests extends RoutingIntegrationTests {} +public class CriteriaQueryException extends UncategorizedDataAccessException { + public CriteriaQueryException(String msg) { + super(msg, null); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryProcessor.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryProcessor.java new file mode 100644 index 000000000..79ced58d4 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryProcessor.java @@ -0,0 +1,368 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.springframework.data.elasticsearch.client.elc.QueryBuilders.*; +import static org.springframework.util.StringUtils.*; + +import co.elastic.clients.elasticsearch._types.FieldValue; +import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode; +import co.elastic.clients.elasticsearch._types.query_dsl.Operator; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.json.JsonData; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil; +import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.core.query.Criteria; +import org.springframework.data.elasticsearch.core.query.Field; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Class to convert a {@link org.springframework.data.elasticsearch.core.query.CriteriaQuery} into an Elasticsearch + * query. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +class CriteriaQueryProcessor { + + /** + * creates a query from the criteria + * + * @param criteria the {@link Criteria} + * @return the optional query, null if the criteria did not contain filter relevant elements + */ + @Nullable + public static Query createQuery(Criteria criteria) { + + Assert.notNull(criteria, "criteria must not be null"); + + List shouldQueries = new ArrayList<>(); + List mustNotQueries = new ArrayList<>(); + List mustQueries = new ArrayList<>(); + + Query firstQuery = null; + boolean negateFirstQuery = false; + + for (Criteria chainedCriteria : criteria.getCriteriaChain()) { + Query queryFragment = queryForEntries(chainedCriteria); + + if (queryFragment != null) { + + if (firstQuery == null) { + firstQuery = queryFragment; + negateFirstQuery = chainedCriteria.isNegating(); + continue; + } + + if (chainedCriteria.isOr()) { + shouldQueries.add(queryFragment); + } else if (chainedCriteria.isNegating()) { + mustNotQueries.add(queryFragment); + } else { + mustQueries.add(queryFragment); + } + } + } + + for (Criteria subCriteria : criteria.getSubCriteria()) { + Query subQuery = createQuery(subCriteria); + if (subQuery != null) { + if (criteria.isOr()) { + shouldQueries.add(subQuery); + } else if (criteria.isNegating()) { + mustNotQueries.add(subQuery); + } else { + mustQueries.add(subQuery); + } + } + } + + if (firstQuery != null) { + + if (!shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) { + shouldQueries.add(0, firstQuery); + } else { + + if (negateFirstQuery) { + mustNotQueries.add(0, firstQuery); + } else { + mustQueries.add(0, firstQuery); + } + } + } + + if (shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) { + return null; + } + + Query query = new Query.Builder().bool(boolQueryBuilder -> { + + if (!shouldQueries.isEmpty()) { + boolQueryBuilder.should(shouldQueries); + } + + if (!mustNotQueries.isEmpty()) { + boolQueryBuilder.mustNot(mustNotQueries); + } + + if (!mustQueries.isEmpty()) { + boolQueryBuilder.must(mustQueries); + } + + return boolQueryBuilder; + }).build(); + + return query; + } + + @Nullable + private static Query queryForEntries(Criteria criteria) { + + Field field = criteria.getField(); + + if (field == null || criteria.getQueryCriteriaEntries().isEmpty()) + return null; + + String fieldName = field.getName(); + Assert.notNull(fieldName, "Unknown field " + fieldName); + + Iterator it = criteria.getQueryCriteriaEntries().iterator(); + + Float boost = Float.isNaN(criteria.getBoost()) ? null : criteria.getBoost(); + Query.Builder queryBuilder; + + if (criteria.getQueryCriteriaEntries().size() == 1) { + queryBuilder = queryFor(it.next(), field, boost); + } else { + queryBuilder = new Query.Builder(); + queryBuilder.bool(boolQueryBuilder -> { + while (it.hasNext()) { + Criteria.CriteriaEntry entry = it.next(); + boolQueryBuilder.must(queryFor(entry, field, null).build()); + } + boolQueryBuilder.boost(boost); + return boolQueryBuilder; + }); + + } + + if (hasText(field.getPath())) { + final Query query = queryBuilder.build(); + queryBuilder = new Query.Builder(); + queryBuilder.nested(nqb -> nqb // + .path(field.getPath()) // + .query(query) // + .scoreMode(ChildScoreMode.Avg)); + } + + return queryBuilder.build(); + } + + private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field, @Nullable Float boost) { + + String fieldName = field.getName(); + boolean isKeywordField = FieldType.Keyword == field.getFieldType(); + + Criteria.OperationKey key = entry.getKey(); + Object value = key.hasValue() ? entry.getValue() : null; + String searchText = value != null ? QueryParserUtil.escape(value.toString()) : "UNKNOWN_VALUE"; + + Query.Builder queryBuilder = new Query.Builder(); + switch (key) { + case EXISTS: + queryBuilder // + .exists(eb -> eb // + .field(fieldName) // + .boost(boost)); + break; + case EMPTY: + queryBuilder // + .bool(bb -> bb // + .must(mb -> mb // + .exists(eb -> eb // + .field(fieldName) // + )) // + .mustNot(mnb -> mnb // + .wildcard(wb -> wb // + .field(fieldName) // + .wildcard("*"))) // + .boost(boost)); + break; + case NOT_EMPTY: + queryBuilder // + .wildcard(wb -> wb // + .field(fieldName) // + .wildcard("*") // + .boost(boost)); + break; + case EQUALS: + queryBuilder.queryString(queryStringQuery(fieldName, searchText, Operator.And, boost)); + break; + case CONTAINS: + queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText + '*', true, boost)); + break; + case STARTS_WITH: + queryBuilder.queryString(queryStringQuery(fieldName, searchText + '*', true, boost)); + break; + case ENDS_WITH: + queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText, true, boost)); + break; + case EXPRESSION: + queryBuilder.queryString(queryStringQuery(fieldName, value.toString(), boost)); + break; + case LESS: + queryBuilder // + .range(rb -> rb // + .field(fieldName) // + .lt(JsonData.of(value)) // + .boost(boost)); // + break; + case LESS_EQUAL: + queryBuilder // + .range(rb -> rb // + .field(fieldName) // + .lte(JsonData.of(value)) // + .boost(boost)); // + break; + case GREATER: + queryBuilder // + .range(rb -> rb // + .field(fieldName) // + .gt(JsonData.of(value)) // + .boost(boost)); // + break; + case GREATER_EQUAL: + queryBuilder // + .range(rb -> rb // + .field(fieldName) // + .gte(JsonData.of(value)) // + .boost(boost)); // + break; + case BETWEEN: + Object[] ranges = (Object[]) value; + queryBuilder // + .range(rb -> { + rb.field(fieldName); + if (ranges[0] != null) { + rb.gte(JsonData.of(ranges[0])); + } + + if (ranges[1] != null) { + rb.lte(JsonData.of(ranges[1])); + } + rb.boost(boost); // + return rb; + }); // + + break; + case FUZZY: + queryBuilder // + .fuzzy(fb -> fb // + .field(fieldName) // + .value(FieldValue.of(searchText)) // + .boost(boost)); // + break; + case MATCHES: + queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.Or, boost)); + break; + case MATCHES_ALL: + queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.And, boost)); + + break; + case IN: + if (value instanceof Iterable) { + Iterable iterable = (Iterable) value; + if (isKeywordField) { + queryBuilder.bool(bb -> bb // + .must(mb -> mb // + .terms(tb -> tb // + .field(fieldName) // + .terms(tsb -> tsb // + .value(toFieldValueList(iterable))))) // + .boost(boost)); // + } else { + queryBuilder // + .queryString(qsb -> qsb // + .fields(fieldName) // + .query(orQueryString(iterable)) // + .boost(boost)); // + } + } else { + throw new CriteriaQueryException("value for " + fieldName + " is not an Iterable"); + } + break; + case NOT_IN: + if (value instanceof Iterable) { + Iterable iterable = (Iterable) value; + if (isKeywordField) { + queryBuilder.bool(bb -> bb // + .mustNot(mnb -> mnb // + .terms(tb -> tb // + .field(fieldName) // + .terms(tsb -> tsb // + .value(toFieldValueList(iterable))))) // + .boost(boost)); // + } else { + queryBuilder // + .queryString(qsb -> qsb // + .fields(fieldName) // + .query("NOT(" + orQueryString(iterable) + ')') // + .boost(boost)); // + } + } else { + throw new CriteriaQueryException("value for " + fieldName + " is not an Iterable"); + } + break; + default: + throw new CriteriaQueryException("Could not build query for " + entry); + } + + return queryBuilder; + } + + private static List toFieldValueList(Iterable iterable) { + List list = new ArrayList<>(); + for (Object item : iterable) { + list.add(item != null ? FieldValue.of(item.toString()) : null); + } + return list; + } + + private static String orQueryString(Iterable iterable) { + StringBuilder sb = new StringBuilder(); + + for (Object item : iterable) { + + if (item != null) { + + if (sb.length() > 0) { + sb.append(' '); + } + sb.append('"'); + sb.append(QueryParserUtil.escape(item.toString())); + sb.append('"'); + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/DocumentAdapters.java new file mode 100644 index 000000000..4ed89ee52 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/DocumentAdapters.java @@ -0,0 +1,212 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.core.GetResponse; +import co.elastic.clients.elasticsearch.core.MgetResponse; +import co.elastic.clients.elasticsearch.core.explain.ExplanationDetail; +import co.elastic.clients.elasticsearch.core.get.GetResult; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.elasticsearch.core.search.NestedIdentity; +import co.elastic.clients.json.JsonData; +import co.elastic.clients.json.JsonpMapper; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.data.elasticsearch.core.MultiGetItem; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.document.Explanation; +import org.springframework.data.elasticsearch.core.document.NestedMetaData; +import org.springframework.data.elasticsearch.core.document.SearchDocument; +import org.springframework.data.elasticsearch.core.document.SearchDocumentAdapter; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Utility class to adapt different Elasticsearch responses to a + * {@link org.springframework.data.elasticsearch.core.document.Document} + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +final class DocumentAdapters { + + private static final Log LOGGER = LogFactory.getLog(DocumentAdapters.class); + + private DocumentAdapters() {} + + /** + * Creates a {@link SearchDocument} from a {@link Hit} returned by the Elasticsearch client. + * + * @param hit the hit object + * @param jsonpMapper to map JsonData objects + * @return the created {@link SearchDocument} + */ + public static SearchDocument from(Hit hit, JsonpMapper jsonpMapper) { + + Assert.notNull(hit, "hit must not be null"); + + Map> highlightFields = hit.highlight(); + + Map innerHits = new LinkedHashMap<>(); + hit.innerHits().forEach((name, innerHitsResult) -> { + // noinspection ReturnOfNull + innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, + searchDocument -> null, jsonpMapper)); + }); + + NestedMetaData nestedMetaData = from(hit.nested()); + + // todo #1973 explanation + Explanation explanation = from(hit.explanation()); + + // todo #1973 matchedQueries + List matchedQueries = null; + // todo #1973 documentFields + Map> documentFields = Collections.emptyMap(); + + Document document; + Object source = hit.source(); + if (source == null) { + // Elasticsearch provides raw JsonData, so we build the fields into a JSON string + StringBuilder sb = new StringBuilder("{"); + final boolean[] firstField = { true }; + hit.fields().forEach((key, jsonData) -> { + if (!firstField[0]) { + sb.append(','); + } + sb.append('"').append(key).append("\":") // + .append(jsonData.toJson(jsonpMapper).toString()); + firstField[0] = false; + }); + sb.append('}'); + document = Document.parse(sb.toString()); + } else { + if (source instanceof EntityAsMap) { + document = Document.from((EntityAsMap) source); + } else if (source instanceof JsonData) { + JsonData jsonData = (JsonData) source; + document = Document.from(jsonData.to(EntityAsMap.class)); + } else { + + if (LOGGER.isWarnEnabled()) { + LOGGER.warn(String.format("Cannot map from type " + source.getClass().getName())); + } + document = Document.create(); + } + } + document.setIndex(hit.index()); + document.setId(hit.id()); + + if (hit.version() != null) { + document.setVersion(hit.version()); + } + document.setSeqNo(hit.seqNo() != null && hit.seqNo() >= 0 ? hit.seqNo() : -2); // -2 was the default value in the + // old client + document.setPrimaryTerm(hit.primaryTerm() != null && hit.primaryTerm() > 0 ? hit.primaryTerm() : 0); + + float score = hit.score() != null ? hit.score().floatValue() : Float.NaN; + return new SearchDocumentAdapter(document, score, hit.sort().toArray(new String[0]), documentFields, + highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing()); + } + + @Nullable + private static Explanation from(@Nullable co.elastic.clients.elasticsearch.core.explain.Explanation explanation) { + + if (explanation == null) { + return null; + } + List details = explanation.details().stream().map(DocumentAdapters::from).collect(Collectors.toList()); + return new Explanation(true, (double) explanation.value(), explanation.description(), details); + } + + private static Explanation from(ExplanationDetail explanationDetail) { + + List details = explanationDetail.details().stream().map(DocumentAdapters::from) + .collect(Collectors.toList()); + return new Explanation(null, (double) explanationDetail.value(), explanationDetail.description(), details); + } + + @Nullable + private static NestedMetaData from(@Nullable NestedIdentity nestedIdentity) { + + if (nestedIdentity == null) { + return null; + } + + NestedMetaData child = from(nestedIdentity.nested()); + return NestedMetaData.of(nestedIdentity.field(), nestedIdentity.offset(), child); + } + + /** + * Creates a {@link Document} from a {@link GetResponse} where the found document is contained as {@link EntityAsMap}. + * + * @param getResponse the response instance + * @return the Document + */ + @Nullable + public static Document from(GetResult getResponse) { + + Assert.notNull(getResponse, "getResponse must not be null"); + + if (!getResponse.found()) { + return null; + } + + Document document = getResponse.source() != null ? Document.from(getResponse.source()) : Document.create(); + document.setIndex(getResponse.index()); + document.setId(getResponse.id()); + + if (getResponse.version() != null) { + document.setVersion(getResponse.version()); + } + + if (getResponse.seqNo() != null) { + document.setSeqNo(getResponse.seqNo()); + } + + if (getResponse.primaryTerm() != null) { + document.setPrimaryTerm(getResponse.primaryTerm()); + } + + return document; + } + + /** + * Creates a list of {@link MultiGetItem}s from a {@link MgetResponse} where the data is contained as + * {@link EntityAsMap} instances. + * + * @param mgetResponse the response instance + * @return list of multiget items + */ + public static List> from(MgetResponse mgetResponse) { + + Assert.notNull(mgetResponse, "mgetResponse must not be null"); + + return mgetResponse.docs().stream() // + .map(itemResponse -> MultiGetItem.of( // + itemResponse.isFailure() ? null : from(itemResponse.result()), // + ResponseConverter.getFailure(itemResponse))) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchAggregations.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchAggregations.java new file mode 100644 index 000000000..13fb295d3 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchAggregations.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.aggregations.Aggregate; + +import java.util.Map; + +import org.springframework.data.elasticsearch.core.AggregationsContainer; + +/** + * AggregationsContainer implementation for the Elasticsearch aggregations. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ElasticsearchAggregations implements AggregationsContainer> { + + private final Map aggregations; + + public ElasticsearchAggregations(Map aggregations) { + this.aggregations = aggregations; + } + + @Override + public Map aggregations() { + return aggregations; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java new file mode 100644 index 000000000..cc845b111 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java @@ -0,0 +1,381 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.transport.rest_client.RestClientOptions; +import co.elastic.clients.transport.rest_client.RestClientTransport; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.apache.http.message.BasicHeader; +import org.apache.http.protocol.HttpContext; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.ClientLogger; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Utility class to create the different Elasticsearch clients + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public final class ElasticsearchClients { + /** + * Name of whose value can be used to correlate log messages for this request. + */ + private static final String LOG_ID_ATTRIBUTE = ElasticsearchClients.class.getName() + ".LOG_ID"; + private static final String X_SPRING_DATA_ELASTICSEARCH_CLIENT = "X-SpringDataElasticsearch-Client"; + private static final String IMPERATIVE_CLIENT = "imperative"; + private static final String REACTIVE_CLIENT = "reactive"; + + /** + * Creates a new {@link ReactiveElasticsearchClient} + * + * @param clientConfiguration configuration options, must not be {@literal null}. + * @return the {@link ReactiveElasticsearchClient} + */ + public static ReactiveElasticsearchClient createReactive(ClientConfiguration clientConfiguration) { + + Assert.notNull(clientConfiguration, "clientConfiguration must not be null"); + + return createReactive(getRestClient(clientConfiguration), null); + } + + /** + * Creates a new {@link ReactiveElasticsearchClient} + * + * @param clientConfiguration configuration options, must not be {@literal null}. + * @param transportOptions options to be added to each request. + * @return the {@link ReactiveElasticsearchClient} + */ + public static ReactiveElasticsearchClient createReactive(ClientConfiguration clientConfiguration, + @Nullable TransportOptions transportOptions) { + + Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!"); + + return createReactive(getRestClient(clientConfiguration), transportOptions); + } + + /** + * Creates a new {@link ReactiveElasticsearchClient}. + * + * @param restClient the underlying {@link RestClient} + * @return the {@link ReactiveElasticsearchClient} + */ + public static ReactiveElasticsearchClient createReactive(RestClient restClient) { + return createReactive(restClient, null); + } + + /** + * Creates a new {@link ReactiveElasticsearchClient}. + * + * @param restClient the underlying {@link RestClient} + * @param transportOptions options to be added to each request. + * @return the {@link ReactiveElasticsearchClient} + */ + public static ReactiveElasticsearchClient createReactive(RestClient restClient, + @Nullable TransportOptions transportOptions) { + return new ReactiveElasticsearchClient(getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions)); + } + + /** + * Creates a new imperative {@link ElasticsearchClient} + * + * @param clientConfiguration configuration options, must not be {@literal null}. + * @return the {@link ElasticsearchClient} + */ + public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration) { + return createImperative(getRestClient(clientConfiguration), null); + } + + /** + * Creates a new imperative {@link ElasticsearchClient} + * + * @param clientConfiguration configuration options, must not be {@literal null}. + * @param transportOptions options to be added to each request. + * @return the {@link ElasticsearchClient} + */ + public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration, + TransportOptions transportOptions) { + return createImperative(getRestClient(clientConfiguration), transportOptions); + } + + /** + * Creates a new imperative {@link ElasticsearchClient} + * + * @param restClient the RestClient to use + * @return the {@link ElasticsearchClient} + */ + public static ElasticsearchClient createImperative(RestClient restClient) { + return createImperative(restClient, null); + } + + /** + * Creates a new imperative {@link ElasticsearchClient} + * + * @param restClient the RestClient to use + * @param transportOptions options to be added to each request. + * @return the {@link ElasticsearchClient} + */ + public static ElasticsearchClient createImperative(RestClient restClient, + @Nullable TransportOptions transportOptions) { + + Assert.notNull(restClient, "restClient must not be null"); + + ElasticsearchTransport transport = getElasticsearchTransport(restClient, IMPERATIVE_CLIENT, transportOptions); + + return new AutoCloseableElasticsearchClient(transport); + } + + /** + * Creates a low level {@link RestClient} for the given configuration. + * + * @param clientConfiguration must not be {@literal null} + * @return the {@link RestClient} + */ + public static RestClient getRestClient(ClientConfiguration clientConfiguration) { + return getRestClientBuilder(clientConfiguration).build(); + } + + private static RestClientBuilder getRestClientBuilder(ClientConfiguration clientConfiguration) { + HttpHost[] httpHosts = formattedHosts(clientConfiguration.getEndpoints(), clientConfiguration.useSsl()).stream() + .map(HttpHost::create).toArray(HttpHost[]::new); + RestClientBuilder builder = RestClient.builder(httpHosts); + + if (clientConfiguration.getPathPrefix() != null) { + builder.setPathPrefix(clientConfiguration.getPathPrefix()); + } + + HttpHeaders headers = clientConfiguration.getDefaultHeaders(); + + if (!headers.isEmpty()) { + builder.setDefaultHeaders(toHeaderArray(headers)); + } + + builder.setHttpClientConfigCallback(clientBuilder -> { + clientConfiguration.getSslContext().ifPresent(clientBuilder::setSSLContext); + clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier); + clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier())); + + if (ClientLogger.isEnabled()) { + HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); + + clientBuilder.addInterceptorLast((HttpRequestInterceptor) interceptor); + clientBuilder.addInterceptorLast((HttpResponseInterceptor) interceptor); + } + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + Duration connectTimeout = clientConfiguration.getConnectTimeout(); + + if (!connectTimeout.isNegative()) { + requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis())); + } + + Duration socketTimeout = clientConfiguration.getSocketTimeout(); + + if (!socketTimeout.isNegative()) { + requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis())); + requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis())); + } + + clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); + + clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy); + + for (ClientConfiguration.ClientConfigurationCallback clientConfigurer : clientConfiguration + .getClientConfigurers()) { + if (clientConfigurer instanceof ElasticsearchClientConfigurationCallback) { + ElasticsearchClientConfigurationCallback restClientConfigurationCallback = (ElasticsearchClientConfigurationCallback) clientConfigurer; + clientBuilder = restClientConfigurationCallback.configure(clientBuilder); + } + } + + return clientBuilder; + }); + return builder; + } + + private static ElasticsearchTransport getElasticsearchTransport(RestClient restClient, String clientType, + @Nullable TransportOptions transportOptions) { + + TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder() + : new RestClientOptions(RequestOptions.DEFAULT).toBuilder(); + TransportOptions transportOptionsWithHeader = transportOptionsBuilder + .addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT, clientType).build(); + + ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper(), + transportOptionsWithHeader); + return transport; + } + + private static List formattedHosts(List hosts, boolean useSsl) { + return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ":" + it.getPort()) + .collect(Collectors.toList()); + } + + private static org.apache.http.Header[] toHeaderArray(HttpHeaders headers) { + return headers.entrySet().stream() // + .flatMap(entry -> entry.getValue().stream() // + .map(value -> new BasicHeader(entry.getKey(), value))) // + .toArray(org.apache.http.Header[]::new); + } + + /** + * Logging interceptors for Elasticsearch client logging. + * + * @see ClientLogger + * @since 4.4 + */ + private static class HttpLoggingInterceptor implements HttpResponseInterceptor, HttpRequestInterceptor { + + @Override + public void process(HttpRequest request, HttpContext context) throws IOException { + + String logId = (String) context.getAttribute(LOG_ID_ATTRIBUTE); + + if (logId == null) { + logId = ClientLogger.newLogId(); + context.setAttribute(LOG_ID_ATTRIBUTE, logId); + } + + String headers = Arrays.stream(request.getAllHeaders()) + .map(header -> header.getName() + + ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue())) + .collect(Collectors.joining(", ", "[", "]")); + + if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) { + + HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request; + HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + entity.writeTo(buffer); + + if (!entity.isRepeatable()) { + entityRequest.setEntity(new ByteArrayEntity(buffer.toByteArray())); + } + + ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "", + headers, buffer::toString); + } else { + ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "", + headers); + } + } + + @Override + public void process(HttpResponse response, HttpContext context) throws IOException { + + String logId = (String) context.getAttribute(LOG_ID_ATTRIBUTE); + + String headers = Arrays.stream(response.getAllHeaders()) + .map(header -> header.getName() + + ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue())) + .collect(Collectors.joining(", ", "[", "]")); + + // no way of logging the body, in this callback, it is not read yset, later there is no callback possibility in + // RestClient or RestClientTransport + ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()), headers); + } + } + + /** + * Interceptor to inject custom supplied headers. + * + * @since 4.4 + */ + private static class CustomHeaderInjector implements HttpRequestInterceptor { + + public CustomHeaderInjector(Supplier headersSupplier) { + this.headersSupplier = headersSupplier; + } + + private final Supplier headersSupplier; + + @Override + public void process(HttpRequest request, HttpContext context) { + HttpHeaders httpHeaders = headersSupplier.get(); + + if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) { + Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader); + } + } + } + + /** + * {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure + * the RestClient with a {@link HttpAsyncClientBuilder} + * + * @since 4.4 + */ + public interface ElasticsearchClientConfigurationCallback + extends ClientConfiguration.ClientConfigurationCallback { + + static ElasticsearchClientConfigurationCallback from( + Function clientBuilderCallback) { + + Assert.notNull(clientBuilderCallback, "clientBuilderCallback must not be null"); + + // noinspection NullableProblems + return clientBuilderCallback::apply; + } + } + + /** + * {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure + * the ReactiveElasticsearchClient with a {@link WebClient} + * + * @since 4.4 + */ + public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback { + + static WebClientConfigurationCallback from(Function webClientCallback) { + + Assert.notNull(webClientCallback, "webClientCallback must not be null"); + + // noinspection NullableProblems + return webClientCallback::apply; + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java new file mode 100644 index 000000000..c868bb3b8 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.transport.rest_client.RestClientOptions; + +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.springframework.context.annotation.Bean; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.util.Assert; + +/** + * Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch + * connection using the Elasticsearch Client. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurationSupport { + + /** + * Must be implemented by deriving classes to provide the {@link ClientConfiguration}. + * + * @return configuration, must not be {@literal null} + */ + @Bean + public abstract ClientConfiguration clientConfiguration(); + + /** + * Provides the underlying low level RestClient. + * + * @param clientConfiguration configuration for the client, must not be {@literal null} + * @return RestClient + */ + @Bean + public RestClient restClient(ClientConfiguration clientConfiguration) { + + Assert.notNull(clientConfiguration, "clientConfiguration must not be null"); + + return ElasticsearchClients.getRestClient(clientConfiguration); + } + + /** + * Provides the {@link ElasticsearchClient} to be used. + * + * @param restClient the low level RestClient to use + * @return ElasticsearchClient instance + */ + @Bean + public ElasticsearchClient elasticsearchClient(RestClient restClient) { + + Assert.notNull(restClient, "restClient must not be null"); + + return ElasticsearchClients.createImperative(restClient, transportOptions()); + } + + /** + * Creates a {@link ElasticsearchOperations} implementation using an + * {@link co.elastic.clients.elasticsearch.ElasticsearchClient}. + * + * @return never {@literal null}. + */ + @Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" }) + public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter, + ElasticsearchClient elasticsearchClient) { + + ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter); + template.setRefreshPolicy(refreshPolicy()); + + return template; + } + + /** + * @return the options that should be added to every request. Must not be {@literal null} + */ + public TransportOptions transportOptions() { + return new RestClientOptions(RequestOptions.DEFAULT); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java new file mode 100644 index 000000000..1d1ebad2f --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java @@ -0,0 +1,108 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.ElasticsearchException; +import co.elastic.clients.elasticsearch._types.ErrorResponse; +import co.elastic.clients.json.JsonpMapper; + +import java.io.IOException; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.elasticsearch.RestStatusException; +import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; + +/** + * Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an + * appropriate exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is + * appropriate: any other exception may have resulted from user code, and should not be translated. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator { + + private final JsonpMapper jsonpMapper; + + public ElasticsearchExceptionTranslator(JsonpMapper jsonpMapper) { + this.jsonpMapper = jsonpMapper; + } + + /** + * translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a + * RuntimeException + * + * @param throwable the Exception to map + * @return the potentially translated RuntimeException. + */ + public RuntimeException translateException(Throwable throwable) { + + RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable + : new RuntimeException(throwable.getMessage(), throwable); + RuntimeException potentiallyTranslatedException = translateExceptionIfPossible(runtimeException); + + return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException; + } + + @Override + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + + if (isSeqNoConflict(ex)) { + return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex); + } + + // todo #1973 index unavailable? + + if (ex instanceof ElasticsearchException) { + ElasticsearchException elasticsearchException = (ElasticsearchException) ex; + + ErrorResponse response = elasticsearchException.response(); + String body = JsonUtils.toJson(response, jsonpMapper); + + return new UncategorizedElasticsearchException(ex.getMessage(), response.status(), body, ex); + } + + Throwable cause = ex.getCause(); + if (cause instanceof IOException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + + return null; + } + + private boolean isSeqNoConflict(Exception exception) { + // todo #1973 check if this works + Integer status = null; + String message = null; + + if (exception instanceof RestStatusException) { + + RestStatusException statusException = (RestStatusException) exception; + status = statusException.getStatus(); + message = statusException.getMessage(); + } + + if (status != null && message != null) { + return status == 409 && message.contains("type=version_conflict_engine_exception") + && message.contains("version conflict, required seqNo"); + } + + return false; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java new file mode 100644 index 000000000..f40abc42c --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java @@ -0,0 +1,496 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.Time; +import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; +import co.elastic.clients.json.JsonpMapper; +import co.elastic.clients.transport.Version; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.data.elasticsearch.BulkFailureException; +import org.springframework.data.elasticsearch.client.UnsupportedBackendOperation; +import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.IndexedObjectInformation; +import org.springframework.data.elasticsearch.core.MultiGetItem; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.SearchScrollHits; +import org.springframework.data.elasticsearch.core.cluster.ClusterOperations; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.BulkOptions; +import org.springframework.data.elasticsearch.core.query.ByQueryResponse; +import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.UpdateQuery; +import org.springframework.data.elasticsearch.core.query.UpdateResponse; +import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; +import org.springframework.data.elasticsearch.core.reindex.ReindexResponse; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Implementation of {@link org.springframework.data.elasticsearch.core.ElasticsearchOperations} using the new + * Elasticsearch client. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { + + private final ElasticsearchClient client; + private final RequestConverter requestConverter; + private final ResponseConverter responseConverter; + private final JsonpMapper jsonpMapper; + private final ElasticsearchExceptionTranslator exceptionTranslator; + + // region _initialization + public ElasticsearchTemplate(ElasticsearchClient client, ElasticsearchConverter elasticsearchConverter) { + super(elasticsearchConverter); + + Assert.notNull(client, "client must not be null"); + + this.client = client; + this.jsonpMapper = client._transport().jsonpMapper(); + requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper); + responseConverter = new ResponseConverter(jsonpMapper); + exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper); + } + + @Override + protected AbstractElasticsearchTemplate doCopy() { + return new ElasticsearchTemplate(client, elasticsearchConverter); + } + // endregion + + // region child templates + @Override + public IndexOperations indexOps(Class clazz) { + return new IndicesTemplate(client.indices(), elasticsearchConverter, clazz); + } + + @Override + public IndexOperations indexOps(IndexCoordinates index) { + return new IndicesTemplate(client.indices(), elasticsearchConverter, index); + } + + @Override + public ClusterOperations cluster() { + return new ClusterTemplate(client.cluster(), elasticsearchConverter); + } + // endregion + + // region document operations + @Override + @Nullable + public T get(String id, Class clazz, IndexCoordinates index) { + + GetRequest getRequest = requestConverter.documentGetRequest(elasticsearchConverter.convertId(id), + routingResolver.getRouting(), index, false); + GetResponse getResponse = execute(client -> client.get(getRequest, EntityAsMap.class)); + + ReadDocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + return callback.doWith(DocumentAdapters.from(getResponse)); + } + + @Override + public List> multiGet(Query query, Class clazz, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(clazz, "clazz must not be null"); + + MgetRequest request = requestConverter.documentMgetRequest(query, clazz, index); + MgetResponse result = execute(client -> client.mget(request, EntityAsMap.class)); + + ReadDocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + + return DocumentAdapters.from(result).stream() // + .map(multiGetItem -> MultiGetItem.of( // + multiGetItem.isFailed() ? null : callback.doWith(multiGetItem.getItem()), multiGetItem.getFailure())) // + .collect(Collectors.toList()); + } + + @Override + public void bulkUpdate(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public ByQueryResponse delete(Query query, Class clazz, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + + DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, clazz, index, + getRefreshPolicy()); + + DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request)); + + return responseConverter.byQueryResponse(response); + } + + @Override + public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public ByQueryResponse updateByQuery(UpdateQuery updateQuery, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public String doIndex(IndexQuery query, IndexCoordinates indexCoordinates) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + IndexRequest indexRequest = requestConverter.documentIndexRequest(query, indexCoordinates, refreshPolicy); + + IndexResponse indexResponse = execute(client -> client.index(indexRequest)); + + Object queryObject = query.getObject(); + + if (queryObject != null) { + query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.id(), + indexResponse.seqNo(), indexResponse.primaryTerm(), indexResponse.version()))); + } + + return indexResponse.id(); + } + + @Override + protected boolean doExists(String id, IndexCoordinates index) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(index, "index must not be null"); + + GetRequest request = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, true); + + return execute(client -> client.get(request, EntityAsMap.class)).found(); + } + + @Override + protected String doDelete(String id, @Nullable String routing, IndexCoordinates index) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(index, "index must not be null"); + + DeleteRequest request = requestConverter.documentDeleteRequest(elasticsearchConverter.convertId(id), routing, index, + getRefreshPolicy()); + return execute(client -> client.delete(request)).id(); + } + + @Override + public ReindexResponse reindex(ReindexRequest reindexRequest) { + + Assert.notNull(reindexRequest, "reindexRequest must not be null"); + + co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest, + true); + co.elastic.clients.elasticsearch.core.ReindexResponse reindexResponse = execute( + client -> client.reindex(reindexRequestES)); + return responseConverter.reindexResponse(reindexResponse); + } + + @Override + public String submitReindex(ReindexRequest reindexRequest) { + + co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest, + false); + co.elastic.clients.elasticsearch.core.ReindexResponse reindexResponse = execute( + client -> client.reindex(reindexRequestES)); + + if (reindexResponse.task() == null) { + // todo #1973 check behaviour and create issue in ES if necessary + throw new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request"); + } + + return reindexResponse.task(); + } + + @Override + public List doBulkOperation(List queries, BulkOptions bulkOptions, + IndexCoordinates index) { + + BulkRequest bulkRequest = requestConverter.documentBulkRequest(queries, bulkOptions, index, refreshPolicy); + BulkResponse bulkResponse = execute(client -> client.bulk(bulkRequest)); + List indexedObjectInformationList = checkForBulkOperationFailure(bulkResponse); + updateIndexedObjectsWithQueries(queries, indexedObjectInformationList); + return indexedObjectInformationList; + } + + // endregion + + @Override + protected String getClusterVersion() { + return execute(client -> client.info().version().number()); + + } + + @Override + protected String getVendor() { + return "Elasticsearch"; + } + + @Override + protected String getRuntimeLibraryVersion() { + return Version.VERSION.toString(); + } + + // region search operations + @Override + 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"); + + SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, true, false); + + SearchResponse searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class)); + + return searchResponse.hits().total().value(); + } + + @Override + public SearchHits search(Query query, Class clazz, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(index, "index must not be null"); + + SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false, false); + SearchResponse searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class)); + + ReadDocumentCallback readDocumentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + SearchDocumentResponse.EntityCreator entityCreator = getEntityCreator(readDocumentCallback); + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); + + return callback.doWith(SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper)); + } + + @Override + protected SearchHits doSearch(MoreLikeThisQuery query, Class clazz, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(clazz, "clazz must not be null"); + Assert.notNull(index, "index must not be null"); + + return search(NativeQuery.builder() // + .withQuery(q -> q.moreLikeThis(requestConverter.moreLikeThisQuery(query, index)))// + .withPageable(query.getPageable()) // + .build(), clazz, index); + } + + @Override + protected SearchScrollHits searchScrollStart(long scrollTimeInMillis, Query query, Class clazz, + IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(query.getPageable(), "pageable of query must not be null."); + + SearchRequest request = requestConverter.searchRequest(query, clazz, index, false, scrollTimeInMillis); + SearchResponse response = execute(client -> client.search(request, EntityAsMap.class)); + + return getSearchScrollHits(clazz, index, response); + } + + @Override + protected SearchScrollHits searchScrollContinue(String scrollId, long scrollTimeInMillis, Class clazz, + IndexCoordinates index) { + + Assert.notNull(scrollId, "scrollId must not be null"); + + ScrollRequest request = ScrollRequest + .of(sr -> sr.scrollId(scrollId).scroll(Time.of(t -> t.time(scrollTimeInMillis + "ms")))); + ScrollResponse response = execute(client -> client.scroll(request, EntityAsMap.class)); + + return getSearchScrollHits(clazz, index, response); + } + + private > SearchScrollHits getSearchScrollHits(Class clazz, + IndexCoordinates index, R response) { + ReadDocumentCallback documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); + SearchDocumentResponseCallback> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz, + index); + + return callback + .doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback), jsonpMapper)); + } + + @Override + protected void searchScrollClear(List scrollIds) { + + Assert.notNull(scrollIds, "scrollIds must not be null"); + + if (!scrollIds.isEmpty()) { + ClearScrollRequest request = ClearScrollRequest.of(csr -> csr.scrollId(scrollIds)); + execute(client -> client.clearScroll(request)); + } + } + + @Override + public List> multiSearch(List queries, Class clazz, IndexCoordinates index) { + + Assert.notNull(queries, "queries must not be null"); + Assert.notNull(clazz, "clazz must not be null"); + + List multiSearchQueryParameters = new ArrayList<>(queries.size()); + for (Query query : queries) { + multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz))); + } + + // noinspection unchecked + return doMultiSearch(multiSearchQueryParameters).stream().map(searchHits -> (SearchHits) searchHits) + .collect(Collectors.toList()); + } + + @Override + public List> multiSearch(List queries, List> classes) { + + Assert.notNull(queries, "queries must not be null"); + Assert.notNull(classes, "classes must not be null"); + Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size"); + + List multiSearchQueryParameters = new ArrayList<>(queries.size()); + Iterator> it = classes.iterator(); + for (Query query : queries) { + Class clazz = it.next(); + multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz))); + } + + return doMultiSearch(multiSearchQueryParameters); + } + + @Override + public List> multiSearch(List queries, List> classes, + IndexCoordinates index) { + + Assert.notNull(queries, "queries must not be null"); + Assert.notNull(classes, "classes must not be null"); + Assert.notNull(index, "index must not be null"); + Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size"); + + List multiSearchQueryParameters = new ArrayList<>(queries.size()); + Iterator> it = classes.iterator(); + for (Query query : queries) { + Class clazz = it.next(); + multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index)); + } + + return doMultiSearch(multiSearchQueryParameters); + } + + private List> doMultiSearch(List multiSearchQueryParameters) { + throw new UnsupportedOperationException("not implemented"); + } + + /** + * value class combining the information needed for a single query in a multisearch request. + */ + private static class MultiSearchQueryParameter { + final Query query; + final Class clazz; + final IndexCoordinates index; + + public MultiSearchQueryParameter(Query query, Class clazz, IndexCoordinates index) { + this.query = query; + this.clazz = clazz; + this.index = index; + } + } + // endregion + + // region client callback + /** + * Callback interface to be used with {@link #execute(ElasticsearchTemplate.ClientCallback)} for operating directly on + * the {@link ElasticsearchClient}. + */ + @FunctionalInterface + public interface ClientCallback { + T doWithClient(ElasticsearchClient client) throws IOException; + } + + /** + * Execute a callback with the {@link ElasticsearchClient} and provide exception translation. + * + * @param callback the callback to execute, must not be {@literal null} + * @param the type returned from the callback + * @return the callback result + */ + public T execute(ElasticsearchTemplate.ClientCallback callback) { + + Assert.notNull(callback, "callback must not be null"); + + try { + return callback.doWithClient(client); + } catch (IOException | RuntimeException e) { + throw exceptionTranslator.translateException(e); + } + } + // endregion + + // region helper methods + @Override + public Query matchAllQuery() { + return NativeQuery.builder().withQuery(qb -> qb.matchAll(mab -> mab)).build(); + } + + @Override + public Query idsQuery(List ids) { + return NativeQuery.builder().withQuery(qb -> qb.ids(iq -> iq.values(ids))).build(); + } + + /** + * extract the list of {@link IndexedObjectInformation} from a {@link BulkResponse}. + * + * @param bulkResponse the response to evaluate + * @return the list of the {@link IndexedObjectInformation}s + */ + protected List checkForBulkOperationFailure(BulkResponse bulkResponse) { + + if (bulkResponse.errors()) { + Map failedDocuments = new HashMap<>(); + for (BulkResponseItem item : bulkResponse.items()) { + + if (item.error() != null) { + failedDocuments.put(item.id(), item.error().reason()); + } + } + throw new BulkFailureException( + "Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages [" + + failedDocuments + ']', + failedDocuments); + } + + return bulkResponse.items().stream() + .map(item -> IndexedObjectInformation.of(item.id(), item.seqNo(), item.primaryTerm(), item.version())) + .collect(Collectors.toList()); + + } + // endregion + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/EntityAsMap.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/EntityAsMap.java new file mode 100644 index 000000000..eb9f9c524 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/EntityAsMap.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; + +/** + * A Map<String,Object> to represent any entity as it's returned from Elasticsearch and before it is converted to a + * {@link org.springframework.data.elasticsearch.core.document.Document}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class EntityAsMap extends DefaultStringObjectMap {} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java new file mode 100644 index 000000000..3e8c72b5b --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/HighlightQueryBuilder.java @@ -0,0 +1,236 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.elasticsearch.core.query.highlight.Highlight; +import org.springframework.data.elasticsearch.core.query.highlight.HighlightField; +import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters; +import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +/** + * Converts the {@link Highlight} annotation from a method to an ElasticsearchClient + * {@link co.elastic.clients.elasticsearch.core.search.Highlight}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +class HighlightQueryBuilder { + private final MappingContext, ElasticsearchPersistentProperty> mappingContext; + + HighlightQueryBuilder( + MappingContext, ElasticsearchPersistentProperty> mappingContext) { + this.mappingContext = mappingContext; + } + + public co.elastic.clients.elasticsearch.core.search.Highlight getHighlight(Highlight highlight, + @Nullable Class type) { + + co.elastic.clients.elasticsearch.core.search.Highlight.Builder highlightBuilder = new co.elastic.clients.elasticsearch.core.search.Highlight.Builder(); + + // in the old implementation we could use one addParameters method, but in the new Elasticsearch client + // the builder for highlight and highlightfield share no code + addParameters(highlight.getParameters(), highlightBuilder); + + for (HighlightField highlightField : highlight.getFields()) { + String mappedName = mapFieldName(highlightField.getName(), type); + highlightBuilder.fields(mappedName, hf -> { + addParameters(highlightField.getParameters(), hf, type); + return hf; + }); + } + + return highlightBuilder.build(); + } + + /* + * the builder for highlight and highlight fields don't share code, so we have these two methods here that basically are almost copies + */ + private void addParameters(HighlightParameters parameters, + co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder) { + + if (StringUtils.hasLength(parameters.getBoundaryChars())) { + builder.boundaryChars(parameters.getBoundaryChars()); + } + + if (parameters.getBoundaryMaxScan() > -1) { + builder.boundaryMaxScan(parameters.getBoundaryMaxScan()); + } + + if (StringUtils.hasLength(parameters.getBoundaryScanner())) { + builder.boundaryScanner(boundaryScanner(parameters.getBoundaryScanner())); + } + + if (StringUtils.hasLength(parameters.getBoundaryScannerLocale())) { + builder.boundaryScannerLocale(parameters.getBoundaryScannerLocale()); + } + + if (parameters.getForceSource()) { // default is false + // todo #1973 parameter missing in new client + } + + if (StringUtils.hasLength(parameters.getFragmenter())) { + builder.fragmenter(highlighterFragmenter(parameters.getFragmenter())); + } + + if (parameters.getFragmentSize() > -1) { + builder.fragmentSize(parameters.getFragmentSize()); + } + + if (parameters.getNoMatchSize() > -1) { + builder.noMatchSize(parameters.getNoMatchSize()); + } + + if (parameters.getNumberOfFragments() > -1) { + builder.numberOfFragments(parameters.getNumberOfFragments()); + } + + if (StringUtils.hasLength(parameters.getOrder())) { + builder.order(highlighterOrder(parameters.getOrder())); + } + + if (parameters.getPhraseLimit() > -1) { + // todo #1973 parameter missing in new client + } + + if (parameters.getPreTags().length > 0) { + builder.preTags(Arrays.asList(parameters.getPreTags())); + } + + if (parameters.getPostTags().length > 0) { + builder.postTags(Arrays.asList(parameters.getPostTags())); + } + + if (!parameters.getRequireFieldMatch()) { // default is true + builder.requireFieldMatch(false); + } + + if (StringUtils.hasLength(parameters.getType())) { + builder.type(highlighterType(parameters.getType())); + } + + if (StringUtils.hasLength(parameters.getEncoder())) { + builder.encoder(highlighterEncoder(parameters.getEncoder())); + } + + if (StringUtils.hasLength(parameters.getTagsSchema())) { + builder.tagsSchema(highlighterTagsSchema(parameters.getTagsSchema())); + } + } + + /* + * the builder for highlight and highlight fields don't share code, so we have these two methods here that basically are almost copies + */ + private void addParameters(HighlightFieldParameters parameters, + co.elastic.clients.elasticsearch.core.search.HighlightField.Builder builder, Class type) { + + if (StringUtils.hasLength(parameters.getBoundaryChars())) { + builder.boundaryChars(parameters.getBoundaryChars()); + } + + if (parameters.getBoundaryMaxScan() > -1) { + builder.boundaryMaxScan(parameters.getBoundaryMaxScan()); + } + + if (StringUtils.hasLength(parameters.getBoundaryScanner())) { + builder.boundaryScanner(boundaryScanner(parameters.getBoundaryScanner())); + } + + if (StringUtils.hasLength(parameters.getBoundaryScannerLocale())) { + builder.boundaryScannerLocale(parameters.getBoundaryScannerLocale()); + } + + if (parameters.getForceSource()) { // default is false + builder.forceSource(parameters.getForceSource()); + } + + if (StringUtils.hasLength(parameters.getFragmenter())) { + builder.fragmenter(highlighterFragmenter(parameters.getFragmenter())); + } + + if (parameters.getFragmentSize() > -1) { + builder.fragmentSize(parameters.getFragmentSize()); + } + + if (parameters.getNoMatchSize() > -1) { + builder.noMatchSize(parameters.getNoMatchSize()); + } + + if (parameters.getNumberOfFragments() > -1) { + builder.numberOfFragments(parameters.getNumberOfFragments()); + } + + if (StringUtils.hasLength(parameters.getOrder())) { + builder.order(highlighterOrder(parameters.getOrder())); + } + + if (parameters.getPhraseLimit() > -1) { + builder.phraseLimit(parameters.getPhraseLimit()); + } + + if (parameters.getPreTags().length > 0) { + builder.preTags(Arrays.asList(parameters.getPreTags())); + } + + if (parameters.getPostTags().length > 0) { + builder.postTags(Arrays.asList(parameters.getPostTags())); + } + + if (!parameters.getRequireFieldMatch()) { // default is true + builder.requireFieldMatch(false); + } + + if (StringUtils.hasLength(parameters.getType())) { + builder.type(highlighterType(parameters.getType())); + } + + if ((parameters).getFragmentOffset() > -1) { + builder.fragmentOffset(parameters.getFragmentOffset()); + } + + if (parameters.getMatchedFields().length > 0) { + builder.matchedFields(Arrays.stream(parameters.getMatchedFields()).map(fieldName -> mapFieldName(fieldName, type)) // + .collect(Collectors.toList())); + } + } + + private String mapFieldName(String fieldName, @Nullable Class type) { + + if (type != null) { + ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(type); + + if (persistentEntity != null) { + ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName); + + if (persistentProperty != null) { + return persistentProperty.getFieldName(); + } + } + } + + return fieldName; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java new file mode 100644 index 000000000..24af22171 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java @@ -0,0 +1,354 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.springframework.util.StringUtils.*; + +import co.elastic.clients.elasticsearch.indices.*; +import co.elastic.clients.transport.endpoints.BooleanResponse; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.core.IndexInformation; +import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.ResourceUtil; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.AliasActions; +import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; +import org.springframework.data.elasticsearch.core.index.MappingBuilder; +import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; +import org.springframework.data.elasticsearch.core.index.Settings; +import org.springframework.data.elasticsearch.core.index.TemplateData; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Implementation of the {@link IndexOperations} interface using en {@link ElasticsearchIndicesClient}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class IndicesTemplate extends ChildTemplate implements IndexOperations { + + private static final Logger LOGGER = LoggerFactory.getLogger(IndicesTemplate.class); + + protected final ElasticsearchConverter elasticsearchConverter; + @Nullable protected final Class boundClass; + @Nullable protected final IndexCoordinates boundIndex; + + public IndicesTemplate(ElasticsearchIndicesClient client, ElasticsearchConverter elasticsearchConverter, + Class boundClass) { + super(client, elasticsearchConverter); + + Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null"); + Assert.notNull(boundClass, "boundClass may not be null"); + + this.elasticsearchConverter = elasticsearchConverter; + this.boundClass = boundClass; + this.boundIndex = null; + + } + + public IndicesTemplate(ElasticsearchIndicesClient client, ElasticsearchConverter elasticsearchConverter, + IndexCoordinates boundIndex) { + super(client, elasticsearchConverter); + + Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null"); + Assert.notNull(boundIndex, "boundIndex must not be null"); + + this.elasticsearchConverter = elasticsearchConverter; + this.boundClass = null; + this.boundIndex = boundIndex; + + } + + protected Class checkForBoundClass() { + if (boundClass == null) { + throw new InvalidDataAccessApiUsageException("IndexOperations are not bound"); + } + return boundClass; + } + + @Override + public boolean create() { + + Settings settings = boundClass != null ? createSettings(boundClass) : new Settings(); + return doCreate(getIndexCoordinates(), settings, null); + } + + @Override + public boolean create(Map settings) { + + Assert.notNull(settings, "settings must not be null"); + + return doCreate(getIndexCoordinates(), settings, null); + } + + @Override + public boolean create(Map settings, Document mapping) { + + Assert.notNull(settings, "settings must not be null"); + Assert.notNull(mapping, "mapping must not be null"); + + return doCreate(getIndexCoordinates(), settings, mapping); + } + + @Override + public boolean createWithMapping() { + return doCreate(getIndexCoordinates(), createSettings(), createMapping()); + } + + protected boolean doCreate(IndexCoordinates indexCoordinates, Map settings, + @Nullable Document mapping) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + Assert.notNull(settings, "settings must not be null"); + + CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping); + CreateIndexResponse createIndexResponse = execute(client -> client.create(createIndexRequest)); + return Boolean.TRUE.equals(createIndexResponse.acknowledged()); + } + + @Override + public boolean delete() { + return doDelete(getIndexCoordinates()); + } + + private boolean doDelete(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + if (doExists(indexCoordinates)) { + DeleteIndexRequest deleteIndexRequest = requestConverter.indicesDeleteRequest(indexCoordinates); + DeleteIndexResponse deleteIndexResponse = execute(client -> client.delete(deleteIndexRequest)); + return deleteIndexResponse.acknowledged(); + } + + return false; + } + + @Override + public boolean exists() { + return doExists(getIndexCoordinates()); + } + + private boolean doExists(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + ExistsRequest existsRequest = requestConverter.indicesExistsRequest(indexCoordinates); + BooleanResponse existsResponse = execute(client -> client.exists(existsRequest)); + return existsResponse.value(); + } + + @Override + public void refresh() { + + RefreshRequest refreshRequest = requestConverter.indicesRefreshRequest(getIndexCoordinates()); + execute(client -> client.refresh(refreshRequest)); + } + + @Override + public Document createMapping() { + return createMapping(checkForBoundClass()); + } + + @Override + public Document createMapping(Class clazz) { + + Assert.notNull(clazz, "clazz must not be null"); + + // load mapping specified in Mapping annotation if present + // noinspection DuplicatedCode + Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); + + if (mappingAnnotation != null) { + String mappingPath = mappingAnnotation.mappingPath(); + + if (hasText(mappingPath)) { + String mappings = ResourceUtil.readFileFromClasspath(mappingPath); + + if (hasText(mappings)) { + return Document.parse(mappings); + } + } + } + + // build mapping from field annotations + try { + String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz); + return Document.parse(mapping); + } catch (Exception e) { + throw new UncategorizedElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e); + } + } + + @Override + public boolean putMapping(Document mapping) { + + Assert.notNull(mapping, "mapping must not be null"); + + PutMappingRequest putMappingRequest = requestConverter.indicesPutMappingRequest(getIndexCoordinates(), mapping); + PutMappingResponse putMappingResponse = execute(client -> client.putMapping(putMappingRequest)); + return putMappingResponse.acknowledged(); + } + + @Override + public Map getMapping() { + + IndexCoordinates indexCoordinates = getIndexCoordinates(); + GetMappingRequest getMappingRequest = requestConverter.indicesGetMappingRequest(indexCoordinates); + GetMappingResponse getMappingResponse = execute(client -> client.getMapping(getMappingRequest)); + + Document mappingResponse = responseConverter.indicesGetMapping(getMappingResponse, indexCoordinates); + return mappingResponse; + } + + @Override + public Settings createSettings() { + return createSettings(checkForBoundClass()); + } + + @Override + public Settings createSettings(Class clazz) { + + Assert.notNull(clazz, "clazz must not be null"); + + ElasticsearchPersistentEntity persistentEntity = getRequiredPersistentEntity(clazz); + String settingPath = persistentEntity.settingPath(); + return hasText(settingPath) // + ? Settings.parse(ResourceUtil.readFileFromClasspath(settingPath)) // + : persistentEntity.getDefaultSettings(); + + } + + @Override + public Settings getSettings() { + return getSettings(false); + } + + @Override + public Settings getSettings(boolean includeDefaults) { + + GetIndicesSettingsRequest getIndicesSettingsRequest = requestConverter + .indicesGetSettingsRequest(getIndexCoordinates(), includeDefaults); + GetIndicesSettingsResponse getIndicesSettingsResponse = execute( + client -> client.getSettings(getIndicesSettingsRequest)); + return responseConverter.indicesGetSettings(getIndicesSettingsResponse, getIndexCoordinates().getIndexName()); + } + + @Override + public boolean alias(AliasActions aliasActions) { + + Assert.notNull(aliasActions, "aliasActions must not be null"); + + UpdateAliasesRequest updateAliasesRequest = requestConverter.indicesUpdateAliasesRequest(aliasActions); + UpdateAliasesResponse updateAliasesResponse = execute(client -> client.updateAliases(updateAliasesRequest)); + return updateAliasesResponse.acknowledged(); + } + + @Override + public Map> getAliases(String... aliasNames) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Map> getAliasesForIndex(String... indexNames) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean putTemplate(PutTemplateRequest putTemplateRequest) { + + Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.PutTemplateRequest putTemplateRequestES = requestConverter + .indicesPutTemplateRequest(putTemplateRequest); + return execute(client -> client.putTemplate(putTemplateRequestES)).acknowledged(); + } + + @Override + public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) { + + Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.GetTemplateRequest getTemplateRequestES = requestConverter + .indicesGetTemplateRequest(getTemplateRequest); + GetTemplateResponse getTemplateResponse = execute(client -> client.getTemplate(getTemplateRequestES)); + + return responseConverter.indicesGetTemplateData(getTemplateResponse, getTemplateRequest.getTemplateName()); + } + + @Override + public boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest) { + + Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.ExistsTemplateRequest existsTemplateRequestSO = requestConverter + .indicesExistsTemplateRequest(existsTemplateRequest); + return execute(client -> client.existsTemplate(existsTemplateRequestSO)).value(); + } + + @Override + public boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) { + + Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.DeleteTemplateRequest deleteTemplateRequestES = requestConverter + .indicesDeleteTemplateRequest(deleteTemplateRequest); + return execute(client -> client.deleteTemplate(deleteTemplateRequestES)).acknowledged(); + } + + @Override + public List getInformation(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + GetIndexRequest getIndexRequest = requestConverter.indicesGetIndexRequest(indexCoordinates); + GetIndexResponse getIndexResponse = execute(client -> client.get(getIndexRequest)); + return responseConverter.indicesGetIndexInformations(getIndexResponse); + } + + // region Helper functions + ElasticsearchPersistentEntity getRequiredPersistentEntity(Class clazz) { + return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz); + } + + @Override + public IndexCoordinates getIndexCoordinates() { + return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : Objects.requireNonNull(boundIndex); + } + + public IndexCoordinates getIndexCoordinatesFor(Class clazz) { + return getRequiredPersistentEntity(clazz).getIndexCoordinates(); + } + // endregion +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/JsonUtils.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/JsonUtils.java new file mode 100644 index 000000000..32c6788d9 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/JsonUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.json.JsonpMapper; +import jakarta.json.stream.JsonGenerator; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +final class JsonUtils { + + private static final Log LOGGER = LogFactory.getLog(JsonUtils.class); + + private JsonUtils() {} + + public static String toJson(Object object, JsonpMapper mapper) { + + // noinspection SpellCheckingInspection + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + JsonGenerator generator = mapper.jsonProvider().createGenerator(baos); + mapper.serialize(object, generator); + generator.close(); + String jsonMapping = "{}"; + try { + jsonMapping = baos.toString("UTF-8"); + } catch (UnsupportedEncodingException e) { + LOGGER.warn("could not read json", e); + } + + return jsonMapping; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQuery.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQuery.java new file mode 100644 index 000000000..6f97ab7c6 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQuery.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.aggregations.Aggregation; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch.core.search.Suggester; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.data.elasticsearch.core.query.BaseQuery; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A {@link org.springframework.data.elasticsearch.core.query.Query} implementation using query builders from the new + * Elasticsearch Client library. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class NativeQuery extends BaseQuery { + + @Nullable private final Query query; + // note: the new client does not have pipeline aggs, these are just set up as normal aggs + private final Map aggregations = new LinkedHashMap<>(); + @Nullable private Suggester suggester; + + public NativeQuery(NativeQueryBuilder builder) { + super(builder); + this.query = builder.getQuery(); + } + + public NativeQuery(@Nullable Query query) { + this.query = query; + } + + public static NativeQueryBuilder builder() { + return new NativeQueryBuilder(); + } + + @Nullable + public Query getQuery() { + return query; + } + + public void addAggregation(String name, Aggregation aggregation) { + + Assert.notNull(name, "name must not be null"); + Assert.notNull(aggregation, "aggregation must not be null"); + + aggregations.put(name, aggregation); + } + + public void setAggregations(Map aggregations) { + + Assert.notNull(aggregations, "aggregations must not be null"); + + this.aggregations.clear(); + this.aggregations.putAll(aggregations); + } + + public Map getAggregations() { + return aggregations; + } + + @Nullable + public Suggester getSuggester() { + return suggester; + } + + public void setSuggester(@Nullable Suggester suggester) { + this.suggester = suggester; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQueryBuilder.java new file mode 100644 index 000000000..2152d1ff7 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/NativeQueryBuilder.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.aggregations.Aggregation; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch.core.search.Suggester; +import co.elastic.clients.util.ObjectBuilder; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class NativeQueryBuilder extends BaseQueryBuilder { + + @Nullable private Query query; + private final Map aggregations = new LinkedHashMap<>(); + @Nullable private Suggester suggester; + + public NativeQueryBuilder() { + } + + @Nullable + public Query getQuery() { + return query; + } + + public NativeQueryBuilder withQuery(Query query) { + + Assert.notNull(query, "query must not be null"); + + this.query = query; + return this; + } + + public NativeQueryBuilder withQuery(Function> fn) { + + Assert.notNull(fn, "fn must not be null"); + + return withQuery(fn.apply(new Query.Builder()).build()); + } + + public NativeQueryBuilder withAggregation(String name, Aggregation aggregation) { + + Assert.notNull(name, "name must not be null"); + Assert.notNull(aggregation, "aggregation must not be null"); + + this.aggregations.put(name, aggregation); + return this; + } + + public NativeQueryBuilder withSuggester(@Nullable Suggester suggester) { + this.suggester = suggester; + return this; + } + + public NativeQuery build() { + NativeQuery nativeQuery = new NativeQuery(this); + nativeQuery.setAggregations(aggregations); + nativeQuery.setSuggester(suggester); + + return nativeQuery; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/QueryBuilders.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/QueryBuilders.java new file mode 100644 index 000000000..2bc28a116 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/QueryBuilders.java @@ -0,0 +1,154 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.FieldValue; +import co.elastic.clients.elasticsearch._types.LatLonGeoLocation; +import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Operator; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryStringQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.WrapperQuery; +import co.elastic.clients.util.ObjectBuilder; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.function.Function; + +import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Utility class simplifying the creation of some more complex queries and type. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public final class QueryBuilders { + + private QueryBuilders() {} + + public static MatchQuery matchQuery(String fieldName, String query, @Nullable Operator operator, + @Nullable Float boost) { + + Assert.notNull(fieldName, "fieldName must not be null"); + Assert.notNull(query, "query must not be null"); + + return MatchQuery.of(mb -> mb.field(fieldName).query(FieldValue.of(query)).operator(operator).boost(boost)); + } + + public static Query matchQueryAsQuery(String fieldName, String query, @Nullable Operator operator, + @Nullable Float boost) { + + Function> builder = b -> b.match(matchQuery(fieldName, query, operator, boost)); + + return builder.apply(new Query.Builder()).build(); + } + + public static MatchAllQuery matchAllQuery() { + + return MatchAllQuery.of(b -> b); + } + + public static Query matchAllQueryAsQuery() { + + Function> builder = b -> b.matchAll(matchAllQuery()); + + return builder.apply(new Query.Builder()).build(); + } + + public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Float boost) { + return queryStringQuery(fieldName, query, null, null, boost); + } + + public static QueryStringQuery queryStringQuery(String fieldName, String query, Operator defaultOperator, + @Nullable Float boost) { + return queryStringQuery(fieldName, query, null, defaultOperator, boost); + } + + public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard, + @Nullable Float boost) { + return queryStringQuery(fieldName, query, analyzeWildcard, null, boost); + } + + public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard, + @Nullable Operator defaultOperator, @Nullable Float boost) { + + Assert.notNull(fieldName, "fieldName must not be null"); + Assert.notNull(query, "query must not be null"); + + return QueryStringQuery.of(qs -> qs.fields(fieldName).query(query).analyzeWildcard(analyzeWildcard) + .defaultOperator(defaultOperator).boost(boost)); + } + + public static TermQuery termQuery(String fieldName, String value) { + + Assert.notNull(fieldName, "fieldName must not be null"); + Assert.notNull(value, "value must not be null"); + + return TermQuery.of(t -> t.field(fieldName).value(FieldValue.of(value))); + } + + public static Query termQueryAsQuery(String fieldName, String value) { + + Function> builder = q -> q.term(termQuery(fieldName, value)); + return builder.apply(new Query.Builder()).build(); + } + + public static WildcardQuery wildcardQuery(String field, String value) { + + Assert.notNull(field, "field must not be null"); + Assert.notNull(value, "value must not be null"); + + return WildcardQuery.of(w -> w.field(field).wildcard(value)); + } + + public static Query wildcardQueryAsQuery(String field, String value) { + Function> builder = q -> q.wildcard(wildcardQuery(field, value)); + return builder.apply(new Query.Builder()).build(); + } + + public static Query wrapperQueryAsQuery(String query) { + + Function> builder = q -> q.wrapper(wrapperQuery(query)); + + return builder.apply(new Query.Builder()).build(); + } + + public static WrapperQuery wrapperQuery(String query) { + + Assert.notNull(query, "query must not be null"); + + String encodedValue = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8)); + + return WrapperQuery.of(wq -> wq.query(encodedValue)); + } + + public static LatLonGeoLocation latLon(GeoPoint geoPoint) { + + Assert.notNull(geoPoint, "geoPoint must not be null"); + + return latLon(geoPoint.getLat(), geoPoint.getLon()); + } + + public static LatLonGeoLocation latLon(double lat, double lon) { + return LatLonGeoLocation.of(_0 -> _0.lat(lat).lon(lon)); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveChildTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveChildTemplate.java new file mode 100644 index 000000000..d0f9c919c --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveChildTemplate.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.ApiClient; +import co.elastic.clients.json.JsonpMapper; +import reactor.core.publisher.Flux; + +import org.reactivestreams.Publisher; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.util.Assert; + +/** + * base class for a reactive template that uses on of the {@link ReactiveElasticsearchClient}'s child clients. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ReactiveChildTemplate { + protected final CLIENT client; + protected final ElasticsearchConverter elasticsearchConverter; + protected final RequestConverter requestConverter; + protected final ResponseConverter responseConverter; + private final JsonpMapper jsonpMapper; + protected final ElasticsearchExceptionTranslator exceptionTranslator; + + public ReactiveChildTemplate(CLIENT client, ElasticsearchConverter elasticsearchConverter) { + this.client = client; + this.elasticsearchConverter = elasticsearchConverter; + jsonpMapper = client._transport().jsonpMapper(); + requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper); + responseConverter = new ResponseConverter(jsonpMapper); + exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper); + } + + /** + * Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on the client. + */ + @FunctionalInterface + public interface ClientCallback> { + RESULT doWithClient(CLIENT client); + } + + /** + * Execute a callback with the client and provide exception translation. + * + * @param callback the callback to execute, must not be {@literal null} + * @param the type returned from the callback + * @return the callback result + */ + public Publisher execute(ClientCallback> callback) { + + Assert.notNull(callback, "callback must not be null"); + + return Flux.defer(() -> callback.doWithClient(client)).onErrorMap(exceptionTranslator::translateException); + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveClusterTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveClusterTemplate.java new file mode 100644 index 000000000..5fd04ed3b --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveClusterTemplate.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.cluster.HealthRequest; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; +import reactor.core.publisher.Mono; + +import org.springframework.data.elasticsearch.core.cluster.ClusterHealth; +import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ReactiveClusterTemplate extends ReactiveChildTemplate + implements ReactiveClusterOperations { + + public ReactiveClusterTemplate(ReactiveElasticsearchClusterClient client, + ElasticsearchConverter elasticsearchConverter) { + super(client, elasticsearchConverter); + } + + @Override + public Mono health() { + + HealthRequest healthRequest = requestConverter.clusterHealthRequest(); + Mono healthResponse = Mono.from(execute(client -> client.health(healthRequest))); + return healthResponse.map(responseConverter::clusterHealth); + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClient.java new file mode 100644 index 000000000..242ed3e7a --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClient.java @@ -0,0 +1,226 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.ApiClient; +import co.elastic.clients.elasticsearch._types.ErrorResponse; +import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.JsonEndpoint; +import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.transport.endpoints.EndpointWithResponseMapperAttr; +import co.elastic.clients.util.ObjectBuilder; +import reactor.core.publisher.Mono; + +import java.util.function.Function; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Reactive version of {@link co.elastic.clients.elasticsearch.ElasticsearchClient}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ReactiveElasticsearchClient extends ApiClient + implements AutoCloseable { + + public ReactiveElasticsearchClient(ElasticsearchTransport transport) { + super(transport, null); + } + + public ReactiveElasticsearchClient(ElasticsearchTransport transport, @Nullable TransportOptions transportOptions) { + super(transport, transportOptions); + } + + @Override + public ReactiveElasticsearchClient withTransportOptions(@Nullable TransportOptions transportOptions) { + return new ReactiveElasticsearchClient(transport, transportOptions); + } + + @Override + public void close() throws Exception { + transport.close(); + } + + // region child clients + + public ReactiveElasticsearchClusterClient cluster() { + return new ReactiveElasticsearchClusterClient(transport, transportOptions); + } + + public ReactiveElasticsearchIndicesClient indices() { + return new ReactiveElasticsearchIndicesClient(transport, transportOptions); + } + + // endregion + // region info + + public Mono info() { + return Mono + .fromFuture(transport.performRequestAsync(InfoRequest._INSTANCE, InfoRequest._ENDPOINT, transportOptions)); + } + + public Mono ping() { + return Mono + .fromFuture(transport.performRequestAsync(PingRequest._INSTANCE, PingRequest._ENDPOINT, transportOptions)); + } + + // endregion + // region document + + public Mono index(IndexRequest request) { + + Assert.notNull(request, "request must not be null"); + + return Mono.fromFuture(transport.performRequestAsync(request, IndexRequest._ENDPOINT, transportOptions)); + } + + public Mono index(Function, ObjectBuilder>> fn) { + + Assert.notNull(fn, "fn must not be null"); + + return index(fn.apply(new IndexRequest.Builder<>()).build()); + } + + public Mono bulk(BulkRequest request) { + + Assert.notNull(request, "request must not be null"); + + return Mono.fromFuture(transport.performRequestAsync(request, BulkRequest._ENDPOINT, transportOptions)); + } + + public Mono bulk(Function> fn) { + + Assert.notNull(fn, "fn must not be null"); + + return bulk(fn.apply(new BulkRequest.Builder()).build()); + } + + public Mono> get(GetRequest request, Class tClass) { + + Assert.notNull(request, "request must not be null"); + + // code adapted from + // co.elastic.clients.elasticsearch.ElasticsearchClient.get(co.elastic.clients.elasticsearch.core.GetRequest, + // java.lang.Class) + // noinspection unchecked + JsonEndpoint, ErrorResponse> endpoint = (JsonEndpoint, ErrorResponse>) GetRequest._ENDPOINT; + endpoint = new EndpointWithResponseMapperAttr<>(endpoint, "co.elastic.clients:Deserializer:_global.get.TDocument", + getDeserializer(tClass)); + + return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions)); + } + + public Mono> get(Function> fn, Class tClass) { + Assert.notNull(fn, "fn must not be null"); + + return get(fn.apply(new GetRequest.Builder()).build(), tClass); + } + + public Mono reindex(ReindexRequest request) { + + Assert.notNull(request, "request must not be null"); + + return Mono.fromFuture(transport.performRequestAsync(request, ReindexRequest._ENDPOINT, transportOptions)); + } + + public Mono reindex(Function> fn) { + + Assert.notNull(fn, "fn must not be null"); + + return reindex(fn.apply(new ReindexRequest.Builder()).build()); + } + + public Mono delete(DeleteRequest request) { + + Assert.notNull(request, "request must not be null"); + + return Mono.fromFuture(transport.performRequestAsync(request, DeleteRequest._ENDPOINT, transportOptions)); + } + + public Mono delete(Function> fn) { + + Assert.notNull(fn, "fn must not be null"); + + return delete(fn.apply(new DeleteRequest.Builder()).build()); + } + + // endregion + // region search + + public Mono> search(SearchRequest request, Class tDocumentClass) { + + Assert.notNull(request, "request must not be null"); + Assert.notNull(tDocumentClass, "tDocumentClass must not be null"); + + return Mono.fromFuture(transport.performRequestAsync(request, + SearchRequest.createSearchEndpoint(this.getDeserializer(tDocumentClass)), transportOptions)); + } + + public Mono> search(Function> fn, + Class tDocumentClass) { + + Assert.notNull(fn, "fn must not be null"); + Assert.notNull(tDocumentClass, "tDocumentClass must not be null"); + + return search(fn.apply(new SearchRequest.Builder()).build(), tDocumentClass); + } + + public Mono> scroll(ScrollRequest request, Class tDocumentClass) { + + Assert.notNull(request, "request must not be null"); + Assert.notNull(tDocumentClass, "tDocumentClass must not be null"); + + // code adapted from + // co.elastic.clients.elasticsearch.ElasticsearchClient.scroll(co.elastic.clients.elasticsearch.core.ScrollRequest, + // java.lang.Class) + // noinspection unchecked + JsonEndpoint, ErrorResponse> endpoint = (JsonEndpoint, ErrorResponse>) ScrollRequest._ENDPOINT; + endpoint = new EndpointWithResponseMapperAttr<>(endpoint, + "co.elastic.clients:Deserializer:_global.scroll.TDocument", getDeserializer(tDocumentClass)); + + return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions)); + } + + public Mono> scroll(Function> fn, + Class tDocumentClass) { + + Assert.notNull(fn, "fn must not be null"); + Assert.notNull(tDocumentClass, "tDocumentClass must not be null"); + + return scroll(fn.apply(new ScrollRequest.Builder()).build(), tDocumentClass); + } + + public Mono clearScroll(ClearScrollRequest request) { + + Assert.notNull(request, "request must not be null"); + + return Mono.fromFuture(transport.performRequestAsync(request, ClearScrollRequest._ENDPOINT, transportOptions)); + } + + public Mono clearScroll( + Function> fn) { + + Assert.notNull(fn, "fn must not be null"); + + return clearScroll(fn.apply(new ClearScrollRequest.Builder()).build()); + } + // endregion + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClusterClient.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClusterClient.java new file mode 100644 index 000000000..224f8dab5 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClusterClient.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.ApiClient; +import co.elastic.clients.elasticsearch.cluster.HealthRequest; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.util.ObjectBuilder; +import reactor.core.publisher.Mono; + +import java.util.function.Function; + +import org.springframework.lang.Nullable; + +/** + * Reactive version of the {@link co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient} + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ReactiveElasticsearchClusterClient + extends ApiClient { + + public ReactiveElasticsearchClusterClient(ElasticsearchTransport transport, + @Nullable TransportOptions transportOptions) { + super(transport, transportOptions); + } + + @Override + public ReactiveElasticsearchClusterClient withTransportOptions(@Nullable TransportOptions transportOptions) { + return new ReactiveElasticsearchClusterClient(transport, transportOptions); + } + + public Mono health(HealthRequest healthRequest) { + return Mono.fromFuture(transport.performRequestAsync(healthRequest, HealthRequest._ENDPOINT, transportOptions)); + } + + public Mono health(Function> fn) { + return health(fn.apply(new HealthRequest.Builder()).build()); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java new file mode 100644 index 000000000..52427d4f4 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.transport.rest_client.RestClientOptions; + +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.springframework.context.annotation.Bean; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport; +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.util.Assert; + +/** + * Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch + * connection using the {@link ReactiveElasticsearchClient}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport { + + /** + * Must be implemented by deriving classes to provide the {@link ClientConfiguration}. + * + * @return configuration, must not be {@literal null} + */ + @Bean + public abstract ClientConfiguration clientConfiguration(); + + /** + * Provides the underlying low level RestClient. + * + * @param clientConfiguration configuration for the client, must not be {@literal null} + * @return RestClient + */ + @Bean + public RestClient restClient(ClientConfiguration clientConfiguration) { + + Assert.notNull(clientConfiguration, "clientConfiguration must not be null"); + + return ElasticsearchClients.getRestClient(clientConfiguration); + } + + /** + * Provides the {@link ReactiveElasticsearchClient} instance used. + * + * @param restClient the low level RestClient to use + * @return ReactiveElasticsearchClient instance. + */ + @Bean + public ReactiveElasticsearchClient reactiveElasticsearchClient(RestClient restClient) { + + Assert.notNull(restClient, "restClient must not be null"); + + return ElasticsearchClients.createReactive(restClient, transportOptions()); + } + + /** + * Creates {@link ReactiveElasticsearchOperations}. + * + * @return never {@literal null}. + */ + @Bean(name = { "reactiveElasticsearchOperations", "reactiveElasticsearchTemplate" }) + public ReactiveElasticsearchOperations reactiveElasticsearchOperations(ElasticsearchConverter elasticsearchConverter, + ReactiveElasticsearchClient reactiveElasticsearchClient) { + + ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient, + elasticsearchConverter); + template.setRefreshPolicy(refreshPolicy()); + + return template; + } + + /** + * @return the options that should be added to every request. Must not be {@literal null} + */ + public TransportOptions transportOptions() { + return new RestClientOptions(RequestOptions.DEFAULT).toBuilder().build(); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchIndicesClient.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchIndicesClient.java new file mode 100644 index 000000000..a84c44fa0 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchIndicesClient.java @@ -0,0 +1,629 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.ApiClient; +import co.elastic.clients.elasticsearch.indices.*; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.util.ObjectBuilder; +import reactor.core.publisher.Mono; + +import java.util.function.Function; + +import org.springframework.lang.Nullable; + +/** + * Reactive version of the {@link co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient} + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ReactiveElasticsearchIndicesClient + extends ApiClient { + + public ReactiveElasticsearchIndicesClient(ElasticsearchTransport transport, + @Nullable TransportOptions transportOptions) { + super(transport, transportOptions); + } + + @Override + public ReactiveElasticsearchIndicesClient withTransportOptions(@Nullable TransportOptions transportOptions) { + return new ReactiveElasticsearchIndicesClient(transport, transportOptions); + } + + public Mono addBlock(AddBlockRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, AddBlockRequest._ENDPOINT, transportOptions)); + } + + public Mono addBlock(Function> fn) { + return addBlock(fn.apply(new AddBlockRequest.Builder()).build()); + } + + public Mono analyze(AnalyzeRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, AnalyzeRequest._ENDPOINT, transportOptions)); + } + + public Mono analyze(Function> fn) { + return analyze(fn.apply(new AnalyzeRequest.Builder()).build()); + } + + public Mono analyze() { + return analyze(builder -> builder); + } + + public Mono clearCache(ClearCacheRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ClearCacheRequest._ENDPOINT, transportOptions)); + } + + public Mono clearCache(Function> fn) { + return clearCache(fn.apply(new ClearCacheRequest.Builder()).build()); + } + + public Mono clearCache() { + return clearCache(builder -> builder); + } + + public Mono clone(CloneIndexRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, CloneIndexRequest._ENDPOINT, transportOptions)); + } + + public Mono clone(Function> fn) { + return clone(fn.apply(new CloneIndexRequest.Builder()).build()); + } + + public Mono close(CloseIndexRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, CloseIndexRequest._ENDPOINT, transportOptions)); + } + + public Mono close(Function> fn) { + return close(fn.apply(new CloseIndexRequest.Builder()).build()); + } + + public Mono create(CreateIndexRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, CreateIndexRequest._ENDPOINT, transportOptions)); + } + + public Mono create(Function> fn) { + return create(fn.apply(new CreateIndexRequest.Builder()).build()); + } + + public Mono createDataStream(CreateDataStreamRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, CreateDataStreamRequest._ENDPOINT, transportOptions)); + } + + public Mono createDataStream( + Function> fn) { + return createDataStream(fn.apply(new CreateDataStreamRequest.Builder()).build()); + } + + public Mono dataStreamsStats(DataStreamsStatsRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, DataStreamsStatsRequest._ENDPOINT, transportOptions)); + } + + public Mono dataStreamsStats( + Function> fn) { + return dataStreamsStats(fn.apply(new DataStreamsStatsRequest.Builder()).build()); + } + + public Mono dataStreamsStats() { + return dataStreamsStats(builder -> builder); + } + + public Mono delete(DeleteIndexRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, DeleteIndexRequest._ENDPOINT, transportOptions)); + } + + public Mono delete(Function> fn) { + return delete(fn.apply(new DeleteIndexRequest.Builder()).build()); + } + + public Mono deleteAlias(DeleteAliasRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, DeleteAliasRequest._ENDPOINT, transportOptions)); + } + + public Mono deleteAlias( + Function> fn) { + return deleteAlias(fn.apply(new DeleteAliasRequest.Builder()).build()); + } + + public Mono deleteDataStream(DeleteDataStreamRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, DeleteDataStreamRequest._ENDPOINT, transportOptions)); + } + + public Mono deleteDataStream( + Function> fn) { + return deleteDataStream(fn.apply(new DeleteDataStreamRequest.Builder()).build()); + } + + public Mono deleteIndexTemplate(DeleteIndexTemplateRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, DeleteIndexTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono deleteIndexTemplate( + Function> fn) { + return deleteIndexTemplate(fn.apply(new DeleteIndexTemplateRequest.Builder()).build()); + } + + public Mono deleteTemplate(DeleteTemplateRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, DeleteTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono deleteTemplate( + Function> fn) { + return deleteTemplate(fn.apply(new DeleteTemplateRequest.Builder()).build()); + } + + public Mono diskUsage(DiskUsageRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, DiskUsageRequest._ENDPOINT, transportOptions)); + } + + public Mono diskUsage(Function> fn) { + return diskUsage(fn.apply(new DiskUsageRequest.Builder()).build()); + } + + public Mono exists(ExistsRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ExistsRequest._ENDPOINT, transportOptions)); + } + + public Mono exists(Function> fn) { + return exists(fn.apply(new ExistsRequest.Builder()).build()); + } + + public Mono existsAlias(ExistsAliasRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ExistsAliasRequest._ENDPOINT, transportOptions)); + } + + public Mono existsAlias(Function> fn) { + return existsAlias(fn.apply(new ExistsAliasRequest.Builder()).build()); + } + + public Mono existsIndexTemplate(ExistsIndexTemplateRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, ExistsIndexTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono existsIndexTemplate( + Function> fn) { + return existsIndexTemplate(fn.apply(new ExistsIndexTemplateRequest.Builder()).build()); + } + + public Mono existsTemplate(ExistsTemplateRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ExistsTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono existsTemplate( + Function> fn) { + return existsTemplate(fn.apply(new ExistsTemplateRequest.Builder()).build()); + } + + public Mono existsType(ExistsTypeRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ExistsTypeRequest._ENDPOINT, transportOptions)); + } + + public Mono existsType(Function> fn) { + return existsType(fn.apply(new ExistsTypeRequest.Builder()).build()); + } + + public Mono flush(FlushRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, FlushRequest._ENDPOINT, transportOptions)); + } + + public Mono flush(Function> fn) { + return flush(fn.apply(new FlushRequest.Builder()).build()); + } + + public Mono flush() { + return flush(builder -> builder); + } + + public Mono flushSynced(FlushSyncedRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, FlushSyncedRequest._ENDPOINT, transportOptions)); + } + + public Mono flushSynced( + Function> fn) { + return flushSynced(fn.apply(new FlushSyncedRequest.Builder()).build()); + } + + public Mono flushSynced() { + return flushSynced(builder -> builder); + } + + @SuppressWarnings("SpellCheckingInspection") + public Mono forcemerge(ForcemergeRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ForcemergeRequest._ENDPOINT, transportOptions)); + } + + @SuppressWarnings("SpellCheckingInspection") + public Mono forcemerge(Function> fn) { + return forcemerge(fn.apply(new ForcemergeRequest.Builder()).build()); + } + + @SuppressWarnings("SpellCheckingInspection") + public Mono forcemerge() { + return forcemerge(builder -> builder); + } + + public Mono freeze(FreezeRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, FreezeRequest._ENDPOINT, transportOptions)); + } + + public Mono freeze(Function> fn) { + return freeze(fn.apply(new FreezeRequest.Builder()).build()); + } + + public Mono get(GetIndexRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, GetIndexRequest._ENDPOINT, transportOptions)); + } + + public Mono get(Function> fn) { + return get(fn.apply(new GetIndexRequest.Builder()).build()); + } + + public Mono getAlias(GetAliasRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, GetAliasRequest._ENDPOINT, transportOptions)); + } + + public Mono getAlias(Function> fn) { + return getAlias(fn.apply(new GetAliasRequest.Builder()).build()); + } + + public Mono getAlias() { + return getAlias(builder -> builder); + } + + public Mono getDataStream(GetDataStreamRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, GetDataStreamRequest._ENDPOINT, transportOptions)); + } + + public Mono getDataStream( + Function> fn) { + return getDataStream(fn.apply(new GetDataStreamRequest.Builder()).build()); + } + + public Mono getDataStream() { + return getDataStream(builder -> builder); + } + + public Mono getFieldMapping(GetFieldMappingRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, GetFieldMappingRequest._ENDPOINT, transportOptions)); + } + + public Mono getFieldMapping( + Function> fn) { + return getFieldMapping(fn.apply(new GetFieldMappingRequest.Builder()).build()); + } + + public Mono getIndexTemplate(GetIndexTemplateRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, GetIndexTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono getIndexTemplate( + Function> fn) { + return getIndexTemplate(fn.apply(new GetIndexTemplateRequest.Builder()).build()); + } + + public Mono getIndexTemplate() { + return getIndexTemplate(builder -> builder); + } + + public Mono getMapping(GetMappingRequest getMappingRequest) { + return Mono + .fromFuture(transport.performRequestAsync(getMappingRequest, GetMappingRequest._ENDPOINT, transportOptions)); + } + + public Mono getMapping(Function> fn) { + return getMapping(fn.apply(new GetMappingRequest.Builder()).build()); + } + + public Mono getMapping() { + return getMapping(builder -> builder); + } + + public Mono getSettings(GetIndicesSettingsRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, GetIndicesSettingsRequest._ENDPOINT, transportOptions)); + } + + public Mono getSettings( + Function> fn) { + return getSettings(fn.apply(new GetIndicesSettingsRequest.Builder()).build()); + } + + public Mono getSettings() { + return getSettings(builder -> builder); + } + + public Mono getTemplate(GetTemplateRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, GetTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono getTemplate( + Function> fn) { + return getTemplate(fn.apply(new GetTemplateRequest.Builder()).build()); + } + + public Mono getTemplate() { + return getTemplate(builder -> builder); + } + + public Mono getUpgrade(GetUpgradeRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, GetUpgradeRequest._ENDPOINT, transportOptions)); + } + + public Mono getUpgrade(Function> fn) { + return getUpgrade(fn.apply(new GetUpgradeRequest.Builder()).build()); + } + + public Mono getUpgrade() { + return getUpgrade(builder -> builder); + } + + public Mono migrateToDataStream(MigrateToDataStreamRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, MigrateToDataStreamRequest._ENDPOINT, transportOptions)); + } + + public Mono migrateToDataStream( + Function> fn) { + return migrateToDataStream(fn.apply(new MigrateToDataStreamRequest.Builder()).build()); + } + + public Mono open(OpenRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, OpenRequest._ENDPOINT, transportOptions)); + } + + public Mono open(Function> fn) { + return open(fn.apply(new OpenRequest.Builder()).build()); + } + + public Mono promoteDataStream(PromoteDataStreamRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, PromoteDataStreamRequest._ENDPOINT, transportOptions)); + } + + public Mono promoteDataStream( + Function> fn) { + return promoteDataStream(fn.apply(new PromoteDataStreamRequest.Builder()).build()); + } + + public Mono putAlias(PutAliasRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, PutAliasRequest._ENDPOINT, transportOptions)); + } + + public Mono putAlias(Function> fn) { + return putAlias(fn.apply(new PutAliasRequest.Builder()).build()); + } + + public Mono putIndexTemplate(PutIndexTemplateRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, PutIndexTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono putIndexTemplate( + Function> fn) { + return putIndexTemplate(fn.apply(new PutIndexTemplateRequest.Builder()).build()); + } + + public Mono putMapping(PutMappingRequest putMappingRequest) { + return Mono + .fromFuture(transport.performRequestAsync(putMappingRequest, PutMappingRequest._ENDPOINT, transportOptions)); + } + + public Mono putMapping(Function> fn) { + return putMapping(fn.apply(new PutMappingRequest.Builder()).build()); + } + + public Mono putSettings(PutIndicesSettingsRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, PutIndicesSettingsRequest._ENDPOINT, transportOptions)); + } + + public Mono putSettings( + Function> fn) { + return putSettings(fn.apply(new PutIndicesSettingsRequest.Builder()).build()); + } + + public Mono putSettings() { + return putSettings(builder -> builder); + } + + public Mono putTemplate(PutTemplateRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, PutTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono putTemplate( + Function> fn) { + return putTemplate(fn.apply(new PutTemplateRequest.Builder()).build()); + } + + public Mono recovery(RecoveryRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, RecoveryRequest._ENDPOINT, transportOptions)); + } + + public Mono recovery(Function> fn) { + return recovery(fn.apply(new RecoveryRequest.Builder()).build()); + } + + public Mono recovery() { + return recovery(builder -> builder); + } + + public Mono refresh(RefreshRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, RefreshRequest._ENDPOINT, transportOptions)); + } + + public Mono refresh(Function> fn) { + return refresh(fn.apply(new RefreshRequest.Builder()).build()); + } + + public Mono refresh() { + return refresh(builder -> builder); + } + + public Mono reloadSearchAnalyzers(ReloadSearchAnalyzersRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, ReloadSearchAnalyzersRequest._ENDPOINT, transportOptions)); + } + + public Mono reloadSearchAnalyzers( + Function> fn) { + return reloadSearchAnalyzers(fn.apply(new ReloadSearchAnalyzersRequest.Builder()).build()); + } + + public Mono resolveIndex(ResolveIndexRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ResolveIndexRequest._ENDPOINT, transportOptions)); + } + + public Mono resolveIndex( + Function> fn) { + return resolveIndex(fn.apply(new ResolveIndexRequest.Builder()).build()); + } + + public Mono rollover(RolloverRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, RolloverRequest._ENDPOINT, transportOptions)); + } + + public Mono rollover(Function> fn) { + return rollover(fn.apply(new RolloverRequest.Builder()).build()); + } + + public Mono segments(SegmentsRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, SegmentsRequest._ENDPOINT, transportOptions)); + } + + public Mono segments(Function> fn) { + return segments(fn.apply(new SegmentsRequest.Builder()).build()); + } + + public Mono segments() { + return segments(builder -> builder); + } + + public Mono shardStores(ShardStoresRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ShardStoresRequest._ENDPOINT, transportOptions)); + } + + public Mono shardStores( + Function> fn) { + return shardStores(fn.apply(new ShardStoresRequest.Builder()).build()); + } + + public Mono shardStores() { + return shardStores(builder -> builder); + } + + public Mono shrink(ShrinkRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ShrinkRequest._ENDPOINT, transportOptions)); + } + + public Mono shrink(Function> fn) { + return shrink(fn.apply(new ShrinkRequest.Builder()).build()); + } + + public Mono simulateIndexTemplate(SimulateIndexTemplateRequest request) { + return Mono + .fromFuture(transport.performRequestAsync(request, SimulateIndexTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono simulateIndexTemplate( + Function> fn) { + return simulateIndexTemplate(fn.apply(new SimulateIndexTemplateRequest.Builder()).build()); + } + + public Mono simulateTemplate(SimulateTemplateRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, SimulateTemplateRequest._ENDPOINT, transportOptions)); + } + + public Mono simulateTemplate( + Function> fn) { + return simulateTemplate(fn.apply(new SimulateTemplateRequest.Builder()).build()); + } + + public Mono simulateTemplate() { + return simulateTemplate(builder -> builder); + } + + public Mono split(SplitRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, SplitRequest._ENDPOINT, transportOptions)); + } + + public Mono split(Function> fn) { + return split(fn.apply(new SplitRequest.Builder()).build()); + } + + public Mono stats(IndicesStatsRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, IndicesStatsRequest._ENDPOINT, transportOptions)); + } + + public Mono stats( + Function> fn) { + return stats(fn.apply(new IndicesStatsRequest.Builder()).build()); + } + + public Mono stats() { + return stats(builder -> builder); + } + + public Mono unfreeze(UnfreezeRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, UnfreezeRequest._ENDPOINT, transportOptions)); + } + + public Mono unfreeze(Function> fn) { + return unfreeze(fn.apply(new UnfreezeRequest.Builder()).build()); + } + + public Mono updateAliases(UpdateAliasesRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, UpdateAliasesRequest._ENDPOINT, transportOptions)); + } + + public Mono updateAliases( + Function> fn) { + return updateAliases(fn.apply(new UpdateAliasesRequest.Builder()).build()); + } + + public Mono updateAliases() { + return updateAliases(builder -> builder); + } + + public Mono upgrade(UpgradeRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, UpgradeRequest._ENDPOINT, transportOptions)); + } + + public Mono upgrade(Function> fn) { + return upgrade(fn.apply(new UpgradeRequest.Builder()).build()); + } + + public Mono upgrade() { + return upgrade(builder -> builder); + } + + public Mono validateQuery(ValidateQueryRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, ValidateQueryRequest._ENDPOINT, transportOptions)); + } + + public Mono validateQuery( + Function> fn) { + return validateQuery(fn.apply(new ValidateQueryRequest.Builder()).build()); + } + + public Mono validateQuery() { + return validateQuery(builder -> builder); + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java new file mode 100644 index 000000000..0c182f39c --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java @@ -0,0 +1,480 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.Result; +import co.elastic.clients.elasticsearch._types.Time; +import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; +import co.elastic.clients.json.JsonpMapper; +import co.elastic.clients.transport.Version; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.elasticsearch.search.suggest.Suggest; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.reactivestreams.Publisher; +import org.springframework.data.elasticsearch.BulkFailureException; +import org.springframework.data.elasticsearch.NoSuchIndexException; +import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; +import org.springframework.data.elasticsearch.client.UnsupportedBackendOperation; +import org.springframework.data.elasticsearch.client.util.ScrollState; +import org.springframework.data.elasticsearch.core.AbstractReactiveElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.AggregationContainer; +import org.springframework.data.elasticsearch.core.IndexedObjectInformation; +import org.springframework.data.elasticsearch.core.MultiGetItem; +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; +import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.SearchDocument; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.BulkOptions; +import org.springframework.data.elasticsearch.core.query.ByQueryResponse; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.UpdateQuery; +import org.springframework.data.elasticsearch.core.query.UpdateResponse; +import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; +import org.springframework.data.elasticsearch.core.reindex.ReindexResponse; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * Implementation of {@link org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations} using the new + * Elasticsearch client. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate { + + private final ReactiveElasticsearchClient client; + private final RequestConverter requestConverter; + private final ResponseConverter responseConverter; + private final JsonpMapper jsonpMapper; + private final ElasticsearchExceptionTranslator exceptionTranslator; + + protected ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) { + super(converter); + + Assert.notNull(client, "client must not be null"); + + this.client = client; + this.jsonpMapper = client._transport().jsonpMapper(); + requestConverter = new RequestConverter(converter, jsonpMapper); + responseConverter = new ResponseConverter(jsonpMapper); + exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper); + } + + // region Document operations + @Override + protected Mono> doIndex(T entity, IndexCoordinates index) { + + IndexRequest indexRequest = requestConverter.documentIndexRequest(getIndexQuery(entity), index, + getRefreshPolicy()); + return Mono.just(entity) // + .zipWith(// + Mono.from(execute((ClientCallback>) client -> client.index(indexRequest))) // + .map(indexResponse -> new IndexResponseMetaData(indexResponse.id(), // + indexResponse.seqNo(), // + indexResponse.primaryTerm(), // + indexResponse.version() // + ))); + } + + @Override + public Mono get(String id, Class entityType, IndexCoordinates index) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(entityType, "entityType must not be null"); + Assert.notNull(index, "index must not be null"); + + GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, false); + + Mono> getResponse = Mono.from(execute( + (ClientCallback>>) client -> client.get(getRequest, EntityAsMap.class))); + + ReadDocumentCallback callback = new ReadDocumentCallback<>(converter, entityType, index); + return getResponse.flatMap(response -> callback.toEntity(DocumentAdapters.from(response))); + } + + @Override + public Mono reindex(ReindexRequest reindexRequest) { + + Assert.notNull(reindexRequest, "reindexRequest must not be null"); + + co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest, + true); + + return Mono.from(execute( // + (ClientCallback>) client -> client + .reindex(reindexRequestES))) + .map(responseConverter::reindexResponse); + } + + @Override + public Mono submitReindex(ReindexRequest reindexRequest) { + + Assert.notNull(reindexRequest, "reindexRequest must not be null"); + + co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest, + false); + + return Mono.from(execute( // + (ClientCallback>) client -> client + .reindex(reindexRequestES))) + .flatMap(response -> (response.task() == null) + ? Mono.error( // todo #1973 check behaviour and create issue in ES if necessary + new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request")) + : Mono.just(response.task())); + } + + @Override + public Mono bulkUpdate(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + + Assert.notNull(queries, "List of UpdateQuery must not be null"); + Assert.notNull(bulkOptions, "BulkOptions must not be null"); + Assert.notNull(index, "Index must not be null"); + + return doBulkOperation(queries, bulkOptions, index).then(); + } + + private Flux doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + + BulkRequest bulkRequest = requestConverter.documentBulkRequest(queries, bulkOptions, index, getRefreshPolicy()); + return client.bulk(bulkRequest) + .onErrorMap(e -> new UncategorizedElasticsearchException("Error executing bulk request", e)) + .flatMap(this::checkForBulkOperationFailure) // + .flatMapMany(response -> Flux.fromIterable(response.items())); + + } + + private Mono checkForBulkOperationFailure(BulkResponse bulkResponse) { + + if (bulkResponse.errors()) { + Map failedDocuments = new HashMap<>(); + + for (BulkResponseItem item : bulkResponse.items()) { + + if (item.error() != null) { + failedDocuments.put(item.id(), item.error().reason()); + } + } + BulkFailureException exception = new BulkFailureException( + "Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages [" + + failedDocuments + ']', + failedDocuments); + return Mono.error(exception); + } else { + return Mono.just(bulkResponse); + } + } + + @Override + protected Mono doDeleteById(String id, @Nullable String routing, IndexCoordinates index) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(index, "index must not be null"); + + return Mono.defer(() -> { + DeleteRequest deleteRequest = requestConverter.documentDeleteRequest(id, routing, index, getRefreshPolicy()); + return doDelete(deleteRequest); + }); + } + + private Mono doDelete(DeleteRequest request) { + + return Mono.from(execute((ClientCallback>) client -> client.delete(request))) // + .flatMap(deleteResponse -> { + if (deleteResponse.result() == Result.NotFound) { + return Mono.empty(); + } + return Mono.just(deleteResponse.id()); + }).onErrorResume(NoSuchIndexException.class, it -> Mono.empty()); + } + + // endregion + + @Override + protected ReactiveElasticsearchTemplate doCopy() { + return new ReactiveElasticsearchTemplate(client, converter); + } + + @Override + protected Mono doExists(String id, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + // region search operations + + @Override + protected Flux doFind(Query query, Class clazz, IndexCoordinates index) { + + return Flux.defer(() -> { + boolean useScroll = !(query.getPageable().isPaged() || query.isLimiting()); + SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false, useScroll); + + if (useScroll) { + return doScroll(searchRequest); + } else { + return doFind(searchRequest); + } + }); + + } + + private Flux doScroll(SearchRequest searchRequest) { + + Time scrollTimeout = searchRequest.scroll() != null ? searchRequest.scroll() : Time.of(t -> t.time("1m")); + + Flux> searchResponses = Flux.usingWhen(Mono.fromSupplier(ScrollState::new), // + state -> Mono + .from(execute((ClientCallback>>) client -> client + .search(searchRequest, EntityAsMap.class))) // + .expand(entityAsMapSearchResponse -> { + + state.updateScrollId(entityAsMapSearchResponse.scrollId()); + + if (entityAsMapSearchResponse.hits() == null + || CollectionUtils.isEmpty(entityAsMapSearchResponse.hits().hits())) { + return Mono.empty(); + } + + return Mono.from(execute((ClientCallback>>) client -> client.scroll( + ScrollRequest.of(sr -> sr.scrollId(state.getScrollId()).scroll(scrollTimeout)), EntityAsMap.class))); + }), + this::cleanupScroll, (state, ex) -> cleanupScroll(state), this::cleanupScroll); + + return searchResponses.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits()) + .map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper)); + } + + private Publisher cleanupScroll(ScrollState state) { + + return execute((ClientCallback>) client -> client + .clearScroll(ClearScrollRequest.of(csr -> csr.scrollId(state.getScrollIds())))); + } + + @Override + protected Mono doCount(Query query, Class entityType, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(index, "index must not be null"); + + SearchRequest searchRequest = requestConverter.searchRequest(query, entityType, index, true, false); + + return Mono + .from(execute((ClientCallback>>) client -> client.search(searchRequest, + EntityAsMap.class))) + .map(searchResponse -> searchResponse.hits().total() != null ? searchResponse.hits().total().value() : 0L); + } + + private Flux doFind(SearchRequest searchRequest) { + + return Mono + .from(execute((ClientCallback>>) client -> client.search(searchRequest, + EntityAsMap.class))) // + .flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits()) // + .map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper)); + } + + @Override + protected Mono doFindForResponse(Query query, Class clazz, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(index, "index must not be null"); + + SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false, false); + + // noinspection unchecked + SearchDocumentCallback callback = new ReadSearchDocumentCallback((Class) clazz, index); + SearchDocumentResponse.EntityCreator entityCreator = searchDocument -> callback.toEntity(searchDocument) + .toFuture(); + + return Mono + .from(execute((ClientCallback>>) client -> client.search(searchRequest, + EntityAsMap.class))) + .map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper)); + } + + // endregion + + @Override + protected Mono getVendor() { + return Mono.just("Elasticsearch"); + } + + @Override + protected Mono getRuntimeLibraryVersion() { + return Mono.just(Version.VERSION.toString()); + } + + @Override + protected Mono getClusterVersion() { + return Mono.from(execute(ReactiveElasticsearchClient::info)).map(infoResponse -> infoResponse.version().number()); + } + + @Override + public Flux saveAll(Mono> entitiesPublisher, IndexCoordinates index) { + + Assert.notNull(entitiesPublisher, "entitiesPublisher must not be null!"); + + return entitiesPublisher // + .flatMapMany(entities -> Flux.fromIterable(entities) // + .concatMap(entity -> maybeCallBeforeConvert(entity, index)) // + ).collectList() // + .map(Entities::new) // + .flatMapMany(entities -> { + + if (entities.isEmpty()) { + return Flux.empty(); + } + + return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index)// + .index() // + .flatMap(indexAndResponse -> { + T savedEntity = entities.entityAt(indexAndResponse.getT1()); + BulkResponseItem response = indexAndResponse.getT2(); + updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.id(), response.seqNo(), + response.primaryTerm(), response.version())); + return maybeCallAfterSave(savedEntity, index); + }); + }); + } + + @Override + public Flux> multiGet(Query query, Class clazz, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Mono delete(Query query, Class entityType, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Mono update(UpdateQuery updateQuery, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Mono updateByQuery(UpdateQuery updateQuery, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + @Deprecated + public Publisher execute(ReactiveElasticsearchOperations.ClientCallback> callback) { + throw new UnsupportedBackendOperation("direct execution on the WebClient is not supported for this class"); + } + + @Override + public Publisher executeWithIndicesClient(IndicesClientCallback> callback) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Publisher executeWithClusterClient(ClusterClientCallback> callback) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public ReactiveIndexOperations indexOps(IndexCoordinates index) { + return new ReactiveIndicesTemplate(client.indices(), converter, index); + } + + @Override + public ReactiveIndexOperations indexOps(Class clazz) { + return new ReactiveIndicesTemplate(client.indices(), converter, clazz); + } + + @Override + public ReactiveClusterOperations cluster() { + return new ReactiveClusterTemplate(client.cluster(), converter); + } + + @Override + public Flux> aggregate(Query query, Class entityType, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Flux suggest(SuggestBuilder suggestion, Class entityType) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Flux suggest(SuggestBuilder suggestion, IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Query matchAllQuery() { + return NativeQuery.builder().withQuery(QueryBuilders.matchAllQueryAsQuery()).build(); + } + + @Override + public Query idsQuery(List ids) { + throw new UnsupportedOperationException("not implemented"); + } + + /** + * Callback interface to be used with {@link #execute(ReactiveElasticsearchOperations.ClientCallback)} for operating + * directly on {@link ReactiveElasticsearchClient}. + * + * @param + */ + interface ClientCallback> { + + T doWithClient(ReactiveElasticsearchClient client); + } + + /** + * Execute a callback with the {@link ReactiveElasticsearchClient} and provide exception translation. + * + * @param callback the callback to execute, must not be {@literal null} + * @param the type returned from the callback + * @return the callback result + */ + public Publisher execute(ReactiveElasticsearchTemplate.ClientCallback> callback) { + return Flux.defer(() -> callback.doWithClient(client)).onErrorMap(this::translateException); + } + + /** + * translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a + * RuntimeException + * + * @param throwable the Exception to map + * @return the potentially translated RuntimeException. + */ + private RuntimeException translateException(Throwable throwable) { + + RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable + : new RuntimeException(throwable.getMessage(), throwable); + RuntimeException potentiallyTranslatedException = exceptionTranslator + .translateExceptionIfPossible(runtimeException); + + return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java new file mode 100644 index 000000000..ac8d91373 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java @@ -0,0 +1,337 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.springframework.util.StringUtils.*; + +import co.elastic.clients.elasticsearch._types.AcknowledgedResponseBase; +import co.elastic.clients.elasticsearch.indices.*; +import co.elastic.clients.transport.endpoints.BooleanResponse; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.elasticsearch.NoSuchIndexException; +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.core.IndexInformation; +import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; +import org.springframework.data.elasticsearch.core.ReactiveResourceUtil; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.AliasActions; +import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; +import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ReactiveMappingBuilder; +import org.springframework.data.elasticsearch.core.index.Settings; +import org.springframework.data.elasticsearch.core.index.TemplateData; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * @author Peter-Josef Meisch + */ +public class ReactiveIndicesTemplate extends ReactiveChildTemplate + implements ReactiveIndexOperations { + + @Nullable private final Class boundClass; + private final IndexCoordinates boundIndexCoordinates; + + public ReactiveIndicesTemplate(ReactiveElasticsearchIndicesClient client, + ElasticsearchConverter elasticsearchConverter, IndexCoordinates index) { + + super(client, elasticsearchConverter); + + Assert.notNull(index, "index must not be null"); + + this.boundClass = null; + this.boundIndexCoordinates = index; + } + + public ReactiveIndicesTemplate(ReactiveElasticsearchIndicesClient client, + ElasticsearchConverter elasticsearchConverter, Class clazz) { + + super(client, elasticsearchConverter); + + Assert.notNull(clazz, "clazz must not be null"); + + this.boundClass = clazz; + this.boundIndexCoordinates = getIndexCoordinatesFor(clazz); + } + + @Override + public Mono create() { + + IndexCoordinates indexCoordinates = getIndexCoordinates(); + + if (boundClass != null) { + return createSettings(boundClass).flatMap(settings -> doCreate(indexCoordinates, settings, null)); + } else { + return doCreate(indexCoordinates, new Settings(), null); + } + } + + @Override + public Mono create(Map settings) { + + Assert.notNull(settings, "settings must not be null"); + + return doCreate(getIndexCoordinates(), settings, null); + } + + @Override + public Mono create(Map settings, Document mapping) { + + Assert.notNull(settings, "settings must not be null"); + Assert.notNull(mapping, "mapping must not be null"); + + return doCreate(getIndexCoordinates(), settings, mapping); + } + + @Override + public Mono createWithMapping() { + return createSettings() // + .flatMap(settings -> // + createMapping().flatMap(mapping -> // + doCreate(getIndexCoordinates(), settings, mapping))); // + } + + private Mono doCreate(IndexCoordinates indexCoordinates, Map settings, + @Nullable Document mapping) { + + CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping); + Mono createIndexResponse = Mono.from(execute(client -> client.create(createIndexRequest))); + return createIndexResponse.map(CreateIndexResponse::acknowledged); + } + + @Override + public Mono delete() { + return exists().flatMap(exists -> { + + if (exists) { + DeleteIndexRequest deleteIndexRequest = requestConverter.indicesDeleteRequest(getIndexCoordinates()); + return Mono.from(execute(client -> client.delete(deleteIndexRequest))) // + .map(DeleteIndexResponse::acknowledged) // + .onErrorResume(NoSuchIndexException.class, e -> Mono.just(false)); + } else { + return Mono.just(false); + } + }); + + } + + @Override + public Mono exists() { + + ExistsRequest existsRequest = requestConverter.indicesExistsRequest(getIndexCoordinates()); + Mono existsResponse = Mono.from(execute(client -> client.exists(existsRequest))); + return existsResponse.map(BooleanResponse::value); + } + + @Override + public Mono refresh() { + return Mono.from(execute(client1 -> client1.refresh())).then(); + } + + @Override + public Mono createMapping() { + return createMapping(checkForBoundClass()); + } + + @Override + public Mono createMapping(Class clazz) { + + Assert.notNull(clazz, "clazz must not be null"); + + Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); + + if (mappingAnnotation != null) { + String mappingPath = mappingAnnotation.mappingPath(); + + if (hasText(mappingPath)) { + return ReactiveResourceUtil.loadDocument(mappingAnnotation.mappingPath(), "@Mapping"); + } + } + + return new ReactiveMappingBuilder(elasticsearchConverter).buildReactivePropertyMapping(clazz).map(Document::parse); + } + + @Override + public Mono putMapping(Mono mapping) { + + Assert.notNull(mapping, "mapping must not be null"); + + Mono putMappingResponse = mapping + .map(document -> requestConverter.indicesPutMappingRequest(getIndexCoordinates(), document)) // + .flatMap(putMappingRequest -> Mono.from(client.putMapping(putMappingRequest))); + return putMappingResponse.map(PutMappingResponse::acknowledged); + } + + @Override + public Mono getMapping() { + + IndexCoordinates indexCoordinates = getIndexCoordinates(); + GetMappingRequest getMappingRequest = requestConverter.indicesGetMappingRequest(indexCoordinates); + Mono getMappingResponse = Mono.from(execute(client -> client.getMapping(getMappingRequest))); + return getMappingResponse.map(response -> responseConverter.indicesGetMapping(response, indexCoordinates)); + } + + @Override + public Mono createSettings() { + return createSettings(checkForBoundClass()); + } + + @Override + public Mono createSettings(Class clazz) { + + Assert.notNull(clazz, "clazz must not be null"); + + ElasticsearchPersistentEntity persistentEntity = elasticsearchConverter.getMappingContext() + .getRequiredPersistentEntity(clazz); + String settingPath = persistentEntity.settingPath(); + return hasText(settingPath) // + ? ReactiveResourceUtil.loadDocument(settingPath, "@Setting") // + .map(Settings::new) // + : Mono.just(persistentEntity.getDefaultSettings()); + } + + @Override + public Mono getSettings(boolean includeDefaults) { + + GetIndicesSettingsRequest getSettingsRequest = requestConverter.indicesGetSettingsRequest(getIndexCoordinates(), + includeDefaults); + Mono getSettingsResponse = Mono + .from(execute(client -> client.getSettings(getSettingsRequest))); + return getSettingsResponse + .map(response -> responseConverter.indicesGetSettings(response, getIndexCoordinates().getIndexName())); + } + + @Override + public Mono alias(AliasActions aliasActions) { + + Assert.notNull(aliasActions, "aliasActions must not be null"); + + UpdateAliasesRequest updateAliasesRequest = requestConverter.indicesUpdateAliasesRequest(aliasActions); + Mono updateAliasesResponse = Mono + .from(execute(client -> client.updateAliases(updateAliasesRequest))); + return updateAliasesResponse.map(AcknowledgedResponseBase::acknowledged); + } + + @Override + public Mono>> getAliases(String... aliasNames) { + return getAliases(aliasNames, null); + } + + @Override + public Mono>> getAliasesForIndex(String... indexNames) { + return getAliases(null, indexNames); + } + + private Mono>> getAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) { + + GetAliasRequest getAliasRequest = requestConverter.indicesGetAliasRequest(aliasNames, indexNames); + Mono getAliasResponse = Mono.from(execute(client -> client.getAlias(getAliasRequest))); + return getAliasResponse.map(responseConverter::indicesGetAliasData); + } + + @Override + public Mono putTemplate(PutTemplateRequest putTemplateRequest) { + + Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.PutTemplateRequest putTemplateRequestES = requestConverter + .indicesPutTemplateRequest(putTemplateRequest); + Mono putTemplateResponse = Mono + .from(execute(client -> client.putTemplate(putTemplateRequestES))); + return putTemplateResponse.map(PutTemplateResponse::acknowledged); + } + + @Override + public Mono getTemplate(GetTemplateRequest getTemplateRequest) { + + Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.GetTemplateRequest getTemplateRequestES = requestConverter + .indicesGetTemplateRequest(getTemplateRequest); + Mono getTemplateResponse = Mono + .from(execute(client -> client.getTemplate(getTemplateRequestES))); + + return getTemplateResponse.flatMap(response -> { + if (response != null) { + TemplateData templateData = responseConverter.indicesGetTemplateData(response, + getTemplateRequest.getTemplateName()); + if (templateData != null) { + return Mono.just(templateData); + } + } + return Mono.empty(); + }); + } + + @Override + public Mono existsTemplate(ExistsTemplateRequest existsTemplateRequest) { + + Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.ExistsTemplateRequest existsTemplateRequestES = requestConverter + .indicesExistsTemplateRequest(existsTemplateRequest); + return Mono.from(execute(client1 -> client1.existsTemplate(existsTemplateRequestES))).map(BooleanResponse::value); + } + + @Override + public Mono deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) { + + Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.DeleteTemplateRequest deleteTemplateRequestES = requestConverter + .indicesDeleteTemplateRequest(deleteTemplateRequest); + return Mono.from(execute(client1 -> client1.deleteTemplate(deleteTemplateRequestES))) + .map(DeleteTemplateResponse::acknowledged); + } + + @Override + public Flux getInformation(IndexCoordinates index) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public IndexCoordinates getIndexCoordinates() { + return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : Objects.requireNonNull(boundIndexCoordinates); + } + + // region helper functions + private IndexCoordinates getIndexCoordinatesFor(Class clazz) { + return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getIndexCoordinates(); + } + + private Class checkForBoundClass() { + if (boundClass == null) { + throw new InvalidDataAccessApiUsageException("IndexOperations are not bound"); + } + return boundClass; + } + // endregion + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java new file mode 100644 index 000000000..1f65c217e --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -0,0 +1,1261 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*; +import static org.springframework.util.ObjectUtils.*; + +import co.elastic.clients.elasticsearch._types.InlineScript; +import co.elastic.clients.elasticsearch._types.OpType; +import co.elastic.clients.elasticsearch._types.SortOptions; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch._types.Time; +import co.elastic.clients.elasticsearch._types.VersionType; +import co.elastic.clients.elasticsearch._types.mapping.FieldType; +import co.elastic.clients.elasticsearch._types.mapping.Property; +import co.elastic.clients.elasticsearch._types.mapping.RuntimeField; +import co.elastic.clients.elasticsearch._types.mapping.RuntimeFieldType; +import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; +import co.elastic.clients.elasticsearch._types.query_dsl.Like; +import co.elastic.clients.elasticsearch.cluster.HealthRequest; +import co.elastic.clients.elasticsearch.core.BulkRequest; +import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest; +import co.elastic.clients.elasticsearch.core.DeleteRequest; +import co.elastic.clients.elasticsearch.core.GetRequest; +import co.elastic.clients.elasticsearch.core.IndexRequest; +import co.elastic.clients.elasticsearch.core.MgetRequest; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.bulk.BulkOperation; +import co.elastic.clients.elasticsearch.core.bulk.CreateOperation; +import co.elastic.clients.elasticsearch.core.bulk.IndexOperation; +import co.elastic.clients.elasticsearch.core.mget.MultiGetOperation; +import co.elastic.clients.elasticsearch.core.search.Highlight; +import co.elastic.clients.elasticsearch.core.search.SourceConfig; +import co.elastic.clients.elasticsearch.indices.*; +import co.elastic.clients.elasticsearch.indices.update_aliases.Action; +import co.elastic.clients.json.JsonData; +import co.elastic.clients.json.JsonpDeserializer; +import co.elastic.clients.json.JsonpMapper; +import jakarta.json.stream.JsonGenerator; +import jakarta.json.stream.JsonParser; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.core.RefreshPolicy; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.AliasAction; +import org.springframework.data.elasticsearch.core.index.AliasActionParameters; +import org.springframework.data.elasticsearch.core.index.AliasActions; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; +import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.*; +import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; +import org.springframework.data.elasticsearch.core.reindex.Remote; +import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Class to create Elasticsearch request and request builders. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +class RequestConverter { + + // the default max result window size of Elasticsearch + public static final Integer INDEX_MAX_RESULT_WINDOW = 10_000; + + protected final JsonpMapper jsonpMapper; + protected final ElasticsearchConverter elasticsearchConverter; + + public RequestConverter(ElasticsearchConverter elasticsearchConverter, JsonpMapper jsonpMapper) { + this.elasticsearchConverter = elasticsearchConverter; + + Assert.notNull(jsonpMapper, "jsonpMapper must not be null"); + + this.jsonpMapper = jsonpMapper; + } + + // region Cluster client + public HealthRequest clusterHealthRequest() { + return new HealthRequest.Builder().build(); + } + // endregion + + // region Indices client + public ExistsRequest indicesExistsRequest(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + return new ExistsRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build(); + } + + public CreateIndexRequest indicesCreateRequest(IndexCoordinates indexCoordinates, Map settings, + @Nullable Document mapping) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + Assert.notNull(settings, "settings must not be null"); + + CreateIndexRequest.Builder createRequestBuilder = new CreateIndexRequest.Builder(); + + createRequestBuilder.index(indexCoordinates.getIndexName()); + + // note: the new client does not support the index.storeType anymore + String settingsJson = Document.from(settings).toJson(); + IndexSettings indexSettings = fromJson(settingsJson, IndexSettings._DESERIALIZER); + createRequestBuilder.settings(indexSettings); + + if (mapping != null) { + String mappingJson = mapping.toJson(); + TypeMapping typeMapping = fromJson(mappingJson, TypeMapping._DESERIALIZER); + createRequestBuilder.mappings(typeMapping); + } + + return createRequestBuilder.build(); + } + + public RefreshRequest indicesRefreshRequest(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + return new RefreshRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build(); + } + + public DeleteIndexRequest indicesDeleteRequest(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + return new DeleteIndexRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build(); + } + + public UpdateAliasesRequest indicesUpdateAliasesRequest(AliasActions aliasActions) { + + Assert.notNull(aliasActions, "aliasActions must not be null"); + + UpdateAliasesRequest.Builder updateAliasRequestBuilder = new UpdateAliasesRequest.Builder(); + + List actions = new ArrayList<>(); + aliasActions.getActions().forEach(aliasAction -> { + + Action.Builder actionBuilder = new Action.Builder(); + + if (aliasAction instanceof AliasAction.Add) { + AliasAction.Add add = (AliasAction.Add) aliasAction; + AliasActionParameters parameters = add.getParameters(); + actionBuilder.add(addActionBuilder -> { + addActionBuilder // + .indices(Arrays.asList(parameters.getIndices())) // + .isHidden(parameters.getHidden()) // + .isWriteIndex(parameters.getWriteIndex()) // + .routing(parameters.getRouting()) // + .indexRouting(parameters.getIndexRouting()) // + .searchRouting(parameters.getSearchRouting()); // + + if (parameters.getAliases() != null) { + addActionBuilder.aliases(Arrays.asList(parameters.getAliases())); + } + + Query filterQuery = parameters.getFilterQuery(); + + if (filterQuery != null) { + elasticsearchConverter.updateQuery(filterQuery, parameters.getFilterQueryClass()); + co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getFilter(filterQuery); + if (esQuery != null) { + addActionBuilder.filter(esQuery); + + } + } + return addActionBuilder; + }); + } + + if (aliasAction instanceof AliasAction.Remove) { + AliasAction.Remove remove = (AliasAction.Remove) aliasAction; + AliasActionParameters parameters = remove.getParameters(); + actionBuilder.remove(removeActionBuilder -> { + removeActionBuilder.indices(Arrays.asList(parameters.getIndices())); + + if (parameters.getAliases() != null) { + removeActionBuilder.aliases(Arrays.asList(parameters.getAliases())); + } + + return removeActionBuilder; + }); + } + + if (aliasAction instanceof AliasAction.RemoveIndex) { + AliasAction.RemoveIndex removeIndex = (AliasAction.RemoveIndex) aliasAction; + AliasActionParameters parameters = removeIndex.getParameters(); + actionBuilder.removeIndex( + removeIndexActionBuilder -> removeIndexActionBuilder.indices(Arrays.asList(parameters.getIndices()))); + } + + actions.add(actionBuilder.build()); + }); + + updateAliasRequestBuilder.actions(actions); + + return updateAliasRequestBuilder.build(); + } + + public PutMappingRequest indicesPutMappingRequest(IndexCoordinates indexCoordinates, Document mapping) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + Assert.notNull(mapping, "mapping must not be null"); + + PutMappingRequest.Builder builder = new PutMappingRequest.Builder(); + builder.index(Arrays.asList(indexCoordinates.getIndexNames())); + addPropertiesToMapping(builder, mapping); + // TODO #1973 what else to add + + return builder.build(); + } + + public GetMappingRequest indicesGetMappingRequest(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + return new GetMappingRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build(); + } + + private void addPropertiesToMapping(PutMappingRequest.Builder builder, Document mapping) { + Object properties = mapping.get("properties"); + + if (properties != null) { + + if (properties instanceof Map) { + Map propertiesMap = new HashMap<>(); + // noinspection unchecked + ((Map) properties).forEach((key, value) -> { + Property property = getProperty(value); + propertiesMap.put(key, property); + }); + builder.properties(propertiesMap); + } + } + } + + private Property getProperty(Object value) { + // noinspection SpellCheckingInspection + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + JsonGenerator generator = jsonpMapper.jsonProvider().createGenerator(baos); + jsonpMapper.serialize(value, generator); + generator.close(); + // noinspection SpellCheckingInspection + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + return fromJson(bais, Property._DESERIALIZER); + } + + public GetIndicesSettingsRequest indicesGetSettingsRequest(IndexCoordinates indexCoordinates, + boolean includeDefaults) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + return new GetIndicesSettingsRequest.Builder() // + .index(Arrays.asList(indexCoordinates.getIndexNames())) // + .includeDefaults(includeDefaults) // + .build(); + } + + public GetIndexRequest indicesGetIndexRequest(IndexCoordinates indexCoordinates) { + + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + return new GetIndexRequest.Builder() // + .index(Arrays.asList(indexCoordinates.getIndexNames())) // + .includeDefaults(true) // + .build(); // + } + + public GetAliasRequest indicesGetAliasRequest(@Nullable String[] aliasNames, @Nullable String[] indexNames) { + GetAliasRequest.Builder builder = new GetAliasRequest.Builder(); + + if (aliasNames != null) { + builder.name(Arrays.asList(aliasNames)); + } + + if (indexNames != null) { + builder.index(Arrays.asList(indexNames)); + } + + return builder.build(); + } + + public co.elastic.clients.elasticsearch.indices.PutTemplateRequest indicesPutTemplateRequest( + PutTemplateRequest putTemplateRequest) { + + Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null"); + + co.elastic.clients.elasticsearch.indices.PutTemplateRequest.Builder builder = new co.elastic.clients.elasticsearch.indices.PutTemplateRequest.Builder(); + + builder.name(putTemplateRequest.getName()).indexPatterns(Arrays.asList(putTemplateRequest.getIndexPatterns())) + .order(putTemplateRequest.getOrder()); + + if (putTemplateRequest.getSettings() != null) { + Function, String> keyMapper = Map.Entry::getKey; + Function, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper); + Map settings = putTemplateRequest.getSettings().entrySet().stream() + .collect(Collectors.toMap(keyMapper, valueMapper)); + builder.settings(settings); + } + + if (putTemplateRequest.getMappings() != null) { + builder.mappings(fromJson(putTemplateRequest.getMappings().toJson(), TypeMapping._DESERIALIZER)); + } + + if (putTemplateRequest.getVersion() != null) { + builder.version(Long.valueOf(putTemplateRequest.getVersion())); + } + AliasActions aliasActions = putTemplateRequest.getAliasActions(); + + if (aliasActions != null) { + aliasActions.getActions().forEach(aliasAction -> { + AliasActionParameters parameters = aliasAction.getParameters(); + String[] parametersAliases = parameters.getAliases(); + + if (parametersAliases != null) { + for (String aliasName : parametersAliases) { + builder.aliases(aliasName, aliasBuilder -> { + + // noinspection DuplicatedCode + if (parameters.getRouting() != null) { + aliasBuilder.routing(parameters.getRouting()); + } + + if (parameters.getIndexRouting() != null) { + aliasBuilder.indexRouting(parameters.getIndexRouting()); + } + + if (parameters.getSearchRouting() != null) { + aliasBuilder.searchRouting(parameters.getSearchRouting()); + } + + if (parameters.getHidden() != null) { + aliasBuilder.isHidden(parameters.getHidden()); + } + + if (parameters.getWriteIndex() != null) { + aliasBuilder.isWriteIndex(parameters.getWriteIndex()); + } + + Query filterQuery = parameters.getFilterQuery(); + + if (filterQuery != null) { + co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getFilter(filterQuery); + + if (esQuery != null) { + aliasBuilder.filter(esQuery); + } + } + return aliasBuilder; + }); + } + } + }); + } + + return builder.build(); + } + + public co.elastic.clients.elasticsearch.indices.ExistsTemplateRequest indicesExistsTemplateRequest( + ExistsTemplateRequest existsTemplateRequest) { + + Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null"); + + return co.elastic.clients.elasticsearch.indices.ExistsTemplateRequest + .of(etr -> etr.name(existsTemplateRequest.getTemplateName())); + } + + public co.elastic.clients.elasticsearch.indices.DeleteTemplateRequest indicesDeleteTemplateRequest( + DeleteTemplateRequest existsTemplateRequest) { + + Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null"); + + return co.elastic.clients.elasticsearch.indices.DeleteTemplateRequest + .of(dtr -> dtr.name(existsTemplateRequest.getTemplateName())); + } + + public co.elastic.clients.elasticsearch.indices.GetTemplateRequest indicesGetTemplateRequest( + GetTemplateRequest getTemplateRequest) { + + Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null"); + + return co.elastic.clients.elasticsearch.indices.GetTemplateRequest + .of(gtr -> gtr.name(getTemplateRequest.getTemplateName()).flatSettings(true)); + } + + // endregion + + // region documents + /* + * the methods documentIndexRequest, bulkIndexOperation and bulkCreateOperation have nearly + * identical code, but the client builders do not have a common accessible base or some reusable parts + * so the code needs to be duplicated. + */ + + @SuppressWarnings("DuplicatedCode") + public IndexRequest documentIndexRequest(IndexQuery query, IndexCoordinates indexCoordinates, + @Nullable RefreshPolicy refreshPolicy) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + IndexRequest.Builder builder = new IndexRequest.Builder<>(); + + builder.index(indexCoordinates.getIndexName()); + + Object queryObject = query.getObject(); + + if (queryObject != null) { + String id = !StringUtils.hasText(query.getId()) ? getPersistentEntityId(queryObject) : query.getId(); + builder // + .id(id) // + .document(elasticsearchConverter.mapObject(queryObject)); + } else if (query.getSource() != null) { + builder // + .id(query.getId()) // + .document(new DefaultStringObjectMap<>().fromJson(query.getSource())); + } else { + throw new InvalidDataAccessApiUsageException( + "object or source is null, failed to index the document [id: " + query.getId() + ']'); + } + + if (query.getVersion() != null) { + VersionType versionType = retrieveVersionTypeFromPersistentEntity( + queryObject != null ? queryObject.getClass() : null); + builder.version(query.getVersion()).versionType(versionType); + } + + builder // + .ifSeqNo(query.getSeqNo()) // + .ifPrimaryTerm(query.getPrimaryTerm()) // + .routing(query.getRouting()); // + + if (query.getOpType() != null) { + switch (query.getOpType()) { + case INDEX: + builder.opType(OpType.Index); + break; + case CREATE: + builder.opType(OpType.Create); + break; + } + } + + builder.refresh(refresh(refreshPolicy)); + + return builder.build(); + } + /* + * the methods documentIndexRequest, bulkIndexOperation and bulkCreateOperation have nearly + * identical code, but the client builders do not have a common accessible base or some reusable parts + * so the code needs to be duplicated. + */ + + @SuppressWarnings("DuplicatedCode") + private IndexOperation bulkIndexOperation(IndexQuery query, IndexCoordinates indexCoordinates, + @Nullable RefreshPolicy refreshPolicy) { + + IndexOperation.Builder builder = new IndexOperation.Builder<>(); + + builder.index(indexCoordinates.getIndexName()); + + Object queryObject = query.getObject(); + + if (queryObject != null) { + String id = StringUtils.hasText(query.getId()) ? getPersistentEntityId(queryObject) : query.getId(); + builder // + .id(id) // + .document(elasticsearchConverter.mapObject(queryObject)); + } else if (query.getSource() != null) { + builder.document(new DefaultStringObjectMap<>().fromJson(query.getSource())); + } else { + throw new InvalidDataAccessApiUsageException( + "object or source is null, failed to index the document [id: " + query.getId() + ']'); + } + + if (query.getVersion() != null) { + VersionType versionType = retrieveVersionTypeFromPersistentEntity( + queryObject != null ? queryObject.getClass() : null); + builder.version(query.getVersion()).versionType(versionType); + } + + builder // + .ifSeqNo(query.getSeqNo()) // + .ifPrimaryTerm(query.getPrimaryTerm()) // + .routing(query.getRouting()); // + + return builder.build(); + } + /* + * the methods documentIndexRequest, bulkIndexOperation and bulkCreateOperation have nearly + * identical code, but the client builders do not have a common accessible base or some reusable parts + * so the code needs to be duplicated. + */ + + @SuppressWarnings("DuplicatedCode") + private CreateOperation bulkCreateOperation(IndexQuery query, IndexCoordinates indexCoordinates, + @Nullable RefreshPolicy refreshPolicy) { + + CreateOperation.Builder builder = new CreateOperation.Builder<>(); + + builder.index(indexCoordinates.getIndexName()); + + Object queryObject = query.getObject(); + + if (queryObject != null) { + String id = StringUtils.hasText(query.getId()) ? getPersistentEntityId(queryObject) : query.getId(); + builder // + .id(id) // + .document(elasticsearchConverter.mapObject(queryObject)); + } else if (query.getSource() != null) { + builder.document(new DefaultStringObjectMap<>().fromJson(query.getSource())); + } else { + throw new InvalidDataAccessApiUsageException( + "object or source is null, failed to index the document [id: " + query.getId() + ']'); + } + + if (query.getVersion() != null) { + VersionType versionType = retrieveVersionTypeFromPersistentEntity( + queryObject != null ? queryObject.getClass() : null); + builder.version(query.getVersion()).versionType(versionType); + } + + builder // + .ifSeqNo(query.getSeqNo()) // + .ifPrimaryTerm(query.getPrimaryTerm()) // + .routing(query.getRouting()); // + + return builder.build(); + } + + public BulkRequest documentBulkRequest(List queries, BulkOptions bulkOptions, IndexCoordinates indexCoordinates, + @Nullable RefreshPolicy refreshPolicy) { + + BulkRequest.Builder builder = new BulkRequest.Builder(); + + if (bulkOptions.getTimeout() != null) { + builder.timeout(tb -> tb.time(Long.valueOf(bulkOptions.getTimeout().toMillis()).toString() + "ms")); + } + + builder.refresh(refresh(refreshPolicy)); + if (bulkOptions.getRefreshPolicy() != null) { + builder.refresh(refresh(bulkOptions.getRefreshPolicy())); + } + + if (bulkOptions.getWaitForActiveShards() != null) { + builder.waitForActiveShards(wasb -> wasb.count(bulkOptions.getWaitForActiveShards().getValue())); + } + + if (bulkOptions.getPipeline() != null) { + builder.pipeline(bulkOptions.getPipeline()); + } + + if (bulkOptions.getRoutingId() != null) { + builder.routing(bulkOptions.getRoutingId()); + } + + List operations = queries.stream().map(query -> { + BulkOperation.Builder ob = new BulkOperation.Builder(); + if (query instanceof IndexQuery) { + IndexQuery indexQuery = (IndexQuery) query; + + if (indexQuery.getOpType() == IndexQuery.OpType.CREATE) { + ob.create(bulkCreateOperation(indexQuery, indexCoordinates, refreshPolicy)); + } else { + ob.index(bulkIndexOperation(indexQuery, indexCoordinates, refreshPolicy)); + } + } else if (query instanceof UpdateQuery) { + // todo #1973 + } + return ob.build(); + }).collect(Collectors.toList()); + + builder.operations(operations); + + return builder.build(); + } + + public GetRequest documentGetRequest(String id, @Nullable String routing, IndexCoordinates indexCoordinates, + boolean forExistsRequest) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + return GetRequest.of(grb -> { + grb // + .index(indexCoordinates.getIndexName()) // + .id(id) // + .routing(routing); + + if (forExistsRequest) { + grb.source(scp -> scp.fetch(false)); + } + + return grb; + }); + + } + + public MgetRequest documentMgetRequest(Query query, Class clazz, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(clazz, "clazz must not be null"); + Assert.notNull(index, "index must not be null"); + + if (query.getIdsWithRouting().isEmpty()) { + throw new IllegalArgumentException("query does not contain any ids"); + } + + elasticsearchConverter.updateQuery(query, clazz); // to get the SourceConfig right + + SourceConfig sourceConfig = getSourceConfig(query); + + List multiGetOperations = query.getIdsWithRouting().stream() + .map(idWithRouting -> MultiGetOperation.of(mgo -> mgo // + .index(index.getIndexName()) // + .id(idWithRouting.getId()) // + .routing(idWithRouting.getRouting()) // + .source(sourceConfig))) + .collect(Collectors.toList()); + + return MgetRequest.of(mg -> mg// + .docs(multiGetOperations)); + } + + public co.elastic.clients.elasticsearch.core.ReindexRequest reindex(ReindexRequest reindexRequest, + boolean waitForCompletion) { + + Assert.notNull(reindexRequest, "reindexRequest must not be null"); + + co.elastic.clients.elasticsearch.core.ReindexRequest.Builder builder = new co.elastic.clients.elasticsearch.core.ReindexRequest.Builder(); + builder // + .source(s -> { + ReindexRequest.Source source = reindexRequest.getSource(); + s.index(Arrays.asList(source.getIndexes().getIndexNames())) // + .size(source.getSize()); + + ReindexRequest.Slice slice = source.getSlice(); + if (slice != null) { + s.slice(sl -> sl.id(slice.getId()).max(slice.getMax())); + } + + if (source.getQuery() != null) { + s.query(getQuery(source.getQuery())); + } + + if (source.getRemote() != null) { + Remote remote = source.getRemote(); + + s.remote(rs -> { + StringBuilder sb = new StringBuilder(remote.getScheme()); + sb.append("://"); + sb.append(remote.getHost()); + sb.append(":"); + sb.append(remote.getPort()); + + if (remote.getPathPrefix() != null) { + sb.append(""); + sb.append(remote.getPathPrefix()); + } + + String socketTimeoutSecs = remote.getSocketTimeout() != null + ? remote.getSocketTimeout().getSeconds() + "s" + : "30s"; + String connectTimeoutSecs = remote.getConnectTimeout() != null + ? remote.getConnectTimeout().getSeconds() + "s" + : "30s"; + return rs // + .host(sb.toString()) // + .username(remote.getUsername()) // + .password(remote.getPassword()) // + .socketTimeout(tv -> tv.time(socketTimeoutSecs)) // + .connectTimeout(tv -> tv.time(connectTimeoutSecs)); + }); + } + + SourceFilter sourceFilter = source.getSourceFilter(); + if (sourceFilter != null) { + s.sourceFields(Arrays.asList(sourceFilter.getIncludes())); + } + return s; + }) // + .dest(d -> { + ReindexRequest.Dest dest = reindexRequest.getDest(); + return d // + .index(dest.getIndex().getIndexName()) // + .versionType(versionType(dest.getVersionType())) // + .opType(opType(dest.getOpType())); + } // + ); + + if (reindexRequest.getConflicts() != null) { + builder.conflicts(conflicts(reindexRequest.getConflicts())); + } + + ReindexRequest.Script script = reindexRequest.getScript(); + if (script != null) { + builder.script(s -> s.inline(InlineScript.of(i -> i.lang(script.getLang()).source(script.getSource())))); + } + + if (reindexRequest.getTimeout() != null) { + builder.timeout(tv -> tv.time(reindexRequest.getTimeout().toMillis() + "ms")); + } + + if (reindexRequest.getScroll() != null) { + builder.scroll(tv -> tv.time(reindexRequest.getScroll().toMillis() + "ms")); + } + + if (reindexRequest.getWaitForActiveShards() != null) { + builder.waitForActiveShards(wfas -> wfas // + .count(waitForActiveShardsCount(reindexRequest.getWaitForActiveShards()))); + } + + builder // + .maxDocs(reindexRequest.getMaxDocs()).waitForCompletion(waitForCompletion) // + .refresh(reindexRequest.getRefresh()) // + .requireAlias(reindexRequest.getRequireAlias()) // + .requestsPerSecond(reindexRequest.getRequestsPerSecond()) // + .slices(reindexRequest.getSlices()); + + return builder.build(); + } + + public DeleteRequest documentDeleteRequest(String id, @Nullable String routing, IndexCoordinates index, + @Nullable RefreshPolicy refreshPolicy) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(index, "index must not be null"); + + return DeleteRequest.of(r -> { + r.id(id).index(index.getIndexName()); + + if (routing != null) { + r.routing(routing); + } + r.refresh(refresh(refreshPolicy)); + return r; + }); + } + + public DeleteByQueryRequest documentDeleteByQueryRequest(Query query, Class clazz, IndexCoordinates index, + @Nullable RefreshPolicy refreshPolicy) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(index, "index must not be null"); + + return DeleteByQueryRequest.of(b -> { + b.index(Arrays.asList(index.getIndexNames())) // + .query(getQuery(query))// + .refresh(deleteByQueryRefresh(refreshPolicy)); + + if (query.isLimiting()) { + // noinspection ConstantConditions + b.maxDocs(Long.valueOf(query.getMaxResults())); + } + + if (query.hasScrollTime()) { + // noinspection ConstantConditions + b.scroll(Time.of(t -> t.time(query.getScrollTime().toMillis() + "ms"))); + } + + if (query.getRoute() != null) { + b.routing(query.getRoute()); + } + + return b; + }); + } + + // endregion + + // region search + + public SearchRequest searchRequest(Query query, @Nullable Class clazz, IndexCoordinates indexCoordinates, + boolean forCount, long scrollTimeInMillis) { + + return searchRequest(query, clazz, indexCoordinates, forCount, true, scrollTimeInMillis); + } + + public SearchRequest searchRequest(Query query, @Nullable Class clazz, IndexCoordinates indexCoordinates, + boolean forCount, boolean useScroll) { + return searchRequest(query, clazz, indexCoordinates, forCount, useScroll, null); + } + + public SearchRequest searchRequest(Query query, @Nullable Class clazz, IndexCoordinates indexCoordinates, + boolean forCount, boolean useScroll, @Nullable Long scrollTimeInMillis) { + + String[] indexNames = indexCoordinates.getIndexNames(); + Assert.notNull(query, "query must not be null"); + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + elasticsearchConverter.updateQuery(query, clazz); + + SearchRequest.Builder builder = new SearchRequest.Builder(); + prepareSearchRequest(query, clazz, indexCoordinates, builder, forCount, useScroll); + + if (scrollTimeInMillis != null) { + builder.scroll(t -> t.time(scrollTimeInMillis + "ms")); + } + + builder.query(getQuery(query)); + + addFilter(query, builder); + + return builder.build(); + } + + private void prepareSearchRequest(Query query, @Nullable Class clazz, IndexCoordinates indexCoordinates, + SearchRequest.Builder builder, boolean forCount, boolean useScroll) { + + String[] indexNames = indexCoordinates.getIndexNames(); + + Assert.notEmpty(indexNames, "indexCoordinates does not contain entries"); + + ElasticsearchPersistentEntity persistentEntity = getPersistentEntity(clazz); + + builder // + .index(Arrays.asList(indexNames)) // + .version(true) // + .trackScores(query.getTrackScores()); + + if (persistentEntity != null && persistentEntity.hasSeqNoPrimaryTermProperty()) { + builder.seqNoPrimaryTerm(true); + } + + if (query.getPageable().isPaged()) { + builder // + .from((int) query.getPageable().getOffset()) // + .size(query.getPageable().getPageSize()); + } else { + builder.from(0).size(INDEX_MAX_RESULT_WINDOW); + } + + builder.source(getSourceConfig(query)); + + if (!isEmpty(query.getFields())) { + builder.fields(fb -> { + query.getFields().forEach(fb::field); + return fb; + }); + } + + if (!isEmpty(query.getStoredFields())) { + builder.storedFields(query.getStoredFields()); + } + + if (query.getIndicesOptions() != null) { + // todo #1973 indices options + } + + if (query.isLimiting()) { + builder.size(query.getMaxResults()); + } + + if (query.getMinScore() > 0) { + builder.minScore((double) query.getMinScore()); + } + + if (query.getPreference() != null) { + builder.preference(query.getPreference()); + } + + // todo #1973 searchType + + if (query.getSort() != null) { + List sortOptions = getSortOptions(query.getSort(), persistentEntity); + + if (!sortOptions.isEmpty()) { + builder.sort(sortOptions); + } + } + + addHighlight(query, builder); + + if (query instanceof NativeQuery) { + prepareNativeSearch((NativeQuery) query, builder); + } + + if (query.getTrackTotalHits() != null) { + // logic from the RHLC, choose between -1 and Integer.MAX_VALUE + int value = query.getTrackTotalHits() ? Integer.MAX_VALUE : -1; + builder.trackTotalHits(th -> th.count(value)); + } else if (query.getTrackTotalHitsUpTo() != null) { + builder.trackTotalHits(th -> th.count(query.getTrackTotalHitsUpTo())); + } + + if (query.getRoute() != null) { + builder.routing(query.getRoute()); + } + + // todo #1973 timeout + + if (query.getExplain()) { + builder.explain(true); + } + + if (!isEmpty(query.getSearchAfter())) { + builder.searchAfter(query.getSearchAfter().stream().map(Object::toString).collect(Collectors.toList())); + } + // todo #1973 rescorer queries + // todo #1973 request cache + + if (!query.getRuntimeFields().isEmpty()) { + + Map runtimeMappings = new HashMap<>(); + query.getRuntimeFields().forEach(runtimeField -> { + runtimeMappings.put(runtimeField.getName(), RuntimeField.of(rt -> rt // + .type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType())) // + .script(s -> s.inline(is -> is.source(runtimeField.getScript()))))); + }); + builder.runtimeMappings(runtimeMappings); + } + + if (forCount) { + builder.size(0) // + .trackTotalHits(th -> th.count(Integer.MAX_VALUE)) // + .source(SourceConfig.of(sc -> sc.fetch(false))); + } else if (useScroll) { + // request_cache is not allowed on scroll requests. + builder.requestCache(null); + Duration scrollTimeout = query.getScrollTime() != null ? query.getScrollTime() : Duration.ofMinutes(1); + builder.scroll(tv -> tv.time(scrollTimeout.toMillis() + "ms")); + // limit the number of documents in a batch + builder.size(500); + } + } + + private void addHighlight(Query query, SearchRequest.Builder builder) { + + Highlight highlight = query.getHighlightQuery() + .map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext()) + .getHighlight(highlightQuery.getHighlight(), highlightQuery.getType())) + .orElse(null); + + builder.highlight(highlight); + } + + private List getSortOptions(Sort sort, @Nullable ElasticsearchPersistentEntity persistentEntity) { + return sort.stream().map(order -> getSortOptions(order, persistentEntity)).collect(Collectors.toList()); + } + + private SortOptions getSortOptions(Sort.Order order, @Nullable ElasticsearchPersistentEntity persistentEntity) { + SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.Desc : SortOrder.Asc; + + Order.Mode mode = Order.DEFAULT_MODE; + String unmappedType = null; + + if (order instanceof Order) { + Order o = (Order) order; + mode = o.getMode(); + unmappedType = o.getUnmappedType(); + } + + if (SortOptions.Kind.Score.jsonValue().equals(order.getProperty())) { + return SortOptions.of(so -> so.score(s -> s.order(sortOrder))); + } else { + ElasticsearchPersistentProperty property = (persistentEntity != null) // + ? persistentEntity.getPersistentProperty(order.getProperty()) // + : null; + String fieldName = property != null ? property.getFieldName() : order.getProperty(); + + Order.Mode finalMode = mode; + if (order instanceof GeoDistanceOrder) { + GeoDistanceOrder geoDistanceOrder = (GeoDistanceOrder) order; + + return SortOptions.of(so -> so // + .geoDistance(gd -> gd // + .field(fieldName) // + .location(loc -> loc.latlon(QueryBuilders.latLon(geoDistanceOrder.getGeoPoint())))// + .distanceType(geoDistanceType(geoDistanceOrder.getDistanceType())).mode(sortMode(finalMode)) // + .unit(distanceUnit(geoDistanceOrder.getUnit())) // + .ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped()))); + } else { + String missing = (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) ? "_first" + : ((order.getNullHandling() == Sort.NullHandling.NULLS_LAST) ? "_last" : null); + String finalUnmappedType = unmappedType; + return SortOptions.of(so -> so // + .field(f -> { + f.field(fieldName) // + .order(sortOrder) // + .mode(sortMode(finalMode)); + + if (finalUnmappedType != null) { + FieldType fieldType = fieldType(finalUnmappedType); + + if (fieldType != null) { + f.unmappedType(fieldType); + } + } + + if (missing != null) { + f.missing(fv -> fv // + .stringValue(missing)); + } + return f; + })); + } + } + } + + private void prepareNativeSearch(NativeQuery query, SearchRequest.Builder builder) { + // todo #1973 script fields + // todo #1973 collapse builder + // todo #1973 indices boost + + if (!isEmpty(query.getAggregations())) { + builder.aggregations(query.getAggregations()); + } + + builder.suggest(query.getSuggester()); + + // todo #1973 searchExt + } + + @Nullable + private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(Query query) { + + // todo #1973 some native stuff + co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null; + + if (query instanceof CriteriaQuery) { + esQuery = CriteriaQueryProcessor.createQuery(((CriteriaQuery) query).getCriteria()); + } else if (query instanceof StringQuery) { + esQuery = QueryBuilders.wrapperQueryAsQuery(((StringQuery) query).getSource()); + } else if (query instanceof NativeQuery) { + NativeQuery nativeQuery = (NativeQuery) query; + + if (nativeQuery.getQuery() != null) { + esQuery = nativeQuery.getQuery(); + } + } else { + throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName()); + } + return esQuery; + } + + private void addFilter(Query query, SearchRequest.Builder builder) { + + if (query instanceof CriteriaQuery) { + CriteriaFilterProcessor.createQuery(((CriteriaQuery) query).getCriteria()).ifPresent(builder::postFilter); + } else if (query instanceof StringQuery) { + // no filter for StringQuery + } else if (query instanceof NativeQuery) { + // todo #1973 NativeQuery filter + } else { + throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName()); + } + } + + public co.elastic.clients.elasticsearch._types.query_dsl.MoreLikeThisQuery moreLikeThisQuery(MoreLikeThisQuery query, + IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(index, "index must not be null"); + + co.elastic.clients.elasticsearch._types.query_dsl.MoreLikeThisQuery moreLikeThisQuery = co.elastic.clients.elasticsearch._types.query_dsl.MoreLikeThisQuery + .of(q -> { + q.like(Like.of(l -> l.document(ld -> ld.index(index.getIndexName()).id(query.getId())))) + .fields(query.getFields()); + + if (query.getMinTermFreq() != null) { + q.minTermFreq(query.getMinTermFreq()); + } + + if (query.getMaxQueryTerms() != null) { + q.maxQueryTerms(query.getMaxQueryTerms()); + } + + if (!isEmpty(query.getStopWords())) { + q.stopWords(query.getStopWords()); + } + + if (query.getMinDocFreq() != null) { + q.minDocFreq(query.getMinDocFreq()); + } + + if (query.getMaxDocFreq() != null) { + q.maxDocFreq(query.getMaxDocFreq()); + } + + if (query.getMinWordLen() != null) { + q.minWordLength(query.getMinWordLen()); + } + + if (query.getMaxWordLen() != null) { + q.maxWordLength(query.getMaxWordLen()); + } + + if (query.getBoostTerms() != null) { + q.boostTerms(Double.valueOf(query.getBoostTerms())); + } + + return q; + }); + + return moreLikeThisQuery; + } + // endregion + + // region helper functions + + public T fromJson(String json, JsonpDeserializer deserializer) { + + Assert.notNull(json, "json must not be null"); + Assert.notNull(deserializer, "deserializer must not be null"); + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + return fromJson(byteArrayInputStream, deserializer); + } + + public T fromJson(ByteArrayInputStream byteArrayInputStream, JsonpDeserializer deserializer) { + + Assert.notNull(byteArrayInputStream, "byteArrayInputStream must not be null"); + Assert.notNull(deserializer, "deserializer must not be null"); + + JsonParser parser = jsonpMapper.jsonProvider().createParser(byteArrayInputStream); + return deserializer.deserialize(parser, jsonpMapper); + } + + @Nullable + private ElasticsearchPersistentEntity getPersistentEntity(Object entity) { + return elasticsearchConverter.getMappingContext().getPersistentEntity(entity.getClass()); + } + + @Nullable + private ElasticsearchPersistentEntity getPersistentEntity(@Nullable Class clazz) { + return clazz != null ? elasticsearchConverter.getMappingContext().getPersistentEntity(clazz) : null; + } + + @Nullable + private String getPersistentEntityId(Object entity) { + + ElasticsearchPersistentEntity persistentEntity = getPersistentEntity(entity); + + if (persistentEntity != null) { + Object identifier = persistentEntity // + .getIdentifierAccessor(entity).getIdentifier(); + + if (identifier != null) { + return identifier.toString(); + } + } + + return null; + } + + private VersionType retrieveVersionTypeFromPersistentEntity(@Nullable Class clazz) { + + ElasticsearchPersistentEntity persistentEntity = getPersistentEntity(clazz); + + VersionType versionType = null; + + if (persistentEntity != null) { + org.springframework.data.elasticsearch.annotations.Document.VersionType entityVersionType = persistentEntity + .getVersionType(); + + if (entityVersionType != null) { + switch (entityVersionType) { + case INTERNAL: + versionType = VersionType.Internal; + break; + case EXTERNAL: + versionType = VersionType.External; + break; + case EXTERNAL_GTE: + versionType = VersionType.ExternalGte; + break; + case FORCE: + versionType = VersionType.Force; + break; + } + } + } + + return versionType != null ? versionType : VersionType.External; + } + + private co.elastic.clients.elasticsearch._types.query_dsl.Query getFilter(Query filterQuery) { + // TODO #1973 add filter query + + throw new UnsupportedOperationException("not implemented"); + } + + @Nullable + private SourceConfig getSourceConfig(Query query) { + + if (query.getSourceFilter() != null) { + return SourceConfig.of(s -> s // + .filter(sfb -> { + SourceFilter sourceFilter = query.getSourceFilter(); + String[] includes = sourceFilter.getIncludes(); + String[] excludes = sourceFilter.getExcludes(); + + if (includes != null) { + sfb.includes(Arrays.asList(includes)); + } + + if (excludes != null) { + sfb.excludes(Arrays.asList(excludes)); + } + + return sfb; + })); + } else { + return null; + } + } + + @Nullable + static Boolean deleteByQueryRefresh(@Nullable RefreshPolicy refreshPolicy) { + + if (refreshPolicy == null) { + return null; + } + + switch (refreshPolicy) { + case IMMEDIATE: + return true; + case WAIT_UNTIL: + return null; + case NONE: + default: + return false; + } + } + + // endregion +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java new file mode 100644 index 000000000..c2d4bca69 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java @@ -0,0 +1,389 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*; + +import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure; +import co.elastic.clients.elasticsearch._types.ErrorCause; +import co.elastic.clients.elasticsearch._types.Time; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; +import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse; +import co.elastic.clients.elasticsearch.core.mget.MultiGetError; +import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem; +import co.elastic.clients.elasticsearch.indices.*; +import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord; +import co.elastic.clients.json.JsonpMapper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.elasticsearch.ElasticsearchErrorCause; +import org.springframework.data.elasticsearch.core.IndexInformation; +import org.springframework.data.elasticsearch.core.MultiGetItem; +import org.springframework.data.elasticsearch.core.cluster.ClusterHealth; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.Settings; +import org.springframework.data.elasticsearch.core.index.TemplateData; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.ByQueryResponse; +import org.springframework.data.elasticsearch.core.reindex.ReindexResponse; +import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Class to convert Elasticsearch responses into Spring Data Elasticsearch classes. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +class ResponseConverter { + + private static final Logger LOGGER = LoggerFactory.getLogger(ResponseConverter.class); + + private final JsonpMapper jsonpMapper; + + public ResponseConverter(JsonpMapper jsonpMapper) { + this.jsonpMapper = jsonpMapper; + } + + // region cluster client + public ClusterHealth clusterHealth(HealthResponse healthResponse) { + + Assert.notNull(healthResponse, "healthResponse must not be null"); + + return ClusterHealth.builder() // + .withActivePrimaryShards(healthResponse.activePrimaryShards()) // + .withActiveShards(healthResponse.activeShards()) // + .withActiveShardsPercent(Double.parseDouble(healthResponse.activeShardsPercentAsNumber()))// + .withClusterName(healthResponse.clusterName()) // + .withDelayedUnassignedShards(healthResponse.delayedUnassignedShards()) // + .withInitializingShards(healthResponse.initializingShards()) // + .withNumberOfDataNodes(healthResponse.numberOfDataNodes()) // + .withNumberOfInFlightFetch(healthResponse.numberOfInFlightFetch()) // + .withNumberOfNodes(healthResponse.numberOfNodes()) // + .withNumberOfPendingTasks(healthResponse.numberOfPendingTasks()) // + .withRelocatingShards(healthResponse.relocatingShards()) // + .withStatus(healthResponse.status().toString()) // + .withTaskMaxWaitingTimeMillis(Long.parseLong(healthResponse.taskMaxWaitingInQueueMillis())) // + .withTimedOut(healthResponse.timedOut()) // + .withUnassignedShards(healthResponse.unassignedShards()) // + .build(); // + } + // endregion + + // region indices client + public Settings indicesGetSettings(GetIndicesSettingsResponse getIndicesSettingsResponse, String indexName) { + + Assert.notNull(getIndicesSettingsResponse, "getIndicesSettingsResponse must not be null"); + Assert.notNull(indexName, "indexName must not be null"); + + Settings settings = new Settings(); + IndexState indexState = getIndicesSettingsResponse.get(indexName); + + if (indexState != null) { + + Function indexSettingsToSettings = indexSettings -> { + Settings parsedSettings = Settings.parse(toJson(indexSettings, jsonpMapper)); + return (indexSettings.index() != null) ? parsedSettings : new Settings().append("index", parsedSettings); + }; + + if (indexState.defaults() != null) { + Settings defaultSettings = indexSettingsToSettings.apply(indexState.defaults()); + settings.merge(defaultSettings); + } + + if (indexState.settings() != null) { + Settings nonDefaultSettings = indexSettingsToSettings.apply(indexState.settings()); + settings.merge(nonDefaultSettings); + } + } + + return settings; + } + + public Document indicesGetMapping(GetMappingResponse getMappingResponse, IndexCoordinates indexCoordinates) { + + Assert.notNull(getMappingResponse, "getMappingResponse must not be null"); + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + + Map mappings = getMappingResponse.result(); + + if (mappings == null || mappings.size() == 0) { + return Document.create(); + } + + IndexMappingRecord indexMappingRecord = mappings.get(indexCoordinates.getIndexName()); + + // this can happen when the mapping was requested with an alias + if (indexMappingRecord == null) { + + if (mappings.size() != 1) { + LOGGER.warn("no mapping returned for index {}", indexCoordinates.getIndexName()); + return Document.create(); + } + String index = mappings.keySet().iterator().next(); + indexMappingRecord = mappings.get(index); + } + + return Document.parse(toJson(indexMappingRecord.mappings(), jsonpMapper)); + } + + public List indicesGetIndexInformations(GetIndexResponse getIndexResponse) { + + Assert.notNull(getIndexResponse, "getIndexResponse must not be null"); + + List indexInformationList = new ArrayList<>(); + + getIndexResponse.result().forEach((indexName, indexState) -> { + Settings settings = indexState.settings() != null ? Settings.parse(toJson(indexState.settings(), jsonpMapper)) + : new Settings(); + Document mappings = indexState.mappings() != null ? Document.parse(toJson(indexState.mappings(), jsonpMapper)) + : Document.create(); + + List aliasDataList = new ArrayList<>(); + indexState.aliases().forEach((aliasName, alias) -> aliasDataList.add(indicesGetAliasData(aliasName, alias))); + + indexInformationList.add(IndexInformation.of(indexName, settings, mappings, aliasDataList)); + + }); + return indexInformationList; + } + + public Map> indicesGetAliasData(GetAliasResponse getAliasResponse) { + + Assert.notNull(getAliasResponse, "getAliasResponse must not be null"); + + Map> aliasDataMap = new HashMap<>(); + getAliasResponse.result().forEach((indexName, alias) -> { + Set aliasDataSet = new HashSet<>(); + alias.aliases() + .forEach((aliasName, aliasDefinition) -> aliasDataSet.add(indicesGetAliasData(aliasName, aliasDefinition))); + aliasDataMap.put(indexName, aliasDataSet); + }); + return aliasDataMap; + } + + private AliasData indicesGetAliasData(String aliasName, Alias alias) { + Query filter = alias.filter(); + String filterJson = filter != null ? toJson(filter, jsonpMapper) : null; + Document filterDocument = filterJson != null ? Document.parse(filterJson) : null; + return AliasData.of(aliasName, filterDocument, alias.indexRouting(), alias.searchRouting(), alias.isWriteIndex(), + alias.isHidden()); + } + + private AliasData indicesGetAliasData(String aliasName, AliasDefinition alias) { + Query filter = alias.filter(); + String filterJson = filter != null ? toJson(filter, jsonpMapper) : null; + Document filterDocument = filterJson != null ? Document.parse(filterJson) : null; + return AliasData.of(aliasName, filterDocument, alias.indexRouting(), alias.searchRouting(), alias.isWriteIndex(), + null); + } + + @Nullable + public TemplateData indicesGetTemplateData(GetTemplateResponse getTemplateResponse, String templateName) { + + Assert.notNull(getTemplateResponse, "getTemplateResponse must not be null"); + Assert.notNull(templateName, "templateName must not be null"); + + TemplateMapping templateMapping = getTemplateResponse.get(templateName); + if (templateMapping != null) { + + Settings settings = new Settings(); + templateMapping.settings().forEach((key, jsonData) -> { + + if (key.contains(".")) { + // returned string contains " quotes + settings.put(key, jsonData.toJson().toString().replaceAll("^\"|\"$", "")); + } else { + settings.put(key, new DefaultStringObjectMap<>().fromJson(jsonData.toJson().toString())); + } + }); + + Function, String> keyMapper = Map.Entry::getKey; + Function, AliasData> valueMapper = entry -> indicesGetAliasData(entry.getKey(), + entry.getValue()); + + Map aliases = templateMapping.aliases().entrySet().stream() + .collect(Collectors.toMap(keyMapper, valueMapper)); + + Document mapping = Document.parse(toJson(templateMapping.mappings(), jsonpMapper)); + + TemplateData.TemplateDataBuilder builder = TemplateData.builder() // + .withIndexPatterns(templateMapping.indexPatterns().toArray(new String[0])) // + .withOrder(templateMapping.order()) // + .withSettings(settings) // + .withMapping(mapping) // + .withAliases(aliases) // + ; + + if (templateMapping.version() != null) { + builder.withVersion(templateMapping.version().intValue()); + } + + return builder.build(); + } + + return null; + } + + // endregion + + // region document operations + public ReindexResponse reindexResponse(co.elastic.clients.elasticsearch.core.ReindexResponse reindexResponse) { + + Assert.notNull(reindexResponse, "reindexResponse must not be null"); + + List failures = reindexResponse.failures() // + .stream() // + .map(this::reindexResponseFailureOf) // + .collect(Collectors.toList()); + + // noinspection ConstantConditions + return ReindexResponse.builder() // + .withTook(timeToLong(reindexResponse.took())) // + .withTimedOut(reindexResponse.timedOut()) // + .withTotal(reindexResponse.total()) // + .withCreated(reindexResponse.created()) // + .withUpdated(reindexResponse.updated()) // + .withDeleted(reindexResponse.deleted()) // + .withBatches(reindexResponse.batches()) // + .withVersionConflicts(reindexResponse.versionConflicts()) // + .withNoops(reindexResponse.noops()) // + .withBulkRetries(reindexResponse.retries().bulk()) // + .withSearchRetries(reindexResponse.retries().search()) // + .withThrottledMillis(Long.parseLong(reindexResponse.throttledMillis())) // + .withRequestsPerSecond(reindexResponse.requestsPerSecond()) // + .withThrottledUntilMillis(Long.parseLong(reindexResponse.throttledUntilMillis())).withFailures(failures) // + .build(); + } + + private ReindexResponse.Failure reindexResponseFailureOf(BulkIndexByScrollFailure failure) { + return ReindexResponse.Failure.builder() // + .withIndex(failure.index()) // + .withType(failure.type()) // + .withId(failure.id()) // + .withStatus(failure.status())// + .withErrorCause(toErrorCause(failure.cause())) // + // seqno, term, aborted are not available in the new client + .build(); + } + + private ByQueryResponse.Failure byQueryResponseFailureOf(BulkIndexByScrollFailure failure) { + return ByQueryResponse.Failure.builder() // + .withIndex(failure.index()) // + .withType(failure.type()) // + .withId(failure.id()) // + .withStatus(failure.status())// + .withErrorCause(toErrorCause(failure.cause())).build(); + } + + @Nullable + public static MultiGetItem.Failure getFailure(MultiGetResponseItem itemResponse) { + + MultiGetError responseFailure = itemResponse.isFailure() ? itemResponse.failure() : null; + + return responseFailure != null + ? MultiGetItem.Failure.of(responseFailure.index(), null, responseFailure.id(), null, + toErrorCause(responseFailure.error())) + : null; + } + + public ByQueryResponse byQueryResponse(DeleteByQueryResponse response) { + List failures = response.failures().stream().map(this::byQueryResponseFailureOf) + .collect(Collectors.toList()); + + ByQueryResponse.ByQueryResponseBuilder builder = ByQueryResponse.builder(); + + if (response.took() != null) { + builder.withTook(response.took()); + } + + if (response.timedOut() != null) { + builder.withTimedOut(response.timedOut()); + } + + if (response.total() != null) { + builder.withTotal(response.total()); + } + + if (response.deleted() != null) { + builder.withDeleted(response.deleted()); + } + + if (response.batches() != null) { + builder.withBatches(Math.toIntExact(response.batches())); + } + + if (response.versionConflicts() != null) { + builder.withVersionConflicts(response.versionConflicts()); + } + + if (response.noops() != null) { + builder.withNoops(response.noops()); + } + + if (response.retries() != null) { + builder.withBulkRetries(response.retries().bulk()); + builder.withSearchRetries(response.retries().search()); + } + + builder.withFailures(failures); + + return builder.build(); + } + + // endregion + + // region helper functions + private long timeToLong(Time time) { + + if (time.isTime()) { + return Long.parseLong(time.time()); + } else { + return time.offset(); + } + } + + @Nullable + private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) { + + if (errorCause != null) { + return new ElasticsearchErrorCause( // + errorCause.type(), // + errorCause.reason(), // + errorCause.stackTrace(), // + toErrorCause(errorCause.causedBy()), // + errorCause.rootCause().stream().map(ResponseConverter::toErrorCause).collect(Collectors.toList()), // + errorCause.suppressed().stream().map(ResponseConverter::toErrorCause).collect(Collectors.toList())); + } else { + return null; + } + } + + // endregion +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/SearchDocumentResponseBuilder.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/SearchDocumentResponseBuilder.java new file mode 100644 index 000000000..ab2b63739 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/SearchDocumentResponseBuilder.java @@ -0,0 +1,123 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.aggregations.Aggregate; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.elasticsearch.core.search.HitsMetadata; +import co.elastic.clients.elasticsearch.core.search.Suggestion; +import co.elastic.clients.elasticsearch.core.search.TotalHits; +import co.elastic.clients.json.JsonpMapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.elasticsearch.search.SearchHits; +import org.springframework.data.elasticsearch.core.TotalHitsRelation; +import org.springframework.data.elasticsearch.core.document.SearchDocument; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.data.elasticsearch.core.suggest.response.Suggest; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Factory class to create {@link SearchDocumentResponse} instances. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +class SearchDocumentResponseBuilder { + /** + * creates a SearchDocumentResponse from the {@link SearchResponse} + * + * @param searchResponse the Elasticsearch search response + * @param entityCreator function to create an entity from a {@link SearchDocument} + * @param jsonpMapper to map JsonData objects + * @return the SearchDocumentResponse + */ + public static SearchDocumentResponse from(SearchResponse searchResponse, + SearchDocumentResponse.EntityCreator entityCreator, JsonpMapper jsonpMapper) { + + Assert.notNull(searchResponse, "searchResponse must not be null"); + Assert.notNull(entityCreator, "entityCreator must not be null"); + + HitsMetadata hitsMetadata = searchResponse.hits(); + String scrollId = searchResponse.scrollId(); + Map aggregations = searchResponse.aggregations(); + Map>> suggest = searchResponse.suggest(); + + return from(hitsMetadata, scrollId, aggregations, suggest, entityCreator, jsonpMapper); + } + + /** + * creates a {@link SearchDocumentResponseBuilder} from {@link SearchHits} with the given scrollId aggregations and + * suggestES + * + * @param entity type + * @param hitsMetadata the {@link SearchHits} to process + * @param scrollId scrollId + * @param aggregations aggregations + * @param suggestES the suggestion response from Elasticsearch + * @param entityCreator function to create an entity from a {@link SearchDocument}, needed in mapping the suggest data + * @param jsonpMapper to map JsonData objects + * @return the {@link SearchDocumentResponse} + */ + public static SearchDocumentResponse from(HitsMetadata hitsMetadata, @Nullable String scrollId, + Map aggregations, Map>> suggestES, + SearchDocumentResponse.EntityCreator entityCreator, JsonpMapper jsonpMapper) { + + Assert.notNull(hitsMetadata, "hitsMetadata must not be null"); + + long totalHits; + String totalHitsRelation; + + TotalHits responseTotalHits = hitsMetadata.total(); + if (responseTotalHits != null) { + totalHits = responseTotalHits.value(); + switch (responseTotalHits.relation().jsonValue()) { + case "eq": + totalHitsRelation = TotalHitsRelation.EQUAL_TO.name(); + break; + case "gte": + totalHitsRelation = TotalHitsRelation.GREATER_THAN_OR_EQUAL_TO.name(); + break; + default: + totalHitsRelation = TotalHitsRelation.OFF.name(); + } + } else { + totalHits = hitsMetadata.hits().size(); + totalHitsRelation = "OFF"; + } + + float maxScore = hitsMetadata.maxScore() != null ? hitsMetadata.maxScore().floatValue() : Float.NaN; + + List searchDocuments = new ArrayList<>(); + for (Hit hit : hitsMetadata.hits()) { + searchDocuments.add(DocumentAdapters.from(hit, jsonpMapper)); + } + + ElasticsearchAggregations aggregationsContainer = aggregations != null ? new ElasticsearchAggregations(aggregations) + : null; + + // todo #1973 + Suggest suggest = null; + + return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, + aggregationsContainer, suggest); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java new file mode 100644 index 000000000..5a3c59541 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java @@ -0,0 +1,298 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch._types.Conflicts; +import co.elastic.clients.elasticsearch._types.DistanceUnit; +import co.elastic.clients.elasticsearch._types.GeoDistanceType; +import co.elastic.clients.elasticsearch._types.OpType; +import co.elastic.clients.elasticsearch._types.Refresh; +import co.elastic.clients.elasticsearch._types.SortMode; +import co.elastic.clients.elasticsearch._types.VersionType; +import co.elastic.clients.elasticsearch._types.mapping.FieldType; +import co.elastic.clients.elasticsearch.core.search.BoundaryScanner; +import co.elastic.clients.elasticsearch.core.search.BuiltinHighlighterType; +import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder; +import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter; +import co.elastic.clients.elasticsearch.core.search.HighlighterOrder; +import co.elastic.clients.elasticsearch.core.search.HighlighterTagsSchema; +import co.elastic.clients.elasticsearch.core.search.HighlighterType; + +import org.springframework.data.elasticsearch.core.RefreshPolicy; +import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder; +import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.Order; +import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; +import org.springframework.lang.Nullable; + +/** + * Utility to handle new Elasticsearch client type values. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +final class TypeUtils { + + @Nullable + static BoundaryScanner boundaryScanner(@Nullable String value) { + + if (value != null) { + switch (value.toLowerCase()) { + case "chars": + return BoundaryScanner.Chars; + case "sentence": + return BoundaryScanner.Sentence; + case "word": + return BoundaryScanner.Word; + default: + return null; + } + } + return null; + } + + static Conflicts conflicts(ReindexRequest.Conflicts conflicts) { + switch (conflicts) { + case ABORT: + return Conflicts.Abort; + case PROCEED: + return Conflicts.Proceed; + } + + throw new IllegalArgumentException("Cannot map conflicts value " + conflicts.name()); + } + + @Nullable + static DistanceUnit distanceUnit(String unit) { + + switch (unit.toLowerCase()) { + case "in": + case "inch": + return DistanceUnit.Inches; + case "yd": + case "yards": + return DistanceUnit.Yards; + case "ft": + case "feet": + return DistanceUnit.Feet; + case "km": + case "kilometers": + return DistanceUnit.Kilometers; + case "nm": + case "nmi": + return DistanceUnit.NauticMiles; + case "mm": + case "millimeters": + return DistanceUnit.Millimeters; + case "cm": + case "centimeters": + return DistanceUnit.Centimeters; + case "mi": + case "miles": + return DistanceUnit.Miles; + case "m": + case "meters": + return DistanceUnit.Meters; + } + return null; + } + + @Nullable + static FieldType fieldType(String type) { + + for (FieldType fieldType : FieldType.values()) { + + if (fieldType.jsonValue().equals(type)) { + return fieldType; + } + } + return null; + } + + @Nullable + static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) { + + switch (distanceType) { + case arc: + return GeoDistanceType.Arc; + case plane: + return GeoDistanceType.Plane; + } + + return null; + } + + @Nullable + static HighlighterFragmenter highlighterFragmenter(@Nullable String value) { + + if (value != null) { + switch (value.toLowerCase()) { + case "simple": + return HighlighterFragmenter.Simple; + case "span": + return HighlighterFragmenter.Span; + default: + return null; + } + } + + return null; + } + + @Nullable + static HighlighterOrder highlighterOrder(@Nullable String value) { + + if (value != null) { + if ("score".equals(value.toLowerCase())) { + return HighlighterOrder.Score; + } + } + + return null; + } + + @Nullable + static HighlighterType highlighterType(@Nullable String value) { + + if (value != null) { + switch (value.toLowerCase()) { + case "unified": + return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.Unified)); + case "plain": + return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.Plain)); + case "fvh": + return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.FastVector)); + default: + return null; + } + } + + return null; + } + + @Nullable + static HighlighterEncoder highlighterEncoder(@Nullable String value) { + + if (value != null) { + switch (value.toLowerCase()) { + case "default": + return HighlighterEncoder.Default; + case "html": + return HighlighterEncoder.Html; + default: + return null; + } + } + + return null; + } + + @Nullable + static HighlighterTagsSchema highlighterTagsSchema(@Nullable String value) { + + if (value != null) { + if ("styled".equals(value.toLowerCase())) { + return HighlighterTagsSchema.Styled; + } + } + + return null; + } + + @Nullable + static OpType opType(@Nullable IndexQuery.OpType opType) { + + if (opType != null) { + switch (opType) { + case INDEX: + return OpType.Index; + case CREATE: + return OpType.Create; + } + } + return null; + } + + static Refresh refresh(@Nullable RefreshPolicy refreshPolicy) { + + if (refreshPolicy == null) { + return Refresh.False; + } + + switch (refreshPolicy) { + case IMMEDIATE: + return Refresh.True; + case WAIT_UNTIL: + return Refresh.WaitFor; + case NONE: + default: + return Refresh.False; + } + } + + @Nullable + static SortMode sortMode(Order.Mode mode) { + + switch (mode) { + case min: + return SortMode.Min; + case max: + return SortMode.Max; + case median: + return SortMode.Median; + case avg: + return SortMode.Avg; + } + + return null; + } + + @Nullable + static VersionType versionType( + @Nullable org.springframework.data.elasticsearch.annotations.Document.VersionType versionType) { + + if (versionType != null) { + switch (versionType) { + case INTERNAL: + return VersionType.Internal; + case EXTERNAL: + return VersionType.External; + case EXTERNAL_GTE: + return VersionType.ExternalGte; + case FORCE: + return VersionType.Force; + } + } + + return null; + } + + static Integer waitForActiveShardsCount(@Nullable String value) { + // values taken from the RHLC implementation + if (value == null) { + return -2; + } else if ("all".equals(value.toUpperCase())) { + return -1; + } else { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Illegale value for waitForActiveShards" + value); + } + } + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/package-info.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/package-info.java new file mode 100644 index 000000000..d9abf5d5e --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains classes that use the new Elasticsearch client library (co.elastic.clients:elasticsearch-java) + * to access Elasticsearch. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.elasticsearch.client.elc; diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java index 89184eca4..34d4a7ef1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java @@ -15,33 +15,19 @@ */ package org.springframework.data.elasticsearch.client.reactive; -import io.netty.channel.ChannelOption; -import io.netty.handler.ssl.ApplicationProtocolConfig; -import io.netty.handler.ssl.ClientAuth; -import io.netty.handler.ssl.IdentityCipherSuiteFilter; -import io.netty.handler.ssl.JdkSslContext; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.handler.timeout.WriteTimeoutHandler; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; -import reactor.netty.transport.ProxyProvider; import java.io.IOException; import java.lang.reflect.Method; import java.net.ConnectException; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.Collection; import java.util.Map.Entry; -import java.util.Optional; -import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; -import javax.net.ssl.SSLContext; - import org.apache.http.util.EntityUtils; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchStatusException; @@ -120,7 +106,6 @@ import org.springframework.data.util.Lazy; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -229,7 +214,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch Assert.notNull(clientConfiguration, "ClientConfiguration must not be null"); Assert.notNull(requestCreator, "RequestCreator must not be null"); - WebClientProvider provider = getWebClientProvider(clientConfiguration); + WebClientProvider provider = WebClientProvider.getWebClientProvider(clientConfiguration); HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(), clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0])); @@ -241,83 +226,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch return client; } - private static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) { - - Duration connectTimeout = clientConfiguration.getConnectTimeout(); - Duration soTimeout = clientConfiguration.getSocketTimeout(); - - HttpClient httpClient = HttpClient.create().compress(true); - - if (!connectTimeout.isNegative()) { - httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis())); - } - - if (!soTimeout.isNegative()) { - httpClient = httpClient.doOnConnected(connection -> connection // - .addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)) - .addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))); - } - - if (clientConfiguration.getProxy().isPresent()) { - String proxy = clientConfiguration.getProxy().get(); - String[] hostPort = proxy.split(":"); - - if (hostPort.length != 2) { - throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\""); - } - httpClient = httpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0]) - .port(Integer.parseInt(hostPort[1]))); - } - - String scheme = "http"; - - if (clientConfiguration.useSsl()) { - - Optional sslContext = clientConfiguration.getSslContext(); - - if (sslContext.isPresent()) { - httpClient = httpClient - .secure(sslContextSpec -> sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, - IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false))); - } else { - httpClient = httpClient.secure(); - } - - scheme = "https"; - } - - WebClientProvider provider = WebClientProvider.create(scheme, new ReactorClientHttpConnector(httpClient)); - - if (clientConfiguration.getPathPrefix() != null) { - provider = provider.withPathPrefix(clientConfiguration.getPathPrefix()); - } - - Function webClientConfigurer = webClient -> { - for (ClientConfiguration.ClientConfigurationCallback clientConfigurer : clientConfiguration - .getClientConfigurers()) { - - if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) { - ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer; - webClient = webClientConfigurationCallback.configure(webClient); - } - } - return webClient; - }; - - provider = provider // - .withDefaultHeaders(clientConfiguration.getDefaultHeaders()) // - .withWebClientConfigurer(webClientConfigurer) // - .withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> { - HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get(); - - if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) { - httpHeaders.addAll(suppliedHeaders); - } - })); - - return provider; - } - public void setHeadersSupplier(Supplier headersSupplier) { Assert.notNull(headersSupplier, "headersSupplier must not be null"); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java index e8204f1a6..12a412ebd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java @@ -99,11 +99,34 @@ public interface HostProvider> { return lookupActiveHost(verification).map(this::createWebClient); } + /** + * Get the {@link WebClient} connecting to an active host utilizing cached {@link ElasticsearchHost}. + * + * @return the {@link Mono} emitting the client for an active host or {@link Mono#error(Throwable) an error} if none + * found. + * @since 4.4 + */ + default Mono getWebClient() { + return getWebClient(Verification.LAZY); + } + + /** + * Get the {@link WebClient} connecting to an active host. + * + * @param verification must not be {@literal null}. + * @return the {@link Mono} emitting the client for an active host or {@link Mono#error(Throwable) an error} if none + * found. + * @since 4.4 + */ + default Mono getWebClient(Verification verification) { + return lookupActiveHost(verification).map(this::createWebClient); + } + /** * Creates a {@link WebClient} for {@link InetSocketAddress endpoint}. * * @param endpoint must not be {@literal null}. - * @return a {@link WebClient} using the the given endpoint as {@literal base url}. + * @return a {@link WebClient} using the the given endpoint as {@literal transport url}. */ WebClient createWebClient(InetSocketAddress endpoint); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java index 6d61a5951..4f448b3c3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java @@ -15,12 +15,30 @@ */ package org.springframework.data.elasticsearch.client.reactive; +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.IdentityCipherSuiteFilter; +import io.netty.handler.ssl.JdkSslContext; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import reactor.netty.http.client.HttpClient; +import reactor.netty.transport.ProxyProvider; + import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; +import javax.net.ssl.SSLContext; + +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients; import org.springframework.http.HttpHeaders; import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.reactive.function.client.WebClient; @@ -152,4 +170,96 @@ public interface WebClientProvider { * @since 4.3 */ WebClientProvider withRequestConfigurer(Consumer> requestConfigurer); + + /** + * Creates a {@link WebClientProvider} for the given configuration + * + * @param clientConfiguration must not be {@literal} null + * @return the {@link WebClientProvider} + * @since 4.3 + */ + static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) { + + Assert.notNull(clientConfiguration, "clientConfiguration must not be null"); + + Duration connectTimeout = clientConfiguration.getConnectTimeout(); + Duration soTimeout = clientConfiguration.getSocketTimeout(); + + HttpClient httpClient = HttpClient.create().compress(true); + + if (!connectTimeout.isNegative()) { + httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis())); + } + + if (!soTimeout.isNegative()) { + httpClient = httpClient.doOnConnected(connection -> connection // + .addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)) + .addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))); + } + + if (clientConfiguration.getProxy().isPresent()) { + String proxy = clientConfiguration.getProxy().get(); + String[] hostPort = proxy.split(":"); + + if (hostPort.length != 2) { + throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\""); + } + httpClient = httpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0]) + .port(Integer.parseInt(hostPort[1]))); + } + + String scheme = "http"; + + if (clientConfiguration.useSsl()) { + + Optional sslContext = clientConfiguration.getSslContext(); + + if (sslContext.isPresent()) { + httpClient = httpClient + .secure(sslContextSpec -> sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, + IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false))); + } else { + httpClient = httpClient.secure(); + } + + scheme = "https"; + } + + WebClientProvider provider = WebClientProvider.create(scheme, new ReactorClientHttpConnector(httpClient)); + + if (clientConfiguration.getPathPrefix() != null) { + provider = provider.withPathPrefix(clientConfiguration.getPathPrefix()); + } + + Function webClientConfigurer = webClient -> { + for (ClientConfiguration.ClientConfigurationCallback clientConfigurer : clientConfiguration + .getClientConfigurers()) { + + if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) { + ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer; + webClient = webClientConfigurationCallback.configure(webClient); + } + + if (clientConfigurer instanceof ElasticsearchClients.WebClientConfigurationCallback) { + ElasticsearchClients.WebClientConfigurationCallback webClientConfigurationCallback = (ElasticsearchClients.WebClientConfigurationCallback) clientConfigurer; + webClient = webClientConfigurationCallback.configure(webClient); + } + } + return webClient; + }; + + provider = provider // + .withDefaultHeaders(clientConfiguration.getDefaultHeaders()) // + .withWebClientConfigurer(webClientConfigurer) // + .withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec // + .headers(httpHeaders -> { + HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get(); + + if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) { + httpHeaders.addAll(suppliedHeaders); + } + })); + + return provider; + } } 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 aa825b3b1..40c53096b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -21,16 +21,17 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.data.convert.EntityReader; -import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.event.AfterConvertCallback; import org.springframework.data.elasticsearch.core.event.AfterLoadCallback; import org.springframework.data.elasticsearch.core.event.AfterSaveCallback; @@ -77,7 +78,6 @@ import org.springframework.util.Assert; public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware { protected ElasticsearchConverter elasticsearchConverter; - protected RequestFactory requestFactory; protected EntityOperations entityOperations; @Nullable protected EntityCallbacks entityCallbacks; @Nullable protected RefreshPolicy refreshPolicy; @@ -96,8 +96,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper this.entityOperations = new EntityOperations(mappingContext); this.routingResolver = new DefaultRoutingResolver(mappingContext); - requestFactory = new RequestFactory(this.elasticsearchConverter); - // initialize the VersionInfo class in the initialization phase // noinspection ResultOfMethodCallIgnored VersionInfo.versionProperties(); @@ -429,7 +427,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper /* * internal use only, not for public API */ - abstract protected SearchScrollHits searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis, + abstract protected SearchScrollHits searchScrollContinue(String scrollId, long scrollTimeInMillis, Class clazz, IndexCoordinates index); /* @@ -455,13 +453,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper return elasticsearchConverter; } - /** - * @since 4.0 - */ - public RequestFactory getRequestFactory() { - return requestFactory; - } - protected static String[] toArray(List values) { String[] valuesAsArray = new String[values.size()]; return values.toArray(valuesAsArray); @@ -551,9 +542,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper @Nullable private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) { - EntityOperations.AdaptibleEntity adaptibleEntity = entityOperations.forEntity(entity, + EntityOperations.AdaptableEntity adaptableEntity = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver); - return adaptibleEntity.hasSeqNoPrimaryTerm() ? adaptibleEntity.getSeqNoPrimaryTerm() : null; + return adaptableEntity.hasSeqNoPrimaryTerm() ? adaptableEntity.getSeqNoPrimaryTerm() : null; } private IndexQuery getIndexQuery(T entity) { @@ -588,6 +579,10 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper return builder.build(); } + protected SearchDocumentResponse.EntityCreator getEntityCreator(ReadDocumentCallback documentCallback) { + return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument)); + } + /** * tries to extract the version of the Elasticsearch cluster * diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java index 8095ddf06..3a2def2a9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java @@ -45,8 +45,6 @@ import org.springframework.util.Assert; public abstract class AbstractIndexTemplate implements IndexOperations { protected final ElasticsearchConverter elasticsearchConverter; - protected final RequestFactory requestFactory; - @Nullable protected final Class boundClass; @Nullable private final IndexCoordinates boundIndex; @@ -55,7 +53,6 @@ public abstract class AbstractIndexTemplate implements IndexOperations { Assert.notNull(boundClass, "boundClass may not be null"); this.elasticsearchConverter = elasticsearchConverter; - requestFactory = new RequestFactory(elasticsearchConverter); this.boundClass = boundClass; this.boundIndex = null; } @@ -65,7 +62,6 @@ public abstract class AbstractIndexTemplate implements IndexOperations { Assert.notNull(boundIndex, "boundIndex may not be null"); this.elasticsearchConverter = elasticsearchConverter; - requestFactory = new RequestFactory(elasticsearchConverter); this.boundClass = null; this.boundIndex = boundIndex; } @@ -195,12 +191,9 @@ public abstract class AbstractIndexTemplate implements IndexOperations { @Override public Document createMapping(Class clazz) { - return buildMapping(clazz); - } - - protected Document buildMapping(Class clazz) { // load mapping specified in Mapping annotation if present + // noinspection DuplicatedCode Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); if (mappingAnnotation != null) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java new file mode 100644 index 000000000..3e083b0a1 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java @@ -0,0 +1,685 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.data.convert.EntityReader; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.document.SearchDocument; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback; +import org.springframework.data.elasticsearch.core.event.ReactiveAfterLoadCallback; +import org.springframework.data.elasticsearch.core.event.ReactiveAfterSaveCallback; +import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; +import org.springframework.data.elasticsearch.core.query.ByQueryResponse; +import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; +import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver; +import org.springframework.data.elasticsearch.core.routing.RoutingResolver; +import org.springframework.data.elasticsearch.core.suggest.response.Suggest; +import org.springframework.data.elasticsearch.support.VersionInfo; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Base class keeping common code for implementations of the {@link ReactiveElasticsearchOperations} interface + * independent of the used client. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +abstract public class AbstractReactiveElasticsearchTemplate + implements ReactiveElasticsearchOperations, ApplicationContextAware { + + protected static final Logger QUERY_LOGGER = LoggerFactory + .getLogger("org.springframework.data.elasticsearch.core.QUERY"); + + protected final ElasticsearchConverter converter; + protected final SimpleElasticsearchMappingContext mappingContext; + protected final EntityOperations entityOperations; + + protected @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE; + protected RoutingResolver routingResolver; + + protected @Nullable ReactiveEntityCallbacks entityCallbacks; + + // region Initialization + protected AbstractReactiveElasticsearchTemplate(@Nullable ElasticsearchConverter converter) { + + this.converter = converter != null ? converter : createElasticsearchConverter(); + this.mappingContext = (SimpleElasticsearchMappingContext) this.converter.getMappingContext(); + this.entityOperations = new EntityOperations(this.mappingContext); + this.routingResolver = new DefaultRoutingResolver(this.mappingContext); + + // initialize the VersionInfo class in the initialization phase + // noinspection ResultOfMethodCallIgnored + VersionInfo.versionProperties(); + } + + @Override + public ElasticsearchConverter getElasticsearchConverter() { + return converter; + } + + /** + * @return copy of this instance. + */ + private AbstractReactiveElasticsearchTemplate copy() { + + AbstractReactiveElasticsearchTemplate copy = doCopy(); + copy.setRefreshPolicy(refreshPolicy); + + if (entityCallbacks != null) { + copy.setEntityCallbacks(entityCallbacks); + } + + copy.setRoutingResolver(routingResolver); + return copy; + } + + abstract protected AbstractReactiveElasticsearchTemplate doCopy(); + + private ElasticsearchConverter createElasticsearchConverter() { + MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter( + new SimpleElasticsearchMappingContext()); + mappingElasticsearchConverter.afterPropertiesSet(); + return mappingElasticsearchConverter; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + if (entityCallbacks == null) { + setEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext)); + } + } + + /** + * Set the default {@link RefreshPolicy} to apply when writing to Elasticsearch. + * + * @param refreshPolicy can be {@literal null}. + */ + public void setRefreshPolicy(@Nullable RefreshPolicy refreshPolicy) { + this.refreshPolicy = refreshPolicy; + } + + /** + * @return the current {@link RefreshPolicy}. + */ + + @Nullable + public RefreshPolicy getRefreshPolicy() { + return refreshPolicy; + } + + /** + * Set the {@link ReactiveEntityCallbacks} instance to use when invoking {@link ReactiveEntityCallbacks callbacks} + * like the {@link ReactiveBeforeConvertCallback}. + *

+ * Overrides potentially existing {@link ReactiveEntityCallbacks}. + * + * @param entityCallbacks must not be {@literal null}. + * @throws IllegalArgumentException if the given instance is {@literal null}. + * @since 4.0 + */ + public void setEntityCallbacks(ReactiveEntityCallbacks entityCallbacks) { + + Assert.notNull(entityCallbacks, "EntityCallbacks must not be null!"); + + this.entityCallbacks = entityCallbacks; + } + + /** + * logs the versions of the different Elasticsearch components. + * + * @return a Mono signalling finished execution + * @since 4.3 + */ + public Mono logVersions() { + + return getVendor() // + .zipWith(getRuntimeLibraryVersion()) // + .zipWith(getClusterVersion()) // + .doOnNext(objects -> { + VersionInfo.logVersions(objects.getT1().getT1(), objects.getT1().getT2(), objects.getT2()); + }).then(); + } + + // endregion + + // region routing + private void setRoutingResolver(RoutingResolver routingResolver) { + + Assert.notNull(routingResolver, "routingResolver must not be null"); + + this.routingResolver = routingResolver; + } + + @Override + public ReactiveElasticsearchOperations withRouting(RoutingResolver routingResolver) { + + Assert.notNull(routingResolver, "routingResolver must not be null"); + + AbstractReactiveElasticsearchTemplate copy = copy(); + copy.setRoutingResolver(routingResolver); + return copy; + } + // endregion + + // region DocumentOperations + @Override + public Mono save(T entity) { + return save(entity, getIndexCoordinatesFor(entity.getClass())); + } + + @Override + public Flux saveAll(Mono> entities, Class clazz) { + return saveAll(entities, getIndexCoordinatesFor(clazz)); + } + + protected IndexQuery getIndexQuery(Object value) { + EntityOperations.AdaptableEntity entity = entityOperations.forEntity(value, converter.getConversionService(), + routingResolver); + + Object id = entity.getId(); + IndexQuery query = new IndexQuery(); + + if (id != null) { + query.setId(id.toString()); + } + query.setObject(value); + + boolean usingSeqNo = false; + + if (entity.hasSeqNoPrimaryTerm()) { + SeqNoPrimaryTerm seqNoPrimaryTerm = entity.getSeqNoPrimaryTerm(); + + if (seqNoPrimaryTerm != null) { + query.setSeqNo(seqNoPrimaryTerm.getSequenceNumber()); + query.setPrimaryTerm(seqNoPrimaryTerm.getPrimaryTerm()); + usingSeqNo = true; + } + } + + // seq_no and version are incompatible in the same request + if (!usingSeqNo && entity.isVersionedEntity()) { + + Number version = entity.getVersion(); + + if (version != null) { + query.setVersion(version.longValue()); + } + } + + query.setRouting(entity.getRouting()); + + return query; + } + + protected T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { + + 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 { + EntityOperations.AdaptableEntity adaptableEntity = entityOperations.forEntity(entity, + converter.getConversionService(), routingResolver); + adaptableEntity.populateIdIfNecessary(indexedObjectInformation.getId()); + } + return entity; + } + + @Override + public Flux> multiGet(Query query, Class clazz) { + return multiGet(query, clazz, getIndexCoordinatesFor(clazz)); + } + + @Override + public Mono exists(String id, Class entityType) { + return doExists(id, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono exists(String id, IndexCoordinates index) { + return doExists(id, index); + } + + @Override + public Mono save(T entity, IndexCoordinates index) { + + Assert.notNull(entity, "Entity must not be null!"); + Assert.notNull(index, "index must not be null"); + + return maybeCallBeforeConvert(entity, index) + .flatMap(entityAfterBeforeConversionCallback -> doIndex(entityAfterBeforeConversionCallback, index)) // + .map(it -> { + T savedEntity = it.getT1(); + IndexResponseMetaData indexResponseMetaData = it.getT2(); + return updateIndexedObject(savedEntity, IndexedObjectInformation.of( // + indexResponseMetaData.getId(), // + indexResponseMetaData.getSeqNo(), // + indexResponseMetaData.getPrimaryTerm(), // + indexResponseMetaData.getVersion())); + }).flatMap(saved -> maybeCallAfterSave(saved, index)); + } + + abstract protected Mono> doIndex(T entity, IndexCoordinates index); + + abstract protected Mono doExists(String id, IndexCoordinates index); + + @Override + public Mono get(String id, Class entityType) { + return get(id, entityType, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono delete(Object entity, IndexCoordinates index) { + + EntityOperations.AdaptableEntity elasticsearchEntity = entityOperations.forEntity(entity, + converter.getConversionService(), routingResolver); + + if (elasticsearchEntity.getId() == null) { + return Mono.error(new IllegalArgumentException("entity must have an id")); + } + + return Mono.defer(() -> { + String id = converter.convertId(elasticsearchEntity.getId()); + String routing = elasticsearchEntity.getRouting(); + return doDeleteById(id, routing, index); + }); + } + + @Override + public Mono delete(Object entity) { + return delete(entity, getIndexCoordinatesFor(entity.getClass())); + } + + @Override + public Mono delete(String id, Class entityType) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(entityType, "entityType must not be null"); + + return delete(id, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono delete(String id, IndexCoordinates index) { + + Assert.notNull(id, "id must not be null"); + Assert.notNull(index, "index must not be null"); + + return doDeleteById(id, routingResolver.getRouting(), index); + } + + abstract protected Mono doDeleteById(String id, @Nullable String routing, IndexCoordinates index); + + @Override + public Mono delete(Query query, Class entityType) { + return delete(query, entityType, getIndexCoordinatesFor(entityType)); + } + // endregion + + // region SearchDocument + @Override + public Flux> search(Query query, Class entityType, Class resultType, IndexCoordinates index) { + SearchDocumentCallback callback = new ReadSearchDocumentCallback<>(resultType, index); + return doFind(query, entityType, index).concatMap(callback::toSearchHit); + } + + @Override + public Flux> search(Query query, Class entityType, Class returnType) { + return search(query, entityType, returnType, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono> searchForPage(Query query, Class entityType, Class resultType) { + return searchForPage(query, entityType, resultType, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono> searchForPage(Query query, Class entityType, Class resultType, + IndexCoordinates index) { + + SearchDocumentCallback callback = new ReadSearchDocumentCallback<>(resultType, index); + + return doFindForResponse(query, entityType, index) // + .flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) // + .flatMap(callback::toEntity) // + .collectList() // + .map(entities -> SearchHitMapping.mappingFor(resultType, converter) // + .mapHits(searchDocumentResponse, entities))) // + .map(searchHits -> SearchHitSupport.searchPageFor(searchHits, query.getPageable())); + } + + @Override + public Mono> searchForHits(Query query, Class entityType, Class resultType) { + return searchForHits(query, entityType, resultType, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono> searchForHits(Query query, Class entityType, Class resultType, + IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(entityType, "entityType must not be null"); + Assert.notNull(resultType, "resultType must not be null"); + Assert.notNull(index, "index must not be null"); + + SearchDocumentCallback callback = new ReadSearchDocumentCallback<>(resultType, index); + + return doFindForResponse(query, entityType, index) // + .flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) // + .flatMap(callback::toEntity) // + .collectList() // + .map(entities -> SearchHitMapping.mappingFor(resultType, converter) // + .mapHits(searchDocumentResponse, entities))) // + .map(ReactiveSearchHitSupport::searchHitsFor); + } + + abstract protected Flux doFind(Query query, Class clazz, IndexCoordinates index); + + abstract protected Mono doFindForResponse(Query query, Class clazz, + IndexCoordinates index); + + @Override + public Flux> aggregate(Query query, Class entityType) { + return aggregate(query, entityType, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono suggest(Query query, Class entityType) { + return suggest(query, entityType, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono suggest(Query query, Class entityType, IndexCoordinates index) { + + Assert.notNull(query, "query must not be null"); + Assert.notNull(entityType, "entityType must not be null"); + Assert.notNull(index, "index must not be null"); + + return doFindForResponse(query, entityType, index).mapNotNull(searchDocumentResponse -> { + Suggest suggest = searchDocumentResponse.getSuggest(); + SearchHitMapping.mappingFor(entityType, converter).mapHitsInCompletionSuggestion(suggest); + return suggest; + }); + } + + @Override + public Mono count(Query query, Class entityType) { + return count(query, entityType, getIndexCoordinatesFor(entityType)); + } + + @Override + public Mono count(Query query, Class entityType, IndexCoordinates index) { + return doCount(query, entityType, index); + } + + abstract protected Mono doCount(Query query, Class entityType, IndexCoordinates index); + // endregion + + // region callbacks + + protected Mono maybeCallBeforeConvert(T entity, IndexCoordinates index) { + + if (null != entityCallbacks) { + return entityCallbacks.callback(ReactiveBeforeConvertCallback.class, entity, index); + } + + return Mono.just(entity); + } + + protected Mono maybeCallAfterSave(T entity, IndexCoordinates index) { + + if (null != entityCallbacks) { + return entityCallbacks.callback(ReactiveAfterSaveCallback.class, entity, index); + } + + return Mono.just(entity); + } + + protected Mono maybeCallAfterConvert(T entity, Document document, IndexCoordinates index) { + + if (null != entityCallbacks) { + return entityCallbacks.callback(ReactiveAfterConvertCallback.class, entity, document, index); + } + + return Mono.just(entity); + } + + protected Mono maybeCallbackAfterLoad(Document document, Class type, IndexCoordinates index) { + if (entityCallbacks != null) { + return entityCallbacks.callback(ReactiveAfterLoadCallback.class, document, type, index); + } + + return Mono.just(document); + } + + protected interface DocumentCallback { + + @NonNull + Mono toEntity(@Nullable Document document); + } + + protected class ReadDocumentCallback implements DocumentCallback { + private final EntityReader reader; + private final Class type; + private final IndexCoordinates index; + + public ReadDocumentCallback(EntityReader reader, Class type, IndexCoordinates index) { + Assert.notNull(reader, "reader is null"); + Assert.notNull(type, "type is null"); + + this.reader = reader; + this.type = type; + this.index = index; + } + + @NonNull + public Mono toEntity(@Nullable Document document) { + if (document == null) { + return Mono.empty(); + } + + return maybeCallbackAfterLoad(document, type, index) // + .flatMap(documentAfterLoad -> { + + T entity = reader.read(type, documentAfterLoad); + + IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( // + documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, // + documentAfterLoad.getSeqNo(), // + documentAfterLoad.getPrimaryTerm(), // + documentAfterLoad.getVersion()); // + entity = updateIndexedObject(entity, indexedObjectInformation); + + return maybeCallAfterConvert(entity, documentAfterLoad, index); + }); + } + } + + protected interface SearchDocumentCallback { + + Mono toEntity(SearchDocument response); + + Mono> toSearchHit(SearchDocument response); + } + + protected class ReadSearchDocumentCallback implements SearchDocumentCallback { + private final DocumentCallback delegate; + private final Class type; + + public ReadSearchDocumentCallback(Class type, IndexCoordinates index) { + Assert.notNull(type, "type is null"); + + this.delegate = new ReadDocumentCallback<>(converter, type, index); + this.type = type; + } + + @Override + public Mono toEntity(SearchDocument response) { + return delegate.toEntity(response); + } + + @Override + public Mono> toSearchHit(SearchDocument response) { + return toEntity(response).map(entity -> SearchHitMapping.mappingFor(type, converter).mapHit(response, entity)); + } + } + + // endregion + + // region Helper methods + @Override + public IndexCoordinates getIndexCoordinatesFor(Class clazz) { + + ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz); + + Assert.notNull(persistentEntity, "could not get indexCoordinates for class " + clazz.getName()); + + return persistentEntity.getIndexCoordinates(); + } + + @Override + @Nullable + public ElasticsearchPersistentEntity getPersistentEntityFor(@Nullable Class type) { + return type != null ? mappingContext.getPersistentEntity(type) : null; + } + + /** + * @return the vendor name of the used cluster and client library + * @since 4.3 + */ + abstract protected Mono getVendor(); + + /** + * @return the version of the used client runtime library. + * @since 4.3 + */ + abstract protected Mono getRuntimeLibraryVersion(); + + abstract protected Mono getClusterVersion(); + + /** + * Value class to capture client independent information from a response to an index request. + */ + public static class IndexResponseMetaData { + private final String id; + private final long seqNo; + private final long primaryTerm; + private final long version; + + public IndexResponseMetaData(String id, long seqNo, long primaryTerm, long version) { + this.id = id; + this.seqNo = seqNo; + this.primaryTerm = primaryTerm; + this.version = version; + } + + public String getId() { + return id; + } + + public long getSeqNo() { + return seqNo; + } + + public long getPrimaryTerm() { + return primaryTerm; + } + + public long getVersion() { + return version; + } + } + // endregion + + protected class Entities { + private final List entities; + + public Entities(List entities) { + + Assert.notNull(entities, "entities cannot be null"); + + this.entities = entities; + } + + public boolean isEmpty() { + return entities.isEmpty(); + } + + public List indexQueries() { + return entities.stream().map(value -> getIndexQuery(value)).collect(Collectors.toList()); + } + + public T entityAt(long index) { + // it's safe to cast to int because the original indexed collection was fitting in memory + int intIndex = (int) index; + return entities.get(intIndex); + } + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java index 49a5594a6..ab49b12fb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java @@ -44,7 +44,6 @@ import org.springframework.util.Assert; * @author Rasmus Faber-Espensen * @author James Bodkin * @author Peter-Josef Meisch - * @since 4.4 */ class CriteriaQueryProcessor { 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 8db157100..2eb74cf44 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -12,7 +12,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - */ +*/ package org.springframework.data.elasticsearch.core; import java.io.IOException; @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -62,7 +61,7 @@ import org.springframework.data.elasticsearch.core.cluster.ClusterOperations; import org.springframework.data.elasticsearch.core.cluster.ElasticsearchClusterOperations; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.DocumentAdapters; -import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponseBuilder; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.BulkOptions; import org.springframework.data.elasticsearch.core.query.ByQueryResponse; @@ -118,13 +117,16 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { private final RestHighLevelClient client; private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator(); + protected RequestFactory requestFactory; - // region Initialization + // region _initialization public ElasticsearchRestTemplate(RestHighLevelClient client) { Assert.notNull(client, "Client must not be null!"); this.client = client; + requestFactory = new RequestFactory(this.elasticsearchConverter); + } public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) { @@ -134,12 +136,23 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { Assert.notNull(client, "Client must not be null!"); this.client = client; + requestFactory = new RequestFactory(this.elasticsearchConverter); } @Override protected AbstractElasticsearchTemplate doCopy() { - return new ElasticsearchRestTemplate(client, elasticsearchConverter); + ElasticsearchRestTemplate copy = new ElasticsearchRestTemplate(client, elasticsearchConverter); + copy.requestFactory = this.requestFactory; + return copy; } + + /** + * @since 4.0 + */ + public RequestFactory getRequestFactory() { + return requestFactory; + } + // endregion // region IndexOperations @@ -282,22 +295,23 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { } @Override - public ReindexResponse reindex(ReindexRequest postReindexRequest) { + public ReindexResponse reindex(ReindexRequest reindexRequest) { - Assert.notNull(postReindexRequest, "postReindexRequest must not be null"); + Assert.notNull(reindexRequest, "reindexRequest must not be null"); - org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest); + org.elasticsearch.index.reindex.ReindexRequest reindexRequestES = requestFactory.reindexRequest(reindexRequest); BulkByScrollResponse bulkByScrollResponse = execute( - client -> client.reindex(reindexRequest, RequestOptions.DEFAULT)); + client -> client.reindex(reindexRequestES, RequestOptions.DEFAULT)); return ResponseConverter.reindexResponseOf(bulkByScrollResponse); } @Override - public String submitReindex(ReindexRequest postReindexRequest) { - Assert.notNull(postReindexRequest, "postReindexRequest must not be null"); + public String submitReindex(ReindexRequest reindexRequest) { - org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest); - return execute(client -> client.submitReindexTask(reindexRequest, RequestOptions.DEFAULT).getTask()); + Assert.notNull(reindexRequest, "reindexRequest must not be null"); + + org.elasticsearch.index.reindex.ReindexRequest reindexRequestES = requestFactory.reindexRequest(reindexRequest); + return execute(client -> client.submitReindexTask(reindexRequestES, RequestOptions.DEFAULT).getTask()); } public List doBulkOperation(List queries, BulkOptions bulkOptions, @@ -387,7 +401,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { ReadDocumentCallback documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); - return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))); + return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback))); } protected SearchHits doSearch(MoreLikeThisQuery query, Class clazz, IndexCoordinates index) { @@ -411,12 +425,12 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { ReadDocumentCallback documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); SearchDocumentResponseCallback> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz, index); - return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))); + return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback))); } @Override - public SearchScrollHits searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis, - Class clazz, IndexCoordinates index) { + public SearchScrollHits searchScrollContinue(String scrollId, long scrollTimeInMillis, Class clazz, + IndexCoordinates index) { SearchScrollRequest request = new SearchScrollRequest(scrollId); request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis)); @@ -426,7 +440,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { ReadDocumentCallback documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); SearchDocumentResponseCallback> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz, index); - return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))); + return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback))); } @Override @@ -459,7 +473,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); List> res = new ArrayList<>(queries.size()); for (int i = 0; i < queries.size(); i++) { - res.add(callback.doWith(SearchDocumentResponse.from(items[i].getResponse(), getEntityCreator(documentCallback)))); + res.add(callback + .doWith(SearchDocumentResponseBuilder.from(items[i].getResponse(), getEntityCreator(documentCallback)))); } return res; } @@ -492,7 +507,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { index); SearchResponse response = items[i].getResponse(); - res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)))); + res.add(callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)))); } return res; } @@ -525,7 +540,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { index); SearchResponse response = items[i].getResponse(); - res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)))); + res.add(callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)))); } return res; } @@ -537,11 +552,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { return items; } - private SearchDocumentResponse.EntityCreator getEntityCreator(ReadDocumentCallback documentCallback) { - return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument)); - } - // endregion + // region ClientCallback /** * Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on 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 38071474c..bac4e3cce 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java @@ -73,15 +73,15 @@ public class EntityOperations { } /** - * Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService} and {@link RoutingResolver}. + * Creates a new {@link AdaptableEntity} for the given bean and {@link ConversionService} and {@link RoutingResolver}. * * @param entity must not be {@literal null}. * @param conversionService must not be {@literal null}. * @param routingResolver the {@link RoutingResolver}, must not be {@literal null} - * @return the {@link AdaptibleEntity} + * @return the {@link AdaptableEntity} */ @SuppressWarnings({ "unchecked", "rawtypes" }) - public AdaptibleEntity forEntity(T entity, ConversionService conversionService, + public AdaptableEntity forEntity(T entity, ConversionService conversionService, RoutingResolver routingResolver) { Assert.notNull(entity, "Bean must not be null!"); @@ -91,7 +91,7 @@ public class EntityOperations { return new SimpleMappedEntity((Map) entity); } - return AdaptibleMappedEntity.of(entity, context, conversionService, routingResolver); + return AdaptableMappedEntity.of(entity, context, conversionService, routingResolver); } /** @@ -199,7 +199,7 @@ public class EntityOperations { * @author Mark Paluch * @author Christoph Strobl */ - public interface AdaptibleEntity extends Entity { + public interface AdaptableEntity extends Entity { /** * Populates the identifier of the backing entity if it has an identifier property and there's no identifier @@ -267,7 +267,7 @@ public class EntityOperations { * @author Christoph Strobl * @since 3.2 */ - private static class MapBackedEntity> implements AdaptibleEntity { + private static class MapBackedEntity> implements AdaptableEntity { public MapBackedEntity(T map) { @@ -289,7 +289,7 @@ public class EntityOperations { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object) + * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#populateIdIfNecessary(java.lang.Object) */ @Nullable @Override @@ -302,7 +302,7 @@ public class EntityOperations { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#initializeVersionProperty() + * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#initializeVersionProperty() */ @Override public T initializeVersionProperty() { @@ -311,7 +311,7 @@ public class EntityOperations { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getVersion() + * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#getVersion() */ @Override @Nullable @@ -331,7 +331,7 @@ public class EntityOperations { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#incrementVersion() + * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#incrementVersion() */ @Override public T incrementVersion() { @@ -476,7 +476,7 @@ public class EntityOperations { * @param * @since 3.2 */ - private static class AdaptibleMappedEntity extends MappedEntity implements AdaptibleEntity { + private static class AdaptableMappedEntity extends MappedEntity implements AdaptableEntity { private final ElasticsearchPersistentEntity entity; private final ConvertingPropertyAccessor propertyAccessor; @@ -484,7 +484,7 @@ public class EntityOperations { private final ConversionService conversionService; private final RoutingResolver routingResolver; - private AdaptibleMappedEntity(T bean, ElasticsearchPersistentEntity entity, + private AdaptableMappedEntity(T bean, ElasticsearchPersistentEntity entity, IdentifierAccessor identifierAccessor, ConvertingPropertyAccessor propertyAccessor, ConversionService conversionService, RoutingResolver routingResolver) { @@ -497,7 +497,7 @@ public class EntityOperations { this.routingResolver = routingResolver; } - static AdaptibleEntity of(T bean, + static AdaptableEntity of(T bean, MappingContext, ElasticsearchPersistentProperty> context, ConversionService conversionService, RoutingResolver routingResolver) { @@ -505,7 +505,7 @@ public class EntityOperations { IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean); PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(bean); - return new AdaptibleMappedEntity<>(bean, entity, identifierAccessor, + return new AdaptableMappedEntity<>(bean, entity, identifierAccessor, new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java index 99f413aaa..e07744627 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java @@ -228,8 +228,7 @@ public interface IndexOperations { boolean putTemplate(PutTemplateRequest putTemplateRequest); /** - * gets an index template using the legacy Elasticsearch - * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * gets an index template using the legacy Elasticsearch interface. * * @param templateName the template name * @return TemplateData, {@literal null} if no template with the given name exists. @@ -241,8 +240,7 @@ public interface IndexOperations { } /** - * gets an index template using the legacy Elasticsearch - * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * gets an index template using the legacy Elasticsearch interface. * * @param getTemplateRequest the request parameters * @return TemplateData, {@literal null} if no template with the given name exists. @@ -252,8 +250,7 @@ public interface IndexOperations { TemplateData getTemplate(GetTemplateRequest getTemplateRequest); /** - * check if an index template exists using the legacy Elasticsearch - * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * check if an index template exists using the legacy Elasticsearch interface. * * @param templateName the template name * @return {@literal true} if the index exists @@ -264,8 +261,7 @@ public interface IndexOperations { } /** - * check if an index template exists using the legacy Elasticsearch - * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * check if an index template exists using the legacy Elasticsearch interface. * * @param existsTemplateRequest the request parameters * @return {@literal true} if the index exists diff --git a/src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java b/src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java index 9e415a94b..cdefdca08 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java @@ -15,12 +15,13 @@ */ package org.springframework.data.elasticsearch.core; +import org.springframework.data.elasticsearch.ElasticsearchErrorCause; import org.springframework.lang.Nullable; /** * Response object for items returned from multiget requests, encapsulating the returned data and potential error * information. - * + * * @param the entity type * @author Peter-Josef Meisch * @since 4.2 @@ -61,16 +62,20 @@ public class MultiGetItem { @Nullable private final String type; @Nullable private final String id; @Nullable private final Exception exception; + @Nullable private final ElasticsearchErrorCause elasticsearchErrorCause; - private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception exception) { + private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception exception, + @Nullable ElasticsearchErrorCause elasticsearchErrorCause) { this.index = index; this.type = type; this.id = id; this.exception = exception; + this.elasticsearchErrorCause = elasticsearchErrorCause; } - public static Failure of(String index, String type, String id, Exception exception) { - return new Failure(index, type, id, exception); + public static Failure of(String index, @Nullable String type, String id, @Nullable Exception exception, + @Nullable ElasticsearchErrorCause elasticsearchErrorCause) { + return new Failure(index, type, id, exception, elasticsearchErrorCause); } @Nullable @@ -92,5 +97,10 @@ public class MultiGetItem { public Exception getException() { return exception; } + + @Nullable + public ElasticsearchErrorCause getElasticsearchErrorCause() { + return elasticsearchErrorCause; + } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java index 0dfcc5abe..031dbc7a5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java @@ -44,7 +44,9 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati * @param callback must not be {@literal null}. * @param the type the Publisher emits * @return the {@link Publisher} emitting results. + * @deprecated since 4.4, use the execute methods from the implementing classes (they are client specific) */ + @Deprecated Publisher execute(ClientCallback> callback); /** @@ -126,7 +128,7 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati * Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on * {@link ReactiveElasticsearchClient}. * - * @param + * @param result type of the callback * @author Christoph Strobl * @since 3.2 */ 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 016f1fbf9..974f79baa 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -23,10 +23,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.elasticsearch.Version; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.bulk.BulkItemResponse; @@ -48,49 +45,27 @@ import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.UpdateByQueryRequest; import org.elasticsearch.search.suggest.SuggestBuilder; import org.reactivestreams.Publisher; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.data.convert.EntityReader; import org.springframework.data.elasticsearch.BulkFailureException; import org.springframework.data.elasticsearch.NoSuchIndexException; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; -import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity; import org.springframework.data.elasticsearch.core.cluster.DefaultReactiveClusterOperations; import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; -import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.document.SearchDocument; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; -import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback; -import org.springframework.data.elasticsearch.core.event.ReactiveAfterLoadCallback; -import org.springframework.data.elasticsearch.core.event.ReactiveAfterSaveCallback; -import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback; -import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; -import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponseBuilder; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.BulkOptions; import org.springframework.data.elasticsearch.core.query.ByQueryResponse; -import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; -import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateResponse; import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; import org.springframework.data.elasticsearch.core.reindex.ReindexResponse; -import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver; -import org.springframework.data.elasticsearch.core.routing.RoutingResolver; -import org.springframework.data.elasticsearch.core.suggest.response.Suggest; -import org.springframework.data.elasticsearch.support.VersionInfo; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.http.HttpStatus; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -109,98 +84,37 @@ import org.springframework.util.Assert; * @author Sijia Liu * @since 3.2 */ -public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware { - - private static final Log QUERY_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.core.QUERY"); +public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate { private final ReactiveElasticsearchClient client; - private final ElasticsearchConverter converter; - private final SimpleElasticsearchMappingContext mappingContext; private final ElasticsearchExceptionTranslator exceptionTranslator; - private final EntityOperations operations; protected RequestFactory requestFactory; - private @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE; private @Nullable IndicesOptions indicesOptions = IndicesOptions.strictExpandOpenAndForbidClosedIgnoreThrottled(); - private @Nullable ReactiveEntityCallbacks entityCallbacks; - - private RoutingResolver routingResolver; - // region Initialization public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client) { - this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext())); + this(client, null); } - public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) { + public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, @Nullable ElasticsearchConverter converter) { + + super(converter); Assert.notNull(client, "client must not be null"); - Assert.notNull(converter, "converter must not be null"); this.client = client; - this.converter = converter; - - this.mappingContext = (SimpleElasticsearchMappingContext) converter.getMappingContext(); - this.routingResolver = new DefaultRoutingResolver(this.mappingContext); - this.exceptionTranslator = new ElasticsearchExceptionTranslator(); - this.operations = new EntityOperations(this.mappingContext); - this.requestFactory = new RequestFactory(converter); - - // initialize the VersionInfo class in the initialization phase - // noinspection ResultOfMethodCallIgnored - VersionInfo.versionProperties(); + this.requestFactory = new RequestFactory(this.converter); } - private ReactiveElasticsearchTemplate copy() { + protected ReactiveElasticsearchTemplate doCopy() { ReactiveElasticsearchTemplate copy = new ReactiveElasticsearchTemplate(client, converter); - copy.setRefreshPolicy(refreshPolicy); copy.setIndicesOptions(indicesOptions); - copy.setEntityCallbacks(entityCallbacks); - copy.setRoutingResolver(routingResolver); return copy; } - /** - * logs the versions of the different Elasticsearch components. - * - * @return a Mono signalling finished execution - * @since 4.3 - */ - public Mono logVersions() { - return getVendor() // - .doOnNext(vendor -> getRuntimeLibraryVersion() // - .doOnNext(runtimeLibraryVersion -> getClusterVersion() // - .doOnNext(clusterVersion -> VersionInfo.logVersions(vendor, runtimeLibraryVersion, clusterVersion)))) // - .then(); // - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - - if (entityCallbacks == null) { - setEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext)); - } - } - - /** - * Set the default {@link RefreshPolicy} to apply when writing to Elasticsearch. - * - * @param refreshPolicy can be {@literal null}. - */ - public void setRefreshPolicy(@Nullable RefreshPolicy refreshPolicy) { - this.refreshPolicy = refreshPolicy; - } - - /** - * @return the current {@link RefreshPolicy}. - */ - @Nullable - public RefreshPolicy getRefreshPolicy() { - return refreshPolicy; - } - /** * Set the default {@link IndicesOptions} for {@link SearchRequest search requests}. * @@ -210,58 +124,14 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera this.indicesOptions = indicesOptions; } - /** - * Set the {@link ReactiveEntityCallbacks} instance to use when invoking {@link ReactiveEntityCallbacks callbacks} - * like the {@link ReactiveBeforeConvertCallback}. - *

- * Overrides potentially existing {@link ReactiveEntityCallbacks}. - * - * @param entityCallbacks must not be {@literal null}. - * @throws IllegalArgumentException if the given instance is {@literal null}. - * @since 4.0 - */ - public void setEntityCallbacks(ReactiveEntityCallbacks entityCallbacks) { - - Assert.notNull(entityCallbacks, "EntityCallbacks must not be null!"); - - this.entityCallbacks = entityCallbacks; - } // endregion // region DocumentOperations - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.ReactiveDElasticsearchOperations#index(Object, IndexCoordinates) - */ - @Override - public Mono save(T entity, IndexCoordinates index) { - - Assert.notNull(entity, "Entity must not be null!"); - - return maybeCallBeforeConvert(entity, index) - .flatMap(entityAfterBeforeConversionCallback -> doIndex(entityAfterBeforeConversionCallback, index)) // - .map(it -> { - T savedEntity = it.getT1(); - IndexResponse indexResponse = it.getT2(); - return updateIndexedObject(savedEntity, IndexedObjectInformation.of(indexResponse.getId(), - indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion())); - }).flatMap(saved -> maybeCallAfterSave(saved, index)); - } - - @Override - public Mono save(T entity) { - return save(entity, getIndexCoordinatesFor(entity.getClass())); - } - - @Override - public Flux saveAll(Mono> entities, Class clazz) { - return saveAll(entities, getIndexCoordinatesFor(clazz)); - } @Override public Flux saveAll(Mono> entitiesPublisher, IndexCoordinates index) { - Assert.notNull(entitiesPublisher, "Entities must not be null!"); + Assert.notNull(entitiesPublisher, "entitiesPublisher must not be null!"); return entitiesPublisher // .flatMapMany(entities -> Flux.fromIterable(entities) // @@ -275,7 +145,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera } return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index) // - .index().flatMap(indexAndResponse -> { + .index() // + .flatMap(indexAndResponse -> { T savedEntity = entities.entityAt(indexAndResponse.getT1()); BulkItemResponse bulkItemResponse = indexAndResponse.getT2(); @@ -288,51 +159,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera }); } - private T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { - - 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; - } - - @Override - public Flux> multiGet(Query query, Class clazz) { - return multiGet(query, clazz, getIndexCoordinatesFor(clazz)); - } - @Override public Flux> multiGet(Query query, Class clazz, IndexCoordinates index) { @@ -352,16 +178,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera ); } - @Override - public Mono bulkUpdate(List queries, BulkOptions bulkOptions, IndexCoordinates index) { - - Assert.notNull(queries, "List of UpdateQuery must not be null"); - Assert.notNull(bulkOptions, "BulkOptions must not be null"); - Assert.notNull(index, "Index must not be null"); - - return doBulkOperation(queries, bulkOptions, index).then(); - } - /** * Customization hook on the actual execution result {@link Publisher}.
* You know what you're doing here? Well fair enough, go ahead on your own risk. @@ -373,7 +189,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera return Mono.from(execute(client -> client.index(request))); } + @Override + public Mono bulkUpdate(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + + Assert.notNull(queries, "List of UpdateQuery must not be null"); + Assert.notNull(bulkOptions, "BulkOptions must not be null"); + Assert.notNull(index, "Index must not be null"); + + return doBulkOperation(queries, bulkOptions, index).then(); + } + protected Flux doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index) { + BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index)); return client.bulk(bulkRequest) // .onErrorMap(e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest, e)) // @@ -401,17 +228,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera } } - @Override - public Mono exists(String id, Class entityType) { - return doExists(id, getIndexCoordinatesFor(entityType)); - } - - @Override - public Mono exists(String id, IndexCoordinates index) { - return doExists(id, index); - } - - private Mono doExists(String id, IndexCoordinates index) { + protected Mono doExists(String id, IndexCoordinates index) { return Mono.defer(() -> doExists(requestFactory.getRequest(id, routingResolver.getRouting(), index))); } @@ -427,54 +244,17 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera .onErrorReturn(NoSuchIndexException.class, false); } - private Mono> doIndex(T entity, IndexCoordinates index) { + protected Mono> doIndex(T entity, IndexCoordinates index) { IndexRequest request = requestFactory.indexRequest(getIndexQuery(entity), index); request = prepareIndexRequest(entity, request); - return Mono.just(entity).zipWith(doIndex(request)); - } - - private IndexQuery getIndexQuery(Object value) { - AdaptibleEntity entity = operations.forEntity(value, converter.getConversionService(), routingResolver); - - Object id = entity.getId(); - IndexQuery query = new IndexQuery(); - - if (id != null) { - query.setId(id.toString()); - } - query.setObject(value); - - boolean usingSeqNo = false; - - if (entity.hasSeqNoPrimaryTerm()) { - SeqNoPrimaryTerm seqNoPrimaryTerm = entity.getSeqNoPrimaryTerm(); - - if (seqNoPrimaryTerm != null) { - query.setSeqNo(seqNoPrimaryTerm.getSequenceNumber()); - query.setPrimaryTerm(seqNoPrimaryTerm.getPrimaryTerm()); - usingSeqNo = true; - } - } - - // seq_no and version are incompatible in the same request - if (!usingSeqNo && entity.isVersionedEntity()) { - - Number version = entity.getVersion(); - - if (version != null) { - query.setVersion(version.longValue()); - } - } - - query.setRouting(entity.getRouting()); - - return query; - } - - @Override - public Mono get(String id, Class entityType) { - return get(id, entityType, getIndexCoordinatesFor(entityType)); + return Mono.just(entity).zipWith(doIndex(request) // + .map(indexResponse -> new IndexResponseMetaData( // + indexResponse.getId(), // + indexResponse.getSeqNo(), // + indexResponse.getPrimaryTerm(), // + indexResponse.getVersion() // + ))); // } @Override @@ -482,13 +262,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera Assert.notNull(id, "Id must not be null!"); + GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(), index); + Mono getResult = doGet(request); + DocumentCallback callback = new ReadDocumentCallback<>(converter, entityType, index); - - return doGet(id, index).flatMap(response -> callback.toEntity(DocumentAdapters.from(response))); - } - - private Mono doGet(String id, IndexCoordinates index) { - return Mono.defer(() -> doGet(requestFactory.getRequest(id, routingResolver.getRouting(), index))); + return getResult.flatMap(response -> callback.toEntity(DocumentAdapters.from(response))); } /** @@ -503,51 +281,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera .onErrorResume(NoSuchIndexException.class, it -> Mono.empty()); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#delete(Object, String, String) - */ - @Override - public Mono delete(Object entity, IndexCoordinates index) { - - AdaptibleEntity elasticsearchEntity = operations.forEntity(entity, converter.getConversionService(), - routingResolver); - - if (elasticsearchEntity.getId() == null) { - return Mono.error(new IllegalArgumentException("entity must have an id")); - } - - return Mono.defer(() -> { - String id = converter.convertId(elasticsearchEntity.getId()); - String routing = elasticsearchEntity.getRouting(); - return doDeleteById(id, routing, index); - }); - } - - @Override - public Mono delete(Object entity) { - return delete(entity, getIndexCoordinatesFor(entity.getClass())); - } - - @Override - public Mono delete(String id, Class entityType) { - - Assert.notNull(id, "id must not be null"); - Assert.notNull(entityType, "entityType must not be null"); - - return delete(id, getIndexCoordinatesFor(entityType)); - } - - @Override - public Mono delete(String id, IndexCoordinates index) { - - Assert.notNull(id, "id must not be null"); - Assert.notNull(index, "index must not be null"); - - return doDeleteById(id, routingResolver.getRouting(), index); - } - - private Mono doDeleteById(String id, @Nullable String routing, IndexCoordinates index) { + protected Mono doDeleteById(String id, @Nullable String routing, IndexCoordinates index) { return Mono.defer(() -> { DeleteRequest request = requestFactory.deleteRequest(id, routing, index); @@ -633,12 +367,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera }); } - @Override - public Mono delete(Query query, Class entityType) { - return delete(query, entityType, getIndexCoordinatesFor(entityType)); - } - - private Mono doDeleteBy(Query query, Class entityType, IndexCoordinates index) { + protected Mono doDeleteBy(Query query, Class entityType, IndexCoordinates index) { return Mono.defer(() -> { DeleteByQueryRequest request = requestFactory.deleteByQueryRequest(query, entityType, index); @@ -747,16 +476,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera // endregion // region SearchOperations - @Override - public Flux> search(Query query, Class entityType, Class resultType, IndexCoordinates index) { - SearchDocumentCallback callback = new ReadSearchDocumentCallback<>(resultType, index); - return doFind(query, entityType, index).concatMap(callback::toSearchHit); - } - - @Override - public Flux> search(Query query, Class entityType, Class returnType) { - return search(query, entityType, returnType, getIndexCoordinatesFor(entityType)); - } @Override public Mono> searchForPage(Query query, Class entityType, Class resultType) { @@ -778,32 +497,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera .map(searchHits -> SearchHitSupport.searchPageFor(searchHits, query.getPageable())); } - @Override - public Mono> searchForHits(Query query, Class entityType, Class resultType) { - return searchForHits(query, entityType, resultType, getIndexCoordinatesFor(entityType)); - } - - @Override - public Mono> searchForHits(Query query, Class entityType, Class resultType, - IndexCoordinates index) { - - Assert.notNull(query, "query must not be null"); - Assert.notNull(entityType, "entityType must not be null"); - Assert.notNull(resultType, "resultType must not be null"); - Assert.notNull(index, "index must not be null"); - - SearchDocumentCallback callback = new ReadSearchDocumentCallback<>(resultType, index); - - return doFindForResponse(query, entityType, index) // - .flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) // - .flatMap(callback::toEntity) // - .collectList() // - .map(entities -> SearchHitMapping.mappingFor(resultType, converter) // - .mapHits(searchDocumentResponse, entities))) // - .map(ReactiveSearchHitSupport::searchHitsFor); - } - - private Flux doFind(Query query, Class clazz, IndexCoordinates index) { + protected Flux doFind(Query query, Class clazz, IndexCoordinates index) { return Flux.defer(() -> { @@ -819,7 +513,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera }); } - private Mono doFindForResponse(Query query, Class clazz, IndexCoordinates index) { + protected Mono doFindForResponse(Query query, Class clazz, IndexCoordinates index) { return Mono.defer(() -> { SearchRequest request = requestFactory.searchRequest(query, clazz, index); @@ -834,11 +528,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera }); } - @Override - public Flux> aggregate(Query query, Class entityType) { - return aggregate(query, entityType, getIndexCoordinatesFor(entityType)); - } - @Override public Flux> aggregate(Query query, Class entityType, IndexCoordinates index) { @@ -869,25 +558,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera .onErrorResume(NoSuchIndexException.class, it -> Flux.empty()).map(ElasticsearchAggregation::new); } - @Override - public Mono suggest(Query query, Class entityType) { - return suggest(query, entityType, getIndexCoordinatesFor(entityType)); - } - - @Override - public Mono suggest(Query query, Class entityType, IndexCoordinates index) { - - Assert.notNull(query, "query must not be null"); - Assert.notNull(entityType, "entityType must not be null"); - Assert.notNull(index, "index must not be null"); - - return doFindForResponse(query, entityType, index).mapNotNull(searchDocumentResponse -> { - Suggest suggest = searchDocumentResponse.getSuggest(); - SearchHitMapping.mappingFor(entityType, converter).mapHitsInCompletionSuggestion(suggest); - return suggest; - }); - } - @Override @Deprecated public Flux suggest(SuggestBuilder suggestion, Class entityType) { @@ -908,17 +578,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera }); } - @Override - public Mono count(Query query, Class entityType) { - return count(query, entityType, getIndexCoordinatesFor(entityType)); - } - - @Override - public Mono count(Query query, Class entityType, IndexCoordinates index) { - return doCount(query, entityType, index); - } - - private Mono doCount(Query query, Class entityType, IndexCoordinates index) { + protected Mono doCount(Query query, Class entityType, IndexCoordinates index) { return Mono.defer(() -> { SearchRequest request = requestFactory.searchRequest(query, entityType, index); @@ -958,7 +618,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera } return Mono.from(execute(client -> client.searchForResponse(request))) - .map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator)); + .map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator)); } /** @@ -1014,10 +674,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera return request; } - // endregion // region Helper methods + @Override protected Mono getClusterVersion() { try { return Mono.from(execute(ReactiveElasticsearchClient::info)) @@ -1030,6 +690,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera * @return the vendor name of the used cluster and client library * @since 4.3 */ + @Override protected Mono getVendor() { return Mono.just("Elasticsearch"); } @@ -1038,6 +699,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera * @return the version of the used client runtime library. * @since 4.3 */ + @Override protected Mono getRuntimeLibraryVersion() { return Mono.just(Version.CURRENT.toString()); } @@ -1072,11 +734,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera return Flux.defer(() -> callback.doWithClient(getClusterClient())).onErrorMap(this::translateException); } - @Override - public ElasticsearchConverter getElasticsearchConverter() { - return converter; - } - @Override public ReactiveIndexOperations indexOps(IndexCoordinates index) { return new ReactiveIndexTemplate(this, index); @@ -1092,17 +749,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera return new DefaultReactiveClusterOperations(this); } - @Override - public IndexCoordinates getIndexCoordinatesFor(Class clazz) { - return getPersistentEntityFor(clazz).getIndexCoordinates(); - } - - @Override - @Nullable - public ElasticsearchPersistentEntity getPersistentEntityFor(@Nullable Class type) { - return type != null ? mappingContext.getPersistentEntity(type) : null; - } - /** * Obtain the {@link ReactiveElasticsearchClient} to operate upon. * @@ -1157,159 +803,4 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException; } - - // region callbacks - protected Mono maybeCallBeforeConvert(T entity, IndexCoordinates index) { - - if (null != entityCallbacks) { - return entityCallbacks.callback(ReactiveBeforeConvertCallback.class, entity, index); - } - - return Mono.just(entity); - } - - protected Mono maybeCallAfterSave(T entity, IndexCoordinates index) { - - if (null != entityCallbacks) { - return entityCallbacks.callback(ReactiveAfterSaveCallback.class, entity, index); - } - - return Mono.just(entity); - } - - protected Mono maybeCallAfterConvert(T entity, Document document, IndexCoordinates index) { - - if (null != entityCallbacks) { - return entityCallbacks.callback(ReactiveAfterConvertCallback.class, entity, document, index); - } - - return Mono.just(entity); - } - - protected Mono maybeCallbackAfterLoad(Document document, Class type, - IndexCoordinates indexCoordinates) { - - if (entityCallbacks != null) { - return entityCallbacks.callback(ReactiveAfterLoadCallback.class, document, type, indexCoordinates); - } - - return Mono.just(document); - } - - // endregion - - // region routing - private void setRoutingResolver(RoutingResolver routingResolver) { - - Assert.notNull(routingResolver, "routingResolver must not be null"); - - this.routingResolver = routingResolver; - } - - @Override - public ReactiveElasticsearchOperations withRouting(RoutingResolver routingResolver) { - - Assert.notNull(routingResolver, "routingResolver must not be null"); - - ReactiveElasticsearchTemplate copy = copy(); - copy.setRoutingResolver(routingResolver); - return copy; - } - // endregion - - protected interface DocumentCallback { - - @NonNull - Mono toEntity(@Nullable Document document); - } - - protected class ReadDocumentCallback implements DocumentCallback { - private final EntityReader reader; - private final Class type; - private final IndexCoordinates index; - - public ReadDocumentCallback(EntityReader reader, Class type, IndexCoordinates index) { - Assert.notNull(reader, "reader is null"); - Assert.notNull(type, "type is null"); - - this.reader = reader; - this.type = type; - this.index = index; - } - - @NonNull - public Mono toEntity(@Nullable Document document) { - if (document == null) { - return Mono.empty(); - } - - return maybeCallbackAfterLoad(document, type, index) // - .flatMap(documentAfterLoad -> { - - T entity = reader.read(type, documentAfterLoad); - - IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( // - documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, // - documentAfterLoad.getSeqNo(), // - documentAfterLoad.getPrimaryTerm(), // - documentAfterLoad.getVersion()); // - entity = updateIndexedObject(entity, indexedObjectInformation); - - return maybeCallAfterConvert(entity, documentAfterLoad, index); - }); - } - } - - protected interface SearchDocumentCallback { - - Mono toEntity(SearchDocument response); - - Mono> toSearchHit(SearchDocument response); - } - - protected class ReadSearchDocumentCallback implements SearchDocumentCallback { - private final DocumentCallback delegate; - private final Class type; - - public ReadSearchDocumentCallback(Class type, IndexCoordinates index) { - Assert.notNull(type, "type is null"); - - this.delegate = new ReadDocumentCallback<>(converter, type, index); - this.type = type; - } - - @Override - public Mono toEntity(SearchDocument response) { - return delegate.toEntity(response); - } - - @Override - public Mono> toSearchHit(SearchDocument response) { - return toEntity(response).map(entity -> SearchHitMapping.mappingFor(type, converter).mapHit(response, entity)); - } - } - - private class Entities { - private final List entities; - - private Entities(List entities) { - Assert.notNull(entities, "entities cannot be null"); - - this.entities = entities; - } - - private boolean isEmpty() { - return entities.isEmpty(); - } - - private List indexQueries() { - return entities.stream().map(ReactiveElasticsearchTemplate.this::getIndexQuery).collect(Collectors.toList()); - } - - private T entityAt(long index) { - // it's safe to cast to int because the original indexed collection was fitting in memory - int intIndex = (int) index; - return entities.get(intIndex); - } - } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java index aa068e99e..c5a41a450 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java @@ -230,8 +230,7 @@ public interface ReactiveIndexOperations { Mono putTemplate(PutTemplateRequest putTemplateRequest); /** - * gets an index template using the legacy Elasticsearch - * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * gets an index template using the legacy Elasticsearch interface. * * @param templateName the template name * @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists. @@ -242,8 +241,7 @@ public interface ReactiveIndexOperations { } /** - * gets an index template using the legacy Elasticsearch - * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * gets an index template using the legacy Elasticsearch interface. * * @param getTemplateRequest the request parameters * @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexTemplate.java index ef95f5cf9..6324a5fe8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexTemplate.java @@ -180,13 +180,14 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations { @Override public Mono createMapping(Class clazz) { + // noinspection DuplicatedCode Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); if (mappingAnnotation != null) { String mappingPath = mappingAnnotation.mappingPath(); if (hasText(mappingPath)) { - return loadDocument(mappingAnnotation.mappingPath(), "@Mapping"); + return ReactiveResourceUtil.loadDocument(mappingAnnotation.mappingPath(), "@Mapping"); } } @@ -230,7 +231,7 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations { ElasticsearchPersistentEntity persistentEntity = getRequiredPersistentEntity(clazz); String settingPath = persistentEntity.settingPath(); return hasText(settingPath) // - ? loadDocument(settingPath, "@Setting") // + ? ReactiveResourceUtil.loadDocument(settingPath, "@Setting") // .map(Settings::new) // : Mono.just(persistentEntity.getDefaultSettings()); } @@ -351,23 +352,6 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations { return converter.getMappingContext().getRequiredPersistentEntity(clazz); } - private Mono loadDocument(String path, String annotation) { - - if (hasText(path)) { - return ReactiveResourceUtil.readFileFromClasspath(path).flatMap(s -> { - if (hasText(s)) { - return Mono.just(Document.parse(s)); - } else { - return Mono.just(Document.create()); - } - }); - } else { - LOGGER.info(String.format("path in %s has to be defined. Using default empty Document instead.", annotation)); - } - - return Mono.just(Document.create()); - } - private Class checkForBoundClass() { if (boundClass == null) { throw new InvalidDataAccessApiUsageException("IndexOperations are not bound"); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java index 3de48be5c..2da5f4113 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java @@ -15,6 +15,8 @@ */ package org.springframework.data.elasticsearch.core; +import static org.springframework.util.StringUtils.*; + import reactor.core.publisher.Mono; import java.io.BufferedReader; @@ -22,10 +24,13 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.data.elasticsearch.ResourceFailureException; +import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.util.Assert; /** @@ -36,6 +41,8 @@ import org.springframework.util.Assert; */ public abstract class ReactiveResourceUtil { + private static final Log LOGGER = LogFactory.getLog(ReactiveResourceUtil.class); + private static final int BUFFER_SIZE = 8_192; /** @@ -74,6 +81,33 @@ public abstract class ReactiveResourceUtil { throwable -> Mono.error(new ResourceFailureException("Could not load resource from " + url, throwable))); } + /** + * loads a Document initialized with data from a given resource path. + * + * @param path the path to load data from + * @param annotation the annotation that had the resource path defined + * @return the parsed document + * @since 4.4 + */ + public static Mono loadDocument(String path, String annotation) { + + if (hasText(path)) { + return readFileFromClasspath(path).flatMap(s -> { + if (hasText(s)) { + return Mono.just(Document.parse(s)); + } else { + return Mono.just(Document.create()); + } + }); + } else { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(String.format("path in %s has to be defined. Using default instead.", annotation)); + } + } + + return Mono.just(Document.create()); + } + // Utility constructor private ReactiveResourceUtil() {} } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java index 8468d46e0..96aca1477 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -23,7 +23,15 @@ import static org.springframework.util.CollectionUtils.*; import java.io.IOException; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.elasticsearch.action.DocWriteRequest; @@ -41,7 +49,6 @@ import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.action.support.WriteRequest; @@ -270,7 +277,7 @@ class RequestFactory { } public GetIndexRequest getIndexRequest(IndexCoordinates index) { - return new GetIndexRequest(index.getIndexNames()); + return new GetIndexRequest(index.getIndexNames()).humanReadable(false); } public IndicesExistsRequest indicesExistsRequest(IndexCoordinates index) { @@ -405,7 +412,7 @@ class RequestFactory { } if (reindexRequest.getMaxDocs() != null) { - request.setMaxDocs(reindexRequest.getMaxDocs()); + request.setMaxDocs(Math.toIntExact(reindexRequest.getMaxDocs())); } // region source build final Source source = reindexRequest.getSource(); @@ -464,12 +471,12 @@ class RequestFactory { final org.springframework.data.elasticsearch.annotations.Document.VersionType versionType = dest.getVersionType(); if (versionType != null) { - request.setDestVersionType(VersionType.fromString(versionType.name().toLowerCase(Locale.ROOT))); + request.setDestVersionType(VersionType.fromString(versionType.getEsName())); } final IndexQuery.OpType opType = dest.getOpType(); if (opType != null) { - request.setDestOpType(opType.name().toLowerCase(Locale.ROOT)); + request.setDestOpType(opType.getEsName()); } // endregion @@ -508,7 +515,7 @@ class RequestFactory { } if (reindexRequest.getSlices() != null) { - request.setSlices(reindexRequest.getSlices()); + request.setSlices(Math.toIntExact(reindexRequest.getSlices())); } // endregion return request; @@ -923,22 +930,6 @@ class RequestFactory { } } - @SuppressWarnings("rawtypes") - private void prepareSort(Query query, SearchRequestBuilder searchRequestBuilder, - @Nullable ElasticsearchPersistentEntity entity) { - if (query.getSort() != null) { - query.getSort().forEach(order -> searchRequestBuilder.addSort(getSortBuilder(order, entity))); - } - - if (query instanceof NativeSearchQuery) { - NativeSearchQuery nativeSearchQuery = (NativeSearchQuery) query; - List> sorts = nativeSearchQuery.getElasticsearchSorts(); - if (sorts != null) { - sorts.forEach(searchRequestBuilder::addSort); - } - } - } - private SortBuilder getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity entity) { SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java index 01095b8ed..b74fcab1d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java @@ -293,7 +293,7 @@ public class ResponseConverter { MultiGetResponse.Failure responseFailure = itemResponse.getFailure(); return responseFailure != null ? MultiGetItem.Failure.of(responseFailure.getIndex(), responseFailure.getType(), - responseFailure.getId(), responseFailure.getFailure()) : null; + responseFailure.getId(), responseFailure.getFailure(), null) : null; } // endregion @@ -387,8 +387,7 @@ public class ResponseConverter { // endregion - // region postReindexResponse - + // region reindex /** * @since 4.4 */ @@ -402,6 +401,7 @@ public class ResponseConverter { .withTook(bulkByScrollResponse.getTook().getMillis()) // .withTimedOut(bulkByScrollResponse.isTimedOut()) // .withTotal(bulkByScrollResponse.getTotal()) // + .withCreated(bulkByScrollResponse.getCreated()) // .withUpdated(bulkByScrollResponse.getUpdated()) // .withDeleted(bulkByScrollResponse.getDeleted()) // .withBatches(bulkByScrollResponse.getBatches()) // diff --git a/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java index 111c42ea8..7ab664df0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java @@ -66,15 +66,18 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations private static final Log LOGGER = LogFactory.getLog(RestIndexTemplate.class); private final ElasticsearchRestTemplate restTemplate; + protected final RequestFactory requestFactory; public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, Class boundClass) { super(restTemplate.getElasticsearchConverter(), boundClass); this.restTemplate = restTemplate; + requestFactory = new RequestFactory(elasticsearchConverter); } public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) { super(restTemplate.getElasticsearchConverter(), boundIndex); this.restTemplate = restTemplate; + requestFactory = new RequestFactory(elasticsearchConverter); } @Override @@ -116,7 +119,7 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations @Override protected Map doGetMapping(IndexCoordinates index) { - Assert.notNull(index, "No index defined for getMapping()"); + Assert.notNull(index, "No index defined for doGetMapping()"); GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(index); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/RuntimeField.java b/src/main/java/org/springframework/data/elasticsearch/core/RuntimeField.java index 59f152084..84007b8a0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RuntimeField.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RuntimeField.java @@ -57,4 +57,18 @@ public class RuntimeField { map.put("script", script); return map; } + + /** + * @since 4.4 + */ + public String getType() { + return type; + } + + /** + * @since 4.4 + */ + public String getScript() { + return script; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java index c17ef5213..016998894 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java @@ -23,11 +23,11 @@ import java.util.Map; import java.util.stream.Collectors; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; -import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.NestedMetaData; import org.springframework.data.elasticsearch.core.document.SearchDocument; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java index 635485606..7eaeee95d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java @@ -19,9 +19,10 @@ import java.util.List; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.search.suggest.SuggestBuilder; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; 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.lang.Nullable; @@ -77,7 +78,9 @@ public interface SearchOperations { * and get the suggest from {@link SearchHits#getSuggest()} */ @Deprecated - SearchResponse suggest(SuggestBuilder suggestion, Class clazz); + default SearchResponse suggest(SuggestBuilder suggestion, Class clazz) { + throw new InvalidDataAccessApiUsageException("Unsupported operation"); + } /** * Does a suggest query @@ -90,7 +93,9 @@ public interface SearchOperations { * and get the suggest from {@link SearchHits#getSuggest()} */ @Deprecated - SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index); + default SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) { + throw new InvalidDataAccessApiUsageException("Unsupported operation"); + } /** * Execute the query against elasticsearch and return the first returned object. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java index 5a0a5d567..9798ee833 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java @@ -122,13 +122,23 @@ public class ClusterHealth { @Override public String toString() { - return "ClusterHealth{" + "clusterName='" + clusterName + '\'' + ", status='" + status + '\'' + ", numberOfNodes=" - + numberOfNodes + ", numberOfDataNodes=" + numberOfDataNodes + ", activeShards=" + activeShards - + ", relocatingShards=" + relocatingShards + ", activePrimaryShards=" + activePrimaryShards - + ", initializingShards=" + initializingShards + ", unassignedShards=" + unassignedShards - + ", activeShardsPercent=" + activeShardsPercent + ", numberOfPendingTasks=" + numberOfPendingTasks - + ", timedOut=" + timedOut + ", numberOfInFlightFetch=" + numberOfInFlightFetch + ", delayedUnassignedShards=" - + delayedUnassignedShards + ", taskMaxWaitingTimeMillis=" + taskMaxWaitingTimeMillis + '}'; + return "ClusterHealth{" + // + "clusterName='" + clusterName + '\'' + // + ", status='" + status + '\'' + // + ", numberOfNodes=" + numberOfNodes + // + ", numberOfDataNodes=" + numberOfDataNodes + // + ", activeShards=" + activeShards + // + ", relocatingShards=" + relocatingShards + // + ", activePrimaryShards=" + activePrimaryShards + // + ", initializingShards=" + initializingShards + // + ", unassignedShards=" + unassignedShards + // + ", activeShardsPercent=" + activeShardsPercent + // + ", numberOfPendingTasks=" + numberOfPendingTasks + // + ", timedOut=" + timedOut + // + ", numberOfInFlightFetch=" + numberOfInFlightFetch + // + ", delayedUnassignedShards=" + delayedUnassignedShards + // + ", taskMaxWaitingTimeMillis=" + taskMaxWaitingTimeMillis + // + '}'; // } public static ClusterHealthBuilder builder() { @@ -160,7 +170,7 @@ public class ClusterHealth { } public ClusterHealthBuilder withStatus(String status) { - this.status = status; + this.status = status.toUpperCase(); return this; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java index dca121a16..60e257ef0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -177,8 +178,8 @@ public final class DocumentAdapters { Map sourceInnerHits = source.getInnerHits(); if (sourceInnerHits != null) { - sourceInnerHits.forEach((name, searchHits) -> innerHits.put(name, - SearchDocumentResponse.from(searchHits, null, null, null, searchDocument -> null))); + sourceInnerHits.forEach((name, searchHits) -> innerHits.put(name, SearchDocumentResponseBuilder.from(searchHits, + null, null, null, searchDocument -> CompletableFuture.completedFuture(null)))); } NestedMetaData nestedMetaData = from(source.getNestedIdentity()); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/Explanation.java b/src/main/java/org/springframework/data/elasticsearch/core/document/Explanation.java index b85cde7ee..3d1c41277 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/Explanation.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/Explanation.java @@ -23,16 +23,16 @@ import org.springframework.util.Assert; /** * class that holds explanations returned from an Elasticsearch search. - * + * * @author Peter-Josef Meisch */ public class Explanation { - private final boolean match; + @Nullable private final Boolean match; private final Double value; @Nullable private final String description; private final List details; - public Explanation(boolean match, Double value, @Nullable String description, List details) { + public Explanation(@Nullable Boolean match, Double value, @Nullable String description, List details) { Assert.notNull(value, "value must not be null"); Assert.notNull(details, "details must not be null"); @@ -44,7 +44,7 @@ public class Explanation { } public boolean isMatch() { - return match; + return match != null && match; } public Double getValue() { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentAdapter.java b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentAdapter.java new file mode 100644 index 000000000..d70308610 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentAdapter.java @@ -0,0 +1,296 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.document; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.springframework.lang.Nullable; + +/** + * {@link SearchDocument} implementation using a {@link Document} delegate. + * + * @author Mark Paluch + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class SearchDocumentAdapter implements SearchDocument { + + private final float score; + private final Object[] sortValues; + private final Map> fields = new HashMap<>(); + private final Document delegate; + private final Map> highlightFields = new HashMap<>(); + private final Map innerHits = new HashMap<>(); + @Nullable private final NestedMetaData nestedMetaData; + @Nullable private final Explanation explanation; + @Nullable private final List matchedQueries; + @Nullable private final String routing; + + public SearchDocumentAdapter(Document delegate, float score, Object[] sortValues, Map> fields, + Map> highlightFields, Map innerHits, + @Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, @Nullable List matchedQueries, + @Nullable String routing) { + + this.delegate = delegate; + this.score = score; + this.sortValues = sortValues; + this.fields.putAll(fields); + this.highlightFields.putAll(highlightFields); + this.innerHits.putAll(innerHits); + this.nestedMetaData = nestedMetaData; + this.explanation = explanation; + this.matchedQueries = matchedQueries; + this.routing = routing; + } + + @Override + public SearchDocument append(String key, Object value) { + delegate.append(key, value); + + return this; + } + + @Override + public float getScore() { + return score; + } + + @Override + public Map> getFields() { + return fields; + } + + @Override + public Object[] getSortValues() { + return sortValues; + } + + @Override + public Map> getHighlightFields() { + return highlightFields; + } + + @Override + public String getIndex() { + return delegate.getIndex(); + } + + @Override + public boolean hasId() { + return delegate.hasId(); + } + + @Override + public String getId() { + return delegate.getId(); + } + + @Override + public void setId(String id) { + delegate.setId(id); + } + + @Override + public boolean hasVersion() { + return delegate.hasVersion(); + } + + @Override + public long getVersion() { + return delegate.getVersion(); + } + + @Override + public void setVersion(long version) { + delegate.setVersion(version); + } + + @Override + public boolean hasSeqNo() { + return delegate.hasSeqNo(); + } + + @Override + public long getSeqNo() { + return delegate.getSeqNo(); + } + + @Override + public void setSeqNo(long seqNo) { + delegate.setSeqNo(seqNo); + } + + @Override + public boolean hasPrimaryTerm() { + return delegate.hasPrimaryTerm(); + } + + @Override + public long getPrimaryTerm() { + return delegate.getPrimaryTerm(); + } + + @Override + public void setPrimaryTerm(long primaryTerm) { + delegate.setPrimaryTerm(primaryTerm); + } + + @Override + public Map getInnerHits() { + return innerHits; + } + + @Override + @Nullable + public NestedMetaData getNestedMetaData() { + return nestedMetaData; + } + + @Override + @Nullable + public T get(Object key, Class type) { + return delegate.get(key, type); + } + + @Override + public String toJson() { + return delegate.toJson(); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public Object get(Object key) { + + if (delegate.containsKey(key)) { + return delegate.get(key); + } + + // fallback to fields + return fields.get(key); + } + + @Override + public Object put(String key, Object value) { + return delegate.put(key, value); + } + + @Override + public Object remove(Object key) { + return delegate.remove(key); + } + + @Override + public void putAll(Map m) { + delegate.putAll(m); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Set keySet() { + return delegate.keySet(); + } + + @Override + public Collection values() { + return delegate.values(); + } + + @Override + public Set> entrySet() { + return delegate.entrySet(); + } + + @Override + @Nullable + public Explanation getExplanation() { + return explanation; + } + + @Override + @Nullable + public List getMatchedQueries() { + return matchedQueries; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SearchDocumentAdapter)) { + return false; + } + SearchDocumentAdapter that = (SearchDocumentAdapter) o; + return Float.compare(that.score, score) == 0 && delegate.equals(that.delegate); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public void forEach(BiConsumer action) { + delegate.forEach(action); + } + + @Override + public boolean remove(Object key, Object value) { + return delegate.remove(key, value); + } + + @Override + public String getRouting() { + return routing; + } + + @Override + public String toString() { + + String id = hasId() ? getId() : "?"; + String version = hasVersion() ? Long.toString(getVersion()) : "?"; + + return getClass().getSimpleName() + '@' + id + '#' + version + ' ' + toJson(); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponse.java b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponse.java index b23b70459..f1d01e427 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponse.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponse.java @@ -15,57 +15,39 @@ */ package org.springframework.data.elasticsearch.core.document; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Function; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.lucene.search.TotalHits; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.common.text.Text; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHits; -import org.elasticsearch.search.aggregations.Aggregations; import org.springframework.data.elasticsearch.core.AggregationsContainer; -import org.springframework.data.elasticsearch.core.ElasticsearchAggregations; -import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion; -import org.springframework.data.elasticsearch.core.suggest.response.PhraseSuggestion; -import org.springframework.data.elasticsearch.core.suggest.response.SortBy; import org.springframework.data.elasticsearch.core.suggest.response.Suggest; -import org.springframework.data.elasticsearch.core.suggest.response.TermSuggestion; -import org.springframework.data.elasticsearch.support.ScoreDoc; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; /** - * This represents the complete search response from Elasticsearch, including the returned documents. Instances must be - * created with the {@link #from(SearchResponse, EntityCreator)} method. + * This represents the complete search response from Elasticsearch, including the returned documents. * * @author Peter-Josef Meisch * @since 4.0 */ public class SearchDocumentResponse { - private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class); - private final long totalHits; private final String totalHitsRelation; private final float maxScore; - private final String scrollId; + @Nullable private final String scrollId; private final List searchDocuments; @Nullable private final AggregationsContainer aggregations; @Nullable private final Suggest suggest; - private SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, String scrollId, - List searchDocuments, @Nullable Aggregations aggregations, @Nullable Suggest suggest) { + public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId, + List searchDocuments, @Nullable AggregationsContainer aggregationsContainer, + @Nullable Suggest suggest) { this.totalHits = totalHits; this.totalHitsRelation = totalHitsRelation; this.maxScore = maxScore; this.scrollId = scrollId; this.searchDocuments = searchDocuments; - this.aggregations = aggregations != null ? new ElasticsearchAggregations(aggregations) : null; + this.aggregations = aggregationsContainer; this.suggest = suggest; } @@ -81,6 +63,7 @@ public class SearchDocumentResponse { return maxScore; } + @Nullable public String getScrollId() { return scrollId; } @@ -99,178 +82,6 @@ public class SearchDocumentResponse { return suggest; } - /** - * creates a SearchDocumentResponse from the {@link SearchResponse} - * - * @param searchResponse must not be {@literal null} - * @param entityCreator function to create an entity from a {@link SearchDocument} - * @param entity type - * @return the SearchDocumentResponse - */ - public static SearchDocumentResponse from(SearchResponse searchResponse, EntityCreator entityCreator) { - - Assert.notNull(searchResponse, "searchResponse must not be null"); - - SearchHits searchHits = searchResponse.getHits(); - String scrollId = searchResponse.getScrollId(); - Aggregations aggregations = searchResponse.getAggregations(); - org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest(); - - return from(searchHits, scrollId, aggregations, suggest, entityCreator); - } - - /** - * creates a {@link SearchDocumentResponse} from {@link SearchHits} with the given scrollId aggregations and suggest - * - * @param searchHits the {@link SearchHits} to process - * @param scrollId scrollId - * @param aggregations aggregations - * @param suggestES the suggestion response from Elasticsearch - * @param entityCreator function to create an entity from a {@link SearchDocument} - * @param entity type - * @return the {@link SearchDocumentResponse} - * @since 4.3 - */ - public static SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId, - @Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES, - EntityCreator entityCreator) { - - TotalHits responseTotalHits = searchHits.getTotalHits(); - - long totalHits; - String totalHitsRelation; - - if (responseTotalHits != null) { - totalHits = responseTotalHits.value; - totalHitsRelation = responseTotalHits.relation.name(); - } else { - totalHits = searchHits.getHits().length; - totalHitsRelation = "OFF"; - } - - float maxScore = searchHits.getMaxScore(); - - List searchDocuments = new ArrayList<>(); - for (SearchHit searchHit : searchHits) { - if (searchHit != null) { - searchDocuments.add(DocumentAdapters.from(searchHit)); - } - } - - Suggest suggest = suggestFrom(suggestES, entityCreator); - return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations, - suggest); - } - - @Nullable - private static Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES, - EntityCreator entityCreator) { - - if (suggestES == null) { - return null; - } - - List>> suggestions = new ArrayList<>(); - - for (org.elasticsearch.search.suggest.Suggest.Suggestion> suggestionES : suggestES) { - - if (suggestionES instanceof org.elasticsearch.search.suggest.term.TermSuggestion) { - org.elasticsearch.search.suggest.term.TermSuggestion termSuggestionES = (org.elasticsearch.search.suggest.term.TermSuggestion) suggestionES; - - List entries = new ArrayList<>(); - for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry entryES : termSuggestionES) { - - List options = new ArrayList<>(); - for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option optionES : entryES) { - options.add(new TermSuggestion.Entry.Option(textToString(optionES.getText()), - textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(), - optionES.getFreq())); - } - - entries.add(new TermSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(), - entryES.getLength(), options)); - } - - suggestions.add(new TermSuggestion(termSuggestionES.getName(), termSuggestionES.getSize(), entries, - suggestFrom(termSuggestionES.getSort()))); - } - - if (suggestionES instanceof org.elasticsearch.search.suggest.phrase.PhraseSuggestion) { - org.elasticsearch.search.suggest.phrase.PhraseSuggestion phraseSuggestionES = (org.elasticsearch.search.suggest.phrase.PhraseSuggestion) suggestionES; - - List entries = new ArrayList<>(); - for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry entryES : phraseSuggestionES) { - - List options = new ArrayList<>(); - for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry.Option optionES : entryES) { - options.add(new PhraseSuggestion.Entry.Option(textToString(optionES.getText()), - textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch())); - } - - entries.add(new PhraseSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(), - entryES.getLength(), options, entryES.getCutoffScore())); - } - - suggestions.add(new PhraseSuggestion(phraseSuggestionES.getName(), phraseSuggestionES.getSize(), entries)); - } - - if (suggestionES instanceof org.elasticsearch.search.suggest.completion.CompletionSuggestion) { - org.elasticsearch.search.suggest.completion.CompletionSuggestion completionSuggestionES = (org.elasticsearch.search.suggest.completion.CompletionSuggestion) suggestionES; - - List> entries = new ArrayList<>(); - for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry entryES : completionSuggestionES) { - - List> options = new ArrayList<>(); - for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) { - SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null; - - T hitEntity = null; - - if (searchDocument != null) { - try { - hitEntity = entityCreator.apply(searchDocument).get(); - } catch (Exception e) { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Error creating entity from SearchDocument"); - } - } - } - - options.add(new CompletionSuggestion.Entry.Option(textToString(optionES.getText()), - textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(), - optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity)); - } - - entries.add(new CompletionSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(), - entryES.getLength(), options)); - } - - suggestions.add( - new CompletionSuggestion(completionSuggestionES.getName(), completionSuggestionES.getSize(), entries)); - } - } - - return new Suggest(suggestions, suggestES.hasScoreDocs()); - } - - private static SortBy suggestFrom(org.elasticsearch.search.suggest.SortBy sort) { - return SortBy.valueOf(sort.name().toUpperCase()); - } - - @Nullable - private static ScoreDoc scoreDocFrom(@Nullable org.apache.lucene.search.ScoreDoc scoreDoc) { - - if (scoreDoc == null) { - return null; - } - - return new ScoreDoc(scoreDoc.score, scoreDoc.doc, scoreDoc.shardIndex); - } - - private static String textToString(@Nullable Text text) { - return text != null ? text.string() : ""; - } - /** * A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the * imperative and the reactive code. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponseBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponseBuilder.java new file mode 100644 index 000000000..bb8b12849 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponseBuilder.java @@ -0,0 +1,224 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.document; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.lucene.search.TotalHits; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.common.text.Text; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchHits; +import org.elasticsearch.search.aggregations.Aggregations; +import org.springframework.data.elasticsearch.core.ElasticsearchAggregations; +import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion; +import org.springframework.data.elasticsearch.core.suggest.response.PhraseSuggestion; +import org.springframework.data.elasticsearch.core.suggest.response.SortBy; +import org.springframework.data.elasticsearch.core.suggest.response.Suggest; +import org.springframework.data.elasticsearch.core.suggest.response.TermSuggestion; +import org.springframework.data.elasticsearch.support.ScoreDoc; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Factory class to create {@link SearchDocumentResponse} instances. + * + * @author Peter-Josef Meisch + * @since 4.0 + */ +public class SearchDocumentResponseBuilder { + + private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class); + + /** + * creates a SearchDocumentResponse from the {@link SearchResponse} + * + * @param searchResponse must not be {@literal null} + * @param entityCreator function to create an entity from a {@link SearchDocument} + * @param entity type + * @return the SearchDocumentResponse + */ + public static SearchDocumentResponse from(SearchResponse searchResponse, + SearchDocumentResponse.EntityCreator entityCreator) { + + Assert.notNull(searchResponse, "searchResponse must not be null"); + + SearchHits searchHits = searchResponse.getHits(); + String scrollId = searchResponse.getScrollId(); + Aggregations aggregations = searchResponse.getAggregations(); + org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest(); + + return from(searchHits, scrollId, aggregations, suggest, entityCreator); + } + + /** + * creates a {@link SearchDocumentResponseBuilder} from {@link SearchHits} with the given scrollId aggregations and + * suggest + * + * @param searchHits the {@link SearchHits} to process + * @param scrollId scrollId + * @param aggregations aggregations + * @param suggestES the suggestion response from Elasticsearch + * @param entityCreator function to create an entity from a {@link SearchDocument} + * @param entity type + * @return the {@link SearchDocumentResponse} + * @since 4.3 + */ + public static SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId, + @Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES, + SearchDocumentResponse.EntityCreator entityCreator) { + + TotalHits responseTotalHits = searchHits.getTotalHits(); + + long totalHits; + String totalHitsRelation; + + if (responseTotalHits != null) { + totalHits = responseTotalHits.value; + totalHitsRelation = responseTotalHits.relation.name(); + } else { + totalHits = searchHits.getHits().length; + totalHitsRelation = "OFF"; + } + + float maxScore = searchHits.getMaxScore(); + + List searchDocuments = new ArrayList<>(); + for (SearchHit searchHit : searchHits) { + if (searchHit != null) { + searchDocuments.add(DocumentAdapters.from(searchHit)); + } + } + + ElasticsearchAggregations aggregationsContainer = aggregations != null ? new ElasticsearchAggregations(aggregations) + : null; + Suggest suggest = suggestFrom(suggestES, entityCreator); + + return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, + aggregationsContainer, suggest); + } + + @Nullable + private static Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES, + SearchDocumentResponse.EntityCreator entityCreator) { + + if (suggestES == null) { + return null; + } + + List>> suggestions = new ArrayList<>(); + + for (org.elasticsearch.search.suggest.Suggest.Suggestion> suggestionES : suggestES) { + + if (suggestionES instanceof org.elasticsearch.search.suggest.term.TermSuggestion) { + org.elasticsearch.search.suggest.term.TermSuggestion termSuggestionES = (org.elasticsearch.search.suggest.term.TermSuggestion) suggestionES; + + List entries = new ArrayList<>(); + for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry entryES : termSuggestionES) { + + List options = new ArrayList<>(); + for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option optionES : entryES) { + options.add(new TermSuggestion.Entry.Option(textToString(optionES.getText()), + textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(), + optionES.getFreq())); + } + + entries.add(new TermSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(), + entryES.getLength(), options)); + } + + suggestions.add(new TermSuggestion(termSuggestionES.getName(), termSuggestionES.getSize(), entries, + suggestFrom(termSuggestionES.getSort()))); + } + + if (suggestionES instanceof org.elasticsearch.search.suggest.phrase.PhraseSuggestion) { + org.elasticsearch.search.suggest.phrase.PhraseSuggestion phraseSuggestionES = (org.elasticsearch.search.suggest.phrase.PhraseSuggestion) suggestionES; + + List entries = new ArrayList<>(); + for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry entryES : phraseSuggestionES) { + + List options = new ArrayList<>(); + for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry.Option optionES : entryES) { + options.add(new PhraseSuggestion.Entry.Option(textToString(optionES.getText()), + textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch())); + } + + entries.add(new PhraseSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(), + entryES.getLength(), options, entryES.getCutoffScore())); + } + + suggestions.add(new PhraseSuggestion(phraseSuggestionES.getName(), phraseSuggestionES.getSize(), entries)); + } + + if (suggestionES instanceof org.elasticsearch.search.suggest.completion.CompletionSuggestion) { + org.elasticsearch.search.suggest.completion.CompletionSuggestion completionSuggestionES = (org.elasticsearch.search.suggest.completion.CompletionSuggestion) suggestionES; + + List> entries = new ArrayList<>(); + for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry entryES : completionSuggestionES) { + + List> options = new ArrayList<>(); + for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) { + SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null; + T hitEntity = null; + + if (searchDocument != null) { + try { + hitEntity = entityCreator.apply(searchDocument).get(); + } catch (Exception e) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Error creating entity from SearchDocument"); + } + } + } + + options.add(new CompletionSuggestion.Entry.Option<>(textToString(optionES.getText()), + textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(), + optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity)); + } + + entries.add(new CompletionSuggestion.Entry<>(textToString(entryES.getText()), entryES.getOffset(), + entryES.getLength(), options)); + } + + suggestions.add( + new CompletionSuggestion<>(completionSuggestionES.getName(), completionSuggestionES.getSize(), entries)); + } + } + + return new Suggest(suggestions, suggestES.hasScoreDocs()); + } + + private static SortBy suggestFrom(org.elasticsearch.search.suggest.SortBy sort) { + return SortBy.valueOf(sort.name().toUpperCase()); + } + + @Nullable + private static ScoreDoc scoreDocFrom(@Nullable org.apache.lucene.search.ScoreDoc scoreDoc) { + + if (scoreDoc == null) { + return null; + } + + return new ScoreDoc(scoreDoc.score, scoreDoc.doc, scoreDoc.shardIndex); + } + + private static String textToString(@Nullable Text text) { + return text != null ? text.string() : ""; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 8e2e0508a..39c9c47aa 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -73,7 +73,7 @@ import com.fasterxml.jackson.databind.util.RawValue; */ public class MappingBuilder { - private static final Log LOGGER = LogFactory.getLog(ElasticsearchRestTemplate.class); + private static final Log LOGGER = LogFactory.getLog(MappingBuilder.class); private static final String FIELD_INDEX = "index"; private static final String FIELD_PROPERTIES = "properties"; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java b/src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java index 2b896cb3f..ef492882e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java @@ -15,9 +15,14 @@ */ package org.springframework.data.elasticsearch.core.index; -import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; - +import java.util.AbstractMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; +import org.springframework.util.Assert; /** * class defining the settings for an index. @@ -47,4 +52,77 @@ public class Settings extends DefaultStringObjectMap { public String toString() { return "Settings: " + toJson(); } + + @Override + public Object get(Object key) { + return containsKey(key) ? super.get(key) : path(key.toString()); + } + + /** + * Merges some other settings onto this one. Other has higher priority on same keys. + * + * @param other the other settings. Must not be {@literal null} + * @since 4.4 + */ + public void merge(Settings other) { + + Assert.notNull(other, "other must not be null"); + + deepMerge(this, other); + } + + /* + * taken from https://stackoverflow.com/a/29698326/4393565 + */ + @SuppressWarnings("unchecked") + private static Map deepMerge(Map original, Map newMap) { + for (Object key : newMap.keySet()) { + if (newMap.get(key) instanceof Map && original.get(key) instanceof Map) { + Map originalChild = (Map) original.get(key); + Map newChild = (Map) newMap.get(key); + original.put(key.toString(), deepMerge(originalChild, newChild)); + } else if (newMap.get(key) instanceof List && original.get(key) instanceof List) { + List originalChild = (List) original.get(key); + List newChild = (List) newMap.get(key); + for (Object each : newChild) { + if (!originalChild.contains(each)) { + originalChild.add(each); + } + } + } else { + original.put(key.toString(), newMap.get(key)); + } + } + return original; + } + + /** + * flattens the nested structure (JSON fields index/foo/bar/: value) into a flat structure (index.foo.bar: value) + * + * @return Settings with the flattened elements. + */ + public Settings flatten() { + return new Settings( // + entrySet().stream() // + .flatMap(Settings::doFlatten) // + .collect(Collectors.toMap(Entry::getKey, Entry::getValue))); // + } + + /** + * flattens a Map to a stream of Map.Entry objects where the keys are the dot separated concatenated + * keys of sub map entries + */ + static private Stream> doFlatten(Map.Entry entry) { + + if (entry.getValue() instanceof Map) { + Map nested = (Map) entry.getValue(); + + // noinspection unchecked + return nested.entrySet().stream() // + .map(e -> new AbstractMap.SimpleEntry<>(entry.getKey() + "." + e.getKey(), e.getValue())) + .flatMap(e2 -> doFlatten((Entry) e2)); + } else { + return Stream.of(entry); + } + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java index 87f404be2..e3dfca5cb 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQuery.java @@ -72,6 +72,23 @@ public class BaseQuery implements Query { private List idsWithRouting = Collections.emptyList(); private final List runtimeFields = new ArrayList<>(); + public BaseQuery() {} + + public > BaseQuery(BaseQueryBuilder builder) { + this.sort = builder.getSort(); + // do a setPageable after setting the sort, because the pageable may contain an additional sort + this.setPageable(builder.getPageable() != null ? builder.getPageable() : DEFAULT_PAGE); + this.ids = builder.getIds(); + this.trackScores = builder.getTrackScores(); + this.maxResults = builder.getMaxResults(); + this.indicesOptions = builder.getIndicesOptions(); + this.minScore = builder.getMinScore(); + this.preference = builder.getPreference(); + this.sourceFilter = builder.getSourceFilter(); + this.fields = builder.getFields(); + // #1973 add the other fields to the builder + } + @Override @Nullable public Sort getSort() { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQueryBuilder.java new file mode 100644 index 000000000..49598038d --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/BaseQueryBuilder.java @@ -0,0 +1,165 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.query; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; + +/** + * base class for query builders. The different implementations of {@link Query} should derive from this class and then + * offer a constructor that takes their builder as argument and passes this on to the super class. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +public abstract class BaseQueryBuilder> { + + @Nullable private Pageable pageable; + @Nullable private Sort sort; + @Nullable private Integer maxResults; + @Nullable private Collection ids; + private boolean trackScores; + @Nullable protected IndicesOptions indicesOptions; + private float minScore; + @Nullable private String preference; + @Nullable private SourceFilter sourceFilter; + private List fields = new ArrayList<>(); + + @Nullable + public Pageable getPageable() { + return pageable; + } + + @Nullable + public Sort getSort() { + return sort; + } + + @Nullable + public Integer getMaxResults() { + return maxResults; + } + + @Nullable + public Collection getIds() { + return ids; + } + + public boolean getTrackScores() { + return trackScores; + } + + @Nullable + public IndicesOptions getIndicesOptions() { + return indicesOptions; + } + + public float getMinScore() { + return minScore; + } + + @Nullable + public String getPreference() { + return preference; + } + + @Nullable + public SourceFilter getSourceFilter() { + return sourceFilter; + } + + public List getFields() { + return fields; + } + + public SELF withPageable(Pageable pageable) { + this.pageable = pageable; + return self(); + } + + public SELF withSort(Sort sort) { + if (this.sort == null) { + this.sort = sort; + } else { + this.sort = this.sort.and(sort); + } + return self(); + } + + public SELF withMaxResults(Integer maxResults) { + this.maxResults = maxResults; + return self(); + } + + public SELF withIds(String... ids) { + this.ids = Arrays.asList(ids); + return self(); + } + + public SELF withIds(Collection ids) { + this.ids = ids; + return self(); + } + + public SELF withTrackScores(boolean trackScores) { + this.trackScores = trackScores; + return self(); + } + + public SELF withIndicesOptions(IndicesOptions indicesOptions) { + this.indicesOptions = indicesOptions; + return self(); + } + + public SELF withMinScore(float minScore) { + this.minScore = minScore; + return self(); + } + + public SELF withPreference(String preference) { + this.preference = preference; + return self(); + } + + public SELF withSourceFilter(SourceFilter sourceFilter) { + this.sourceFilter = sourceFilter; + return self(); + } + + public SELF withFields(String... fields) { + Collections.addAll(this.fields, fields); + return self(); + } + + public SELF withFields(Collection fields) { + this.fields.addAll(fields); + return self(); + } + + public abstract Q build(); + + private SELF self() { + // noinspection unchecked + return (SELF) this; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java b/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java index 9dbeea565..e349dada0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java @@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.query; import java.util.Collections; import java.util.List; +import org.springframework.data.elasticsearch.ElasticsearchErrorCause; import org.springframework.lang.Nullable; /** @@ -173,9 +174,11 @@ public class ByQueryResponse { @Nullable private final Long seqNo; @Nullable private final Long term; @Nullable private final Boolean aborted; + @Nullable private final ElasticsearchErrorCause elasticsearchErrorCause; private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception cause, - @Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted) { + @Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted, + @Nullable ElasticsearchErrorCause elasticsearchErrorCause) { this.index = index; this.type = type; this.id = id; @@ -184,6 +187,7 @@ public class ByQueryResponse { this.seqNo = seqNo; this.term = term; this.aborted = aborted; + this.elasticsearchErrorCause = elasticsearchErrorCause; } @Nullable @@ -247,6 +251,7 @@ public class ByQueryResponse { @Nullable private Long seqNo; @Nullable private Long term; @Nullable private Boolean aborted; + @Nullable private ElasticsearchErrorCause elasticsearchErrorCause; private FailureBuilder() {} @@ -290,8 +295,13 @@ public class ByQueryResponse { return this; } + public FailureBuilder withErrorCause(ElasticsearchErrorCause elasticsearchErrorCause) { + this.elasticsearchErrorCause = elasticsearchErrorCause; + return this; + } + public Failure build() { - return new Failure(index, type, id, cause, status, seqNo, term, aborted); + return new Failure(index, type, id, cause, status, seqNo, term, aborted, elasticsearchErrorCause); } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java index 59709596f..1d617adb1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java @@ -28,7 +28,19 @@ import org.springframework.util.Assert; */ public class CriteriaQuery extends BaseQuery { - private Criteria criteria; + private final Criteria criteria; + + /** + * @since 4.4 + */ + public CriteriaQuery(CriteriaQueryBuilder builder) { + super(builder); + this.criteria = builder.getCriteria(); + } + + public static CriteriaQueryBuilder builder(Criteria criteria) { + return new CriteriaQueryBuilder(criteria); + } public CriteriaQuery(Criteria criteria) { this(criteria, Pageable.unpaged()); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryBuilder.java new file mode 100644 index 000000000..3b5d7a98e --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.query; + +import org.springframework.util.Assert; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class CriteriaQueryBuilder extends BaseQueryBuilder { + + private final Criteria criteria; + + public CriteriaQueryBuilder(Criteria criteria) { + + Assert.notNull(criteria, "criteria must not be null"); + + this.criteria = criteria; + } + + public Criteria getCriteria() { + return criteria; + } + + @Override + public CriteriaQuery build() { + return new CriteriaQuery(this); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java index ba2607ecd..bf321adf7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java @@ -32,7 +32,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** - * Converts the {@link Highlight} annotation from a method to an Elasticsearch {@link HighlightBuilder}. + * Converts the {@link Highlight} annotation from a method to an Elasticsearch 7 {@link HighlightBuilder}. * * @author Peter-Josef Meisch */ diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/IndexQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/IndexQuery.java index b0cacb8ed..3e0e2b150 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/IndexQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/IndexQuery.java @@ -168,6 +168,16 @@ public class IndexQuery { * @since 4.2 */ public enum OpType { - INDEX, CREATE + INDEX("index"), CREATE("create"); + + private final String esName; + + OpType(String esName) { + this.esName = esName; + } + + public String getEsName() { + return esName; + } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java index 5104f2bd8..fd76d6dcd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java @@ -61,7 +61,6 @@ public class NativeSearchQuery extends BaseQuery { @Nullable private List searchExtBuilders; public NativeSearchQuery(@Nullable QueryBuilder query) { - this.query = query; } @@ -99,6 +98,17 @@ public class NativeSearchQuery extends BaseQuery { this.highlightFields = highlightFields; } + public NativeSearchQuery(NativeSearchQueryBuilder builder, @Nullable QueryBuilder query, + @Nullable QueryBuilder filter, @Nullable List> sorts, @Nullable HighlightBuilder highlightBuilder, + @Nullable HighlightBuilder.Field[] highlightFields) { + super(builder); + this.query = query; + this.filter = filter; + this.sorts = sorts; + this.highlightBuilder = highlightBuilder; + this.highlightFields = highlightFields; + } + @Nullable public QueryBuilder getQuery() { return query; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 9dc7a5ddc..ec696c3c9 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -32,7 +32,6 @@ import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.suggest.SuggestBuilder; -import org.springframework.data.domain.Pageable; import org.springframework.lang.Nullable; /** @@ -52,7 +51,7 @@ import org.springframework.lang.Nullable; * @author vdisk * @author owen.qq */ -public class NativeSearchQueryBuilder { +public class NativeSearchQueryBuilder extends BaseQueryBuilder { @Nullable private QueryBuilder queryBuilder; @Nullable private QueryBuilder filterBuilder; @@ -62,21 +61,12 @@ public class NativeSearchQueryBuilder { private final List pipelineAggregationBuilders = new ArrayList<>(); @Nullable private HighlightBuilder highlightBuilder; @Nullable private List highlightFields = new ArrayList<>(); - private Pageable pageable = Pageable.unpaged(); - @Nullable private List fields = new ArrayList<>(); @Nullable protected List storedFields; - @Nullable private SourceFilter sourceFilter; @Nullable private CollapseBuilder collapseBuilder; @Nullable private List indicesBoost = new ArrayList<>(); @Nullable private SearchTemplateRequestBuilder searchTemplateBuilder; - private float minScore; - private boolean trackScores; - @Nullable private List ids = new ArrayList<>(); @Nullable private String route; @Nullable private SearchType searchType; - @Nullable private IndicesOptions indicesOptions; - @Nullable private String preference; - @Nullable private Integer maxResults; @Nullable private Boolean trackTotalHits; @Nullable private Duration timeout; private final List rescorerQueries = new ArrayList<>(); @@ -223,24 +213,6 @@ public class NativeSearchQueryBuilder { return this; } - public NativeSearchQueryBuilder withPageable(Pageable pageable) { - this.pageable = pageable; - return this; - } - - /** - * @since 4.3 - */ - public NativeSearchQueryBuilder withFields(Collection fields) { - this.fields.addAll(fields); - return this; - } - - public NativeSearchQueryBuilder withFields(String... fields) { - Collections.addAll(this.fields, fields); - return this; - } - public NativeSearchQueryBuilder withStoredFields(Collection storedFields) { if (this.storedFields == null) { @@ -260,39 +232,6 @@ public class NativeSearchQueryBuilder { return this; } - public NativeSearchQueryBuilder withSourceFilter(SourceFilter sourceFilter) { - this.sourceFilter = sourceFilter; - return this; - } - - public NativeSearchQueryBuilder withMinScore(float minScore) { - this.minScore = minScore; - return this; - } - - /** - * @param trackScores whether to track scores. - * @return this object - * @since 3.1 - */ - public NativeSearchQueryBuilder withTrackScores(boolean trackScores) { - this.trackScores = trackScores; - return this; - } - - public NativeSearchQueryBuilder withIds(Collection ids) { - this.ids.addAll(ids); - return this; - } - - /** - * @since 4.3 - */ - public NativeSearchQueryBuilder withIds(String... ids) { - Collections.addAll(this.ids, ids); - return this; - } - public NativeSearchQueryBuilder withRoute(String route) { this.route = route; return this; @@ -308,16 +247,6 @@ public class NativeSearchQueryBuilder { return this; } - public NativeSearchQueryBuilder withPreference(String preference) { - this.preference = preference; - return this; - } - - public NativeSearchQueryBuilder withMaxResults(Integer maxResults) { - this.maxResults = maxResults; - return this; - } - /** * @since 4.2 */ @@ -356,27 +285,17 @@ public class NativeSearchQueryBuilder { public NativeSearchQuery build() { NativeSearchQuery nativeSearchQuery = new NativeSearchQuery( // + this, // queryBuilder, // filterBuilder, // sortBuilders, // highlightBuilder, // highlightFields.toArray(new HighlightBuilder.Field[highlightFields.size()])); - nativeSearchQuery.setPageable(pageable); - nativeSearchQuery.setTrackScores(trackScores); - - if (fields != null) { - nativeSearchQuery.setFields(fields); - } - if (storedFields != null) { nativeSearchQuery.setStoredFields(storedFields); } - if (sourceFilter != null) { - nativeSearchQuery.addSourceFilter(sourceFilter); - } - if (indicesBoost != null) { nativeSearchQuery.setIndicesBoost(indicesBoost); } @@ -401,14 +320,6 @@ public class NativeSearchQueryBuilder { nativeSearchQuery.setPipelineAggregations(pipelineAggregationBuilders); } - if (minScore > 0) { - nativeSearchQuery.setMinScore(minScore); - } - - if (ids != null) { - nativeSearchQuery.setIds(ids); - } - if (route != null) { nativeSearchQuery.setRoute(route); } @@ -421,14 +332,6 @@ public class NativeSearchQueryBuilder { nativeSearchQuery.setIndicesOptions(indicesOptions); } - if (preference != null) { - nativeSearchQuery.setPreference(preference); - } - - if (maxResults != null) { - nativeSearchQuery.setMaxResults(maxResults); - } - nativeSearchQuery.setTrackTotalHits(trackTotalHits); if (timeout != null) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java index 82c1be8af..588b391b1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java @@ -339,7 +339,7 @@ public interface Query { void setScrollTime(@Nullable Duration scrollTime); /** - * @return {@literal true} if scrollTimeMillis is set. + * @return {@literal true} if a scroll time is set. * @since 4.0 */ default boolean hasScrollTime() { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/StringQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/StringQuery.java index e70266a8b..cc07735f0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/StringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/StringQuery.java @@ -23,10 +23,11 @@ import org.springframework.data.domain.Sort; * * @author Rizwan Idrees * @author Mohsin Husen + * @author Peter-Josef Meisch */ public class StringQuery extends BaseQuery { - private String source; + private final String source; public StringQuery(String source) { this.source = source; @@ -43,6 +44,21 @@ public class StringQuery extends BaseQuery { this.source = source; } + /** + * @since 4.4 + */ + public StringQuery(StringQueryBuilder builder) { + super(builder); + this.source = builder.getSource(); + } + + /** + * @since 4.4 + */ + public static StringQueryBuilder builder(String source) { + return new StringQueryBuilder(source); + } + public String getSource() { return source; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/StringQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/StringQueryBuilder.java new file mode 100644 index 000000000..b059e6421 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/StringQueryBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.query; + +import org.springframework.util.Assert; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class StringQueryBuilder extends BaseQueryBuilder { + + private final String source; + + public StringQueryBuilder(String source) { + + Assert.notNull(source, "source must not be null"); + + this.source = source; + } + + public String getSource() { + return source; + } + + @Override + public StringQuery build() { + return new StringQuery(this); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexRequest.java b/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexRequest.java index b387f0d6e..906117762 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexRequest.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexRequest.java @@ -37,7 +37,7 @@ public class ReindexRequest { // Request body private final Source source; private final Dest dest; - @Nullable private final Integer maxDocs; + @Nullable private final Long maxDocs; @Nullable private final Conflicts conflicts; @Nullable private final Script script; @@ -46,14 +46,14 @@ public class ReindexRequest { @Nullable private final Boolean requireAlias; @Nullable private final Boolean refresh; @Nullable private final String waitForActiveShards; - @Nullable private final Integer requestsPerSecond; + @Nullable private final Long requestsPerSecond; @Nullable private final Duration scroll; - @Nullable private final Integer slices; + @Nullable private final Long slices; - private ReindexRequest(Source source, Dest dest, @Nullable Integer maxDocs, @Nullable Conflicts conflicts, + private ReindexRequest(Source source, Dest dest, @Nullable Long maxDocs, @Nullable Conflicts conflicts, @Nullable Script script, @Nullable Duration timeout, @Nullable Boolean requireAlias, @Nullable Boolean refresh, - @Nullable String waitForActiveShards, @Nullable Integer requestsPerSecond, @Nullable Duration scroll, - @Nullable Integer slices) { + @Nullable String waitForActiveShards, @Nullable Long requestsPerSecond, @Nullable Duration scroll, + @Nullable Long slices) { Assert.notNull(source, "source must not be null"); Assert.notNull(dest, "dest must not be null"); @@ -73,7 +73,7 @@ public class ReindexRequest { } @Nullable - public Integer getMaxDocs() { + public Long getMaxDocs() { return maxDocs; } @@ -116,7 +116,7 @@ public class ReindexRequest { } @Nullable - public Integer getRequestsPerSecond() { + public Long getRequestsPerSecond() { return requestsPerSecond; } @@ -126,7 +126,7 @@ public class ReindexRequest { } @Nullable - public Integer getSlices() { + public Long getSlices() { return slices; } @@ -275,16 +275,16 @@ public class ReindexRequest { private final Source source; private final Dest dest; - @Nullable private Integer maxDocs; + @Nullable private Long maxDocs; @Nullable private Conflicts conflicts; @Nullable private Script script; @Nullable private Duration timeout; @Nullable private Boolean requireAlias; @Nullable private Boolean refresh; @Nullable private String waitForActiveShards; - @Nullable private Integer requestsPerSecond; + @Nullable private Long requestsPerSecond; @Nullable private Duration scroll; - @Nullable private Integer slices; + @Nullable private Long slices; public ReindexRequestBuilder(IndexCoordinates sourceIndex, IndexCoordinates destIndex) { @@ -297,7 +297,7 @@ public class ReindexRequest { // region setter - public ReindexRequestBuilder withMaxDocs(@Nullable Integer maxDocs) { + public ReindexRequestBuilder withMaxDocs(@Nullable Long maxDocs) { this.maxDocs = maxDocs; return this; } @@ -377,7 +377,7 @@ public class ReindexRequest { return this; } - public ReindexRequestBuilder withRequestsPerSecond(int requestsPerSecond) { + public ReindexRequestBuilder withRequestsPerSecond(long requestsPerSecond) { this.requestsPerSecond = requestsPerSecond; return this; } @@ -387,7 +387,7 @@ public class ReindexRequest { return this; } - public ReindexRequestBuilder withSlices(int slices) { + public ReindexRequestBuilder withSlices(long slices) { this.slices = slices; return this; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexResponse.java b/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexResponse.java index 2f3f1e345..65b058518 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexResponse.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/reindex/ReindexResponse.java @@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.reindex; import java.util.Collections; import java.util.List; +import org.springframework.data.elasticsearch.ElasticsearchErrorCause; import org.springframework.lang.Nullable; /** @@ -32,9 +33,10 @@ public class ReindexResponse { private final long took; private final boolean timedOut; private final long total; + private final long created; private final long updated; private final long deleted; - private final int batches; + private final long batches; private final long versionConflicts; private final long noops; private final long bulkRetries; @@ -44,12 +46,13 @@ public class ReindexResponse { private final long throttledUntilMillis; private final List failures; - private ReindexResponse(long took, boolean timedOut, long total, long updated, long deleted, int batches, - long versionConflicts, long noops, long bulkRetries, long searchRetries, long throttledMillis, + private ReindexResponse(long took, boolean timedOut, long total, long created, long updated, long deleted, + long batches, long versionConflicts, long noops, long bulkRetries, long searchRetries, long throttledMillis, double requestsPerSecond, long throttledUntilMillis, List failures) { this.took = took; this.timedOut = timedOut; this.total = total; + this.created = created; this.updated = updated; this.deleted = deleted; this.batches = batches; @@ -84,6 +87,13 @@ public class ReindexResponse { return total; } + /** + * The number of documents that were successfully created. + */ + public long getCreated() { + return created; + } + /** * The number of documents that were successfully updated. */ @@ -101,7 +111,7 @@ public class ReindexResponse { /** * The number of scroll responses pulled back by the update by query. */ - public int getBatches() { + public long getBatches() { return batches; } @@ -184,9 +194,11 @@ public class ReindexResponse { @Nullable private final Long seqNo; @Nullable private final Long term; @Nullable private final Boolean aborted; + @Nullable private final ElasticsearchErrorCause elasticsearchErrorCause; private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception cause, - @Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted) { + @Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted, + @Nullable ElasticsearchErrorCause elasticsearchErrorCause) { this.index = index; this.type = type; this.id = id; @@ -195,6 +207,7 @@ public class ReindexResponse { this.seqNo = seqNo; this.term = term; this.aborted = aborted; + this.elasticsearchErrorCause = elasticsearchErrorCause; } @Nullable @@ -237,6 +250,11 @@ public class ReindexResponse { return aborted; } + @Nullable + public ElasticsearchErrorCause getElasticsearchErrorCause() { + return elasticsearchErrorCause; + } + /** * Create a new {@link Failure.FailureBuilder} to build {@link Failure} * @@ -258,6 +276,7 @@ public class ReindexResponse { @Nullable private Long seqNo; @Nullable private Long term; @Nullable private Boolean aborted; + @Nullable private ElasticsearchErrorCause elasticsearchErrorCause; private FailureBuilder() {} @@ -276,7 +295,7 @@ public class ReindexResponse { return this; } - public Failure.FailureBuilder withCause(Exception cause) { + public Failure.FailureBuilder withCause(@Nullable Exception cause) { this.cause = cause; return this; } @@ -301,8 +320,13 @@ public class ReindexResponse { return this; } + public Failure.FailureBuilder withErrorCause(@Nullable ElasticsearchErrorCause elasticsearchErrorCause) { + this.elasticsearchErrorCause = elasticsearchErrorCause; + return this; + } + public Failure build() { - return new Failure(index, type, id, cause, status, seqNo, term, aborted); + return new Failure(index, type, id, cause, status, seqNo, term, aborted, elasticsearchErrorCause); } } } @@ -311,9 +335,10 @@ public class ReindexResponse { private long took; private boolean timedOut; private long total; + private long created; private long updated; private long deleted; - private int batches; + private long batches; private long versionConflicts; private long noops; private long bulkRetries; @@ -340,6 +365,11 @@ public class ReindexResponse { return this; } + public ReindexResponseBuilder withCreated(long created) { + this.created = created; + return this; + } + public ReindexResponseBuilder withUpdated(long updated) { this.updated = updated; return this; @@ -350,7 +380,7 @@ public class ReindexResponse { return this; } - public ReindexResponseBuilder withBatches(int batches) { + public ReindexResponseBuilder withBatches(long batches) { this.batches = batches; return this; } @@ -396,8 +426,8 @@ public class ReindexResponse { } public ReindexResponse build() { - return new ReindexResponse(took, timedOut, total, updated, deleted, batches, versionConflicts, noops, bulkRetries, - searchRetries, throttledMillis, requestsPerSecond, throttledUntilMillis, failures); + return new ReindexResponse(took, timedOut, total, created, updated, deleted, batches, versionConflicts, noops, + bulkRetries, searchRetries, throttledMillis, requestsPerSecond, throttledUntilMillis, failures); } } } 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 95ecbc9ab..4f66d9e84 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,7 +15,6 @@ */ package org.springframework.data.elasticsearch.repository.support; -import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.util.CollectionUtils.*; import java.util.ArrayList; @@ -29,8 +28,6 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; @@ -42,7 +39,9 @@ import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.SearchPage; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.BaseQuery; 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.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.util.StreamUtils; @@ -116,25 +115,34 @@ public class SimpleElasticsearchRepository implements ElasticsearchReposi @SuppressWarnings("unchecked") @Override public Page findAll(Pageable pageable) { - NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withPageable(pageable).build(); + + Assert.notNull(pageable, "pageable must not be null"); + + Query query = Query.findAll(); + query.setPageable(pageable); SearchHits searchHits = execute(operations -> operations.search(query, entityClass, getIndexCoordinates())); - // noinspection ConstantConditions SearchPage page = SearchHitSupport.searchPageFor(searchHits, query.getPageable()); + // noinspection ConstantConditions return (Page) SearchHitSupport.unwrapSearchHits(page); } @SuppressWarnings("unchecked") @Override public Iterable findAll(Sort sort) { + + Assert.notNull(sort, "sort must not be null"); + int itemCount = (int) this.count(); if (itemCount == 0) { return new PageImpl<>(Collections.emptyList()); } - NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withPageable(PageRequest.of(0, itemCount, sort)).build(); + Pageable pageable = PageRequest.of(0, itemCount, sort); + Query query = Query.findAll(); + query.setPageable(pageable); List> searchHitList = execute( operations -> operations.search(query, entityClass, getIndexCoordinates()).getSearchHits()); + // noinspection ConstantConditions return (List) SearchHitSupport.unwrapSearchHits(searchHitList); } @@ -166,8 +174,8 @@ public class SimpleElasticsearchRepository implements ElasticsearchReposi @Override public long count() { - NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - // noinspection ConstantConditions + Query query = Query.findAll(); + ((BaseQuery) query).setMaxResults(0); return execute(operations -> operations.count(query, entityClass, getIndexCoordinates())); } @@ -287,10 +295,9 @@ public class SimpleElasticsearchRepository implements ElasticsearchReposi @Override public void deleteAll() { IndexCoordinates indexCoordinates = getIndexCoordinates(); - Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); executeAndRefresh((OperationsCallback) operations -> { - operations.delete(query, entityClass, indexCoordinates); + operations.delete(Query.findAll(), entityClass, indexCoordinates); return null; }); } diff --git a/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java b/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java index cc11ed788..072f392ee 100644 --- a/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java +++ b/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import com.fasterxml.jackson.core.JsonProcessingException; @@ -87,6 +88,7 @@ public class DefaultStringObjectMap> implements Str } @Override + @Nullable public Object get(Object key) { return delegate.get(key); } @@ -150,4 +152,40 @@ public class DefaultStringObjectMap> implements Str public String toString() { return "DefaultStringObjectMap: " + toJson(); } + + /** + * Gets the object at the given key path (dot separated) or null if no object exists with this path. + * + * @param path the key path, must not be {@literal null} + * @return the found object or null + */ + @Nullable + public Object path(String path) { + + Assert.notNull(path, "path must not be null"); + + Map current = this; + + String[] segments = path.split("\\."); + for (int i = 0; i < segments.length; i++) { + String segment = segments[i]; + + if (current.containsKey(segment)) { + Object currentObject = current.get(segment); + + if (i == segments.length - 1) { + return currentObject; + } + + if (currentObject instanceof Map) { + current = (Map) currentObject; + } else { + return null; + } + } else { + return null; + } + } + return null; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/support/ExceptionUtils.java b/src/main/java/org/springframework/data/elasticsearch/support/ExceptionUtils.java new file mode 100644 index 000000000..505a7a7b1 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/support/ExceptionUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.support; + +import java.net.ConnectException; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +public final class ExceptionUtils { + private ExceptionUtils() {} + + /** + * checks if the given throwable is a {@link ConnectException} or has one in its cause chain + * + * @param throwable the throwable to check + * @return true if throwable is caused by a {@link ConnectException} + */ + public static boolean isCausedByConnectionException(Throwable throwable) { + + Throwable t = throwable; + do { + + if (t instanceof ConnectException) { + return true; + } + + t = t.getCause(); + } while (t != null); + + return false; + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleElasticsearchTemplateTests.java new file mode 100644 index 000000000..89cdf4359 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleElasticsearchTemplateTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.test.context.ContextConfiguration; + +/** + * class demonstrating the setup of a JUnit 5 test in Spring Data Elasticsearch that uses the new rest client. The + * ContextConfiguration must include the {@link ElasticsearchTemplateConfiguration} class. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@SpringIntegrationTest +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +@DisplayName("a sample JUnit 5 test with the new rest client") +public class JUnit5SampleElasticsearchTemplateTests { + + @Autowired private ElasticsearchOperations elasticsearchOperations; + + @Test + @DisplayName("should have an ElasticsearchTemplate") + void shouldHaveAElasticsearchTemplate() { + assertThat(elasticsearchOperations).isNotNull().isInstanceOf(ElasticsearchTemplate.class); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveELCTests.java b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveELCTests.java new file mode 100644 index 000000000..074474231 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveELCTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.test.context.ContextConfiguration; + +/** + * class demonstrating the setup of a reactive JUnit 5 test in Spring Data Elasticsearch that uses the new rest client. + * The ContextConfiguration must include the {@link ReactiveElasticsearchTemplateConfiguration} class. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@SpringIntegrationTest +@ContextConfiguration(classes = { ReactiveElasticsearchTemplateConfiguration.class }) +@DisplayName("a sample reactive JUnit 5 test with the new rest client") +public class JUnit5SampleReactiveELCTests { + + @Autowired private ReactiveElasticsearchOperations elasticsearchOperations; + + @Test + @DisplayName("should have an ReactiveElasticsearchTemplate") + void shouldHaveAElasticsearchTemplate() { + assertThat(elasticsearchOperations).isNotNull().isInstanceOf(ReactiveElasticsearchTemplate.class); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveRestClientBasedTests.java b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveERHLCTests.java similarity index 97% rename from src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveRestClientBasedTests.java rename to src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveERHLCTests.java index 3c22bc0d9..2dd2f78e0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveRestClientBasedTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleReactiveERHLCTests.java @@ -35,7 +35,7 @@ import org.springframework.test.context.ContextConfiguration; @SpringIntegrationTest @ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) @DisplayName("a sample JUnit 5 test with reactive rest client") -public class JUnit5SampleReactiveRestClientBasedTests { +public class JUnit5SampleReactiveERHLCTests { @Autowired private ReactiveElasticsearchOperations elasticsearchOperations; diff --git a/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestTemplateBasedTests.java b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestTemplateTests.java similarity index 97% rename from src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestTemplateBasedTests.java rename to src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestTemplateTests.java index 72bfbc7b5..e41e5c681 100644 --- a/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestTemplateBasedTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestTemplateTests.java @@ -35,7 +35,7 @@ import org.springframework.test.context.ContextConfiguration; @SpringIntegrationTest @ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) @DisplayName("a sample JUnit 5 test with rest client") -public class JUnit5SampleRestTemplateBasedTests { +public class JUnit5SampleRestTemplateTests { @Autowired private ElasticsearchOperations elasticsearchOperations; diff --git a/src/test/java/org/springframework/data/elasticsearch/NewElasticsearchClientDevelopment.java b/src/test/java/org/springframework/data/elasticsearch/NewElasticsearchClientDevelopment.java new file mode 100644 index 000000000..81b13ea90 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/NewElasticsearchClientDevelopment.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch; + +/** + * TODO #1973 remove when the new Elasticsearch client is fully working + * + * @author Peter-Josef Meisch + */ +public interface NewElasticsearchClientDevelopment { + + boolean forceEnable = false; + + default boolean usesNewElasticsearchClient() { + return false; + } + + default boolean newElasticsearchClient() { + return !forceEnable && usesNewElasticsearchClient(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java index 3fa238c2d..7d3c82c69 100644 --- a/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java +++ b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java @@ -28,11 +28,12 @@ public class BlockHoundIntegrationCustomizer implements BlockHoundIntegration { public void applyTo(BlockHound.Builder builder) { // Elasticsearch classes reading from the classpath on initialization, needed for parsing Elasticsearch responses builder // + .allowBlockingCallsInside("org.elasticsearch.Build", "") // .allowBlockingCallsInside("org.elasticsearch.common.xcontent.XContentBuilder", "") // pre 7.16 .allowBlockingCallsInside("org.elasticsearch.common.XContentBuilder", "") // from 7.16 on .allowBlockingCallsInside("org.elasticsearch.xcontent.json.JsonXContent", "contentBuilder") // from 7.16 on - .allowBlockingCallsInside("org.elasticsearch.Build", ""); - + .allowBlockingCallsInside("jakarta.json.spi.JsonProvider", "provider") // + ; builder.blockingMethodCallback(it -> { new Error(it.toString()).printStackTrace(); throw new BlockingOperationError(it); diff --git a/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java b/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java index 5bee3072b..6e5481e64 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java @@ -21,6 +21,8 @@ import static io.specto.hoverfly.junit.dsl.HoverflyDsl.*; import static io.specto.hoverfly.junit.verification.HoverflyVerifications.*; import static org.assertj.core.api.Assertions.*; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.transport.endpoints.BooleanResponse; import io.specto.hoverfly.junit.core.Hoverfly; import io.specto.hoverfly.junit5.HoverflyExtension; import io.specto.hoverfly.junit5.api.HoverflyCapture; @@ -40,7 +42,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients; +import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient; import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients; import org.springframework.http.HttpHeaders; @@ -60,7 +63,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping; @ExtendWith(HoverflyExtension.class) public class RestClientsTest { - @ParameterizedTest // DATAES-700 + @ParameterizedTest(name = "{0}") // DATAES-700 @MethodSource("clientUnderTestFactorySource") @DisplayName("should use configured proxy") void shouldUseConfiguredProxy(ClientUnderTestFactory clientUnderTestFactory, Hoverfly hoverfly) throws IOException { @@ -87,10 +90,11 @@ public class RestClientsTest { }); } - @ParameterizedTest // DATAES-801, DATAES-588 + @ParameterizedTest(name = "{0}") // DATAES-801, DATAES-588 @MethodSource("clientUnderTestFactorySource") @DisplayName("should configure client and set all required headers") void shouldConfigureClientAndSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory) { + wireMockServer(server -> { HttpHeaders defaultHeaders = new HttpHeaders(); @@ -112,17 +116,29 @@ public class RestClientsTest { return httpHeaders; }); - if (clientUnderTestFactory instanceof RestClientUnderTestFactory) { + if (clientUnderTestFactory instanceof ERHLCUnderTestFactory) { configurationBuilder .withClientConfigurer(RestClients.RestClientConfigurationCallback.from(httpClientBuilder -> { clientConfigurerCount.incrementAndGet(); return httpClientBuilder; })); - } else if (clientUnderTestFactory instanceof ReactiveElasticsearchClientUnderTestFactory) { + } else if (clientUnderTestFactory instanceof ReactiveERHLCUnderTestFactory) { configurationBuilder.withClientConfigurer(ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> { clientConfigurerCount.incrementAndGet(); return webClient; })); + } else if (clientUnderTestFactory instanceof ELCUnderTestFactory) { + configurationBuilder.withClientConfigurer( + ElasticsearchClients.ElasticsearchClientConfigurationCallback.from(httpClientBuilder -> { + clientConfigurerCount.incrementAndGet(); + return httpClientBuilder; + })); + } else if (clientUnderTestFactory instanceof ReactiveELCUnderTestFactory) { + configurationBuilder + .withClientConfigurer(ElasticsearchClients.ElasticsearchClientConfigurationCallback.from(webClient -> { + clientConfigurerCount.incrementAndGet(); + return webClient; + })); } ClientConfiguration clientConfiguration = configurationBuilder.build(); @@ -130,7 +146,8 @@ public class RestClientsTest { ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration); // do several calls to check that the headerSupplier provided values are set - for (int i = 1; i <= 3; i++) { + int startValue = clientUnderTest.usesInitialRequest() ? 2 : 1; + for (int i = startValue; i <= startValue + 2; i++) { clientUnderTest.ping(); verify(headRequestedFor(urlEqualTo("/")) // @@ -140,7 +157,7 @@ public class RestClientsTest { .withHeader("def2", new EqualToPattern("def2-1")) // .withHeader("supplied", new EqualToPattern("val0")) // // on the first call Elasticsearch does the version check and thus already increments the counter - .withHeader("supplied", new EqualToPattern("val" + (i + 1))) // + .withHeader("supplied", new EqualToPattern("val" + (i))) // ); } @@ -155,7 +172,8 @@ public class RestClientsTest { wireMockServer(server -> { - stubFor(put(urlMatching("^/index/_doc/42(\\?.*)$?")) // + String urlPattern = "^/index/_doc/42(\\?.*)?$"; + stubFor(put(urlMatching(urlPattern)) // .willReturn(jsonResponse("{\n" + // " \"_id\": \"42\",\n" + // " \"_index\": \"test\",\n" + // @@ -196,9 +214,9 @@ public class RestClientsTest { } ; - clientUnderTest.save(new Foo("42")); + clientUnderTest.index(new Foo("42")); - verify(putRequestedFor(urlMatching("^/index/_doc/42(\\?.*)$?")) // + verify(putRequestedFor(urlMatching(urlPattern)) // .withHeader("Accept", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) // .withHeader("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) // ); @@ -286,7 +304,15 @@ public class RestClientsTest { */ boolean ping() throws Exception; - void save(T entity) throws IOException; + /** + * @return true if the client send an initial request for discovery, version check or similar. + */ + boolean usesInitialRequest(); + + /** + * save an entity using an index request + */ + void index(T entity) throws IOException; } /** @@ -304,9 +330,9 @@ public class RestClientsTest { } /** - * {@link ClientUnderTestFactory} implementation for the Standard {@link RestHighLevelClient}. + * {@link ClientUnderTestFactory} implementation for the old {@link RestHighLevelClient}. */ - static class RestClientUnderTestFactory extends ClientUnderTestFactory { + static class ERHLCUnderTestFactory extends ClientUnderTestFactory { @Override protected String getDisplayName() { @@ -323,7 +349,12 @@ public class RestClientsTest { } @Override - public void save(T entity) throws IOException { + public boolean usesInitialRequest() { + return true; + } + + @Override + public void index(T entity) throws IOException { IndexRequest indexRequest = new IndexRequest("index"); indexRequest.id("42"); indexRequest.source(entity, XContentType.JSON); @@ -335,35 +366,108 @@ public class RestClientsTest { } /** - * {@link ClientUnderTestFactory} implementation for the {@link ReactiveElasticsearchClient}. + * {@link ClientUnderTestFactory} implementation for the {@link co.elastic.clients.elasticsearch.ElasticsearchClient}. */ - static class ReactiveElasticsearchClientUnderTestFactory extends ClientUnderTestFactory { + static class ELCUnderTestFactory extends ClientUnderTestFactory { @Override protected String getDisplayName() { - return "ReactiveElasticsearchClient"; + return "ElasticsearchClient"; } @Override ClientUnderTest create(ClientConfiguration clientConfiguration) { - ReactiveElasticsearchClient client = ReactiveRestClients.create(clientConfiguration); + + ElasticsearchClient client = ElasticsearchClients.createImperative(clientConfiguration); return new ClientUnderTest() { @Override public boolean ping() throws Exception { - return client.ping().block(); + return client.ping().value(); } @Override - public void save(T entity) throws IOException { + public boolean usesInitialRequest() { + return false; + } + + @Override + public void index(T entity) throws IOException { + client.index(ir -> ir.index("index").id("42").document(entity)); + } + }; + } + } + + /** + * {@link ClientUnderTestFactory} implementation for the + * {@link org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient}. + */ + static class ReactiveERHLCUnderTestFactory extends ClientUnderTestFactory { + + @Override + protected String getDisplayName() { + return "ReactiveElasticsearchClient (RHLC based)"; + } + + @Override + ClientUnderTest create(ClientConfiguration clientConfiguration) { + org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient client = ReactiveRestClients + .create(clientConfiguration); + return new ClientUnderTest() { + @Override + public boolean ping() throws Exception { + return Boolean.TRUE.equals(client.ping().block()); + } + + @Override + public boolean usesInitialRequest() { + return true; + } + + @Override + public void index(T entity) throws IOException { IndexRequest indexRequest = new IndexRequest("index"); indexRequest.id("42"); - indexRequest.source("{}", XContentType.JSON); + indexRequest.source(entity, XContentType.JSON); client.index(indexRequest).block(); } }; } } + /** + * {@link ClientUnderTestFactory} implementation for the {@link ReactiveElasticsearchClient}. + */ + static class ReactiveELCUnderTestFactory extends ClientUnderTestFactory { + + @Override + protected String getDisplayName() { + return "ReactiveElasticsearchClient (ELC based)"; + } + + @Override + ClientUnderTest create(ClientConfiguration clientConfiguration) { + + ReactiveElasticsearchClient client = ElasticsearchClients.createReactive(clientConfiguration); + return new ClientUnderTest() { + @Override + public boolean ping() throws Exception { + return Boolean.TRUE.equals(client.ping().map(BooleanResponse::value).block()); + } + + @Override + public boolean usesInitialRequest() { + return false; + } + + @Override + public void index(T entity) throws IOException { + client.index(ir -> ir.index("index").id("42").document(entity)).block(); + } + }; + } + } + /** * Provides the factories to use in the parameterized tests * @@ -371,8 +475,9 @@ public class RestClientsTest { */ static Stream clientUnderTestFactorySource() { return Stream.of( // - new RestClientUnderTestFactory(), // - new ReactiveElasticsearchClientUnderTestFactory() // - ); + new ERHLCUnderTestFactory(), // + new ReactiveERHLCUnderTestFactory(), // + new ELCUnderTestFactory(), // + new ReactiveELCUnderTestFactory()); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClientTest.java b/src/test/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClientTest.java new file mode 100644 index 000000000..3b3aff5eb --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/client/elc/AutoCloseableElasticsearchClientTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.mockito.Mockito.*; + +import co.elastic.clients.json.JsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; + +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ExtendWith(MockitoExtension.class) +class AutoCloseableElasticsearchClientTest { + + @Mock private RestClient restClient; + @Mock private JsonpMapper jsonMapper; + + @Test // #1973 + @DisplayName("should close the RestClient") + void shouldCloseTheRestClient() throws Exception { + + ElasticsearchTransport transport = new RestClientTransport(restClient, jsonMapper); + // noinspection EmptyTryBlock + try (AutoCloseableElasticsearchClient ignored = new AutoCloseableElasticsearchClient(transport)) {} + + verify(restClient).close(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java b/src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java new file mode 100644 index 000000000..b28275186 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java @@ -0,0 +1,363 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import static org.assertj.core.api.Assertions.*; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.FieldValue; +import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; +import co.elastic.clients.elasticsearch.cluster.HealthRequest; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; +import co.elastic.clients.elasticsearch.core.IndexRequest; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient; +import co.elastic.clients.elasticsearch.indices.IndexSettings; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.TransportOptions; +import co.elastic.clients.transport.rest_client.RestClientOptions; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.function.Function; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.elasticsearch.client.RequestOptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; +import org.springframework.http.HttpHeaders; +import org.springframework.lang.Nullable; + +/** + * Not really tests, but a class to check the first implementations of the new Elasticsearch client. Needs Elasticsearch + * on port 9200 and an intercepting proxy on port 8080. + * + * @author Peter-Josef Meisch + */ +@Disabled +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class DevTests { + + private static Log LOGGER = LogFactory.getLog(DevTests.class); + + private static final String INDEX = "appdata-index"; + + private static final SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext(); + private static final MappingElasticsearchConverter converter = new MappingElasticsearchConverter(mappingContext); + + private final TransportOptions transportOptions = new RestClientOptions(RequestOptions.DEFAULT).toBuilder() + .addHeader("X-SpringDataElasticsearch-AlwaysThere", "true").setParameter("pretty", "true").build(); + + private final ReactiveElasticsearchClient reactiveElasticsearchClient = ElasticsearchClients + .createReactive(clientConfiguration(), transportOptions); + private final ElasticsearchClient imperativeElasticsearchClient = ElasticsearchClients + .createImperative(ElasticsearchClients.getRestClient(clientConfiguration()), transportOptions); + + @Test + void someTest() throws IOException { + + ElasticsearchClient client = imperativeElasticsearchClient; + + String index = "testindex"; + + Person person = new Person("42", new Name("Ford", "Prefect")); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info(String.format("Person: %s", person)); + } + + IndexRequest indexRequest = IndexRequest.of(b -> b.index(index).id(person.id).document(person)); + + client.index(indexRequest); + client.search(srb -> srb.index(index).trackTotalHits(thb -> thb.enabled(false)), Person.class); + + ReactiveClient asyncClient = new ReactiveClient(client._transport()); + + asyncClient.index(indexRequest).block(); + } + + static class ReactiveClient { + private final ElasticsearchTransport transport; + + ReactiveClient(ElasticsearchTransport transport) { + this.transport = transport; + } + + public Mono index(IndexRequest request) { + return Mono.fromFuture(transport.performRequestAsync(request, IndexRequest._ENDPOINT, null)); + } + } + + static class Person { + @Nullable String id; + @Nullable Name name; + + public Person() {} + + public Person(String id, Name name) { + this.id = id; + this.name = name; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Nullable + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } + + @Override + public String toString() { + return "Person{" + "id='" + id + '\'' + ", name=" + name + '}'; + } + } + + static class Name { + @Nullable String first; + @Nullable String last; + + public Name() {} + + public Name(String first, String last) { + this.first = first; + this.last = last; + } + + @Nullable + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + @Nullable + public String getLast() { + return last; + } + + public void setLast(String last) { + this.last = last; + } + + @Override + public String toString() { + return "Name{" + "first='" + first + '\'' + ", last='" + last + '\'' + '}'; + } + } + + // region cluster health + @Test + @Order(10) + void clusterHealth() { + + HealthRequest healthRequest = new HealthRequest.Builder().build(); + + try { + HealthResponse healthResponse = clusterHealthImperative(healthRequest); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + HealthResponse healthResponse = clusterHealthReactive(healthRequest); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private HealthResponse clusterHealthImperative(HealthRequest healthRequest) throws IOException { + return imperativeElasticsearchClient.cluster().health(healthRequest); + } + + private HealthResponse clusterHealthReactive(HealthRequest healthRequest) throws IOException { + return Objects.requireNonNull(reactiveElasticsearchClient.cluster().health(healthRequest).block()); + } + // endregion + + @Test + @Order(15) + void indexCreation() throws IOException { + + RequestConverter requestConverter = new RequestConverter(converter, + imperativeElasticsearchClient._transport().jsonpMapper()); + + String index = "pjtestindex"; + ElasticsearchIndicesClient indicesClient = imperativeElasticsearchClient.indices(); + + if (indicesClient.exists(erb -> erb.index(index)).value()) { + indicesClient.delete(drb -> drb.index(index)); + } + + String jsonSettings = "{\n" + // + " \"index\": {\n" + // + " \"number_of_shards\": \"1\",\n" + // + " \"number_of_replicas\": \"0\",\n" + // + " \"analysis\": {\n" + // + " \"analyzer\": {\n" + // + " \"emailAnalyzer\": {\n" + // + " \"type\": \"custom\",\n" + // + " \"tokenizer\": \"uax_url_email\"\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + "}\n"; + + String jsonMapping = "{\n" + // + " \"properties\": {\n" + // + " \"email\": {\n" + // + " \"type\": \"text\",\n" + // + " \"analyzer\": \"emailAnalyzer\"\n" + // + " }\n" + // + " }\n" + // + "}\n"; + + indicesClient.create(crb -> crb // + .index(index) // + .settings(requestConverter.fromJson(jsonSettings, IndexSettings._DESERIALIZER)) // + .mappings(requestConverter.fromJson(jsonMapping, TypeMapping._DESERIALIZER))); + } + + // region save + @Test + @Order(20) + void save() { + + Function> indexRequestBuilder = (Integer id) -> { + AppData appData = new AppData(); + appData.setId("id" + id); + appData.setContent("content" + id); + + return new IndexRequest.Builder().id(appData.getId()).document(appData).index(INDEX).build(); + }; + + try { + indexImperative(indexRequestBuilder.apply(1)); + } catch (IOException e) { + e.printStackTrace(); + } + try { + indexReactive(indexRequestBuilder.apply(2)); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private IndexResponse indexImperative(IndexRequest indexRequest) throws IOException { + return imperativeElasticsearchClient.index(indexRequest); + } + + private IndexResponse indexReactive(IndexRequest indexRequest) throws IOException { + return Objects.requireNonNull(reactiveElasticsearchClient.index(indexRequest).block()); + } + + // endregion + // region search + @Test + @Order(30) + void search() { + + SearchRequest searchRequest = new SearchRequest.Builder().index(INDEX) + .query(query -> query.match(matchQuery -> matchQuery.field("content").query(FieldValue.of("content1")))) + .build(); + + SearchResponse searchResponse = null; + try { + searchResponse = searchImperative(searchRequest); + assertThat(searchResponse).isNotNull(); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + searchResponse = searchReactive(searchRequest); + assertThat(searchResponse).isNotNull(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private SearchResponse searchImperative(SearchRequest searchRequest) throws IOException { + return imperativeElasticsearchClient.search(searchRequest, EntityAsMap.class); + } + + private SearchResponse searchReactive(SearchRequest searchRequest) { + return Objects.requireNonNull(reactiveElasticsearchClient.search(searchRequest, EntityAsMap.class).block()); + } + // endregion + + private ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() // + .connectedTo("localhost:9200")// + .withBasicAuth("elastic", "hcraescitsale").withProxy("localhost:8080") // + .withHeaders(() -> { + HttpHeaders headers = new HttpHeaders(); + headers.add("X-SpringDataElasticsearch-timestamp", + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + return headers; + }) // + .build(); + } + + private static class AppData { + @Nullable private String id; + @Nullable private String content; + + @Nullable + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Nullable + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/client/elc/DocumentAdaptersUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/client/elc/DocumentAdaptersUnitTests.java new file mode 100644 index 000000000..5133d39a4 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/client/elc/DocumentAdaptersUnitTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.json.JsonData; +import co.elastic.clients.json.JsonpMapper; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; + +import java.util.Collections; +import java.util.List; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.elasticsearch.core.document.SearchDocument; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +// todo #1973 check that all is tested what was in the elasticsearch7 version +class DocumentAdaptersUnitTests { + + private final JsonpMapper jsonpMapper = new JacksonJsonpMapper(); + + @Test // #1973 + @DisplayName("should adapt search Hit from fields") + void shouldAdaptSearchHitFromFields() { + + Hit searchHit = new Hit.Builder() // + .index("index") // + .id("my-id") // + .score(42d) // + .fields("field1", JsonData.of(Collections.singletonList("listValue"))) // + .fields("field2", JsonData.of("stringValue")) // + .seqNo(1l) // + .primaryTerm(2l) // + .build(); // + + SearchDocument document = DocumentAdapters.from(searchHit, jsonpMapper); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(document.getIndex()).isEqualTo("index"); + softly.assertThat(document.hasId()).isTrue(); + softly.assertThat(document.getId()).isEqualTo("my-id"); + softly.assertThat(document.hasVersion()).isFalse(); + softly.assertThat(document.getScore()).isBetween(42f, 42f); + Object field1 = document.get("field1"); + softly.assertThat(field1).isInstanceOf(List.class); + // noinspection unchecked + List fieldList = (List) field1; + softly.assertThat(fieldList).containsExactly("listValue"); + softly.assertThat(document.get("field2")).isEqualTo("stringValue"); + softly.assertThat(document.hasSeqNo()).isTrue(); + softly.assertThat(document.getSeqNo()).isEqualTo(1); + softly.assertThat(document.hasPrimaryTerm()).isTrue(); + softly.assertThat(document.getPrimaryTerm()).isEqualTo(2); + + softly.assertAll(); + } + + @Test // #1973 + @DisplayName("should adapt search Hit from source") + void shouldAdaptSearchHitFromSource() { + + EntityAsMap eam = new EntityAsMap(); + eam.put("field", "value"); + Hit searchHit = new Hit.Builder() // + .index("index") // + .id("my-id") // + .score(42d) // + .seqNo(1l) // + .primaryTerm(2l) // + .source(eam) // + .build(); // + + SearchDocument document = DocumentAdapters.from(searchHit, jsonpMapper); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(document.getIndex()).isEqualTo("index"); + softly.assertThat(document.hasId()).isTrue(); + softly.assertThat(document.getId()).isEqualTo("my-id"); + softly.assertThat(document.hasVersion()).isFalse(); + softly.assertThat(document.getScore()).isBetween(42f, 42f); + softly.assertThat(document.get("field")).isEqualTo("value"); + softly.assertThat(document.hasSeqNo()).isTrue(); + softly.assertThat(document.getSeqNo()).isEqualTo(1); + softly.assertThat(document.hasPrimaryTerm()).isTrue(); + softly.assertThat(document.getPrimaryTerm()).isEqualTo(2); + + softly.assertAll(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java new file mode 100644 index 000000000..58f65d655 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchPartQueryELCIntegrationTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.client.elc; + +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.query.ElasticsearchPartQueryIntegrationTests; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class ElasticsearchPartQueryELCIntegrationTests extends ElasticsearchPartQueryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config {} + + @Override + protected String buildQueryString(CriteriaQuery criteriaQuery, Class clazz) { + + JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper(); + RequestConverter requestConverter = new RequestConverter(operations.getElasticsearchConverter(), jsonpMapper); + SearchRequest request = requestConverter.searchRequest(criteriaQuery, clazz, IndexCoordinates.of("dummy"), false, + false); + + return JsonUtils.toJson(request, jsonpMapper); + // return "{\"query\":" + JsonUtils.toJson(request.query(), jsonpMapper) + "}"; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/AuditingELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingELCIntegrationTests.java new file mode 100644 index 000000000..536fdf511 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/AuditingELCIntegrationTests.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config; + +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { AuditingELCIntegrationTests.Config.class }) +public class AuditingELCIntegrationTests extends AuditingIntegrationTests { + + @Import({ ElasticsearchTemplateConfiguration.class, AuditingIntegrationTests.Config.class }) + static class Config {} +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingERHLCIntegrationTests.java similarity index 71% rename from src/test/java/org/springframework/data/elasticsearch/core/InnerHitsRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/config/AuditingERHLCIntegrationTests.java index 26e63570e..c6e6953b4 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/AuditingERHLCIntegrationTests.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core; +package org.springframework.data.elasticsearch.config; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.test.context.ContextConfiguration; @@ -23,9 +22,9 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { InnerHitsRestTemplateIntegrationTests.Config.class }) -public class InnerHitsRestTemplateIntegrationTests extends InnerHitsIntegrationTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) +@ContextConfiguration(classes = { AuditingERHLCIntegrationTests.Config.class }) +public class AuditingERHLCIntegrationTests extends AuditingIntegrationTests { + + @Import({ ElasticsearchRestTemplateConfiguration.class, AuditingIntegrationTests.Config.class }) static class Config {} } diff --git a/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTests.java index b5a51594b..552004d91 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTests.java @@ -23,6 +23,8 @@ import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; @@ -33,6 +35,7 @@ import org.springframework.data.domain.Persistable; import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.lang.Nullable; @@ -40,8 +43,18 @@ import org.springframework.lang.Nullable; * @author Peter-Josef Meisch * @author Roman Puchkovskiy */ +@SpringIntegrationTest public abstract class AuditingIntegrationTests { + @Configuration + @EnableElasticsearchAuditing(auditorAwareRef = "auditorAware") + static class Config { + @Bean + public AuditorAware auditorAware() { + return auditorProvider(); + } + } + public static AuditorAware auditorProvider() { return new AuditorAware() { int count = 0; diff --git a/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveELCIntegrationTests.java new file mode 100644 index 000000000..196f6295f --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveELCIntegrationTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config; + +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { AuditingReactiveELCIntegrationTests.Config.class }) +public class AuditingReactiveELCIntegrationTests extends AuditingReactiveIntegrationTest { + @Import({ ReactiveElasticsearchTemplateConfiguration.class, AuditingReactiveIntegrationTest.Config.class }) + static class Config { + + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveERHLCIntegrationTests.java new file mode 100644 index 000000000..d702960fb --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveERHLCIntegrationTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config; + +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { AuditingReactiveERHLCIntegrationTests.Config.class }) +public class AuditingReactiveERHLCIntegrationTests extends AuditingReactiveIntegrationTest { + + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class, AuditingReactiveIntegrationTest.Config.class }) + static class Config { + + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/ReactiveAuditingIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveIntegrationTest.java similarity index 91% rename from src/test/java/org/springframework/data/elasticsearch/config/ReactiveAuditingIntegrationTest.java rename to src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveIntegrationTest.java index 29262feb1..3059c5601 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/ReactiveAuditingIntegrationTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/AuditingReactiveIntegrationTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Configuration; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; @@ -36,19 +36,25 @@ import org.springframework.data.domain.ReactiveAuditorAware; import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch * @author Roman Puchkovskiy */ @SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveAuditingIntegrationTest.Config.class }) -public class ReactiveAuditingIntegrationTest { +public abstract class AuditingReactiveIntegrationTest { + + @Configuration + @EnableReactiveElasticsearchAuditing(auditorAwareRef = "auditorAware") + static class Config { + @Bean + public ReactiveAuditorAware auditorAware() { + return auditorProvider(); + } + } public static ReactiveAuditorAware auditorProvider() { return new ReactiveAuditorAware() { @@ -61,16 +67,6 @@ public class ReactiveAuditingIntegrationTest { }; } - @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) - @EnableReactiveElasticsearchAuditing(auditorAwareRef = "auditorAware") - static class Config { - - @Bean - public ReactiveAuditorAware auditorAware() { - return auditorProvider(); - } - } - @Autowired ApplicationContext applicationContext; @Test // DATAES-68 diff --git a/src/test/java/org/springframework/data/elasticsearch/config/AuditingRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingRestTemplateIntegrationTests.java deleted file mode 100644 index c7f935eab..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/config/AuditingRestTemplateIntegrationTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.data.domain.AuditorAware; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@SpringIntegrationTest -@ContextConfiguration(classes = { AuditingRestTemplateIntegrationTests.Config.class }) -public class AuditingRestTemplateIntegrationTests extends AuditingIntegrationTests { - - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchAuditing(auditorAwareRef = "auditorAware") - static class Config { - - @Bean - public AuditorAware auditorAware() { - return auditorProvider(); - } - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/abstractelasticsearchconfiguration/package-info.java b/src/test/java/org/springframework/data/elasticsearch/config/abstractelasticsearchconfiguration/package-info.java deleted file mode 100644 index 90bdb2cb8..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/config/abstractelasticsearchconfiguration/package-info.java +++ /dev/null @@ -1,3 +0,0 @@ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields -package org.springframework.data.elasticsearch.config.abstractelasticsearchconfiguration; diff --git a/src/test/java/org/springframework/data/elasticsearch/config/configuration/ElasticsearchConfigurationELCTests.java b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ElasticsearchConfigurationELCTests.java new file mode 100644 index 000000000..321561ea9 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ElasticsearchConfigurationELCTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config.configuration; + +import static org.assertj.core.api.Assertions.*; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; + +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Tests for {@link ElasticsearchConfiguration}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class ElasticsearchConfigurationELCTests { + + @Configuration + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.config.configuration" }, + considerNestedRepositories = true) + static class Config extends ElasticsearchConfiguration { + @NonNull + @Override + public ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() // + .connectedTo("localhost:9200") // + .build(); + } + } + + /* + * using a repository with an entity that is set to createIndex = false as we have no elastic running for this test + * and just check that all the necessary beans are created. + */ + @Autowired private RestClient restClient; + @Autowired private ElasticsearchClient elasticsearchClient; + @Autowired private ElasticsearchOperations elasticsearchOperations; + + @Autowired private CreateIndexFalseRepository repository; + + @Test + public void providesRequiredBeans() { + assertThat(restClient).isNotNull(); + assertThat(elasticsearchClient).isNotNull(); + assertThat(elasticsearchOperations).isNotNull(); + assertThat(repository).isNotNull(); + } + + @Document(indexName = "test-index-config-configuration", createIndex = false) + static class CreateIndexFalseEntity { + + @Nullable + @Id private String id; + } + + interface CreateIndexFalseRepository extends ElasticsearchRepository {} +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/abstractelasticsearchconfiguration/ElasticsearchConfigurationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ElasticsearchConfigurationERHLCTests.java similarity index 86% rename from src/test/java/org/springframework/data/elasticsearch/config/abstractelasticsearchconfiguration/ElasticsearchConfigurationTests.java rename to src/test/java/org/springframework/data/elasticsearch/config/configuration/ElasticsearchConfigurationERHLCTests.java index a47c1254b..0130a01d9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/abstractelasticsearchconfiguration/ElasticsearchConfigurationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ElasticsearchConfigurationERHLCTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.config.abstractelasticsearchconfiguration; +package org.springframework.data.elasticsearch.config.configuration; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; @@ -39,7 +39,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; */ @ExtendWith(SpringExtension.class) @ContextConfiguration -public class ElasticsearchConfigurationTests { +public class ElasticsearchConfigurationERHLCTests { /* * using a repository with an entity that is set to createIndex = false as we have no elastic running for this test @@ -48,8 +48,7 @@ public class ElasticsearchConfigurationTests { @Autowired private CreateIndexFalseRepository repository; @Configuration - @EnableElasticsearchRepositories( - basePackages = { "org.springframework.data.elasticsearch.config.abstractelasticsearchconfiguration" }, + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.config.configuration" }, considerNestedRepositories = true) static class Config extends AbstractElasticsearchConfiguration { @@ -66,7 +65,7 @@ public class ElasticsearchConfigurationTests { assertThat(repository).isNotNull(); } - @Document(indexName = "test-index-config-abstractelasticsearchconfiguraiton", createIndex = false) + @Document(indexName = "test-index-config-configuration", createIndex = false) static class CreateIndexFalseEntity { @Nullable diff --git a/src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationELCTests.java b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationELCTests.java new file mode 100644 index 000000000..c43a21980 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationELCTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config.configuration; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient; +import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration; +import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration; +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository; +import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Tests for {@link AbstractElasticsearchConfiguration}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class ReactiveElasticsearchConfigurationELCTests { + + @Configuration + @EnableReactiveElasticsearchRepositories( + basePackages = { "org.springframework.data.elasticsearch.config.configuration" }, + considerNestedRepositories = true) + static class Config extends ReactiveElasticsearchConfiguration { + + @NonNull + @Override + public ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() // + .connectedTo("localhost:9200") // + .build(); + } + } + + /* + * using a repository with an entity that is set to createIndex = false as we have no elastic running for this test + * and just check that all the necessary beans are created. + */ + // @Autowired private WebClient webClient; + @Autowired private ReactiveElasticsearchClient reactiveElasticsearchClient; + @Autowired private ReactiveElasticsearchOperations reactiveElasticsearchOperations; + @Autowired private CreateIndexFalseRepository repository; + + @Test + public void providesRequiredBeans() { + // assertThat(webClient).isNotNull(); + assertThat(reactiveElasticsearchClient).isNotNull(); + assertThat(reactiveElasticsearchOperations).isNotNull(); + assertThat(repository).isNotNull(); + } + + @Document(indexName = "test-index-config-configuration", createIndex = false) + static class CreateIndexFalseEntity { + + @Nullable + @Id private String id; + } + + interface CreateIndexFalseRepository extends ReactiveElasticsearchRepository {} +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationERHLCTests.java b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationERHLCTests.java new file mode 100644 index 000000000..084193ec6 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/configuration/ReactiveElasticsearchConfigurationERHLCTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config.configuration; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; +import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration; +import org.springframework.data.elasticsearch.config.AbstractReactiveElasticsearchConfiguration; +import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository; +import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Tests for {@link AbstractElasticsearchConfiguration}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class ReactiveElasticsearchConfigurationERHLCTests { + + /* + * using a repository with an entity that is set to createIndex = false as we have no elastic running for this test + * and just check that all the necessary beans are created. + */ + @Autowired private CreateIndexFalseRepository repository; + + @Configuration + @EnableReactiveElasticsearchRepositories( + basePackages = { "org.springframework.data.elasticsearch.config.configuration" }, + considerNestedRepositories = true) + static class Config extends AbstractReactiveElasticsearchConfiguration { + + @Override + public ReactiveElasticsearchClient reactiveElasticsearchClient() { + return mock(ReactiveElasticsearchClient.class); + } + } + + @Test + public void bootstrapsRepository() { + + assertThat(repository).isNotNull(); + } + + @Document(indexName = "test-index-config-configuration", createIndex = false) + static class CreateIndexFalseEntity { + + @Nullable + @Id private String id; + } + + interface CreateIndexFalseRepository extends ReactiveElasticsearchRepository {} +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesELCIntegrationTests.java new file mode 100644 index 000000000..3d0c4cd14 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesELCIntegrationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config.nested; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { EnableNestedRepositoriesELCIntegrationTests.Config.class }) +public class EnableNestedRepositoriesELCIntegrationTests + extends EnableNestedRepositoriesIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.config.nested" }, + considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("nested-repositories"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesERHLCIntegrationTests.java similarity index 73% rename from src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesERHLCIntegrationTests.java index ca334593d..0a4e971fb 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesERHLCIntegrationTests.java @@ -15,22 +15,30 @@ */ package org.springframework.data.elasticsearch.config.nested; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { EnableNestedElasticsearchRepositoriesRestTemplateIntegrationTests.Config.class }) -public class EnableNestedElasticsearchRepositoriesRestTemplateIntegrationTests - extends EnableNestedElasticsearchRepositoriesIntegrationTests { +@ContextConfiguration(classes = { EnableNestedRepositoriesERHLCIntegrationTests.Config.class }) +public class EnableNestedRepositoriesERHLCIntegrationTests extends EnableNestedRepositoriesIntegrationTests { + @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.config.nested" }, considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("nested-repositories-es7"); + } + + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesIntegrationTests.java similarity index 82% rename from src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesIntegrationTests.java index 3d0f05b83..44a0175fa 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedRepositoriesIntegrationTests.java @@ -21,8 +21,8 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import java.lang.Double; import java.lang.Long; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -32,7 +32,9 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.ScriptedField; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.repository.Repository; import org.springframework.lang.Nullable; @@ -41,19 +43,22 @@ import org.springframework.lang.Nullable; * @author Peter-Josef Meisch */ @SpringIntegrationTest -public abstract class EnableNestedElasticsearchRepositoriesIntegrationTests { +public abstract class EnableNestedRepositoriesIntegrationTests { @Autowired(required = false) private SampleRepository nestedRepository; @Autowired ElasticsearchOperations operations; + @Autowired IndexNameProvider indexNameProvider; @BeforeEach void setUp() { + indexNameProvider.increment(); operations.indexOps(SampleEntity.class).delete(); } - @AfterEach - void tearDown() { - operations.indexOps(SampleEntity.class).delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -61,7 +66,7 @@ public abstract class EnableNestedElasticsearchRepositoriesIntegrationTests { assertThat(nestedRepository).isNotNull(); } - @Document(indexName = "test-index-sample-config-nested") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesELCIntegrationTests.java new file mode 100644 index 000000000..1d892f1b1 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesELCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.config.notnested; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +public class EnableRepositoriesELCIntegrationTests extends EnableRepositoriesIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("repositories"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesERHLCIntegrationTests.java similarity index 76% rename from src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesRestTemplateTests.java rename to src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesERHLCIntegrationTests.java index 88a54f45b..fbc141d55 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesRestTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesERHLCIntegrationTests.java @@ -15,18 +15,25 @@ */ package org.springframework.data.elasticsearch.config.notnested; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; /** * @author Peter-Josef Meisch */ -public class EnableElasticsearchRepositoriesRestTemplateTests extends EnableElasticsearchRepositoriesTests { +public class EnableRepositoriesERHLCIntegrationTests extends EnableRepositoriesIntegrationTests { + @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories - static class Config {} - + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("repositories-es7"); + } + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesIntegrationTests.java similarity index 90% rename from src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java rename to src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesIntegrationTests.java index 3cc02df12..83c013e70 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableRepositoriesIntegrationTests.java @@ -22,8 +22,8 @@ import java.lang.Double; import java.lang.Long; import java.util.UUID; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; @@ -36,12 +36,11 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.ScriptedField; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.repository.Repository; import org.springframework.lang.Nullable; @@ -53,18 +52,17 @@ import org.springframework.lang.Nullable; * @author Peter-Josef Meisch */ @SpringIntegrationTest -public abstract class EnableElasticsearchRepositoriesTests implements ApplicationContextAware { +public abstract class EnableRepositoriesIntegrationTests implements ApplicationContextAware { @Nullable ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.context = applicationContext; } @Autowired ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired IndexNameProvider indexNameProvider; @Autowired private SampleElasticsearchRepository repository; @@ -74,14 +72,14 @@ public abstract class EnableElasticsearchRepositoriesTests implements Applicatio @BeforeEach public void before() { - indexOperations = operations.indexOps(SampleEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(SampleEntity.class).createWithMapping(); } - @AfterEach - void tearDown() { - operations.indexOps(IndexCoordinates.of("test-index-sample-config-not-nested")).delete(); - operations.indexOps(IndexCoordinates.of("test-index-uuid-keyed-config-not-nested")).delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -109,7 +107,7 @@ public abstract class EnableElasticsearchRepositoriesTests implements Applicatio assertThat(nestedRepository).isNull(); } - @Document(indexName = "test-index-sample-config-not-nested") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; @@ -206,7 +204,7 @@ public abstract class EnableElasticsearchRepositoriesTests implements Applicatio } } - @Document(indexName = "test-index-uuid-keyed-config-not-nested") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntityUUIDKeyed { @Nullable @Id private UUID id; diff --git a/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleElasticsearchRepository.java b/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleElasticsearchRepository.java index 616eb9273..28cf34d76 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleElasticsearchRepository.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleElasticsearchRepository.java @@ -25,13 +25,13 @@ import org.springframework.data.elasticsearch.repository.ElasticsearchRepository * @author Christoph Strobl */ public interface SampleElasticsearchRepository - extends ElasticsearchRepository { + extends ElasticsearchRepository { long deleteSampleEntityById(String id); - List deleteByAvailable(boolean available); + List deleteByAvailable(boolean available); - List deleteByMessage(String message); + List deleteByMessage(String message); void deleteByType(String type); diff --git a/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleUUIDKeyedElasticsearchRepository.java b/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleUUIDKeyedElasticsearchRepository.java index 2c14def5a..cb4e7aef3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleUUIDKeyedElasticsearchRepository.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/notnested/SampleUUIDKeyedElasticsearchRepository.java @@ -25,13 +25,13 @@ import org.springframework.data.elasticsearch.repository.ElasticsearchRepository * @author Christoph Strobl */ interface SampleUUIDKeyedElasticsearchRepository - extends ElasticsearchRepository { + extends ElasticsearchRepository { long deleteSampleEntityUUIDKeyedById(UUID id); - List deleteByAvailable(boolean available); + List deleteByAvailable(boolean available); - List deleteByMessage(String message); + List deleteByMessage(String message); void deleteByType(String type); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchELCIntegrationTests.java new file mode 100644 index 000000000..4c99f5122 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchELCIntegrationTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import static org.springframework.data.elasticsearch.client.elc.QueryBuilders.*; + +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; + +import org.junit.jupiter.api.DisplayName; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Farid Faoudi + * @since 4.4 + */ +@ContextConfiguration(classes = { ElasticsearchELCIntegrationTests.Config.class }) +@DisplayName("Using Elasticsearch Client") +public class ElasticsearchELCIntegrationTests extends ElasticsearchIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("integration"); + } + } + + @Override + public boolean usesNewElasticsearchClient() { + return true; + } + + @Override + protected Query queryWithIds(String... ids) { + return NativeQuery.builder().withIds(ids).build(); + } + + @Override + protected Query getTermQuery(String field, String value) { + return NativeQuery.builder().withQuery(termQueryAsQuery(field, value)).build(); + } + + @Override + protected BaseQueryBuilder getBuilderWithMatchAllQuery() { + return NativeQuery.builder().withQuery(matchAllQueryAsQuery()); + } + + @Override + protected BaseQueryBuilder getBuilderWithMatchQuery(String field, String value) { + return NativeQuery.builder().withQuery(matchQueryAsQuery(field, value, null, null)); + } + + @Override + protected BaseQueryBuilder getBuilderWithTermQuery(String field, String value) { + return NativeQuery.builder().withQuery(termQueryAsQuery(field, value)); + } + + @Override + protected BaseQueryBuilder getBuilderWithWildcardQuery(String field, String value) { + return NativeQuery.builder().withQuery(wildcardQueryAsQuery(field, value)); + } + + @Override + protected Query getBoolQueryWithWildcardsFirstMustSecondShouldAndMinScore(String firstField, String firstValue, + String secondField, String secondValue, float minScore) { + + return NativeQuery.builder().withQuery(q -> q // + .bool(BoolQuery.of(b -> b // + .must(m -> m.wildcard(w1 -> w1.field(firstField).wildcard(firstValue))) // + .should(s -> s.wildcard(w2 -> w2.field(secondField).wildcard(secondValue)))))) // + .withMinScore(minScore) // + .build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchERHLCIntegrationTests.java similarity index 76% rename from src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchERHLCIntegrationTests.java index f8ffb104a..9d3a66cf6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchERHLCIntegrationTests.java @@ -18,7 +18,6 @@ package org.springframework.data.elasticsearch.core; import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; import static org.skyscreamer.jsonassert.JSONAssert.*; -import static org.springframework.data.elasticsearch.utils.IdGenerator.*; import java.time.Duration; import java.util.Collections; @@ -37,11 +36,11 @@ import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.dao.DataAccessException; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder; import org.springframework.data.elasticsearch.core.query.IndicesOptions; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.utils.IndexNameProvider; @@ -62,28 +61,58 @@ import org.springframework.test.context.ContextConfiguration; * @author Peter-Josef Meisch * @author Farid Faoudi */ -@ContextConfiguration(classes = { ElasticsearchRestTemplateTests.Config.class }) -@DisplayName("ElasticsearchRestTemplate") -public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests { +@ContextConfiguration(classes = { ElasticsearchERHLCIntegrationTests.Config.class }) +@DisplayName("Using Elasticsearch RestHighLevelClient") +public class ElasticsearchERHLCIntegrationTests extends ElasticsearchIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) static class Config { @Bean IndexNameProvider indexNameProvider() { - return new IndexNameProvider("rest-template"); + return new IndexNameProvider("integration-es7"); } } - @Test - public void shouldThrowDataAccessExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() { + @Override + protected Query queryWithIds(String... ids) { + return new NativeSearchQueryBuilder().withIds(ids).build(); + } - // when - org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document - .create(); - UpdateQuery updateQuery = UpdateQuery.builder(nextIdAsString()).withDocument(document).build(); - assertThatThrownBy(() -> operations.update(updateQuery, IndexCoordinates.of(indexNameProvider.indexName()))) - .isInstanceOf(DataAccessException.class); + @Override + protected Query getTermQuery(String field, String value) { + return new NativeSearchQueryBuilder().withQuery(termQuery(field, value)).build(); + } + + @Override + protected BaseQueryBuilder getBuilderWithTermQuery(String field, String value) { + return new NativeSearchQueryBuilder().withQuery(termQuery(field, value)); + } + + @Override + protected BaseQueryBuilder getBuilderWithMatchQuery(String field, String value) { + return new NativeSearchQueryBuilder().withQuery(matchQuery(field, value)); + } + + @Override + protected BaseQueryBuilder getBuilderWithWildcardQuery(String field, String value) { + return new NativeSearchQueryBuilder().withQuery(wildcardQuery(field, value)); + } + + @Override + protected BaseQueryBuilder getBuilderWithMatchAllQuery() { + return new NativeSearchQueryBuilder().withQuery(matchAllQuery()); + + } + + @Override + protected Query getBoolQueryWithWildcardsFirstMustSecondShouldAndMinScore(String firstField, String firstValue, + String secondField, String secondValue, float minScore) { + return new NativeSearchQueryBuilder().withQuery( // + boolQuery() // + .must(wildcardQuery(firstField, firstValue)) // + .should(wildcardQuery(secondField, secondValue))) // + .withMinScore(minScore).build(); } @Test // DATAES-768 @@ -127,7 +156,7 @@ public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests { @Test // #1446 void shouldUseAllOptionsFromUpdateByQuery() throws JSONException { - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) // + Query searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) // .withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN) // .build(); // searchQuery.setScrollTime(Duration.ofMillis(1000)); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchExceptionTranslatorTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchExceptionTranslatorTests.java index 8b7d44f93..f5daff6c9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchExceptionTranslatorTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchExceptionTranslatorTests.java @@ -22,7 +22,6 @@ import org.elasticsearch.index.engine.VersionConflictEngineException; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.rest.RestStatus; import org.junit.jupiter.api.Test; - import org.springframework.dao.DataAccessException; import org.springframework.dao.OptimisticLockingFailureException; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java similarity index 88% rename from src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java index ed5b5208f..a0d3c417a 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java @@ -29,22 +29,13 @@ import java.lang.Double; import java.lang.Integer; import java.lang.Long; import java.lang.Object; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.assertj.core.api.SoftAssertions; import org.assertj.core.util.Lists; -import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.common.lucene.search.function.CombineFunction; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; @@ -57,16 +48,14 @@ import org.elasticsearch.join.query.ParentIdQueryBuilder; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.collapse.CollapseBuilder; -import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.elasticsearch.search.sort.FieldSortBuilder; -import org.elasticsearch.search.sort.SortBuilders; -import org.elasticsearch.search.sort.SortOrder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.annotation.Id; @@ -74,6 +63,7 @@ import org.springframework.data.annotation.Version; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.NewElasticsearchClientDevelopment; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; @@ -88,19 +78,18 @@ import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.core.index.AliasAction; import org.springframework.data.elasticsearch.core.index.AliasActionParameters; import org.springframework.data.elasticsearch.core.index.AliasActions; +import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.join.JoinField; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; import org.springframework.data.elasticsearch.core.query.RescorerQuery.ScoreMode; -import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; -import org.springframework.data.elasticsearch.core.reindex.ReindexResponse; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.util.StreamUtils; import org.springframework.lang.Nullable; /** - * Base for testing rest/transport templates. Contains the test common to both implementing classes. + * All the integration tests that are not in separate files. * * @author Rizwan Idrees * @author Mohsin Husen @@ -128,11 +117,13 @@ import org.springframework.lang.Nullable; * @author Sijia Liu */ @SpringIntegrationTest -public abstract class ElasticsearchTemplateTests { +public abstract class ElasticsearchIntegrationTests implements NewElasticsearchClientDevelopment { - private static final String INDEX_1_NAME = "test-index-1"; - private static final String INDEX_2_NAME = "test-index-2"; - private static final String INDEX_3_NAME = "test-index-3"; + private static final String MULTI_INDEX_PREFIX = "test-index"; + private static final String MULTI_INDEX_ALL = MULTI_INDEX_PREFIX + "*"; + private static final String MULTI_INDEX_1_NAME = MULTI_INDEX_PREFIX + "-1"; + private static final String MULTI_INDEX_2_NAME = MULTI_INDEX_PREFIX + "-2"; + private static final String MULTI_INDEX_3_NAME = MULTI_INDEX_PREFIX + "-3"; @Autowired protected ElasticsearchOperations operations; private IndexOperations indexOperations; @@ -151,6 +142,43 @@ public abstract class ElasticsearchTemplateTests { @Order(java.lang.Integer.MAX_VALUE) void cleanup() { operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_ALL)).delete(); + } + + /** + * creates a Query that has the given ids set + * + * @param ids the ids for the query + * @return Query object + */ + protected abstract Query queryWithIds(String... ids); + + /** + * @return a {@link BaseQueryBuilder} that allready has a matchAll query set + */ + // protected abstract BaseQueryBuilder getMatchAllQueryBuilder(); + + private Query queryWithIds(Collection ids) { + return queryWithIds(ids.toArray(new String[ids.size()])); + } + + protected abstract BaseQueryBuilder getBuilderWithMatchAllQuery(); + + protected abstract BaseQueryBuilder getBuilderWithMatchQuery(String field, String value); + + protected abstract BaseQueryBuilder getBuilderWithTermQuery(String field, String value); + + protected abstract BaseQueryBuilder getBuilderWithWildcardQuery(String field, String value); + + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation + @Test + public void shouldThrowDataAccessExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() { + + org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document + .create(); + UpdateQuery updateQuery = UpdateQuery.builder(nextIdAsString()).withDocument(document).build(); + assertThatThrownBy(() -> operations.update(updateQuery, IndexCoordinates.of(indexNameProvider.indexName()))) + .isInstanceOf(DataAccessException.class); } @Test // DATAES-106 @@ -178,7 +206,6 @@ public abstract class ElasticsearchTemplateTests { @Test public void shouldReturnCountForGivenSearchQuery() { - // given String documentId = nextIdAsString(); SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("some message") .version(System.currentTimeMillis()).build(); @@ -186,13 +213,9 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery = getIndexQuery(sampleEntity); operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); - ; - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - - // when + Query searchQuery = operations.matchAllQuery(); long count = operations.count(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); - // then assertThat(count).isEqualTo(1); } @@ -233,7 +256,7 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when - NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)).build(); + Query query = queryWithIds(documentId, documentId2); List> sampleEntities = operations.multiGet(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -265,7 +288,7 @@ public abstract class ElasticsearchTemplateTests { List idsToSearch = Arrays.asList(documentId, nextIdAsString(), documentId2); assertThat(idsToSearch).hasSize(3); - NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(idsToSearch).build(); + Query query = queryWithIds(idsToSearch); List> sampleEntities = operations.multiGet(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -280,7 +303,7 @@ public abstract class ElasticsearchTemplateTests { @DisplayName("should return failure in multiget result") void shouldReturnFailureInMultigetResult() { - NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList("42")).build(); + Query query = queryWithIds("42"); List> sampleEntities = operations.multiGet(query, SampleEntity.class, IndexCoordinates.of("not-existing-index")); @@ -309,7 +332,7 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when - NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)) + Query query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)) .withFields("message", "type").build(); List> sampleEntities = operations.multiGet(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -330,7 +353,7 @@ public abstract class ElasticsearchTemplateTests { operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, @@ -345,23 +368,17 @@ public abstract class ElasticsearchTemplateTests { @Test // DATAES-595 public void shouldReturnSearchHitsUsingLocalPreferenceForGivenSearchQuery() { - // given String documentId = nextIdAsString(); SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("some message") .version(System.currentTimeMillis()).build(); - IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); - - NativeSearchQuery searchQueryWithValidPreference = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + Query query = getBuilderWithMatchAllQuery() .withPreference("_local").build(); - // when - SearchHits searchHits = operations.search(searchQueryWithValidPreference, SampleEntity.class, + SearchHits searchHits = operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); } @@ -369,20 +386,15 @@ public abstract class ElasticsearchTemplateTests { @Test // DATAES-595 public void shouldThrowExceptionWhenInvalidPreferenceForSearchQuery() { - // given String documentId = nextIdAsString(); SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("some message") .version(System.currentTimeMillis()).build(); - IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); - - NativeSearchQuery searchQueryWithInvalidPreference = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + Query query = getBuilderWithMatchAllQuery() .withPreference("_only_nodes:oops").build(); - // when - assertThatThrownBy(() -> operations.search(searchQueryWithInvalidPreference, SampleEntity.class, + assertThatThrownBy(() -> operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName()))).isInstanceOf(Exception.class); } @@ -396,15 +408,14 @@ public abstract class ElasticsearchTemplateTests { IndexQuery idxQuery = new IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity).build(); - operations.index(idxQuery, IndexCoordinates.of(INDEX_1_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); + operations.index(idxQuery, IndexCoordinates.of(MULTI_INDEX_1_NAME)); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).refresh(); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN).build(); + Query query = getBuilderWithMatchAllQuery().withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, - IndexCoordinates.of(INDEX_1_NAME, INDEX_2_NAME)); + SearchHits searchHits = operations.search(query, SampleEntity.class, + IndexCoordinates.of(MULTI_INDEX_1_NAME, MULTI_INDEX_2_NAME)); // then assertThat(searchHits).isNotNull(); @@ -430,7 +441,7 @@ public abstract class ElasticsearchTemplateTests { indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2)); operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, @@ -440,6 +451,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(searchHits.getTotalHits()).isEqualTo(2); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test public void shouldDoBulkUpdate() { @@ -490,7 +502,7 @@ public abstract class ElasticsearchTemplateTests { operations.delete(documentId, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); + Query searchQuery = getTermQuery("id", documentId); SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -513,7 +525,7 @@ public abstract class ElasticsearchTemplateTests { operations.delete(documentId, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); + Query searchQuery = getTermQuery("id", documentId); SearchHits sampleEntities = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(sampleEntities.getTotalHits()).isEqualTo(0); @@ -532,11 +544,11 @@ public abstract class ElasticsearchTemplateTests { operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); // when - Query query = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); + Query query = getTermQuery("id", documentId); operations.delete(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); + Query searchQuery = getTermQuery("id", documentId); SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(0); @@ -545,7 +557,10 @@ public abstract class ElasticsearchTemplateTests { @Test // DATAES-547 public void shouldDeleteAcrossIndex() { - // given + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_ALL)).delete(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).create(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)).create(); + SampleEntity sampleEntity = SampleEntity.builder() // .message("foo") // .version(System.currentTimeMillis()) // @@ -553,27 +568,23 @@ public abstract class ElasticsearchTemplateTests { IndexQuery idxQuery1 = new IndexQueryBuilder().withId(nextIdAsString()).withObject(sampleEntity).build(); - operations.index(idxQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); + operations.index(idxQuery1, IndexCoordinates.of(MULTI_INDEX_1_NAME)); IndexQuery idxQuery2 = new IndexQueryBuilder().withId(nextIdAsString()).withObject(sampleEntity).build(); - operations.index(idxQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(idxQuery2, IndexCoordinates.of(MULTI_INDEX_2_NAME)); // when - Query query = new NativeSearchQueryBuilder().withQuery(termQuery("message", "foo")).build(); - operations.delete(query, SampleEntity.class, IndexCoordinates.of("test-index-*")); - - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + Query query = getTermQuery("message", "foo"); + operations.delete(query, SampleEntity.class, IndexCoordinates.of(MULTI_INDEX_ALL)); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("message", "foo")).build(); - - assertThat(operations.count(searchQuery, IndexCoordinates.of(INDEX_1_NAME, INDEX_2_NAME))).isEqualTo(0); + assertThat(operations.count(query, IndexCoordinates.of(MULTI_INDEX_1_NAME, MULTI_INDEX_2_NAME))).isEqualTo(0); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_PREFIX)).delete(); } + protected abstract Query getTermQuery(String field, String value); + @Test // DATAES-547 public void shouldDeleteAcrossIndexWhenNoMatchingDataPresent() { @@ -585,28 +596,29 @@ public abstract class ElasticsearchTemplateTests { IndexQuery idxQuery1 = new IndexQueryBuilder().withId(nextIdAsString()).withObject(sampleEntity).build(); - operations.index(idxQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); + operations.index(idxQuery1, IndexCoordinates.of(MULTI_INDEX_1_NAME)); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).refresh(); IndexQuery idxQuery2 = new IndexQueryBuilder().withId(nextIdAsString()).withObject(sampleEntity).build(); - operations.index(idxQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(idxQuery2, IndexCoordinates.of(MULTI_INDEX_2_NAME)); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)).refresh(); // when - Query query = new NativeSearchQueryBuilder().withQuery(termQuery("message", "negative")).build(); + Query query = getTermQuery("message", "negative"); operations.delete(query, SampleEntity.class, IndexCoordinates.of("test-index-*")); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).refresh(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)).refresh(); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("message", "positive")).build(); + Query searchQuery = getTermQuery("message", "positive"); assertThat(operations.count(searchQuery, IndexCoordinates.of("test-index-*"))).isEqualTo(2); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test public void shouldFilterSearchResultsForGivenFilter() { @@ -653,11 +665,11 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withSort(new FieldSortBuilder("rate").order(SortOrder.ASC)).build(); + Query query = operations.matchAllQuery(); + query.addSort(Sort.by(Sort.Direction.ASC, "rate")); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + SearchHits searchHits = operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then @@ -665,41 +677,26 @@ public abstract class ElasticsearchTemplateTests { assertThat(searchHits.getSearchHit(0).getContent().getRate()).isEqualTo(sampleEntity2.getRate()); } + @Test public void shouldSortResultsGivenMultipleSortCriteria() { - // given - List indexQueries = new ArrayList<>(); - // first document - String documentId = nextIdAsString(); - SampleEntity sampleEntity1 = SampleEntity.builder().id(documentId).message("abc").rate(10) + SampleEntity sampleEntity1 = SampleEntity.builder().id(nextIdAsString()).message("abc").rate(10) + .version(System.currentTimeMillis()).build(); + SampleEntity sampleEntity2 = SampleEntity.builder().id(nextIdAsString()).message("xyz").rate(5) + .version(System.currentTimeMillis()).build(); + SampleEntity sampleEntity3 = SampleEntity.builder().id(nextIdAsString()).message("xyz").rate(15) .version(System.currentTimeMillis()).build(); - // second document - String documentId2 = nextIdAsString(); - SampleEntity sampleEntity2 = SampleEntity.builder().id(documentId2).message("xyz").rate(5) - .version(System.currentTimeMillis()).build(); - - // third document - String documentId3 = nextIdAsString(); - SampleEntity sampleEntity3 = SampleEntity.builder().id(documentId3).message("xyz").rate(15) - .version(System.currentTimeMillis()).build(); - - indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); - + List indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); + Query query = operations.matchAllQuery(); + query.addSort(Sort.by(Sort.Direction.ASC, "rate")); + query.addSort(Sort.by(Sort.Direction.ASC, "message")); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) // - .withSorts( // - new FieldSortBuilder("rate").order(SortOrder.ASC), // - new FieldSortBuilder("message").order(SortOrder.ASC)) // - .build(); - - // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + SearchHits searchHits = operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); - // then assertThat(searchHits.getTotalHits()).isEqualTo(3); assertThat(searchHits.getSearchHit(0).getContent().getRate()).isEqualTo(sampleEntity2.getRate()); assertThat(searchHits.getSearchHit(1).getContent().getMessage()).isEqualTo(sampleEntity1.getMessage()); @@ -792,13 +789,12 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(getIndexQueries(entities), IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() // - .withQuery(matchQuery("message", "green")) // + Query query = CriteriaQuery.builder(new Criteria("message").is("green")) .withPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("_score")))) // .build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + SearchHits searchHits = operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then @@ -829,6 +825,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(searchHits.getTotalHits()).isEqualTo(1); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test public void shouldUseScriptedFields() { @@ -938,7 +935,7 @@ public abstract class ElasticsearchTemplateTests { // when // creation is done in setup method - Map setting = indexOperations.getSettings(); + Settings setting = indexOperations.getSettings().flatten(); // then assertThat(setting.get("index.number_of_shards")).isEqualTo("1"); @@ -1008,7 +1005,7 @@ public abstract class ElasticsearchTemplateTests { FetchSourceFilterBuilder sourceFilter = new FetchSourceFilterBuilder(); sourceFilter.withIncludes("message"); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + Query searchQuery = getBuilderWithMatchAllQuery() .withSourceFilter(sourceFilter.build()).build(); // when @@ -1197,8 +1194,7 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withFields("message") - .withQuery(matchAllQuery()).withPageable(PageRequest.of(0, 10)).build(); + Query searchQuery = getBuilderWithMatchAllQuery().withFields("message").withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -1251,8 +1247,7 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withPageable(PageRequest.of(0, 10)).build(); + Query searchQuery = getBuilderWithMatchAllQuery().withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -1305,8 +1300,7 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withPageable(PageRequest.of(0, 10)).build(); + Query searchQuery = getBuilderWithMatchAllQuery().withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -1457,7 +1451,7 @@ public abstract class ElasticsearchTemplateTests { // given Class entity = SampleEntity.class; - IndexOperations indexOperations1 = operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)); + IndexOperations indexOperations1 = operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)); indexOperations1.delete(); indexOperations1.create(); @@ -1508,6 +1502,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(indexOperations.exists()).isFalse(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test public void shouldDoPartialUpdateForExistingDocument() { @@ -1539,6 +1534,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test void shouldDoUpdateByQueryForExistingDocument() { // given @@ -1570,6 +1566,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-227 public void shouldUseUpsertOnUpdate() { @@ -1594,29 +1591,8 @@ public abstract class ElasticsearchTemplateTests { assertThat(request.upsertRequest()).isNotNull(); } - @Test // DATAES-693 - public void shouldReturnSourceWhenRequested() { - // given - Map doc = new HashMap<>(); - doc.put("id", "1"); - doc.put("message", "test"); - - org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document - .from(doc); - - UpdateQuery updateQuery = UpdateQuery.builder("1") // - .withDocument(document) // - .withFetchSource(true) // - .build(); - - // when - UpdateRequest request = getRequestFactory().updateRequest(updateQuery, IndexCoordinates.of("index")); - - // then - assertThat(request).isNotNull(); - assertThat(request.fetchSource()).isEqualTo(FetchSourceContext.FETCH_SOURCE); - } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test public void shouldDoUpsertIfDocumentDoesNotExist() { @@ -1650,15 +1626,15 @@ public abstract class ElasticsearchTemplateTests { IndexQuery idxQuery = new IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity).build(); - IndexCoordinates index = IndexCoordinates.of(INDEX_1_NAME); + IndexCoordinates index = IndexCoordinates.of(MULTI_INDEX_1_NAME); operations.index(idxQuery, index); // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + Query query = getBuilderWithMatchAllQuery() .withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations) - .searchScrollStart(scrollTimeInMillis, searchQuery, SampleEntity.class, index); + .searchScrollStart(scrollTimeInMillis, query, SampleEntity.class, index); List> entities = new ArrayList<>(scroll.getSearchHits()); @@ -1674,6 +1650,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(entities.size()).isGreaterThanOrEqualTo(1); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-487 public void shouldReturnSameEntityForMultiSearch() { @@ -1701,6 +1678,7 @@ public abstract class ElasticsearchTemplateTests { } } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-487 public void shouldReturnDifferentEntityForMultiSearch() { @@ -1752,8 +1730,7 @@ public abstract class ElasticsearchTemplateTests { IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); operations.index(indexQuery, index); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", indexQuery.getId())) - .build(); + Query searchQuery = getTermQuery("id", indexQuery.getId()); // then SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); @@ -1776,16 +1753,13 @@ public abstract class ElasticsearchTemplateTests { @Test // DATAES-848 public void shouldReturnIndexName() { - // given + List entities = createSampleEntitiesWithMessage("Test message", 3); - // when String indexName = indexNameProvider.indexName(); operations.bulkIndex(entities, IndexCoordinates.of(indexName)); + Query query = getBuilderWithTermQuery("message", "message").withPageable(PageRequest.of(0, 100)).build(); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("message", "message")) - .withPageable(PageRequest.of(0, 100)).build(); - // then - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class); + SearchHits searchHits = operations.search(query, SampleEntity.class); searchHits.forEach(searchHit -> { assertThat(searchHit.getIndex()).isEqualTo(indexName); @@ -1804,9 +1778,8 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(boolQuery().must(wildcardQuery("message", "*a*")).should(wildcardQuery("message", "*b*"))) - .withMinScore(2.0F).build(); + Query searchQuery = getBoolQueryWithWildcardsFirstMustSecondShouldAndMinScore("message", "*a*", "message", "*b*", + 2.0F); SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -1816,10 +1789,12 @@ public abstract class ElasticsearchTemplateTests { assertThat(searchHits.getSearchHit(0).getContent().getMessage()).isEqualTo("ab"); } + protected abstract Query getBoolQueryWithWildcardsFirstMustSecondShouldAndMinScore(String firstField, + String firstValue, String secondField, String secondValue, float minScore); + @Test // DATAES-462 public void shouldReturnScores() { - // given List indexQueries = new ArrayList<>(); indexQueries.add(buildIndex(SampleEntity.builder().id("1").message("ab xz").build())); @@ -1828,14 +1803,11 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); - // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("message", "xz")) - .withSort(SortBuilders.fieldSort("message")).withTrackScores(true).build(); + Query query = getBuilderWithTermQuery("message", "xz").withSort(Sort.by("message")).withTrackScores(true).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + SearchHits searchHits = operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); - // then assertThat(searchHits.getMaxScore()).isGreaterThan(0f); assertThat(searchHits.getSearchHit(0).getScore()).isGreaterThan(0f); } @@ -1890,7 +1862,7 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -1934,7 +1906,7 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); SearchHits searchHits = operations.search(searchQuery, Map.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -1960,7 +1932,7 @@ public abstract class ElasticsearchTemplateTests { IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); operations.index(indexQueryBuilder.build(), index); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when SearchHits entities = operations.search(searchQuery, GTEVersionEntity.class, index); // then @@ -1989,7 +1961,7 @@ public abstract class ElasticsearchTemplateTests { operations.index(indexQuery, IndexCoordinates.of(indexName)); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, @@ -2032,7 +2004,7 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery = getIndexQuery(sampleEntity); operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when long count = operations.count(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -2072,7 +2044,7 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery = getIndexQuery(sampleEntity); operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when long count = operations.count(searchQuery, IndexCoordinates.of(indexNameProvider.indexName())); @@ -2098,15 +2070,15 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery2 = new IndexQueryBuilder().withId(sampleEntity2.getId()).withObject(sampleEntity2).build(); - operations.index(indexQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.index(indexQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(indexQuery1, IndexCoordinates.of(MULTI_INDEX_1_NAME)); + operations.index(indexQuery2, IndexCoordinates.of(MULTI_INDEX_2_NAME)); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).refresh(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)).refresh(); CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); // when - long count = operations.count(criteriaQuery, IndexCoordinates.of(INDEX_1_NAME, INDEX_2_NAME)); + long count = operations.count(criteriaQuery, IndexCoordinates.of(MULTI_INDEX_1_NAME, MULTI_INDEX_2_NAME)); // then assertThat(count).isEqualTo(2); @@ -2129,41 +2101,41 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery2 = new IndexQueryBuilder().withId(sampleEntity2.getId()).withObject(sampleEntity2).build(); - operations.index(indexQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.index(indexQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(indexQuery1, IndexCoordinates.of(MULTI_INDEX_1_NAME)); + operations.index(indexQuery2, IndexCoordinates.of(MULTI_INDEX_2_NAME)); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).refresh(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)).refresh(); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when - long count = operations.count(searchQuery, IndexCoordinates.of(INDEX_1_NAME, INDEX_2_NAME)); + long count = operations.count(searchQuery, IndexCoordinates.of(MULTI_INDEX_1_NAME, MULTI_INDEX_2_NAME)); // then assertThat(count).isEqualTo(2); } private void cleanUpIndices() { - IndexOperations indexOperations1 = operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)); + IndexOperations indexOperations1 = operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)); indexOperations1.delete(); - IndexOperations indexOperations2 = operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)); + IndexOperations indexOperations2 = operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)); indexOperations2.delete(); indexOperations1.create(); indexOperations2.create(); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME, INDEX_2_NAME)).refresh(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME, MULTI_INDEX_2_NAME)).refresh(); } @Test // DATAES-71 public void shouldCreatedIndexWithSpecifiedIndexName() { // given - operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)).delete(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_3_NAME)).delete(); // when - operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)).create(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_3_NAME)).create(); // then - assertThat(operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)).exists()).isTrue(); + assertThat(operations.indexOps(IndexCoordinates.of(MULTI_INDEX_3_NAME)).exists()).isTrue(); } @Test // DATAES-72 @@ -2199,15 +2171,15 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery2 = new IndexQueryBuilder().withId(sampleEntity2.getId()).withObject(sampleEntity2).build(); - operations.index(indexQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.index(indexQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(indexQuery1, IndexCoordinates.of(MULTI_INDEX_1_NAME)); + operations.index(indexQuery2, IndexCoordinates.of(MULTI_INDEX_2_NAME)); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).refresh(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)).refresh(); CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); // when - long count = operations.count(criteriaQuery, IndexCoordinates.of(INDEX_1_NAME)); + long count = operations.count(criteriaQuery, IndexCoordinates.of(MULTI_INDEX_1_NAME)); // then assertThat(count).isEqualTo(1); @@ -2230,15 +2202,15 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery2 = new IndexQueryBuilder().withId(sampleEntity2.getId()).withObject(sampleEntity2).build(); - operations.index(indexQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.index(indexQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(indexQuery1, IndexCoordinates.of(MULTI_INDEX_1_NAME)); + operations.index(indexQuery2, IndexCoordinates.of(MULTI_INDEX_2_NAME)); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_1_NAME)).refresh(); + operations.indexOps(IndexCoordinates.of(MULTI_INDEX_2_NAME)).refresh(); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when - long count = operations.count(searchQuery, IndexCoordinates.of(INDEX_1_NAME)); + long count = operations.count(searchQuery, IndexCoordinates.of(MULTI_INDEX_1_NAME)); // then assertThat(count).isEqualTo(1); @@ -2273,7 +2245,7 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery = getIndexQuery(sampleEntity); operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when assertThatThrownBy(() -> operations.count(searchQuery, (IndexCoordinates) null)) @@ -2283,75 +2255,79 @@ public abstract class ElasticsearchTemplateTests { @Test // DATAES-71 public void shouldCreateIndexWithGivenSettings() { - // given - String settings = "{\n" + " \"index\": {\n" + " \"number_of_shards\": \"1\",\n" - + " \"number_of_replicas\": \"0\",\n" + " \"analysis\": {\n" - + " \"analyzer\": {\n" + " \"emailAnalyzer\": {\n" - + " \"type\": \"custom\",\n" - + " \"tokenizer\": \"uax_url_email\"\n" + " }\n" - + " }\n" + " }\n" + " }\n" + '}'; + String settings = "{\n" // + + " \"index\": {\n" // + + " \"number_of_shards\": \"1\",\n" // + + " \"number_of_replicas\": \"0\",\n" // + + " \"analysis\": {\n" // + + " \"analyzer\": {\n" // + + " \"emailAnalyzer\": {\n" // + + " \"type\": \"custom\",\n" // + + " \"tokenizer\": \"uax_url_email\"\n" // + + " }\n" // + + " }\n" // + + " }\n" // + + " }\n" // + + '}'; // - IndexOperations indexOperations3 = operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)); - indexOperations3.delete(); + indexOperations.delete(); + indexOperations.create(parse(settings)); - // when - operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)).create(parse(settings)); - - // then - Map map = indexOperations3.getSettings(); - assertThat(indexOperations3.exists()).isTrue(); - assertThat(map.containsKey("index.analysis.analyzer.emailAnalyzer.tokenizer")).isTrue(); - assertThat(map.get("index.analysis.analyzer.emailAnalyzer.tokenizer")).isEqualTo("uax_url_email"); + Settings storedSettings = indexOperations.getSettings().flatten(); + assertThat(indexOperations.exists()).isTrue(); + assertThat(storedSettings.containsKey("index.analysis.analyzer.emailAnalyzer.tokenizer")).isTrue(); + assertThat(storedSettings.get("index.analysis.analyzer.emailAnalyzer.tokenizer")).isEqualTo("uax_url_email"); } @Test // DATAES-71 public void shouldCreateGivenSettingsForGivenIndex() { - // given // delete , create and apply mapping in before method - // then - Map map = indexOperations.getSettings(); + Settings storedSettings = indexOperations.getSettings().flatten(); assertThat(indexOperations.exists()).isTrue(); - assertThat(map.containsKey("index.refresh_interval")).isTrue(); - assertThat(map.containsKey("index.number_of_replicas")).isTrue(); - assertThat(map.containsKey("index.number_of_shards")).isTrue(); - assertThat(map.containsKey("index.store.type")).isTrue(); - assertThat(map.get("index.refresh_interval")).isEqualTo("-1"); - assertThat(map.get("index.number_of_replicas")).isEqualTo("0"); - assertThat(map.get("index.number_of_shards")).isEqualTo("1"); - assertThat(map.get("index.store.type")).isEqualTo("fs"); + assertThat(storedSettings.containsKey("index.refresh_interval")).isTrue(); + assertThat(storedSettings.containsKey("index.number_of_replicas")).isTrue(); + assertThat(storedSettings.containsKey("index.number_of_shards")).isTrue(); + assertThat(storedSettings.get("index.refresh_interval")).isEqualTo("-1"); + assertThat(storedSettings.get("index.number_of_replicas")).isEqualTo("0"); + assertThat(storedSettings.get("index.number_of_shards")).isEqualTo("1"); } @Test // DATAES-88 public void shouldCreateIndexWithGivenClassAndSettings() { - // given - String settings = "{\n" + " \"index\": {\n" + " \"number_of_shards\": \"1\",\n" - + " \"number_of_replicas\": \"0\",\n" + " \"analysis\": {\n" - + " \"analyzer\": {\n" + " \"emailAnalyzer\": {\n" - + " \"type\": \"custom\",\n" - + " \"tokenizer\": \"uax_url_email\"\n" + " }\n" - + " }\n" + " }\n" + " }\n" + '}'; + String settings = "{\n" // + + " \"index\": {\n" // + + " \"number_of_shards\": \"1\",\n" // + + " \"number_of_replicas\": \"0\",\n" // + + " \"analysis\": {\n" // + + " \"analyzer\": {\n" // + + " \"emailAnalyzer\": {\n" // + + " \"type\": \"custom\",\n" // + + " \"tokenizer\": \"uax_url_email\"\n" // + + " }\n" // + + " }\n" // + + " }\n" // + + " }\n" // + + '}'; // - // when indexOperations.delete(); indexOperations.create(parse(settings)); indexOperations.putMapping(SampleEntity.class); - // then - Map map = indexOperations.getSettings(); + Settings storedSettings = indexOperations.getSettings().flatten(); assertThat(operations.indexOps(IndexCoordinates.of(indexNameProvider.indexName())).exists()).isTrue(); - assertThat(map.containsKey("index.number_of_replicas")).isTrue(); - assertThat(map.containsKey("index.number_of_shards")).isTrue(); - assertThat((String) map.get("index.number_of_replicas")).isEqualTo("0"); - assertThat((String) map.get("index.number_of_shards")).isEqualTo("1"); + assertThat(storedSettings.containsKey("index.number_of_replicas")).isTrue(); + assertThat(storedSettings.containsKey("index.number_of_shards")).isTrue(); + assertThat((String) storedSettings.get("index.number_of_replicas")).isEqualTo("0"); + assertThat((String) storedSettings.get("index.number_of_shards")).isEqualTo("1"); } @Test public void shouldTestResultsAcrossMultipleIndices() { - IndexCoordinates index1 = IndexCoordinates.of(INDEX_1_NAME); - IndexCoordinates index2 = IndexCoordinates.of(INDEX_2_NAME); + IndexCoordinates index1 = IndexCoordinates.of(MULTI_INDEX_1_NAME); + IndexCoordinates index2 = IndexCoordinates.of(MULTI_INDEX_2_NAME); operations.indexOps(index1).delete(); operations.indexOps(index2).delete(); @@ -2370,11 +2346,11 @@ public abstract class ElasticsearchTemplateTests { operations.index(indexQuery1, index1); operations.index(indexQuery2, index2); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); // when SearchHits sampleEntities = operations.search(searchQuery, SampleEntity.class, - IndexCoordinates.of(INDEX_1_NAME, INDEX_2_NAME)); + IndexCoordinates.of(MULTI_INDEX_1_NAME, MULTI_INDEX_2_NAME)); // then assertThat(sampleEntities).hasSize(2); @@ -2386,8 +2362,8 @@ public abstract class ElasticsearchTemplateTests { */ public void shouldComposeObjectsReturnedFromHeterogeneousIndexes() { - IndexCoordinates index1 = IndexCoordinates.of(INDEX_1_NAME); - IndexCoordinates index2 = IndexCoordinates.of(INDEX_2_NAME); + IndexCoordinates index1 = IndexCoordinates.of(MULTI_INDEX_1_NAME); + IndexCoordinates index2 = IndexCoordinates.of(MULTI_INDEX_2_NAME); operations.indexOps(index1).delete(); operations.indexOps(index2).delete(); @@ -2401,24 +2377,23 @@ public abstract class ElasticsearchTemplateTests { operations.index(indexQuery2, index2); // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); SearchHits page = operations.search(searchQuery, ResultAggregator.class, - IndexCoordinates.of(INDEX_1_NAME, INDEX_2_NAME)); + IndexCoordinates.of(MULTI_INDEX_1_NAME, MULTI_INDEX_2_NAME)); assertThat(page.getTotalHits()).isEqualTo(2); } @Test public void shouldCreateIndexUsingServerDefaultConfiguration() { - // given + + indexNameProvider.increment(); IndexOperations indexOps = operations.indexOps(UseServerConfigurationEntity.class); - // when boolean created = indexOps.create(); - // then assertThat(created).isTrue(); - Map setting = indexOps.getSettings(); + Settings setting = indexOps.getSettings(); assertThat(setting.get("index.number_of_shards")).isEqualTo("1"); assertThat(setting.get("index.number_of_replicas")).isEqualTo("1"); } @@ -2459,7 +2434,7 @@ public abstract class ElasticsearchTemplateTests { // then // document with id "remainingDocumentId" should still be indexed - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(1); @@ -2489,7 +2464,7 @@ public abstract class ElasticsearchTemplateTests { // then // document with id "remainingDocumentId" should still be indexed - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(1); @@ -2517,7 +2492,7 @@ public abstract class ElasticsearchTemplateTests { // then // document with id "remainingDocumentId" should still be indexed - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Query searchQuery = operations.matchAllQuery(); SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(1L); @@ -2578,11 +2553,11 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("message", "message")) + Query query = getBuilderWithMatchQuery("message", "message") .withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); + query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); @@ -2610,8 +2585,10 @@ public abstract class ElasticsearchTemplateTests { // then SourceFilter sourceFilter = new FetchSourceFilterBuilder().withIncludes("id").build(); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withPageable(PageRequest.of(0, 10)).withSourceFilter(sourceFilter).build(); + Query searchQuery = getBuilderWithMatchAllQuery() // + .withPageable(PageRequest.of(0, 10)) // + .withSourceFilter(sourceFilter) // + .build(); // SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); @@ -2653,13 +2630,13 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withSort(new FieldSortBuilder("rate").order(SortOrder.ASC)) - .withSort(new FieldSortBuilder("message").order(SortOrder.DESC)).withPageable(PageRequest.of(0, 10)).build(); + Query query = getBuilderWithMatchAllQuery() // + .withSort(Sort.by(Sort.Order.asc("rate"))) // + .withSort(Sort.by(Sort.Order.desc("message"))) // + .withPageable(PageRequest.of(0, 10)).build(); - // when SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); + query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); @@ -2667,7 +2644,6 @@ public abstract class ElasticsearchTemplateTests { SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } - // then assertThat(sampleEntities).hasSize(3); assertThat(sampleEntities.get(0).getContent().getRate()).isEqualTo(sampleEntity2.getRate()); assertThat(sampleEntities.get(1).getContent().getRate()).isEqualTo(sampleEntity3.getRate()); @@ -2699,14 +2675,14 @@ public abstract class ElasticsearchTemplateTests { operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + Query query = getBuilderWithMatchAllQuery() .withPageable( PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "rate").and(Sort.by(Sort.Direction.DESC, "message")))) .build(); // when SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); + query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); @@ -2723,6 +2699,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(sampleEntities.get(2).getContent().getMessage()).isEqualTo(sampleEntity1.getMessage()); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-593 public void shouldReturnDocumentWithCollapsedField() { @@ -2753,6 +2730,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(searchHits.getSearchHit(1).getContent().getMessage()).isEqualTo("message 2"); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // #1493 @DisplayName("should return document with collapse field and inner hits") public void shouldReturnDocumentWithCollapsedFieldAndInnerHits() { @@ -2786,6 +2764,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(searchHits.getSearchHit(1).getInnerHits("innerHits").getTotalHits()).isEqualTo(1); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // #1997 @DisplayName("should return document with inner hits size zero") void shouldReturnDocumentWithInnerHitsSizeZero() { @@ -2826,7 +2805,7 @@ public abstract class ElasticsearchTemplateTests { return indexQueries; } - @Document(indexName = INDEX_2_NAME) + @Document(indexName = MULTI_INDEX_2_NAME) class ResultAggregator { private String id; @@ -2840,48 +2819,24 @@ public abstract class ElasticsearchTemplateTests { } } - @Test // DATAES-187 - public void shouldUsePageableOffsetToSetFromInSearchRequest() { - - // given - Pageable pageable = new PageRequest(1, 10, Sort.unsorted()) { - @Override - public long getOffset() { - return 30; - } - }; - - NativeSearchQuery query = new NativeSearchQueryBuilder() // - .withPageable(pageable) // - .build(); - - // when - SearchRequest searchRequest = getRequestFactory().searchRequest(query, null, IndexCoordinates.of("test")); - - // then - assertThat(searchRequest.source().from()).isEqualTo(30); - } - @Test // DATAES-709 public void shouldNotIncludeDefaultsGetIndexSettings() { // given // when - Map map = indexOperations.getSettings(); + Settings settings = indexOperations.getSettings().flatten(); // then - assertThat(map).doesNotContainKey("index.max_result_window"); + assertThat(settings).doesNotContainKey("index.max_result_window"); } @Test // DATAES-709 public void shouldIncludeDefaultsOnGetIndexSettings() { - // given - // when - Map map = indexOperations.getSettings(true); + Settings settings = indexOperations.getSettings(true).flatten(); // then - assertThat(map).containsKey("index.max_result_window"); + assertThat(settings).containsKey("index.max_result_window"); } @Test // DATAES-714 @@ -2898,10 +2853,10 @@ public abstract class ElasticsearchTemplateTests { IndexQuery indexQuery = new IndexQueryBuilder().withId(entity.getId()).withObject(entity).build(); operations.index(indexQuery, index); - NativeSearchQuery query = new NativeSearchQueryBuilder() // - .withQuery(matchAllQuery()) // - .withSort(new FieldSortBuilder("keyword").order(SortOrder.ASC)) - .withSort(new FieldSortBuilder("number").order(SortOrder.DESC)).build(); + Query query = getBuilderWithMatchAllQuery() // + .withSort(Sort.by(Sort.Direction.ASC, "keyword")) // + .withSort(Sort.by(Sort.Direction.DESC, "number")) // + .build(); SearchHits searchHits = operations.search(query, SearchHitsEntity.class); @@ -2912,7 +2867,7 @@ public abstract class ElasticsearchTemplateTests { List sortValues = searchHit.getSortValues(); assertThat(sortValues).hasSize(2); assertThat(sortValues.get(0)).isInstanceOf(String.class).isEqualTo("thousands"); - // transport client returns Long, rest client Integer + // transport client returns Long, RestHghlevelClient Integer, new ElasticsearchClient String java.lang.Object o = sortValues.get(1); if (o instanceof Integer) { Integer i = (Integer) o; @@ -2920,11 +2875,14 @@ public abstract class ElasticsearchTemplateTests { } else if (o instanceof Long) { Long l = (Long) o; assertThat(o).isInstanceOf(Long.class).isEqualTo(1000L); + } else if (o instanceof String) { + assertThat(o).isInstanceOf(String.class).isEqualTo("1000"); } else { fail("unexpected object type " + o); } } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-715 void shouldReturnHighlightFieldsInSearchHit() { IndexCoordinates index = IndexCoordinates.of("test-index-highlight-entity-template"); @@ -2952,6 +2910,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(highlightField.get(1)).contains("message"); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // #1686 void shouldRunRescoreQueryInSearchQuery() { IndexCoordinates index = IndexCoordinates.of("test-index-rescore-entity-template"); @@ -3145,19 +3104,17 @@ public abstract class ElasticsearchTemplateTests { OptimisticEntity original = new OptimisticEntity(); original.setMessage("It's fine"); OptimisticEntity saved = operations.save(original); - operations.indexOps(OptimisticEntity.class).refresh(); - List> retrievedList = operations.multiGet(queryForOne(saved.getId()), + List> retrievedList = operations.multiGet( + queryWithIds(Objects.requireNonNull(saved.getId())), OptimisticEntity.class, operations.getIndexCoordinatesFor(OptimisticEntity.class)); + + assertThat(retrievedList).hasSize(1); OptimisticEntity retrieved = retrievedList.get(0).getItem(); assertThatSeqNoPrimaryTermIsFilled(retrieved); } - private Query queryForOne(String id) { - return new NativeSearchQueryBuilder().withIds(singletonList(id)).build(); - } - @Test // DATAES-799 void searchShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); @@ -3165,12 +3122,13 @@ public abstract class ElasticsearchTemplateTests { OptimisticEntity saved = operations.save(original); operations.indexOps(OptimisticEntity.class).refresh(); - SearchHits retrievedHits = operations.search(queryForOne(saved.getId()), OptimisticEntity.class); + SearchHits retrievedHits = operations.search(queryWithIds(saved.getId()), OptimisticEntity.class); OptimisticEntity retrieved = retrievedHits.getSearchHit(0).getContent(); assertThatSeqNoPrimaryTermIsFilled(retrieved); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-799 void multiSearchShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); @@ -3178,7 +3136,7 @@ public abstract class ElasticsearchTemplateTests { OptimisticEntity saved = operations.save(original); operations.indexOps(OptimisticEntity.class).refresh(); - List queries = singletonList(queryForOne(saved.getId())); + List queries = singletonList(queryWithIds(saved.getId())); List> retrievedHits = operations.multiSearch(queries, OptimisticEntity.class, operations.getIndexCoordinatesFor(OptimisticEntity.class)); OptimisticEntity retrieved = retrievedHits.get(0).getSearchHit(0).getContent(); @@ -3193,13 +3151,14 @@ public abstract class ElasticsearchTemplateTests { OptimisticEntity saved = operations.save(original); operations.indexOps(OptimisticEntity.class).refresh(); - SearchHitsIterator retrievedHits = operations.searchForStream(queryForOne(saved.getId()), + SearchHitsIterator retrievedHits = operations.searchForStream(queryWithIds(saved.getId()), OptimisticEntity.class); OptimisticEntity retrieved = retrievedHits.next().getContent(); assertThatSeqNoPrimaryTermIsFilled(retrieved); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-799 void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnEntityWithSeqNoPrimaryTermProperty() { OptimisticEntity original = new OptimisticEntity(); @@ -3216,6 +3175,7 @@ public abstract class ElasticsearchTemplateTests { assertThatThrownBy(() -> operations.save(forEdit2)).isInstanceOf(OptimisticLockingFailureException.class); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-799 void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnVersionedEntityWithSeqNoPrimaryTermProperty() { OptimisticAndVersionedEntity original = new OptimisticAndVersionedEntity(); @@ -3244,6 +3204,7 @@ public abstract class ElasticsearchTemplateTests { operations.save(forEdit); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test void shouldSupportCRUDOpsForEntityWithJoinFields() throws Exception { String qId1 = java.util.UUID.randomUUID().toString(); @@ -3379,7 +3340,7 @@ public abstract class ElasticsearchTemplateTests { } protected RequestFactory getRequestFactory() { - return ((AbstractElasticsearchTemplate) operations).getRequestFactory(); + return ((ElasticsearchRestTemplate) operations).getRequestFactory(); } @Test // DATAES-908 @@ -3448,8 +3409,7 @@ public abstract class ElasticsearchTemplateTests { @DisplayName("should track_total_hits with default value") void shouldTrackTotalHitsWithDefaultValue() { - NativeSearchQuery queryAll = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - operations.delete(queryAll, SampleEntity.class); + Query queryAll = operations.matchAllQuery().setPageable(Pageable.unpaged()); List entities = IntStream.rangeClosed(1, 15_000) .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); @@ -3469,8 +3429,7 @@ public abstract class ElasticsearchTemplateTests { @DisplayName("should track total hits") void shouldTrackTotalHits() { - NativeSearchQuery queryAll = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - operations.delete(queryAll, SampleEntity.class); + Query queryAll = operations.matchAllQuery().setPageable(Pageable.unpaged()); List entities = IntStream.rangeClosed(1, 15_000) .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); @@ -3491,8 +3450,7 @@ public abstract class ElasticsearchTemplateTests { @DisplayName("should track total hits to specific value") void shouldTrackTotalHitsToSpecificValue() { - NativeSearchQuery queryAll = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - operations.delete(queryAll, SampleEntity.class); + Query queryAll = operations.matchAllQuery().setPageable(Pageable.unpaged()); List entities = IntStream.rangeClosed(1, 15_000) .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); @@ -3513,8 +3471,7 @@ public abstract class ElasticsearchTemplateTests { @DisplayName("should track total hits is off") void shouldTrackTotalHitsIsOff() { - NativeSearchQuery queryAll = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - operations.delete(queryAll, SampleEntity.class); + Query queryAll = operations.matchAllQuery().setPageable(Pageable.unpaged()); List entities = IntStream.rangeClosed(1, 15_000) .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); @@ -3582,6 +3539,7 @@ public abstract class ElasticsearchTemplateTests { assertThat(retrieved).isEqualTo(saved); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // #1488 @DisplayName("should set scripted fields on immutable objects") void shouldSetScriptedFieldsOnImmutableObjects() { @@ -3646,39 +3604,6 @@ public abstract class ElasticsearchTemplateTests { operations.search(query, SampleEntity.class); } - @Test // #1529 - void shouldWorkReindexForExistingIndex() { - String sourceIndexName = indexNameProvider.indexName(); - String documentId = nextIdAsString(); - SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("abc").build(); - operations.save(sampleEntity); - - indexNameProvider.increment(); - String destIndexName = indexNameProvider.indexName(); - operations.indexOps(IndexCoordinates.of(destIndexName)).create(); - - final ReindexRequest reindexRequest = ReindexRequest - .builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName)).withRefresh(true).build(); - final ReindexResponse reindex = operations.reindex(reindexRequest); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - - assertThat(reindex.getTotal()).isEqualTo(1); - assertThat(operations.count(searchQuery, IndexCoordinates.of(destIndexName))).isEqualTo(1); - } - - @Test // #1529 - void shouldWorkSubmitReindexTask() { - String sourceIndexName = indexNameProvider.indexName(); - indexNameProvider.increment(); - String destIndexName = indexNameProvider.indexName(); - operations.indexOps(IndexCoordinates.of(destIndexName)).create(); - final ReindexRequest reindexRequest = ReindexRequest - .builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName)).build(); - String task = operations.submitReindex(reindexRequest); - - assertThat(task).isNotBlank(); - } - // region entities @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(shards = 1, replicas = 0, refreshInterval = "-1") @@ -4204,7 +4129,7 @@ public abstract class ElasticsearchTemplateTests { } } - @Document(indexName = "test-index-server-configuration") + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(useServerConfiguration = true, shards = 10, replicas = 10, refreshInterval = "-1") private static class UseServerConfigurationEntity { @@ -4348,7 +4273,7 @@ public abstract class ElasticsearchTemplateTests { } } - @Document(indexName = "test-index-optimistic-entity-template") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class OptimisticEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchPartQueryERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchPartQueryERHLCIntegrationTests.java new file mode 100644 index 000000000..5a6621fcf --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchPartQueryERHLCIntegrationTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.query.ElasticsearchPartQueryIntegrationTests; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * The base class for these tests lives in the org.springframework.data.elasticsearch.core.query package, but we need + * access to the {@link RequestFactory} class here + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ElasticsearchPartQueryERHLCIntegrationTests.Config.class }) +public class ElasticsearchPartQueryERHLCIntegrationTests extends ElasticsearchPartQueryIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config {} + + protected String buildQueryString(CriteriaQuery criteriaQuery, Class clazz) { + SearchSourceBuilder source = new RequestFactory(operations.getElasticsearchConverter()) + .searchRequest(criteriaQuery, clazz, IndexCoordinates.of("dummy")).source(); + // remove defaultboost values + return source.toString().replaceAll("(\\^\\d+\\.\\d+)", ""); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsTest.java b/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsUnitTests.java similarity index 89% rename from src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsTest.java rename to src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsUnitTests.java index cf266ab92..1374ee989 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsUnitTests.java @@ -37,7 +37,7 @@ import org.springframework.lang.Nullable; /** * @author Peter-Josef Meisch */ -class EntityOperationsTest { +class EntityOperationsUnitTests { @Nullable private static ConversionService conversionService; @Nullable private static EntityOperations entityOperations; @@ -64,10 +64,10 @@ class EntityOperationsTest { EntityWithRouting entity = new EntityWithRouting(); entity.setId("42"); entity.setRouting("theRoute"); - EntityOperations.AdaptibleEntity adaptibleEntity = entityOperations.forEntity(entity, + EntityOperations.AdaptableEntity adaptableEntity = entityOperations.forEntity(entity, conversionService, new DefaultRoutingResolver(mappingContext)); - String routing = adaptibleEntity.getRouting(); + String routing = adaptableEntity.getRouting(); assertThat(routing).isEqualTo("theRoute"); } @@ -80,10 +80,10 @@ class EntityOperationsTest { entity.setId("42"); entity.setJoinField(new JoinField<>("foo", "foo-routing")); - EntityOperations.AdaptibleEntity adaptibleEntity = entityOperations.forEntity(entity, + EntityOperations.AdaptableEntity adaptableEntity = entityOperations.forEntity(entity, conversionService, new DefaultRoutingResolver(mappingContext)); - String routing = adaptibleEntity.getRouting(); + String routing = adaptableEntity.getRouting(); assertThat(routing).isEqualTo("foo-routing"); } @@ -96,10 +96,10 @@ class EntityOperationsTest { entity.setRouting("theRoute"); entity.setJoinField(new JoinField<>("foo", "foo-routing")); - EntityOperations.AdaptibleEntity adaptibleEntity = entityOperations.forEntity(entity, + EntityOperations.AdaptableEntity adaptableEntity = entityOperations.forEntity(entity, conversionService, new DefaultRoutingResolver(mappingContext)); - String routing = adaptibleEntity.getRouting(); + String routing = adaptableEntity.getRouting(); assertThat(routing).isEqualTo("theRoute"); } @@ -107,7 +107,8 @@ class EntityOperationsTest { @Document(indexName = "entity-operations-test") @Routing("routing") static class EntityWithRouting { - @Nullable @Id private String id; + @Nullable + @Id private String id; @Nullable private String routing; @Nullable @@ -132,7 +133,8 @@ class EntityOperationsTest { @Document(indexName = "entity-operations-test") @Routing("routing") static class EntityWithRoutingAndJoinField { - @Nullable @Id private String id; + @Nullable + @Id private String id; @Nullable private String routing; @Nullable private JoinField joinField; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/IndexCoordinatesTest.java b/src/test/java/org/springframework/data/elasticsearch/core/IndexCoordinatesUnitTests.java similarity index 97% rename from src/test/java/org/springframework/data/elasticsearch/core/IndexCoordinatesTest.java rename to src/test/java/org/springframework/data/elasticsearch/core/IndexCoordinatesUnitTests.java index c823a2173..716f9f247 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/IndexCoordinatesTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/IndexCoordinatesUnitTests.java @@ -23,7 +23,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; /** * @author Peter-Josef Meisch */ -class IndexCoordinatesTest { +class IndexCoordinatesUnitTests { @Test void cannotBeInitializedWithNullIndexName() { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsELCIntegrationTests.java new file mode 100644 index 000000000..a27043d41 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsELCIntegrationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery; +import co.elastic.clients.elasticsearch.core.search.InnerHits; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { InnerHitsELCIntegrationTests.Config.class }) +public class InnerHitsELCIntegrationTests extends InnerHitsIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("innerhits"); + } + } + + @Override + protected Query buildQueryForInnerHits(String innerHitName, String nestedQueryPath, String matchField, + String matchValue) { + + return NativeQuery.builder() // + .withQuery(q -> q.nested( // + NestedQuery.of(n -> n // + .path(nestedQueryPath) // + .query(q2 -> q2.match( // + MatchQuery.of(m -> m // + .field(matchField) // + .query(fv -> fv.stringValue(matchValue)) // + ))) // + .innerHits(InnerHits.of(ih -> ih.name(innerHitName))) // + ))) // + .build(); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsERHLCIntegrationTests.java new file mode 100644 index 000000000..62e1e5e9a --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsERHLCIntegrationTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import static org.elasticsearch.index.query.QueryBuilders.*; + +import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.index.query.InnerHitBuilder; +import org.elasticsearch.index.query.NestedQueryBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { InnerHitsERHLCIntegrationTests.Config.class }) +public class InnerHitsERHLCIntegrationTests extends InnerHitsIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("innerhits-es7"); + } + } + + @Override + protected Query buildQueryForInnerHits(String innerHitName, String nestedQueryPath, String matchField, + String matchValue) { + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + + NestedQueryBuilder nestedQueryBuilder = nestedQuery(nestedQueryPath, matchQuery(matchField, matchValue), + ScoreMode.Avg); + nestedQueryBuilder.innerHit(new InnerHitBuilder(innerHitName)); + queryBuilder.withQuery(nestedQueryBuilder); + + return queryBuilder.build(); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsIntegrationTests.java index b136b25ac..5886a480b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsIntegrationTests.java @@ -16,29 +16,26 @@ package org.springframework.data.elasticsearch.core; import static org.assertj.core.api.Assertions.*; -import static org.elasticsearch.index.query.QueryBuilders.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import org.apache.lucene.search.join.ScoreMode; import org.assertj.core.api.SoftAssertions; -import org.elasticsearch.index.query.InnerHitBuilder; -import org.elasticsearch.index.query.NestedQueryBuilder; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; 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.core.query.NativeSearchQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.document.NestedMetaData; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -49,16 +46,16 @@ import org.springframework.lang.Nullable; @SpringIntegrationTest public abstract class InnerHitsIntegrationTests { - public static final String INDEX_NAME = "tests-inner-hits"; - @Autowired ElasticsearchOperations operations; + @Autowired IndexNameProvider indexNameProvider; @Nullable IndexOperations indexOps; @BeforeEach void setUp() { + indexNameProvider.increment(); + indexOps = operations.indexOps(City.class); - indexOps.create(); - indexOps.putMapping(City.class); + indexOps.createWithMapping(); Inhabitant john = new Inhabitant("John", "Smith"); Inhabitant carla = new Inhabitant("Carla", "Miller"); @@ -71,26 +68,19 @@ public abstract class InnerHitsIntegrationTests { City village = new City("Village", Arrays.asList(mainStreet)); operations.save(Arrays.asList(metropole, village)); - indexOps.refresh(); } - @AfterEach - void tearDown() { - indexOps.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test void shouldReturnInnerHits() { - String innerHitName = "inner_hit_name"; - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); - - NestedQueryBuilder nestedQueryBuilder = nestedQuery("hou-ses.in-habi-tants", - matchQuery("hou-ses.in-habi-tants.first-name", "Carla"), ScoreMode.Avg); - nestedQueryBuilder.innerHit(new InnerHitBuilder(innerHitName)); - queryBuilder.withQuery(nestedQueryBuilder); - - NativeSearchQuery query = queryBuilder.build(); + Query query = buildQueryForInnerHits("inner_hit_name", "hou-ses.in-habi-tants", "hou-ses.in-habi-tants.first-name", + "Carla"); SoftAssertions softly = new SoftAssertions(); SearchHits searchHits = operations.search(query, City.class); @@ -100,7 +90,7 @@ public abstract class InnerHitsIntegrationTests { SearchHit searchHit = searchHits.getSearchHit(0); softly.assertThat(searchHit.getInnerHits()).hasSize(1); - SearchHits innerHits = searchHit.getInnerHits(innerHitName); + SearchHits innerHits = searchHit.getInnerHits("inner_hit_name"); softly.assertThat(innerHits).hasSize(1); SearchHit innerHit = innerHits.getSearchHit(0); @@ -119,7 +109,10 @@ public abstract class InnerHitsIntegrationTests { softly.assertAll(); } - @Document(indexName = INDEX_NAME) + abstract protected Query buildQueryForInnerHits(String innerHitName, String nestedQueryPath, String matchField, + String matchValue); + + @Document(indexName = "#{@indexNameProvider.indexName()}") static class City { @Nullable @Id private String name; @@ -156,7 +149,7 @@ public abstract class InnerHitsIntegrationTests { @Field(type = FieldType.Text) private String street; @Nullable @Field(type = FieldType.Text) private String streetNumber; - // NOTE: using a custom names here to cover property name matching + // NOTE: using custom names here to cover property name matching @Nullable @Field(name = "in-habi-tants", type = FieldType.Nested) private List inhabitants = new ArrayList<>(); @@ -195,7 +188,7 @@ public abstract class InnerHitsIntegrationTests { } static class Inhabitant { - // NOTE: using a custom names here to cover property name matching + // NOTE: using custom names here to cover property name matching @Nullable @Field(name = "first-name", type = FieldType.Text) private String firstName; @Nullable diff --git a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityELCIntegrationTests.java new file mode 100644 index 000000000..8ca3a4ae8 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityELCIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import co.elastic.clients.elasticsearch._types.FieldValue; +import co.elastic.clients.json.JsonData; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { LogEntityELCIntegrationTests.Config.class }) +public class LogEntityELCIntegrationTests extends LogEntityIntegrationTests { + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("logentity"); + } + } + + @Override + Query termQueryForIp(String ip) { + return NativeQuery.builder() // + .withQuery(qb -> qb // + .term(tq -> tq // + .field("ip") // + .value(FieldValue.of(ip)))) + .build(); + } + + @Override + Query rangeQueryForIp(String from, String to) { + return NativeQuery.builder() // + .withQuery(qb -> qb // + .range(rqb -> rqb // + .field("ip") // + .gte(JsonData.of(from))// + .lte(JsonData.of(to))// + )).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityERHLCIntegrationTests.java new file mode 100644 index 000000000..a5b09679d --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityERHLCIntegrationTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import static org.elasticsearch.index.query.QueryBuilders.*; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { LogEntityERHLCIntegrationTests.Config.class }) +public class LogEntityERHLCIntegrationTests extends LogEntityIntegrationTests { + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("logentity-es7"); + } + } + + @Override + Query termQueryForIp(String ip) { + return new NativeSearchQueryBuilder().withQuery(termQuery("ip", ip)).build(); + } + + @Override + Query rangeQueryForIp(String from, String to) { + return new NativeSearchQueryBuilder().withQuery(rangeQuery("ip").from("10.10.10.1").to("10.10.10.3")).build(); + + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityIntegrationTests.java index a63f40c0b..bb2426544 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityIntegrationTests.java @@ -16,7 +16,6 @@ package org.springframework.data.elasticsearch.core; import static org.assertj.core.api.Assertions.*; -import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; import java.text.ParseException; @@ -24,8 +23,8 @@ import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; @@ -33,12 +32,11 @@ import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -51,14 +49,14 @@ import org.springframework.lang.Nullable; @SpringIntegrationTest public abstract class LogEntityIntegrationTests { - private final IndexCoordinates index = IndexCoordinates.of("test-index-log-core"); @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() throws ParseException { - indexOperations = operations.indexOps(LogEntity.class); - IndexInitializer.init(indexOperations); + + indexNameProvider.increment(); + operations.indexOps(LogEntity.class).createWithMapping(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm"); IndexQuery indexQuery1 = new LogEntityBuilder("1").action("update").date(dateFormatter.parse("2013-10-18 18:01")) @@ -73,57 +71,62 @@ public abstract class LogEntityIntegrationTests { IndexQuery indexQuery4 = new LogEntityBuilder("4").action("update").date(dateFormatter.parse("2013-10-19 18:04")) .code(2).ip("10.10.10.4").buildIndex(); - operations.bulkIndex(Arrays.asList(indexQuery1, indexQuery2, indexQuery3, indexQuery4), index); - indexOperations.refresh(); + operations.bulkIndex(Arrays.asList(indexQuery1, indexQuery2, indexQuery3, indexQuery4), + IndexCoordinates.of(indexNameProvider.indexName())); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } + /** + * Creates a Query that will be executed as term query on the "ip" field + * + * @param ip the parameter for the query + * @return Query instance + */ + abstract Query termQueryForIp(String ip); + + /** + * Creates a Query that will be executed as range query on the "ip" field + * + * @param from the parameter for the query + * @param to the parameter for the query + * @return Query instance + */ + abstract Query rangeQueryForIp(String from, String to); + @Test // DATAES-66 public void shouldIndexGivenLogEntityWithIPFieldType() { - // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("ip", "10.10.10.1")).build(); - SearchHits entities = operations.search(searchQuery, LogEntity.class, index); + Query searchQuery = termQueryForIp("10.10.10.1"); + SearchHits entities = operations.search(searchQuery, LogEntity.class); - // then assertThat(entities).isNotNull().hasSize(1); } - protected Class invalidIpExceptionClass() { - return DataAccessException.class; - } - @Test // DATAES-66 public void shouldThrowExceptionWhenInvalidIPGivenForSearchQuery() { - // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("ip", "10.10.10")).build(); + Query searchQuery = termQueryForIp("10.10.10"); assertThatThrownBy(() -> { - SearchHits entities = operations.search(searchQuery, LogEntity.class, index); - }).isInstanceOf(invalidIpExceptionClass()); + SearchHits entities = operations.search(searchQuery, LogEntity.class); + }).isInstanceOf(DataAccessException.class); } @Test // DATAES-66 public void shouldReturnLogsForGivenIPRanges() { - // when - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(rangeQuery("ip").from("10.10.10.1").to("10.10.10.3")).build(); - SearchHits entities = operations.search(searchQuery, LogEntity.class, index); + Query searchQuery = rangeQueryForIp("10.10.10.1", "10.10.10.3"); + SearchHits entities = operations.search(searchQuery, LogEntity.class); - // then assertThat(entities).isNotNull().hasSize(3); } - /** - * Simple type to test facets - */ - @Document(indexName = "test-index-log-core") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class LogEntity { private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); @@ -188,15 +191,9 @@ public abstract class LogEntityIntegrationTests { } } - /** - * Simple type to test facets - * - * @author Artur Konczak - * @author Mohsin Husen - */ static class LogEntityBuilder { - private LogEntity result; + private final LogEntity result; public LogEntityBuilder(String id) { result = new LogEntity(id); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchELCIntegrationTests.java new file mode 100644 index 000000000..3dd324c82 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchELCIntegrationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = ReactiveElasticsearchELCIntegrationTests.Config.class) +public class ReactiveElasticsearchELCIntegrationTests extends ReactiveElasticsearchIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-template"); + } + } + + @Override + public boolean usesNewElasticsearchClient() { + return true; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchERHLCIntegrationTests.java new file mode 100644 index 000000000..a6294b413 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchERHLCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = ReactiveElasticsearchERHLCIntegrationTests.Config.class) +public class ReactiveElasticsearchERHLCIntegrationTests extends ReactiveElasticsearchIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-template-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchIntegrationTests.java similarity index 94% rename from src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchIntegrationTests.java index 730ffc668..a55ebbad7 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchIntegrationTests.java @@ -51,11 +51,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.OptimisticLockingFailureException; @@ -64,6 +62,7 @@ import org.springframework.data.annotation.Version; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.NewElasticsearchClientDevelopment; import org.springframework.data.elasticsearch.RestStatusException; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; @@ -78,8 +77,6 @@ import org.springframework.data.elasticsearch.core.index.AliasActions; import org.springframework.data.elasticsearch.core.index.AliasData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; -import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; @@ -101,19 +98,9 @@ import org.springframework.util.StringUtils; */ @SuppressWarnings("SpringJavaAutowiredMembersInspection") @SpringIntegrationTest -public class ReactiveElasticsearchTemplateIntegrationTests { - - @Configuration - @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) - static class Config { - @Bean - IndexNameProvider indexNameProvider() { - return new IndexNameProvider("reactive-template"); - } - } +public abstract class ReactiveElasticsearchIntegrationTests implements NewElasticsearchClientDevelopment { @Autowired private ReactiveElasticsearchOperations operations; - @Autowired private IndexNameProvider indexNameProvider; // region Setup @@ -132,6 +119,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { // endregion // region Tests + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void executeShouldProvideResource() { @@ -141,6 +129,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void executeShouldConvertExceptions() { @@ -168,6 +157,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void insertWithAutogeneratedIdShouldUpdateEntityId() { @@ -185,6 +175,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { return operations.exists(id, IndexCoordinates.of(index)); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void insertWithExplicitIndexNameShouldOverwriteMetadata() { @@ -299,6 +290,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-519 public void existsShouldReturnFalseWhenIndexDoesNotExist() { @@ -308,6 +300,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void existsShouldReturnTrueWhenFound() { @@ -320,6 +313,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void existsShouldReturnFalseWhenNotFound() { @@ -438,6 +432,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-595, DATAES-767 public void shouldThrowDataAccessExceptionWhenInvalidPreferenceForGivenCriteria() { @@ -503,6 +498,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-567 public void aggregateShouldReturnAggregations() { @@ -529,6 +525,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { }).verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-567, DATAES-767 public void aggregateShouldErrorWhenIndexDoesNotExist() { operations @@ -624,6 +621,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-519 public void deleteByQueryShouldReturnZeroWhenIndexDoesNotExist() { @@ -636,6 +634,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { }).verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-547 public void shouldDeleteAcrossIndex() { @@ -664,6 +663,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { operations.indexOps(thisIndex).delete().then(operations.indexOps(thatIndex).delete()).block(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-547 public void shouldDeleteAcrossIndexWhenNoMatchingDataPresent() { @@ -692,6 +692,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { operations.indexOps(thisIndex).delete().then(operations.indexOps(thatIndex).delete()).block(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void deleteByQueryShouldReturnNumberOfDeletedDocuments() { @@ -706,6 +707,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-504 public void deleteByQueryShouldReturnZeroIfNothingDeleted() { @@ -720,6 +722,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-593 public void shouldReturnDocumentWithCollapsedField() { @@ -743,6 +746,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test void shouldReturnSortFields() { SampleEntity entity = randomEntity("test message"); @@ -764,6 +768,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-623, #1678 public void shouldReturnObjectsForGivenIdsUsingMultiGet() { SampleEntity entity1 = randomEntity("test message 1"); @@ -783,6 +788,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-623 public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() { SampleEntity entity1 = randomEntity("test message 1"); @@ -803,6 +809,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-623. #1678 public void shouldDoBulkUpdate() { SampleEntity entity1 = randomEntity("test message 1"); @@ -840,6 +847,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-623 void shouldSaveAll() { SampleEntity entity1 = randomEntity("test message 1"); @@ -883,6 +891,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { assertThat(retrieved.seqNoPrimaryTerm.getPrimaryTerm()).isPositive(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-799, #1678 void multiGetShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); @@ -901,6 +910,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { return new NativeSearchQueryBuilder().withIds(singletonList(id)).build(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-799 void searchShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); @@ -920,6 +930,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { return new NativeSearchQueryBuilder().withFilter(new IdsQueryBuilder().addIds(id)).build(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-799 void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnEntityWithSeqNoPrimaryTermProperty() { OptimisticEntity original = new OptimisticEntity(); @@ -939,6 +950,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verify(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-799 void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnVersionedEntityWithSeqNoPrimaryTermProperty() { OptimisticAndVersionedEntity original = new OptimisticAndVersionedEntity(); @@ -967,6 +979,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { operations.save(forEdit).as(StepVerifier::create).expectNextCount(1).verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // DATAES-909 void shouldDoUpdate() { SampleEntity entity = randomEntity("test message"); @@ -1073,39 +1086,40 @@ public class ReactiveElasticsearchTemplateIntegrationTests { @DisplayName("should not return explanation when not requested") void shouldNotReturnExplanationWhenNotRequested() { - ElasticsearchTemplateTests.SampleEntity entity = ElasticsearchTemplateTests.SampleEntity.builder().id("42") - .message("a message with text").build(); + SampleEntity entity = new SampleEntity(); + entity.setId("42"); + entity.setMessage("a message with text"); operations.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete(); Criteria criteria = new Criteria("message").contains("with"); CriteriaQuery query = new CriteriaQuery(criteria); - operations.search(query, ElasticsearchTemplateTests.SampleEntity.class).as(StepVerifier::create) - .consumeNextWith(searchHit -> { - Explanation explanation = searchHit.getExplanation(); - assertThat(explanation).isNull(); - }).verifyComplete(); + operations.search(query, SampleEntity.class).as(StepVerifier::create).consumeNextWith(searchHit -> { + Explanation explanation = searchHit.getExplanation(); + assertThat(explanation).isNull(); + }).verifyComplete(); } @Test // #725 @DisplayName("should return explanation when requested") void shouldReturnExplanationWhenRequested() { - ElasticsearchTemplateTests.SampleEntity entity = ElasticsearchTemplateTests.SampleEntity.builder().id("42") - .message("a message with text").build(); + SampleEntity entity = new SampleEntity(); + entity.setId("42"); + entity.setMessage("a message with text"); operations.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete(); Criteria criteria = new Criteria("message").contains("with"); CriteriaQuery query = new CriteriaQuery(criteria); query.setExplain(true); - operations.search(query, ElasticsearchTemplateTests.SampleEntity.class).as(StepVerifier::create) - .consumeNextWith(searchHit -> { - Explanation explanation = searchHit.getExplanation(); - assertThat(explanation).isNotNull(); - }).verifyComplete(); + operations.search(query, SampleEntity.class).as(StepVerifier::create).consumeNextWith(searchHit -> { + Explanation explanation = searchHit.getExplanation(); + assertThat(explanation).isNotNull(); + }).verifyComplete(); } + @DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation @Test // #1646, #1718 @DisplayName("should return a list of info for specific index") void shouldReturnInformationListOfAllIndices() { @@ -1190,35 +1204,6 @@ public class ReactiveElasticsearchTemplateIntegrationTests { .verifyComplete(); } - @Test // #1529 - void shouldWorkReindexForExistingIndex() { - String sourceIndexName = indexNameProvider.indexName(); - SampleEntity sampleEntity = randomEntity("abc"); - operations.save(sampleEntity).block(); - - indexNameProvider.increment(); - String destIndexName = indexNameProvider.indexName(); - operations.indexOps(IndexCoordinates.of(destIndexName)).create(); - final ReindexRequest reindexRequest = ReindexRequest - .builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName)).withRefresh(true).build(); - operations.reindex(reindexRequest).as(StepVerifier::create) - .consumeNextWith(postReindexResponse -> assertThat(postReindexResponse.getTotal()).isEqualTo(1L)) - .verifyComplete(); - operations.count(operations.matchAllQuery(), SampleEntity.class, IndexCoordinates.of(destIndexName)) - .as(StepVerifier::create).expectNext(1L).verifyComplete(); - } - - @Test // #1529 - void shouldWorkSubmitReindexTask() { - String sourceIndexName = indexNameProvider.indexName(); - indexNameProvider.increment(); - String destIndexName = indexNameProvider.indexName(); - operations.indexOps(IndexCoordinates.of(destIndexName)).create(); - final ReindexRequest reindexRequest = ReindexRequest - .builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName)).build(); - operations.submitReindex(reindexRequest).as(StepVerifier::create) - .consumeNextWith(task -> assertThat(task).isNotBlank()).verifyComplete(); - } // endregion // region Helper functions diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexELCIntegrationTests.java new file mode 100644 index 000000000..18a09c100 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexELCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveReindexELCIntegrationTests.Config.class }) +public class ReactiveReindexELCIntegrationTests extends ReactiveReindexIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-reindex"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexERHLCIntegrationTests.java new file mode 100644 index 000000000..2d5cb0483 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexERHLCIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReactiveReindexERHLCIntegrationTests.Config.class }) +public class ReactiveReindexERHLCIntegrationTests extends ReactiveReindexIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-reindex-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexIntegrationTests.java new file mode 100644 index 000000000..269544362 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveReindexIntegrationTests.java @@ -0,0 +1,159 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.elasticsearch.utils.IdGenerator.*; + +import reactor.test.StepVerifier; + +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.Id; +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.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.lang.Nullable; + +/** + * x * Note: the imperative version of these tests have more details and test methods, but they test that the request is + * built correctly. The same method from the {@link org.springframework.data.elasticsearch.client.elc.RequestConverter} + * are used here, so there is no need to test this more than once. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@SpringIntegrationTest +public abstract class ReactiveReindexIntegrationTests { + + @Autowired private ReactiveElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; + + @BeforeEach + public void beforeEach() { + + indexNameProvider.increment(); + operations.indexOps(Entity.class).createWithMapping().block(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); + } + + @Test + // #1529 + void shouldReindex() { + + String sourceIndexName = indexNameProvider.indexName(); + String documentId = nextIdAsString(); + + Entity entity = new Entity(); + entity.setId(documentId); + entity.setMessage("abc"); + operations.save(entity) // + .as(StepVerifier::create) // + .expectNextCount(1) // + .verifyComplete(); + + indexNameProvider.increment(); + String destIndexName = indexNameProvider.indexName(); + ReactiveIndexOperations indexOpsNew = operations.indexOps(IndexCoordinates.of(destIndexName)); + indexOpsNew.create() // + .then(indexOpsNew.putMapping(Entity.class)) // + .as(StepVerifier::create) // + .expectNext(true) // + .verifyComplete(); + + ReindexRequest reindexRequest = ReindexRequest.builder( // + IndexCoordinates.of(sourceIndexName), // + IndexCoordinates.of(destIndexName)) // + .withRefresh(true) // + .build(); // + + operations.reindex(reindexRequest) // + .as(StepVerifier::create) // + .consumeNextWith(reindexResponse -> { + assertThat(reindexResponse.getTotal()).isEqualTo(1L); + assertThat(reindexResponse.getCreated()).isEqualTo(1L); + }) // + .verifyComplete(); + + operations.count(operations.matchAllQuery(), Entity.class, IndexCoordinates.of(destIndexName)) // + .as(StepVerifier::create) // + .expectNext(1L) // + .verifyComplete(); + } + + @Test + // #1529 + void shouldSubmitReindexTask() { + + String sourceIndexName = indexNameProvider.indexName(); + indexNameProvider.increment(); + String destIndexName = indexNameProvider.indexName(); + ReactiveIndexOperations indexOpsNew = operations.indexOps(IndexCoordinates.of(destIndexName)); + indexOpsNew.create() // + .then(indexOpsNew.putMapping(Entity.class)).as(StepVerifier::create) // + .expectNext(true) // + .verifyComplete(); + + ReindexRequest reindexRequest = ReindexRequest.builder( // + IndexCoordinates.of(sourceIndexName), // + IndexCoordinates.of(destIndexName)) // + .build(); + + operations.submitReindex(reindexRequest) // + .as(StepVerifier::create) // + .consumeNextWith(task -> assertThat(task).matches(Pattern.compile("^.*:\\d+$"))) // + .verifyComplete(); + } + + @Document(indexName = "#{@indexNameProvider.indexName()}") + static class Entity { + @Nullable + @Id private String id; + @Nullable + @Field(type = FieldType.Text) private String message; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReindexELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReindexELCIntegrationTests.java new file mode 100644 index 000000000..1c32b125f --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReindexELCIntegrationTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import static org.springframework.data.elasticsearch.client.elc.QueryBuilders.*; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReindexELCIntegrationTests.Config.class }) +public class ReindexELCIntegrationTests extends ReindexIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reindex"); + } + } + + @Override + protected Query queryForId(String id) { + return NativeQuery.builder().withQuery(termQueryAsQuery("_id", id)).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReindexERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReindexERHLCIntegrationTests.java new file mode 100644 index 000000000..f50ed8d49 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReindexERHLCIntegrationTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.elasticsearch.index.query.QueryBuilders; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReindexERHLCIntegrationTests.Config.class }) +public class ReindexERHLCIntegrationTests extends ReindexIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reindex-es7"); + } + } + + @Override + protected Query queryForId(String id) { + return new NativeSearchQueryBuilder().withQuery(QueryBuilders.termQuery("_id", id)).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReindexIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReindexIntegrationTests.java new file mode 100644 index 000000000..64d659a08 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReindexIntegrationTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.elasticsearch.utils.IdGenerator.*; + +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.Id; +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.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.reindex.ReindexRequest; +import org.springframework.data.elasticsearch.core.reindex.ReindexResponse; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.lang.Nullable; + +/** + * @author Peter-Josef Meisch + */ +@SpringIntegrationTest +public abstract class ReindexIntegrationTests { + + @Autowired private ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; + + @BeforeEach + public void setup() { + + indexNameProvider.increment(); + operations.indexOps(Entity.class).createWithMapping(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); + } + + @Test // #1529 + void shouldReindex() { + + String sourceIndexName = indexNameProvider.indexName(); + + String documentId1 = nextIdAsString(); + Entity entity1 = new Entity(); + entity1.setId(documentId1); + entity1.setMessage("abc"); + String documentId2 = nextIdAsString(); + Entity entity2 = new Entity(); + entity2.setId(documentId2); + entity2.setMessage("abc"); + operations.save(entity1, entity2); + + indexNameProvider.increment(); + IndexCoordinates destIndex = IndexCoordinates.of(indexNameProvider.indexName()); + IndexOperations indexOpsNew = operations.indexOps(destIndex); + indexOpsNew.create(); + indexOpsNew.putMapping(Entity.class); + + ReindexRequest reindexRequest = ReindexRequest.builder( // + IndexCoordinates.of(sourceIndexName), // + destIndex) // + .withSourceQuery(queryForId(documentId1)) // + .withScript("ctx._source.newMessage = ctx._source.remove(\"message\")", "painless").withRefresh(true) // + .build(); // + ReindexResponse reindex = operations.reindex(reindexRequest); + + assertThat(reindex.getTotal()).isEqualTo(1); + assertThat(reindex.getCreated()).isEqualTo(1); + assertThat(operations.count(operations.matchAllQuery(), destIndex)).isEqualTo(1); + + Entity newEntity = operations.get(documentId1, Entity.class, destIndex); + assertThat(newEntity).isNotNull(); + assertThat(newEntity.getNewMessage()).isEqualTo(entity1.getMessage()); + assertThat(newEntity.getMessage()).isNull(); + } + + protected abstract Query queryForId(String id); + + @Test // #1529 + void shouldSubmitReindexTask() { + + String sourceIndexName = indexNameProvider.indexName(); + indexNameProvider.increment(); + String destIndexName = indexNameProvider.indexName(); + IndexOperations indexOpsNew = operations.indexOps(IndexCoordinates.of(destIndexName)); + indexOpsNew.create(); + indexOpsNew.putMapping(Entity.class); + + ReindexRequest reindexRequest = ReindexRequest.builder( // + IndexCoordinates.of(sourceIndexName), // + IndexCoordinates.of(destIndexName)) // + .build(); + + String task = operations.submitReindex(reindexRequest); + + assertThat(task).matches(Pattern.compile("^.*:\\d+$")); // nodeid:tasknr + } + + @Document(indexName = "#{@indexNameProvider.indexName()}") + static class Entity { + @Nullable + @Id private String id; + @Nullable + @Field(type = FieldType.Text) private String message; + @Nullable + @Field(type = FieldType.Text) private String newMessage; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Nullable + public String getNewMessage() { + return newMessage; + } + + public void setNewMessage(@Nullable String newMessage) { + this.newMessage = newMessage; + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java index 06a64aa3b..714a9aeac 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java @@ -23,7 +23,9 @@ import java.io.IOException; import java.time.Duration; import java.util.Arrays; import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; @@ -38,16 +40,16 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder; +import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.xcontent.ToXContent; import org.elasticsearch.xcontent.XContentType; import org.json.JSONException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.annotation.Id; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.annotations.Document; @@ -75,7 +77,6 @@ import org.springframework.lang.Nullable; * @author Peter Nowak */ @SuppressWarnings("ConstantConditions") -@ExtendWith(MockitoExtension.class) class RequestFactoryTests { @Nullable private static RequestFactory requestFactory; @@ -93,8 +94,53 @@ class RequestFactoryTests { requestFactory = new RequestFactory((converter)); } + @Test // DATAES-187 + public void shouldUsePageableOffsetToSetFromInSearchRequest() { + + // given + Pageable pageable = new PageRequest(1, 10, Sort.unsorted()) { + @Override + public long getOffset() { + return 30; + } + }; + + NativeSearchQuery query = new NativeSearchQueryBuilder() // + .withPageable(pageable) // + .build(); + + // when + SearchRequest searchRequest = requestFactory.searchRequest(query, null, IndexCoordinates.of("test")); + + // then + assertThat(searchRequest.source().from()).isEqualTo(30); + } + + @Test // DATAES-693 + public void shouldReturnSourceWhenRequested() { + // given + Map doc = new HashMap<>(); + doc.put("id", "1"); + doc.put("message", "test"); + + org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document + .from(doc); + + UpdateQuery updateQuery = UpdateQuery.builder("1") // + .withDocument(document) // + .withFetchSource(true) // + .build(); + + // when + UpdateRequest request = requestFactory.updateRequest(updateQuery, IndexCoordinates.of("index")); + + // then + assertThat(request).isNotNull(); + assertThat(request.fetchSource()).isEqualTo(FetchSourceContext.FETCH_SOURCE); + } + @Test - // FPI-734 + // DATAES-734 void shouldBuildSearchWithGeoSortSort() throws JSONException { CriteriaQuery query = new CriteriaQuery(new Criteria("lastName").is("Smith")); Sort sort = Sort.by(new GeoDistanceOrder("location", new GeoPoint(49.0, 8.4))); @@ -607,7 +653,7 @@ class RequestFactoryTests { ReindexRequest reindexRequest = ReindexRequest .builder(IndexCoordinates.of("source_1", "source_2"), IndexCoordinates.of("destination")) - .withConflicts(ReindexRequest.Conflicts.PROCEED).withMaxDocs(10) + .withConflicts(ReindexRequest.Conflicts.PROCEED).withMaxDocs(10L) .withSourceQuery(new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build()).withSourceSize(5) .withSourceSourceFilter(new FetchSourceFilterBuilder().withIncludes("name").build()).withSourceRemote(remote) .withSourceSlice(1, 20).withDestOpType(IndexQuery.OpType.CREATE) diff --git a/src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsELCIntegrationTests.java new file mode 100644 index 000000000..417554f4a --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsELCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { RuntimeFieldsELCIntegrationTests.Config.class }) +public class RuntimeFieldsELCIntegrationTests extends RuntimeFieldsIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("runtime-fields-rest-template"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsERHLCIntegrationTests.java similarity index 67% rename from src/test/java/org/springframework/data/elasticsearch/core/LogEntityRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsERHLCIntegrationTests.java index 05bd4c062..d9d7bf01c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsERHLCIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 2021-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,23 +15,25 @@ */ package org.springframework.data.elasticsearch.core; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.RestStatusException; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { LogEntityRestTemplateIntegrationTests.Config.class }) -public class LogEntityRestTemplateIntegrationTests extends LogEntityIntegrationTests { +@ContextConfiguration(classes = { RuntimeFieldsERHLCIntegrationTests.Config.class }) +public class RuntimeFieldsERHLCIntegrationTests extends RuntimeFieldsIntegrationTests { + @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) - static class Config {} - - @Override - protected Class invalidIpExceptionClass() { - return RestStatusException.class; + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("runtime-fields-rest-template-es7"); + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeELCIntegrationTests.java new file mode 100644 index 000000000..a53d836e4 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeELCIntegrationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { SearchAsYouTypeELCIntegrationTests.Config.class }) +public class SearchAsYouTypeELCIntegrationTests extends SearchAsYouTypeIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("search-as-you-type"); + } + } + + @Override + protected Query buildMultiMatchQuery(String text) { + return NativeQuery.builder() // + .withQuery(q -> q // + .multiMatch(mm -> mm // + .query(text) // + .fields("suggest", "suggest._2gram", "suggest._3gram", "suggest._4gram") // + .type(TextQueryType.BoolPrefix))) + .build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeERHLCIntegrationTests.java new file mode 100644 index 000000000..8f0f3ea6c --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeERHLCIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.elasticsearch.index.query.MultiMatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { SearchAsYouTypeERHLCIntegrationTests.Config.class }) +public class SearchAsYouTypeERHLCIntegrationTests extends SearchAsYouTypeIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("search-as-you-type-es7"); + } + } + + @Override + protected Query buildMultiMatchQuery(String text) { + return new NativeSearchQuery(QueryBuilders.multiMatchQuery(text, // + "suggest", "suggest._2gram", "suggest._3gram", "suggest._4gram").type(MultiMatchQueryBuilder.Type.BOOL_PREFIX)); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeIntegrationTests.java similarity index 66% rename from src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeIntegrationTests.java index b0f460915..f1be3f067 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeIntegrationTests.java @@ -23,48 +23,40 @@ import java.util.stream.Collectors; import javax.annotation.Nonnull; -import org.elasticsearch.index.query.MultiMatchQueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; 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.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.Query; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Aleksei Arsenev */ @SpringIntegrationTest -@ContextConfiguration(classes = { SearchAsYouTypeTests.Config.class }) -public class SearchAsYouTypeTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - static class Config {} +public abstract class SearchAsYouTypeIntegrationTests { @Autowired private ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach private void setup() { - IndexInitializer.init(operations.indexOps(SearchAsYouTypeEntity.class)); + indexNameProvider.increment(); + operations.indexOps(SearchAsYouTypeEntity.class).createWithMapping(); } - @AfterEach - void after() { - operations.indexOps(SearchAsYouTypeEntity.class).delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } private void loadEntities() { @@ -73,29 +65,26 @@ public class SearchAsYouTypeTests { indexQueries.add(new SearchAsYouTypeEntity("2", "test 2", "test 5678").toIndex()); indexQueries.add(new SearchAsYouTypeEntity("3", "test 3", "asd 5678").toIndex()); indexQueries.add(new SearchAsYouTypeEntity("4", "test 4", "not match").toIndex()); - IndexCoordinates index = IndexCoordinates.of("test-index-core-search-as-you-type"); - operations.bulkIndex(indexQueries, index); - operations.indexOps(SearchAsYouTypeEntity.class).refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); } - @Test // DATAES-773 + @Test + // DATAES-773 void shouldRetrieveEntityById() { loadEntities(); - IndexCoordinates index = IndexCoordinates.of("test-index-core-search-as-you-type"); - operations.get("1", SearchAsYouTypeEntity.class, index); + operations.get("1", SearchAsYouTypeEntity.class); } - @Test // DATAES-773 + @Test + // DATAES-773 void shouldReturnCorrectResultsForTextString() { // given loadEntities(); // when - Query query = new NativeSearchQuery(QueryBuilders.multiMatchQuery("test ", // - "suggest", "suggest._2gram", "suggest._3gram", "suggest._4gram").type(MultiMatchQueryBuilder.Type.BOOL_PREFIX)); - IndexCoordinates index = IndexCoordinates.of("test-index-core-search-as-you-type"); - List result = operations.search(query, SearchAsYouTypeEntity.class, index) // + Query query = buildMultiMatchQuery("test "); + List result = operations.search(query, SearchAsYouTypeEntity.class) // .getSearchHits() // .stream() // .map(SearchHit::getContent) // @@ -106,17 +95,18 @@ public class SearchAsYouTypeTests { assertThat(ids).containsExactlyInAnyOrder("1", "2"); } - @Test // DATAES-773 + protected abstract Query buildMultiMatchQuery(String text); + + @Test + // DATAES-773 void shouldReturnCorrectResultsForNumQuery() { // given loadEntities(); // when - Query query = new NativeSearchQuery(QueryBuilders.multiMatchQuery("5678 ", // - "suggest", "suggest._2gram", "suggest._3gram", "suggest._4gram").type(MultiMatchQueryBuilder.Type.BOOL_PREFIX)); - IndexCoordinates index = IndexCoordinates.of("test-index-core-search-as-you-type"); - List result = operations.search(query, SearchAsYouTypeEntity.class, index) // + Query query = buildMultiMatchQuery("5678 "); + List result = operations.search(query, SearchAsYouTypeEntity.class) // .getSearchHits() // .stream() // .map(SearchHit::getContent) // @@ -127,17 +117,16 @@ public class SearchAsYouTypeTests { assertThat(ids).containsExactlyInAnyOrder("2", "3"); } - @Test // DATAES-773 + @Test + // DATAES-773 void shouldReturnCorrectResultsForNotMatchQuery() { // given loadEntities(); // when - Query query = new NativeSearchQuery(QueryBuilders.multiMatchQuery("n mat", // - "suggest", "suggest._2gram", "suggest._3gram", "suggest._4gram").type(MultiMatchQueryBuilder.Type.BOOL_PREFIX)); - IndexCoordinates index = IndexCoordinates.of("test-index-core-search-as-you-type"); - List result = operations.search(query, SearchAsYouTypeEntity.class, index) // + Query query = buildMultiMatchQuery("n mat"); + List result = operations.search(query, SearchAsYouTypeEntity.class) // .getSearchHits() // .stream() // .map(SearchHit::getContent) // @@ -147,10 +136,7 @@ public class SearchAsYouTypeTests { assertThat(result.get(0).getId()).isEqualTo("4"); } - /** - * @author Aleksei Arsenev - */ - @Document(indexName = "test-index-core-search-as-you-type") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SearchAsYouTypeEntity { public SearchAsYouTypeEntity(@Nonnull String id) { @@ -207,17 +193,21 @@ public class SearchAsYouTypeTests { @Override public boolean equals(Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; + } SearchAsYouTypeEntity that = (SearchAsYouTypeEntity) o; - if (id != null ? !id.equals(that.id) : that.id != null) + if (id != null ? !id.equals(that.id) : that.id != null) { return false; - if (name != null ? !name.equals(that.name) : that.name != null) + } + if (name != null ? !name.equals(that.name) : that.name != null) { return false; + } return suggest != null ? suggest.equals(that.suggest) : that.suggest == null; } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterELCIntegrationTests.java new file mode 100644 index 000000000..74a78fb00 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { SourceFilterELCIntegrationTests.Config.class }) +public class SourceFilterELCIntegrationTests extends SourceFilterIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("source-filter"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterERHLCIntegrationTests.java similarity index 83% rename from src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/SourceFilterERHLCIntegrationTests.java index 23d49ad64..e8c85bdcd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RuntimeFieldsRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterERHLCIntegrationTests.java @@ -25,15 +25,16 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { RuntimeFieldsRestTemplateIntegrationTests.Config.class }) -public class RuntimeFieldsRestTemplateIntegrationTests extends RuntimeFieldsIntegrationTests { +@ContextConfiguration(classes = { SourceFilterERHLCIntegrationTests.Config.class }) +public class SourceFilterERHLCIntegrationTests extends SourceFilterIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) static class Config { @Bean IndexNameProvider indexNameProvider() { - return new IndexNameProvider("runtime-fields-rest-template"); + return new IndexNameProvider("source-filter-es7"); } } + } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java index 4e9603ca6..5b705480b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java @@ -20,19 +20,21 @@ import static org.assertj.core.api.Assertions.*; import java.util.Collections; import java.util.List; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; 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.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SourceFilter; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -42,13 +44,12 @@ import org.springframework.lang.Nullable; public abstract class SourceFilterIntegrationTests { @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOps; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach void setUp() { - indexOps = operations.indexOps(Entity.class); - indexOps.create(); - indexOps.putMapping(); + indexNameProvider.increment(); + operations.indexOps(Entity.class).createWithMapping(); Entity entity = new Entity(); entity.setId("42"); @@ -58,9 +59,10 @@ public abstract class SourceFilterIntegrationTests { operations.save(entity); } - @AfterEach - void tearDown() { - indexOps.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // #1659, #1678 @@ -184,7 +186,7 @@ public abstract class SourceFilterIntegrationTests { assertThat(entity.getField3()).isNull(); } - @Document(indexName = "sourcefilter-tests") + @Document(indexName = "#{@indexNameProvider.indexName()}") public static class Entity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationELCIntegrationTests.java new file mode 100644 index 000000000..5b52b9a62 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationELCIntegrationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.aggregation; + +import static org.assertj.core.api.Assertions.*; + +import co.elastic.clients.elasticsearch._types.aggregations.Aggregate; +import co.elastic.clients.elasticsearch._types.aggregations.Aggregation; +import co.elastic.clients.elasticsearch._types.aggregations.StatsBucketAggregate; + +import java.util.Map; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregations; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.client.elc.QueryBuilders; +import org.springframework.data.elasticsearch.core.AggregationsContainer; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { AggregationELCIntegrationTests.Config.class }) +public class AggregationELCIntegrationTests extends AggregationIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("aggs"); + } + } + + @Override + protected Query getTermsAggsQuery(String aggsName, String aggsField) { + return NativeQuery.builder() // + .withQuery(QueryBuilders.matchAllQueryAsQuery()) // + .withAggregation(aggsName, Aggregation.of(a -> a // + .terms(ta -> ta.field(aggsField)))) // + .withMaxResults(0) // + .build(); + } + + @Override + protected void assertThatAggsHasResult(AggregationsContainer aggregationsContainer, String aggsName) { + Map aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations(); + assertThat(aggregations).containsKey(aggsName); + } + + @Override + protected Query getPipelineAggsQuery(String aggsName, String aggsField, String aggsNamePipeline, String bucketsPath) { + return NativeQuery.builder() // + .withQuery(QueryBuilders.matchAllQueryAsQuery()) // + .withAggregation(aggsName, Aggregation.of(a -> a // + .terms(ta -> ta.field(aggsField)))) // + .withAggregation(aggsNamePipeline, Aggregation.of(a -> a // + .statsBucket(sb -> sb.bucketsPath(bp -> bp.single(bucketsPath))))) // + .withMaxResults(0) // + .build(); + } + + @Override + protected void assertThatPipelineAggsAreCorrect(AggregationsContainer aggregationsContainer, String aggsName, + String pipelineAggsName) { + Map aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations(); + assertThat(aggregations).containsKey(aggsName); + Aggregate aggregate = aggregations.get(pipelineAggsName); + assertThat(aggregate.isStatsBucket()).isTrue(); + StatsBucketAggregate statsBucketAggregate = aggregate.statsBucket(); + assertThat(statsBucketAggregate.min()).isEqualTo(1.0); + assertThat(statsBucketAggregate.max()).isEqualTo(3.0); + assertThat(statsBucketAggregate.avg()).isEqualTo(2.0); + assertThat(statsBucketAggregate.sum()).isEqualTo(6.0); + assertThat(statsBucketAggregate.count()).isEqualTo(3L); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationERHLCIntegrationTests.java new file mode 100644 index 000000000..4b91fa44a --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationERHLCIntegrationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.aggregation; + +import static org.assertj.core.api.Assertions.*; +import static org.elasticsearch.index.query.QueryBuilders.*; +import static org.elasticsearch.search.aggregations.AggregationBuilders.*; +import static org.elasticsearch.search.aggregations.PipelineAggregatorBuilders.*; + +import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.pipeline.ParsedStatsBucket; +import org.elasticsearch.search.aggregations.pipeline.StatsBucket; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.AggregationsContainer; +import org.springframework.data.elasticsearch.core.ElasticsearchAggregations; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { AggregationERHLCIntegrationTests.Config.class }) +public class AggregationERHLCIntegrationTests extends AggregationIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("aggs-es7"); + } + } + + protected Query getTermsAggsQuery(String aggsName, String aggsField) { + return new NativeSearchQueryBuilder() // + .withQuery(matchAllQuery()) // + .withSearchType(SearchType.DEFAULT) // + .withAggregations(terms(aggsName).field(aggsField)) // + .withMaxResults(0) // + .build(); + } + + protected void assertThatAggsHasResult(AggregationsContainer aggregationsContainer, String aggsName) { + Aggregations aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations(); + assertThat(aggregations.asMap().get(aggsName)).isNotNull(); + } + + protected Query getPipelineAggsQuery(String aggsName, String aggsField, String aggsNamePipeline, String bucketsPath) { + return new NativeSearchQueryBuilder() // + .withQuery(matchAllQuery()) // + .withSearchType(SearchType.DEFAULT) // + .withAggregations(terms(aggsName).field(aggsField)) // + .withPipelineAggregations(statsBucket(aggsNamePipeline, bucketsPath)) // + .withMaxResults(0) // + .build(); + } + + protected void assertThatPipelineAggsAreCorrect(AggregationsContainer aggregationsContainer, String aggsName, + String pipelineAggsName) { + Aggregations aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations(); + + assertThat(aggregations.asMap().get(aggsName)).isNotNull(); + Aggregation keyword_bucket_stats = aggregations.asMap().get(pipelineAggsName); + assertThat(keyword_bucket_stats).isInstanceOf(StatsBucket.class); + if (keyword_bucket_stats instanceof ParsedStatsBucket) { + // Rest client + ParsedStatsBucket statsBucket = (ParsedStatsBucket) keyword_bucket_stats; + assertThat(statsBucket.getMin()).isEqualTo(1.0); + assertThat(statsBucket.getMax()).isEqualTo(3.0); + assertThat(statsBucket.getAvg()).isEqualTo(2.0); + assertThat(statsBucket.getSum()).isEqualTo(6.0); + assertThat(statsBucket.getCount()).isEqualTo(3L); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationIntegrationTests.java index 0becc1576..41524b3cd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationIntegrationTests.java @@ -16,9 +16,6 @@ package org.springframework.data.elasticsearch.core.aggregation; import static org.assertj.core.api.Assertions.*; -import static org.elasticsearch.index.query.QueryBuilders.*; -import static org.elasticsearch.search.aggregations.AggregationBuilders.*; -import static org.elasticsearch.search.aggregations.PipelineAggregatorBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Integer; @@ -26,15 +23,9 @@ import java.lang.Integer; import java.util.ArrayList; import java.util.List; -import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.search.aggregations.Aggregation; -import org.elasticsearch.search.aggregations.Aggregations; -import org.elasticsearch.search.aggregations.pipeline.InternalStatsBucket; -import org.elasticsearch.search.aggregations.pipeline.ParsedStatsBucket; -import org.elasticsearch.search.aggregations.pipeline.StatsBucket; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -43,16 +34,13 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.InnerField; import org.springframework.data.elasticsearch.annotations.MultiField; import org.springframework.data.elasticsearch.core.AggregationsContainer; -import org.springframework.data.elasticsearch.core.ElasticsearchAggregations; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -62,6 +50,7 @@ import org.springframework.lang.Nullable; * @author Artur Konczak * @author Peter-Josef Meisch */ +@SuppressWarnings("SpellCheckingInspection") @SpringIntegrationTest public abstract class AggregationIntegrationTests { @@ -74,67 +63,60 @@ public abstract class AggregationIntegrationTests { static final int YEAR_2000 = 2000; static final String INDEX_NAME = "test-index-articles-core-aggregation"; + @Autowired IndexNameProvider indexNameProvider; @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOperations; @BeforeEach public void before() { - indexOperations = operations.indexOps(ArticleEntity.class); - IndexInitializer.init(indexOperations); - IndexQuery article1 = new ArticleEntityBuilder("1").title("article four").subject("computing") + indexNameProvider.increment(); + operations.indexOps(ArticleEntity.class).createWithMapping(); + operations.indexOps(PipelineAggsEntity.class).createWithMapping(); + + ArticleEntity article1 = new ArticleEntityBuilder("1").title("article four").subject("computing") .addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addAuthor(MOHSIN_HUSEN).addAuthor(JONATHAN_YAN).score(10) - .buildIndex(); - IndexQuery article2 = new ArticleEntityBuilder("2").title("article three").subject("computing") + .build(); + ArticleEntity article2 = new ArticleEntityBuilder("2").title("article three").subject("computing") .addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addAuthor(MOHSIN_HUSEN).addPublishedYear(YEAR_2000).score(20) - .buildIndex(); - IndexQuery article3 = new ArticleEntityBuilder("3").title("article two").subject("computing") + .build(); + ArticleEntity article3 = new ArticleEntityBuilder("3").title("article two").subject("computing") .addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addPublishedYear(YEAR_2001).addPublishedYear(YEAR_2000) - .score(30).buildIndex(); - IndexQuery article4 = new ArticleEntityBuilder("4").title("article one").subject("accounting") + .score(30).build(); + ArticleEntity article4 = new ArticleEntityBuilder("4").title("article one").subject("accounting") .addAuthor(RIZWAN_IDREES).addPublishedYear(YEAR_2002).addPublishedYear(YEAR_2001).addPublishedYear(YEAR_2000) - .score(40).buildIndex(); + .score(40).build(); - IndexCoordinates index = IndexCoordinates.of(INDEX_NAME); - operations.index(article1, index); - operations.index(article2, index); - operations.index(article3, index); - operations.index(article4, index); - indexOperations.refresh(); + operations.save(article1, article2, article3, article4); } - @AfterEach - public void after() { - indexOperations.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // DATAES-96 public void shouldReturnAggregatedResponseForGivenSearchQuery() { - // given - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() // - .withQuery(matchAllQuery()) // - .withSearchType(SearchType.DEFAULT) // - .addAggregation(terms("subjects").field("subject")) // - .withMaxResults(0) // - .build(); - // when - SearchHits searchHits = operations.search(searchQuery, ArticleEntity.class, - IndexCoordinates.of(INDEX_NAME)); + String aggsName = "subjects"; + Query searchQuery = getTermsAggsQuery(aggsName, "subject"); + + SearchHits searchHits = operations.search(searchQuery, ArticleEntity.class); AggregationsContainer aggregationsContainer = searchHits.getAggregations(); - // then assertThat(aggregationsContainer).isNotNull(); - Aggregations aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations(); - assertThat(aggregations.asMap().get("subjects")).isNotNull(); + assertThatAggsHasResult(aggregationsContainer, aggsName); assertThat(searchHits.hasSearchHits()).isFalse(); } + protected abstract Query getTermsAggsQuery(String aggsName, String aggsField); + + protected abstract void assertThatAggsHasResult(AggregationsContainer aggregationsContainer, String aggsName); + @Test // #1255 @DisplayName("should work with pipeline aggregations") void shouldWorkWithPipelineAggregations() { - IndexInitializer.init(operations.indexOps(PipelineAggsEntity.class)); operations.save( // new PipelineAggsEntity("1-1", "one"), // new PipelineAggsEntity("2-1", "two"), // @@ -144,45 +126,28 @@ public abstract class AggregationIntegrationTests { new PipelineAggsEntity("3-3", "three") // ); // - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() // - .withQuery(matchAllQuery()) // - .withSearchType(SearchType.DEFAULT) // - .addAggregation(terms("keyword_aggs").field("keyword")) // - .addAggregation(statsBucket("keyword_bucket_stats", "keyword_aggs._count")) // - .withMaxResults(0) // - .build(); + String aggsName = "keyword_aggs"; + String aggsField = "keyword"; + String pipelineAggsName = "keyword_bucket_stats"; + String bucketsPath = "keyword_aggs._count"; + + Query searchQuery = getPipelineAggsQuery(aggsName, aggsField, pipelineAggsName, bucketsPath); SearchHits searchHits = operations.search(searchQuery, PipelineAggsEntity.class); AggregationsContainer aggregationsContainer = searchHits.getAggregations(); assertThat(aggregationsContainer).isNotNull(); - Aggregations aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations(); - - assertThat(aggregations.asMap().get("keyword_aggs")).isNotNull(); - Aggregation keyword_bucket_stats = aggregations.asMap().get("keyword_bucket_stats"); - assertThat(keyword_bucket_stats).isInstanceOf(StatsBucket.class); - if (keyword_bucket_stats instanceof ParsedStatsBucket) { - // Rest client - ParsedStatsBucket statsBucket = (ParsedStatsBucket) keyword_bucket_stats; - assertThat(statsBucket.getMin()).isEqualTo(1.0); - assertThat(statsBucket.getMax()).isEqualTo(3.0); - assertThat(statsBucket.getAvg()).isEqualTo(2.0); - assertThat(statsBucket.getSum()).isEqualTo(6.0); - assertThat(statsBucket.getCount()).isEqualTo(3L); - } - if (keyword_bucket_stats instanceof InternalStatsBucket) { - // transport client - InternalStatsBucket statsBucket = (InternalStatsBucket) keyword_bucket_stats; - assertThat(statsBucket.getMin()).isEqualTo(1.0); - assertThat(statsBucket.getMax()).isEqualTo(3.0); - assertThat(statsBucket.getAvg()).isEqualTo(2.0); - assertThat(statsBucket.getSum()).isEqualTo(6.0); - assertThat(statsBucket.getCount()).isEqualTo(3L); - } + assertThatPipelineAggsAreCorrect(aggregationsContainer, aggsName, pipelineAggsName); } + protected abstract Query getPipelineAggsQuery(String aggsName, String aggsField, String aggsNamePipeline, + String bucketsPath); + + protected abstract void assertThatPipelineAggsAreCorrect(AggregationsContainer aggregationsContainer, + String aggsName, String pipelineAggsName); + // region entities - @Document(indexName = "test-index-articles-core-aggregation") + @Document(indexName = "#{@indexNameProvider.indexName()}-article") static class ArticleEntity { @Nullable @@ -312,7 +277,7 @@ public abstract class AggregationIntegrationTests { } } - @Document(indexName = "pipeline-aggs") + @Document(indexName = "#{@indexNameProvider.indexName()}-pipeline") static class PipelineAggsEntity { @Id private String id; @Field(type = Keyword) private String keyword; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationRestTemplateIntegrationTests.java deleted file mode 100644 index 9fe9b7741..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/AggregationRestTemplateIntegrationTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.core.aggregation; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { AggregationRestTemplateIntegrationTests.Config.class }) -public class AggregationRestTemplateIntegrationTests extends AggregationIntegrationTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsELCIntegrationTests.java similarity index 73% rename from src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsELCIntegrationTests.java index 683420a57..0a4e82cfd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsELCIntegrationTests.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core.paginating; +package org.springframework.data.elasticsearch.core.cluster; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch + * @since 4.4 */ -@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) -public class SearchAfterRestTemplateIntegrationTests extends SearchAfterIntegrationTests {} +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +public class ClusterOperationsELCIntegrationTests extends ClusterOperationsIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsERHLCIntegrationTests.java similarity index 90% rename from src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsERHLCIntegrationTests.java index d6f9520e8..e4383a6a1 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsERHLCIntegrationTests.java @@ -22,4 +22,4 @@ import org.springframework.test.context.ContextConfiguration; * @author Peter-Josef Meisch */ @ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) -public class ClusterOperationsRestTemplateIntegrationTests extends ClusterOperationsIntegrationTests {} +public class ClusterOperationsERHLCIntegrationTests extends ClusterOperationsIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveELCIntegrationTests.java new file mode 100644 index 000000000..6961f86d7 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveELCIntegrationTests.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveElasticsearchTemplateConfiguration.class }) +public class ClusterOperationsReactiveELCIntegrationTests + extends ClusterOperationsReactiveIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveERHLCIntegrationTests.java new file mode 100644 index 000000000..b8f7b0118 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveERHLCIntegrationTests.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) +public class ClusterOperationsReactiveERHLCIntegrationTests extends ClusterOperationsReactiveIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveIntegrationTests.java similarity index 84% rename from src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveIntegrationTests.java index ba9304350..63944585d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsReactiveIntegrationTests.java @@ -27,16 +27,13 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ @SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) -public class ClusterOperationsReactiveTemplateIntegrationTests { +abstract public class ClusterOperationsReactiveIntegrationTests { @Autowired private ReactiveElasticsearchOperations operations; private ReactiveClusterOperations clusterOperations; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchCustomConversionsTest.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchCustomConversionsUnitTests.java similarity index 96% rename from src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchCustomConversionsTest.java rename to src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchCustomConversionsUnitTests.java index 6edc1e144..9481e9874 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchCustomConversionsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchCustomConversionsUnitTests.java @@ -24,11 +24,12 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomCo /** * @author Peter-Josef Meisch */ -class ElasticsearchCustomConversionsTest { +class ElasticsearchCustomConversionsUnitTests { private byte[] bytes = new byte[] { 0x01, 0x02, 0x03, 0x04 }; private String base64 = "AQIDBA=="; + @Test void shouldConvertFromByteArrayToBase64() { assertThat(ByteArrayToBase64Converter.INSTANCE.convert(bytes)).isEqualTo(base64); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/document/DocumentAdaptersUnitTests.java similarity index 96% rename from src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/document/DocumentAdaptersUnitTests.java index 795cdb325..aa7f7aa50 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/document/DocumentAdaptersUnitTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core; +package org.springframework.data.elasticsearch.core.document; import static org.assertj.core.api.Assertions.*; @@ -33,10 +33,6 @@ import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchShardTarget; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.data.elasticsearch.core.document.DocumentAdapters; -import org.springframework.data.elasticsearch.core.document.Document; -import org.springframework.data.elasticsearch.core.document.Explanation; -import org.springframework.data.elasticsearch.core.document.SearchDocument; /** * Unit tests for {@link DocumentAdapters}. diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackELCIntegrationTests.java new file mode 100644 index 000000000..3fca7ced6 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.event; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { CallbackELCIntegrationTests.Config.class }) +class CallbackELCIntegrationTests extends CallbackIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class, CallbackIntegrationTests.Config.class }) + + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("callback"); + } + + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackERHLCIntegrationTests.java new file mode 100644 index 000000000..7c2437fff --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackERHLCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.event; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { CallbackERHLCIntegrationTests.Config.class }) +class CallbackERHLCIntegrationTests extends CallbackIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class, CallbackIntegrationTests.Config.class }) + + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("callback-es7"); + } + + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchOperationsCallbackIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackIntegrationTests.java similarity index 92% rename from src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchOperationsCallbackIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/event/CallbackIntegrationTests.java index 6d4d5ab03..b2db76fca 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchOperationsCallbackIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/event/CallbackIntegrationTests.java @@ -21,9 +21,9 @@ import static org.mockito.Mockito.*; import java.util.Collections; import java.util.List; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; @@ -41,6 +41,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; @@ -49,12 +50,12 @@ import org.springframework.stereotype.Component; * @author Roman Puchkovskiy */ @SpringIntegrationTest -abstract class ElasticsearchOperationsCallbackIntegrationTests { +abstract class CallbackIntegrationTests { - private static final String INDEX = "test-operations-callback"; @Autowired private ElasticsearchOperations originalOperations; // need a spy here on the abstract implementation class private AbstractElasticsearchTemplate operations; + @Autowired private IndexNameProvider indexNameProvider; @Nullable private static SeqNoPrimaryTerm seqNoPrimaryTerm = null; @@ -91,24 +92,21 @@ abstract class ElasticsearchOperationsCallbackIntegrationTests { @BeforeEach void setUp() { + indexNameProvider.increment(); seqNoPrimaryTerm = null; operations = (AbstractElasticsearchTemplate) spy(originalOperations); IndexOperations indexOps = operations.indexOps(SampleEntity.class); - indexOps.delete(); - indexOps.create(); - indexOps.putMapping(SampleEntity.class); + indexOps.createWithMapping(); // store one entity to have a seq_no and primary_term - final SampleEntity initial = new SampleEntity("1", "initial"); - final SampleEntity saved = operations.save(initial); - seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm(); + seqNoPrimaryTerm = operations.save(new SampleEntity("1", "initial")).getSeqNoPrimaryTerm(); } - @AfterEach - void tearDown() { - IndexOperations indexOps = operations.indexOps(SampleEntity.class); - indexOps.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + originalOperations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // DATAES-68 @@ -148,11 +146,7 @@ abstract class ElasticsearchOperationsCallbackIntegrationTests { SampleEntity entity = new SampleEntity("1", "test"); - final IndexQuery indexQuery = new IndexQuery(); - indexQuery.setId(entity.getId()); - indexQuery.setObject(entity); - - operations.index(indexQuery, IndexCoordinates.of(INDEX)); + operations.save(entity); ArgumentCaptor indexQueryCaptor = ArgumentCaptor.forClass(IndexQuery.class); verify(operations, times(2)).doIndex(indexQueryCaptor.capture(), any()); @@ -184,7 +178,7 @@ abstract class ElasticsearchOperationsCallbackIntegrationTests { indexQuery.setSeqNo(seqNoPrimaryTermOriginal.getSequenceNumber()); indexQuery.setPrimaryTerm(seqNoPrimaryTermOriginal.getPrimaryTerm()); - operations.index(indexQuery, IndexCoordinates.of(INDEX)); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); ArgumentCaptor indexQueryCaptor = ArgumentCaptor.forClass(IndexQuery.class); verify(operations, times(2)).doIndex(indexQueryCaptor.capture(), any()); @@ -241,7 +235,7 @@ abstract class ElasticsearchOperationsCallbackIntegrationTests { assertThat(loaded.className).isEqualTo(SampleEntity.class.getName()); } - @Document(indexName = INDEX) + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackELCIntegrationTests.java new file mode 100644 index 000000000..736924291 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackELCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.event; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveCallbackELCIntegrationTests.Config.class }) +public class ReactiveCallbackELCIntegrationTests extends ReactiveCallbackIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class, ReactiveCallbackIntegrationTests.Config.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-callback"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackERHLCIntegrationTests.java new file mode 100644 index 000000000..1139974ed --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackERHLCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.event; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveCallbackERHLCIntegrationTests.Config.class }) +public class ReactiveCallbackERHLCIntegrationTests extends ReactiveCallbackIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class, ReactiveCallbackIntegrationTests.Config.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-callback-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveElasticsearchOperationsCallbackTest.java b/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackIntegrationTests.java similarity index 71% rename from src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveElasticsearchOperationsCallbackTest.java rename to src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackIntegrationTests.java index 3de3f7efb..21502ac3a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveElasticsearchOperationsCallbackTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/event/ReactiveCallbackIntegrationTests.java @@ -20,37 +20,31 @@ import static org.assertj.core.api.Assertions.*; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.elasticsearch.annotations.Document; -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch * @author Roman Puchkovskiy */ @SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveElasticsearchOperationsCallbackTest.Config.class }) -public class ReactiveElasticsearchOperationsCallbackTest { +public abstract class ReactiveCallbackIntegrationTests { @Configuration - @Import({ ReactiveElasticsearchRestTemplateConfiguration.class, ElasticsearchRestTemplateConfiguration.class }) static class Config { @Component @@ -63,13 +57,12 @@ public class ReactiveElasticsearchOperationsCallbackTest { } @Component - static class SampleEntityAfterLoadCallback - implements ReactiveAfterLoadCallback { + static class SampleEntityAfterLoadCallback implements ReactiveAfterLoadCallback { @Override public Mono onAfterLoad( - org.springframework.data.elasticsearch.core.document.Document document, - Class type, IndexCoordinates indexCoordinates) { + org.springframework.data.elasticsearch.core.document.Document document, Class type, + IndexCoordinates indexCoordinates) { document.put("className", document.get("_class")); return Mono.just(document); @@ -79,19 +72,19 @@ public class ReactiveElasticsearchOperationsCallbackTest { } @Autowired private ReactiveElasticsearchOperations operations; - @Autowired private ElasticsearchOperations nonreactiveOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach void setUp() { - IndexOperations indexOps = nonreactiveOperations.indexOps(SampleEntity.class); - indexOps.create(); - indexOps.putMapping(SampleEntity.class); + indexNameProvider.increment(); + ReactiveIndexOperations indexOps = operations.indexOps(SampleEntity.class); + indexOps.createWithMapping().block(); } - @AfterEach - void tearDown() { - IndexOperations indexOps = nonreactiveOperations.indexOps(SampleEntity.class); - indexOps.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); } @Test // DATES-68 @@ -121,7 +114,7 @@ public class ReactiveElasticsearchOperationsCallbackTest { }).verifyComplete(); // } - @Document(indexName = "test-operations-reactive-callback") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Id private String id; private String text; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoELCIntegrationTests.java new file mode 100644 index 000000000..05742ca77 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoELCIntegrationTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.geo; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.client.elc.QueryBuilders; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.data.elasticsearch.utils.geohash.Geohash; +import org.springframework.data.elasticsearch.utils.geohash.Rectangle; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { GeoELCIntegrationTests.Config.class }) +public class GeoELCIntegrationTests extends GeoIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("geo-integration"); + } + } + + @Override + protected Query nativeQueryForBoundingBox(String fieldName, double top, double left, double bottom, double right) { + return NativeQuery.builder() // + .withQuery(q -> q // + .geoBoundingBox(bb -> bb // + .field(fieldName) // + .boundingBox(gb -> gb // + .tlbr(tlbr -> tlbr // + .topLeft(tl -> tl // + .latlon(QueryBuilders.latLon(top, left))) + .bottomRight(br -> br // + .latlon(QueryBuilders.latLon(bottom, right))))))) + .build(); + } + + @Override + protected Query nativeQueryForBoundingBox(String fieldName, String geoHash) { + Rectangle rect = Geohash.toBoundingBox(geoHash); + return nativeQueryForBoundingBox(fieldName, rect.getMaxY(), rect.getMinX(), rect.getMinY(), rect.getMaxX()); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoERHLCIntegrationTests.java new file mode 100644 index 000000000..4cfbca928 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoERHLCIntegrationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.geo; + +import org.elasticsearch.index.query.QueryBuilders; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { GeoERHLCIntegrationTests.Config.class }) +public class GeoERHLCIntegrationTests extends GeoIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("geo-integration-es7"); + } + } + + @Override + protected Query nativeQueryForBoundingBox(String fieldName, double top, double left, double bottom, double right) { + return new NativeSearchQueryBuilder() + .withFilter(QueryBuilders.geoBoundingBoxQuery(fieldName).setCorners(top, left, bottom, right)).build(); + } + + @Override + protected Query nativeQueryForBoundingBox(String fieldName, String geoHash) { + return new NativeSearchQueryBuilder().withFilter(QueryBuilders.geoBoundingBoxQuery(fieldName).setCorners(geoHash)) + .build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoIntegrationTests.java index 8ebae1beb..95a76292a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoIntegrationTests.java @@ -20,16 +20,13 @@ import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.List; -import org.elasticsearch.geometry.utils.Geohash; -import org.elasticsearch.index.query.QueryBuilders; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.GeoPointField; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; @@ -37,8 +34,10 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.data.elasticsearch.utils.geohash.Geohash; import org.springframework.data.geo.Point; import org.springframework.lang.Nullable; @@ -56,32 +55,31 @@ import org.springframework.lang.Nullable; @SpringIntegrationTest public abstract class GeoIntegrationTests { - private final IndexCoordinates locationMarkerIndex = IndexCoordinates.of("test-index-location-marker-core-geo"); - private final IndexCoordinates authorMarkerIndex = IndexCoordinates.of("test-index-author-marker-core-geo"); - @Autowired private ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - IndexInitializer.init(operations.indexOps(AuthorMarkerEntity.class)); - IndexInitializer.init(operations.indexOps(LocationMarkerEntity.class)); + indexNameProvider.increment(); + operations.indexOps(AuthorMarkerEntity.class).createWithMapping(); + operations.indexOps(LocationMarkerEntity.class).createWithMapping(); } - @AfterEach - void after() { - operations.indexOps(AuthorMarkerEntity.class).delete(); - operations.indexOps(LocationMarkerEntity.class).delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*" + indexNameProvider.getPrefix() + "*")).delete(); } private void loadClassBaseEntities() { List indexQueries = new ArrayList<>(); - indexQueries - .add(new AuthorMarkerEntityBuilder("1").name("Franck Marchand").location(45.7806d, 3.0875d).buildIndex()); - indexQueries.add(new AuthorMarkerEntityBuilder("2").name("Mohsin Husen").location(51.5171d, 0.1062d).buildIndex()); - indexQueries.add(new AuthorMarkerEntityBuilder("3").name("Rizwan Idrees").location(51.5171d, 0.1062d).buildIndex()); - operations.bulkIndex(indexQueries, authorMarkerIndex); - operations.indexOps(AuthorMarkerEntity.class).refresh(); + indexQueries.add(new AuthorMarkerEntityBuilder("1").name("abc").location(45.7806d, 3.0875d).buildIndex()); + indexQueries.add(new AuthorMarkerEntityBuilder("2").name("def").location(51.5171d, 0.1062d).buildIndex()); + indexQueries.add(new AuthorMarkerEntityBuilder("3").name("ghi").location(51.5171d, 0.1062d).buildIndex()); + indexQueries.add( + new AuthorMarkerEntityBuilder("4").name("jkl").location(38.77353441278326d, -9.09882204680034d).buildIndex()); + operations.bulkIndex(indexQueries, AuthorMarkerEntity.class); } private void loadAnnotationBaseEntities() { @@ -91,30 +89,39 @@ public abstract class GeoIntegrationTests { String latLonString = "51.000000, 0.100000"; String geohash = "u10j46mkfekr"; Geohash.stringEncode(0.100000, 51.000000); + LocationMarkerEntity location1 = new LocationMarkerEntity(); location1.setId("1"); - location1.setName("Artur Konczak"); + location1.setName("location 1"); location1.setLocationAsString(latLonString); location1.setLocationAsArray(lonLatArray); location1.setLocationAsGeoHash(geohash); + LocationMarkerEntity location2 = new LocationMarkerEntity(); location2.setId("2"); - location2.setName("Mohsin Husen"); + location2.setName("location 2"); location2.setLocationAsString(geohash.substring(0, 8)); location2.setLocationAsArray(lonLatArray); location2.setLocationAsGeoHash(geohash.substring(0, 8)); + LocationMarkerEntity location3 = new LocationMarkerEntity(); location3.setId("3"); - location3.setName("Rizwan Idrees"); + location3.setName("location 3"); location3.setLocationAsString(geohash); location3.setLocationAsArray(lonLatArray); location3.setLocationAsGeoHash(geohash); + + LocationMarkerEntity location4 = new LocationMarkerEntity(); + location4.setId("4"); + location4.setName("location 4"); + location4.setLocationAsArray(new double[] { -9.09882204680034d, 38.77353441278326d }); + indexQueries.add(buildIndex(location1)); indexQueries.add(buildIndex(location2)); indexQueries.add(buildIndex(location3)); + indexQueries.add(buildIndex(location4)); - operations.bulkIndex(indexQueries, locationMarkerIndex); - operations.indexOps(LocationMarkerEntity.class).refresh(); + operations.bulkIndex(indexQueries, LocationMarkerEntity.class); } @Test @@ -127,11 +134,11 @@ public abstract class GeoIntegrationTests { // when SearchHits geoAuthorsForGeoCriteria = operations.search(geoLocationCriteriaQuery, - AuthorMarkerEntity.class, authorMarkerIndex); + AuthorMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria).hasSize(1); - assertThat(geoAuthorsForGeoCriteria.getSearchHit(0).getContent().getName()).isEqualTo("Franck Marchand"); + assertThat(geoAuthorsForGeoCriteria.getSearchHit(0).getContent().getName()).isEqualTo("abc"); } @Test @@ -140,15 +147,15 @@ public abstract class GeoIntegrationTests { // given loadClassBaseEntities(); CriteriaQuery geoLocationCriteriaQuery2 = new CriteriaQuery( - new Criteria("name").is("Mohsin Husen").and("location").within(new GeoPoint(51.5171d, 0.1062d), "20km")); + new Criteria("name").is("def").and("location").within(new GeoPoint(51.5171d, 0.1062d), "20km")); // when SearchHits geoAuthorsForGeoCriteria2 = operations.search(geoLocationCriteriaQuery2, - AuthorMarkerEntity.class, authorMarkerIndex); + AuthorMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria2).hasSize(1); - assertThat(geoAuthorsForGeoCriteria2.getSearchHit(0).getContent().getName()).isEqualTo("Mohsin Husen"); + assertThat(geoAuthorsForGeoCriteria2.getSearchHit(0).getContent().getName()).isEqualTo("def"); } @Test @@ -160,7 +167,7 @@ public abstract class GeoIntegrationTests { new Criteria("locationAsString").within(new GeoPoint(51.000000, 0.100000), "1km")); // when SearchHits geoAuthorsForGeoCriteria = operations.search(geoLocationCriteriaQuery, - LocationMarkerEntity.class, locationMarkerIndex); + LocationMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria).hasSize(1); @@ -176,7 +183,7 @@ public abstract class GeoIntegrationTests { // when SearchHits geoAuthorsForGeoCriteria = operations.search(geoLocationCriteriaQuery, - LocationMarkerEntity.class, locationMarkerIndex); + LocationMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria).hasSize(3); @@ -191,7 +198,7 @@ public abstract class GeoIntegrationTests { new Criteria("locationAsArray").within("51.001000, 0.10100", "1km")); // when SearchHits geoAuthorsForGeoCriteria = operations.search(geoLocationCriteriaQuery, - LocationMarkerEntity.class, locationMarkerIndex); + LocationMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria).hasSize(3); @@ -206,7 +213,7 @@ public abstract class GeoIntegrationTests { // when SearchHits geoAuthorsForGeoCriteria = operations.search(geoLocationCriteriaQuery, - LocationMarkerEntity.class, locationMarkerIndex); + LocationMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria).hasSize(3); @@ -217,17 +224,18 @@ public abstract class GeoIntegrationTests { // given loadAnnotationBaseEntities(); - NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder() - .withFilter(QueryBuilders.geoBoundingBoxQuery("locationAsArray").setCorners(52, -1, 50, 1)); // when - SearchHits geoAuthorsForGeoCriteria = operations.search(queryBuilder.build(), - LocationMarkerEntity.class, locationMarkerIndex); + Query query = nativeQueryForBoundingBox("locationAsArray", 52, -1, 50, 1); + SearchHits geoAuthorsForGeoCriteria = operations.search(query, LocationMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria).hasSize(3); } + protected abstract Query nativeQueryForBoundingBox(String fieldName, double top, double left, double bottom, + double right); + @Test public void shouldFindAuthorMarkersInBoxForGivenCriteriaQueryUsingGeoBox() { @@ -238,12 +246,12 @@ public abstract class GeoIntegrationTests { // when SearchHits geoAuthorsForGeoCriteria3 = operations.search(geoLocationCriteriaQuery3, - AuthorMarkerEntity.class, authorMarkerIndex); + AuthorMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria3).hasSize(2); assertThat(geoAuthorsForGeoCriteria3.stream().map(SearchHit::getContent).map(AuthorMarkerEntity::getName)) - .containsExactlyInAnyOrder("Mohsin Husen", "Rizwan Idrees"); + .containsExactlyInAnyOrder("def", "ghi"); } @Test @@ -256,12 +264,12 @@ public abstract class GeoIntegrationTests { // when SearchHits geoAuthorsForGeoCriteria3 = operations.search(geoLocationCriteriaQuery3, - AuthorMarkerEntity.class, authorMarkerIndex); + AuthorMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria3).hasSize(2); assertThat(geoAuthorsForGeoCriteria3.stream().map(SearchHit::getContent).map(AuthorMarkerEntity::getName)) - .containsExactlyInAnyOrder("Mohsin Husen", "Rizwan Idrees"); + .containsExactlyInAnyOrder("def", "ghi"); } @Test @@ -274,12 +282,12 @@ public abstract class GeoIntegrationTests { // when SearchHits geoAuthorsForGeoCriteria3 = operations.search(geoLocationCriteriaQuery3, - AuthorMarkerEntity.class, authorMarkerIndex); + AuthorMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria3).hasSize(2); assertThat(geoAuthorsForGeoCriteria3.stream().map(SearchHit::getContent).map(AuthorMarkerEntity::getName)) - .containsExactlyInAnyOrder("Mohsin Husen", "Rizwan Idrees"); + .containsExactlyInAnyOrder("def", "ghi"); } @Test @@ -292,12 +300,12 @@ public abstract class GeoIntegrationTests { // when SearchHits geoAuthorsForGeoCriteria3 = operations.search(geoLocationCriteriaQuery3, - AuthorMarkerEntity.class, authorMarkerIndex); + AuthorMarkerEntity.class); // then assertThat(geoAuthorsForGeoCriteria3).hasSize(2); assertThat(geoAuthorsForGeoCriteria3.stream().map(SearchHit::getContent).map(AuthorMarkerEntity::getName)) - .containsExactlyInAnyOrder("Mohsin Husen", "Rizwan Idrees"); + .containsExactlyInAnyOrder("def", "ghi"); } @Test @@ -305,32 +313,20 @@ public abstract class GeoIntegrationTests { // given loadAnnotationBaseEntities(); - NativeSearchQueryBuilder location1 = new NativeSearchQueryBuilder() - .withFilter(QueryBuilders.geoBoundingBoxQuery("locationAsGeoHash").setCorners("u")); - NativeSearchQueryBuilder location2 = new NativeSearchQueryBuilder() - .withFilter(QueryBuilders.geoBoundingBoxQuery("locationAsGeoHash").setCorners("u1")); - NativeSearchQueryBuilder location3 = new NativeSearchQueryBuilder() - .withFilter(QueryBuilders.geoBoundingBoxQuery("locationAsGeoHash").setCorners("u10")); - NativeSearchQueryBuilder location4 = new NativeSearchQueryBuilder() - .withFilter(QueryBuilders.geoBoundingBoxQuery("locationAsGeoHash").setCorners("u10j")); - NativeSearchQueryBuilder location5 = new NativeSearchQueryBuilder() - .withFilter(QueryBuilders.geoBoundingBoxQuery("locationAsGeoHash").setCorners("u10j4")); - NativeSearchQueryBuilder location11 = new NativeSearchQueryBuilder() - .withFilter(QueryBuilders.geoBoundingBoxQuery("locationAsGeoHash").setCorners("u10j46mkfek")); + Query location1 = nativeQueryForBoundingBox("locationAsGeoHash", "u"); + Query location2 = nativeQueryForBoundingBox("locationAsGeoHash", "u1"); + Query location3 = nativeQueryForBoundingBox("locationAsGeoHash", "u10"); + Query location4 = nativeQueryForBoundingBox("locationAsGeoHash", "u10j"); + Query location5 = nativeQueryForBoundingBox("locationAsGeoHash", "u10j4"); + Query location11 = nativeQueryForBoundingBox("locationAsGeoHash", "u10j46mkfek"); // when - SearchHits result1 = operations.search(location1.build(), LocationMarkerEntity.class, - locationMarkerIndex); - SearchHits result2 = operations.search(location2.build(), LocationMarkerEntity.class, - locationMarkerIndex); - SearchHits result3 = operations.search(location3.build(), LocationMarkerEntity.class, - locationMarkerIndex); - SearchHits result4 = operations.search(location4.build(), LocationMarkerEntity.class, - locationMarkerIndex); - SearchHits result5 = operations.search(location5.build(), LocationMarkerEntity.class, - locationMarkerIndex); - SearchHits result11 = operations.search(location11.build(), LocationMarkerEntity.class, - locationMarkerIndex); + SearchHits result1 = operations.search(location1, LocationMarkerEntity.class); + SearchHits result2 = operations.search(location2, LocationMarkerEntity.class); + SearchHits result3 = operations.search(location3, LocationMarkerEntity.class); + SearchHits result4 = operations.search(location4, LocationMarkerEntity.class); + SearchHits result5 = operations.search(location5, LocationMarkerEntity.class); + SearchHits result11 = operations.search(location11, LocationMarkerEntity.class); // then assertThat(result1).hasSize(3); @@ -341,6 +337,8 @@ public abstract class GeoIntegrationTests { assertThat(result11).hasSize(2); } + protected abstract Query nativeQueryForBoundingBox(String fieldName, String geoHash); + private IndexQuery buildIndex(LocationMarkerEntity result) { IndexQuery indexQuery = new IndexQuery(); indexQuery.setId(result.getId()); @@ -348,11 +346,7 @@ public abstract class GeoIntegrationTests { return indexQuery; } - /** - * @author Franck Marchand - * @author Mohsin Husen - */ - @Document(indexName = "test-index-author-marker-core-geo") + @Document(indexName = "author-#{@indexNameProvider.indexName()}") static class AuthorMarkerEntity { @Nullable @Id private String id; @@ -393,11 +387,6 @@ public abstract class GeoIntegrationTests { } } - /** - * @author Franck Marchand - * @author Mohsin Husen - */ - static class AuthorMarkerEntityBuilder { private AuthorMarkerEntity result; @@ -428,7 +417,7 @@ public abstract class GeoIntegrationTests { } } - @Document(indexName = "test-index-location-marker-core-geo") + @Document(indexName = "location-#{@indexNameProvider.indexName()}") static class LocationMarkerEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchRestTemplateCallbackIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonELCIntegrationTests.java similarity index 64% rename from src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchRestTemplateCallbackIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonELCIntegrationTests.java index 083a52a00..9995237ae 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchRestTemplateCallbackIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonELCIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core.event; +package org.springframework.data.elasticsearch.core.geo; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.junit.jupiter.api.DisplayName; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch + * @since 4.4 */ -@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class, - ElasticsearchOperationsCallbackIntegrationTests.Config.class }) -class ElasticsearchRestTemplateCallbackIntegrationTests extends ElasticsearchOperationsCallbackIntegrationTests {} +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +@DisplayName("GeoJson integration test with ElasticsearchClient") +public class GeoJsonELCIntegrationTests extends GeoJsonIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonERHLCIntegrationTests.java similarity index 87% rename from src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonERHLCIntegrationTests.java index a0d52cd60..958d83f17 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonERHLCIntegrationTests.java @@ -23,5 +23,5 @@ import org.springframework.test.context.ContextConfiguration; * @author Peter-Josef Meisch */ @ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) -@DisplayName("GeoJson integration test with REST client") -public class GeoJsonRestTemplateIntegrationTests extends GeoJsonIntegrationTests {} +@DisplayName("GeoJson integration test with RestHighLevelClient") +public class GeoJsonERHLCIntegrationTests extends GeoJsonIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoRestTemplateIntegrationTests.java deleted file mode 100644 index f5cee6a0d..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoRestTemplateIntegrationTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.core.geo; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { GeoRestTemplateIntegrationTests.Config.class }) -public class GeoRestTemplateIntegrationTests extends GeoIntegrationTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationRestTemplateIntegrationTests.java deleted file mode 100644 index 164521507..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationRestTemplateIntegrationTests.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.springframework.data.elasticsearch.core.index; - -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author George Popides - */ -@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) -public class IndexOperationRestTemplateIntegrationTests extends IndexOperationIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateELCIntegrationTests.java similarity index 69% rename from src/test/java/org/springframework/data/elasticsearch/core/SourceFilterRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateELCIntegrationTests.java index 3367cb048..f7d3de0dc 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateELCIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the original author or authors. + * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core; +package org.springframework.data.elasticsearch.core.index; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch + * @since 4.4 */ -@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) -public class SourceFilterRestTemplateIntegrationTests extends SourceFilterIntegrationTests {} +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +public class IndexTemplateELCIntegrationTests extends IndexTemplateIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateERHLCIntegrationTests.java similarity index 90% rename from src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateERHLCIntegrationTests.java index f6c3f9e26..3bb251cb7 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexTemplateERHLCIntegrationTests.java @@ -22,4 +22,4 @@ import org.springframework.test.context.ContextConfiguration; * @author Peter-Josef Meisch */ @ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) -public class IndexTemplateRestTemplateIntegrationTests extends IndexTemplateIntegrationTests {} +public class IndexTemplateERHLCIntegrationTests extends IndexTemplateIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderELCIntegrationTests.java new file mode 100644 index 000000000..6fbfd2e24 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderELCIntegrationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.elasticsearch.core.index; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { MappingBuilderELCIntegrationTests.Config.class }) +public class MappingBuilderELCIntegrationTests extends MappingBuilderIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("mappingbuilder"); + } + } + + @Override + public boolean usesNewElasticsearchClient() { + return true; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderERHLCIntegrationTests.java new file mode 100644 index 000000000..c058cd6fb --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderERHLCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.elasticsearch.core.index; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { MappingBuilderERHLCIntegrationTests.Config.class }) +public class MappingBuilderERHLCIntegrationTests extends MappingBuilderIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("mappingbuilder-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index df29ea339..33b51ac60 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -17,14 +17,11 @@ package org.springframework.data.elasticsearch.core.index; import static org.assertj.core.api.Assertions.*; -import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Object; -import static org.springframework.data.elasticsearch.utils.IndexBuilder.*; import java.lang.Integer; import java.lang.Object; -import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDate; import java.util.Collection; @@ -34,10 +31,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; -import org.assertj.core.data.Percentage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Order; @@ -47,27 +42,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.NewElasticsearchClientDevelopment; import org.springframework.data.elasticsearch.annotations.*; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.MappingContextBaseTests; -import org.springframework.data.elasticsearch.core.SearchHits; -import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.core.query.IndexQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; -import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; -import org.springframework.data.elasticsearch.core.suggest.Completion; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexNameProvider; -import org.springframework.data.geo.Box; -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Point; -import org.springframework.data.geo.Polygon; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Stuart Stevenson @@ -84,8 +68,8 @@ import org.springframework.test.context.ContextConfiguration; * @author Morgan Lutz */ @SpringIntegrationTest -@ContextConfiguration(classes = { MappingBuilderIntegrationTests.Config.class }) -public class MappingBuilderIntegrationTests extends MappingContextBaseTests { +public abstract class MappingBuilderIntegrationTests extends MappingContextBaseTests + implements NewElasticsearchClientDevelopment { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @@ -97,10 +81,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } @Autowired private ElasticsearchOperations operations; - @Autowired IndexNameProvider indexNameProvider; + @Autowired protected IndexNameProvider indexNameProvider; @BeforeEach - public void before() { + void setUp() { indexNameProvider.increment(); } @@ -115,80 +99,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { IndexOperations indexOperations = operations.indexOps(SimpleRecursiveEntity.class); indexOperations.createWithMapping(); - indexOperations.refresh(); - } - - @Test // DATAES-530 - public void shouldAddStockPriceDocumentToIndex() { - - // Given - IndexOperations indexOps = operations.indexOps(StockPrice.class); - - // When - indexOps.create(); - indexOps.putMapping(StockPrice.class); - String symbol = "AU"; - double price = 2.34; - String id = "abc"; - - IndexCoordinates index = IndexCoordinates.of("test-index-stock-mapping-builder"); - StockPrice stockPrice = new StockPrice(); // - stockPrice.setId(id); - stockPrice.setSymbol(symbol); - stockPrice.setPrice(BigDecimal.valueOf(price)); - operations.index(buildIndex(stockPrice), index); - operations.indexOps(StockPrice.class).refresh(); - - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - SearchHits result = operations.search(searchQuery, StockPrice.class, index); - - // Then - assertThat(result).hasSize(1); - StockPrice entry = result.getSearchHit(0).getContent(); - assertThat(entry.getSymbol()).isEqualTo(symbol); - assertThat(entry.getPrice()).isCloseTo(BigDecimal.valueOf(price), Percentage.withPercentage(0.01)); - } - - @Test // DATAES-76 - public void shouldAddSampleInheritedEntityDocumentToIndex() { - // given - IndexOperations indexOps = operations.indexOps(SampleInheritedEntity.class); - - // when - indexOps.create(); - indexOps.putMapping(SampleInheritedEntity.class); - Date createdDate = new Date(); - String message = "msg"; - String id = "abc"; - operations.index(new SampleInheritedEntityBuilder(id).createdDate(createdDate).message(message).buildIndex(), - IndexCoordinates.of(indexNameProvider.indexName())); - - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - SearchHits result = operations.search(searchQuery, SampleInheritedEntity.class); - - // then - assertThat(result).hasSize(1); - - SampleInheritedEntity entry = result.getSearchHit(0).getContent(); - assertThat(entry.getCreatedDate()).isEqualTo(createdDate); - assertThat(entry.getMessage()).isEqualTo(message); - } - - @Test // DATAES-260 - StackOverflow when two reverse relationship. - public void shouldHandleReverseRelationship() { - - // given - IndexOperations indexOpsUser = operations.indexOps(User.class); - indexOpsUser.create(); - indexOpsUser.putMapping(User.class); - indexNameProvider.increment(); - IndexOperations indexOpsGroup = operations.indexOps(Group.class); - indexOpsGroup.create(); - indexOpsGroup.putMapping(Group.class); - - // when - - // then } @Test // DATAES-420 @@ -197,8 +107,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { // given IndexOperations indexOps = this.operations.indexOps(Book.class); - indexOps.create(); - indexOps.putMapping(Book.class); + indexOps.createWithMapping(); // when Map mapping = indexOps.getMapping(); @@ -220,8 +129,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { // given IndexOperations indexOps = operations.indexOps(NormalizerEntity.class); - indexOps.create(); - indexOps.putMapping(); + indexOps.createWithMapping(); // when Map mapping = indexOps.getMapping(); @@ -242,8 +150,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { // given IndexOperations indexOps = operations.indexOps(CopyToEntity.class); - indexOps.create(); - indexOps.putMapping(CopyToEntity.class); + indexOps.createWithMapping(); // when Map mapping = indexOps.getMapping(); @@ -262,8 +169,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteCorrectTermVectorValues() { IndexOperations indexOps = operations.indexOps(TermVectorFieldEntity.class); - indexOps.create(); - indexOps.putMapping(); + indexOps.createWithMapping(); } @Test // DATAES-946 @@ -271,8 +177,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteWildcardFieldMapping() { IndexOperations indexOps = operations.indexOps(WildcardEntity.class); - indexOps.create(); - indexOps.putMapping(); + indexOps.createWithMapping(); } @Test // #1700 @@ -280,8 +185,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteDenseVectorFieldMapping() { IndexOperations indexOps = operations.indexOps(DenseVectorEntity.class); - indexOps.create(); - indexOps.putMapping(); + indexOps.createWithMapping(); } @Test // #1370 @@ -289,9 +193,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteMappingForDisabledEntity() { IndexOperations indexOps = operations.indexOps(DisabledMappingEntity.class); - indexOps.create(); - indexOps.putMapping(); - + indexOps.createWithMapping(); } @Test // #1370 @@ -299,9 +201,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteMappingForDisabledProperty() { IndexOperations indexOps = operations.indexOps(DisabledMappingProperty.class); - indexOps.create(); - indexOps.putMapping(); - + indexOps.createWithMapping(); } @Test // #1767 @@ -309,9 +209,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteDynamicMappingAnnotations() { IndexOperations indexOps = operations.indexOps(DynamicMappingAnnotationEntity.class); - indexOps.create(); - indexOps.putMapping(); - + indexOps.createWithMapping(); } @Test // #1871 @@ -319,9 +217,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteDynamicMapping() { IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class); - indexOps.create(); - indexOps.putMapping(); - + indexOps.createWithMapping(); } @Test // #638 @@ -329,9 +225,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteDynamicDetectionValues() { IndexOperations indexOps = operations.indexOps(DynamicDetectionMapping.class); - indexOps.create(); - indexOps.putMapping(); - + indexOps.createWithMapping(); } @Test // #1816 @@ -339,9 +233,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteRuntimeFields() { IndexOperations indexOps = operations.indexOps(RuntimeFieldEntity.class); - indexOps.create(); - indexOps.putMapping(); - + indexOps.createWithMapping(); } @Test // #796 @@ -349,127 +241,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { void shouldWriteSourceExcludes() { IndexOperations indexOps = operations.indexOps(ExcludedFieldEntity.class); - indexOps.create(); - indexOps.putMapping(); - - } - - @Test // #2024 - @DisplayName("should map all field type values") - void shouldMapAllFieldTypeValues() { - operations.indexOps(EntityWithAllTypes.class).createWithMapping(); - } - - // region entities - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class IgnoreAboveEntity { - @Nullable - @Id private String id; - @Nullable - @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message; - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - - @Nullable - public String getMessage() { - return message; - } - - public void setMessage(@Nullable String message) { - this.message = message; - } - } - - static class FieldNameEntity { - - @Document(indexName = "fieldname-index") - static class IdEntity { - @Nullable - @Id - @Field("id-property") private String id; - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class TextEntity { - - @Nullable - @Id - @Field("id-property") private String id; - - @Field(name = "text-property", type = FieldType.Text) // - @Nullable private String textProperty; - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class MappingEntity { - - @Nullable - @Id - @Field("id-property") private String id; - - @Field("mapping-property") - @Mapping(mappingPath = "/mappings/test-field-analyzed-mappings.json") // - @Nullable private byte[] mappingProperty; - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class GeoPointEntity { - - @Nullable - @Id - @Field("id-property") private String id; - - @Nullable - @Field("geopoint-property") private GeoPoint geoPoint; - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class CircularEntity { - - @Nullable - @Id - @Field("id-property") private String id; - - @Nullable - @Field(name = "circular-property", type = FieldType.Object, ignoreFields = { "circular-property" }) // - private CircularEntity circularProperty; - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class CompletionEntity { - - @Nullable - @Id - @Field("id-property") private String id; - - @Nullable - @Field("completion-property") - @CompletionField(maxInputLength = 100) // - private Completion suggest; - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class MultiFieldEntity { - - @Nullable - @Id - @Field("id-property") private String id; - - @Nullable // - @MultiField(mainField = @Field(name = "main-field", type = FieldType.Text, analyzer = "whitespace"), - otherFields = { - @InnerField(suffix = "suff-ix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) // - private String description; - } + indexOps.createWithMapping(); } + // region Entities @Document(indexName = "#{@indexNameProvider.indexName()}") static class Book { @Nullable @@ -683,73 +458,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } } - static class SampleInheritedEntityBuilder { - - private final SampleInheritedEntity result; - - public SampleInheritedEntityBuilder(String id) { - result = new SampleInheritedEntity(); - result.setId(id); - } - - public SampleInheritedEntityBuilder createdDate(Date createdDate) { - result.setCreatedDate(createdDate); - return this; - } - - public SampleInheritedEntityBuilder message(String message) { - result.setMessage(message); - return this; - } - - public SampleInheritedEntity build() { - return result; - } - - public IndexQuery buildIndex() { - IndexQuery indexQuery = new IndexQuery(); - indexQuery.setId(Objects.requireNonNull(result.getId())); - indexQuery.setObject(result); - return indexQuery; - } - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class StockPrice { - @Nullable - @Id private String id; - @Nullable private String symbol; - @Nullable - @Field(type = FieldType.Double) private BigDecimal price; - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - - @Nullable - public String getSymbol() { - return symbol; - } - - public void setSymbol(@Nullable String symbol) { - this.symbol = symbol; - } - - @Nullable - public BigDecimal getPrice() { - return price; - } - - public void setPrice(@Nullable BigDecimal price) { - this.price = price; - } - } - static class AbstractInheritedEntity { @Nullable @Id private String id; @@ -776,121 +484,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } @Document(indexName = "#{@indexNameProvider.indexName()}") - static class GeoEntity { - @Nullable - @Id private String id; - // geo shape - Spring Data - @Nullable private Box box; - @Nullable private Circle circle; - @Nullable private Polygon polygon; - // geo point - Custom implementation + Spring Data - @Nullable - @GeoPointField private Point pointA; - @Nullable private GeoPoint pointB; - @Nullable - @GeoPointField private String pointC; - @Nullable - @GeoPointField private double[] pointD; - // geo shape, until e have the classes for this, us a strng - @Nullable - @GeoShapeField private String shape1; - @Nullable - @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false, - orientation = GeoShapeField.Orientation.clockwise) private String shape2; - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - - @Nullable - public Box getBox() { - return box; - } - - public void setBox(@Nullable Box box) { - this.box = box; - } - - @Nullable - public Circle getCircle() { - return circle; - } - - public void setCircle(@Nullable Circle circle) { - this.circle = circle; - } - - @Nullable - public Polygon getPolygon() { - return polygon; - } - - public void setPolygon(@Nullable Polygon polygon) { - this.polygon = polygon; - } - - @Nullable - public Point getPointA() { - return pointA; - } - - public void setPointA(@Nullable Point pointA) { - this.pointA = pointA; - } - - @Nullable - public GeoPoint getPointB() { - return pointB; - } - - public void setPointB(@Nullable GeoPoint pointB) { - this.pointB = pointB; - } - - @Nullable - public String getPointC() { - return pointC; - } - - public void setPointC(@Nullable String pointC) { - this.pointC = pointC; - } - - @Nullable - public double[] getPointD() { - return pointD; - } - - public void setPointD(@Nullable double[] pointD) { - this.pointD = pointD; - } - - @Nullable - public String getShape1() { - return shape1; - } - - public void setShape1(@Nullable String shape1) { - this.shape1 = shape1; - } - - @Nullable - public String getShape2() { - return shape2; - } - - public void setShape2(@Nullable String shape2) { - this.shape2 = shape2; - } - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class User { @Nullable @Id private String id; @@ -899,7 +492,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } @Document(indexName = "#{@indexNameProvider.indexName()}") - static class Group { @Nullable @@ -908,108 +500,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { @Field(type = FieldType.Nested, ignoreFields = { "groups" }) private Set users = new HashSet<>(); } - static class ValueObject { - private String value; - - public ValueObject(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - - static class CompletionDocument { - @Nullable - @Id private String id; - @Nullable - @CompletionField(contexts = { @CompletionContext(name = "location", type = CompletionContext.ContextMappingType.GEO, - path = "proppath") }) private Completion suggest; - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - - @Nullable - public Completion getSuggest() { - return suggest; - } - - public void setSuggest(@Nullable Completion suggest) { - this.suggest = suggest; - } - } - - @Document(indexName = "#{@indexNameProvider.indexName()}") - static class EntityWithSeqNoPrimaryTerm { - @Nullable - @Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm; - - @Nullable - public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { - return seqNoPrimaryTerm; - } - - public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { - this.seqNoPrimaryTerm = seqNoPrimaryTerm; - } - } - - static class RankFeatureEntity { - @Nullable - @Id private String id; - @Nullable - @Field(type = FieldType.Rank_Feature) private Integer pageRank; - @Nullable - @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength; - @Nullable - @Field(type = FieldType.Rank_Features) private Map topics; - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - - @Nullable - public java.lang.Integer getPageRank() { - return pageRank; - } - - public void setPageRank(@Nullable java.lang.Integer pageRank) { - this.pageRank = pageRank; - } - - @Nullable - public java.lang.Integer getUrlLength() { - return urlLength; - } - - public void setUrlLength(@Nullable java.lang.Integer urlLength) { - this.urlLength = urlLength; - } - - @Nullable - public Map getTopics() { - return topics; - } - - public void setTopics(@Nullable Map topics) { - this.topics = topics; - } - } - @Document(indexName = "#{@indexNameProvider.indexName()}") static class TermVectorFieldEntity { @Nullable @@ -1157,7 +647,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } } - @Document(indexName = "disabled-property-mapping") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class DisabledMappingProperty { @Nullable @Id private String id; @@ -1245,7 +735,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } } - @Document(indexName = "#{@indexNameProvider.indexName()}") + @Document(indexName = "#{@indexNameProvider.indexName()}", dynamic = Dynamic.FALSE) static class DynamicMappingEntity { @Nullable diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderUnitTests.java similarity index 97% rename from src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderUnitTests.java index 4df71eb15..1453093c3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderUnitTests.java @@ -37,7 +37,7 @@ import org.springframework.lang.Nullable; /** * @author Peter-Josef Meisch */ -public class ReactiveMappingBuilderTests extends MappingContextBaseTests { +public class ReactiveMappingBuilderUnitTests extends MappingContextBaseTests { ReactiveMappingBuilder getReactiveMappingBuilder() { return new ReactiveMappingBuilder(elasticsearchConverter.get()); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/SettingsUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/SettingsUnitTests.java new file mode 100644 index 000000000..0d43a25c4 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/SettingsUnitTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.index; + +import static org.assertj.core.api.Assertions.*; +import static org.skyscreamer.jsonassert.JSONAssert.*; + +import org.json.JSONException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +class SettingsUnitTests { + + @Test + @DisplayName("should merge other Settings on this settings") + void shouldMergeOtherSettingsOnThisSettings() throws JSONException { + + String thisSettingsJson = "{\n" + // + " \"index\": {\n" + // + " \"weather\": \"sunny\",\n" + // + " \"backup\": {\n" + // + " \"interval\": 5,\n" + // + " \"target\": {\n" + // + " \"type\":\"cloud\",\n" + // + " \"provider\": \"prov1\"\n" + // + " }\n" + // + " },\n" + // + " \"music\": \"The Eagles\"\n" + // + " }\n" + // + "}\n"; // + String otherSettingsJson = "{\n" + // + " \"index\": {\n" + // + " \"weather\": \"rainy\",\n" + // + " \"backup\": {\n" + // + " \"interval\": 13,\n" + // + " \"target\": {\n" + // " \"type\":\"cloud\",\n" + // + " \"provider\": \"prov2\"\n" + // + " }\n" + // + " },\n" + // + " \"drink\": \"wine\"\n" + // + " }\n" + // + "}\n"; // + String mergedSettingsJson = "{\n" + // + " \"index\": {\n" + // + " \"weather\": \"rainy\",\n" + // + " \"backup\": {\n" + // + " \"interval\": 13,\n" + // + " \"target\": {\n" + // + " \"type\":\"cloud\",\n" + // + " \"provider\": \"prov2\"\n" + // + " }\n" + // + " },\n" + // + " \"music\": \"The Eagles\",\n" + // + " \"drink\": \"wine\"\n" + // + " }\n" + // + "}\n"; // + + Settings thisSettings = Settings.parse(thisSettingsJson); + Settings otherSettings = Settings.parse(otherSettingsJson); + + thisSettings.merge(otherSettings); + + assertEquals(mergedSettingsJson, thisSettings.toJson(), true); + } + + @Test + @DisplayName("should flatten its content") + void shouldFlattenItsContent() { + + String settingsJson = "{\n" + // + " \"index\": {\n" + // + " \"weather\": \"sunny\",\n" + // + " \"backup\": {\n" + // + " \"interval\": 5,\n" + // + " \"target\": {\n" + // + " \"type\":\"cloud\",\n" + // + " \"provider\": \"prov1\"\n" + // + " }\n" + // + " },\n" + // + " \"music\": \"The Eagles\"\n" + // + " }\n" + // + "}\n"; // + + Settings settings = Settings.parse(settingsJson); + + Settings flattened = settings.flatten(); + + String flattenedKey = "index.backup.target.type"; + assertThat(flattened).containsKey(flattenedKey); + assertThat(flattened.get(flattenedKey)).isEqualTo("cloud"); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsELCIntegrationTests.java new file mode 100644 index 000000000..a035bb5d7 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsELCIntegrationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.indices; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * a@author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { IndexOperationsELCIntegrationTests.Config.class }) +public class IndexOperationsELCIntegrationTests extends IndexOperationsIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("indexoperations-es"); + } + } + + @Override + public boolean usesNewElasticsearchClient() { + return true; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsERHLCIntegrationTests.java new file mode 100644 index 000000000..778a8f0e8 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsERHLCIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.indices; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { IndexOperationsERHLCIntegrationTests.Config.class }) +public class IndexOperationsERHLCIntegrationTests extends IndexOperationsIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("indexoperations-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsIntegrationTests.java similarity index 51% rename from src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsIntegrationTests.java index ed654cdb5..e69ee61eb 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/indices/IndexOperationsIntegrationTests.java @@ -13,75 +13,99 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core.index; +package org.springframework.data.elasticsearch.core.indices; import static org.assertj.core.api.Assertions.*; import java.util.List; +import org.assertj.core.api.SoftAssertions; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.NewElasticsearchClientDevelopment; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Mapping; import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexInformation; import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.index.AliasAction; +import org.springframework.data.elasticsearch.core.index.AliasActionParameters; +import org.springframework.data.elasticsearch.core.index.AliasActions; +import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.Settings; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** - * @author George Popides + * @author Peter-Josef Meisch */ @SpringIntegrationTest -public abstract class IndexOperationIntegrationTests { +public abstract class IndexOperationsIntegrationTests implements NewElasticsearchClientDevelopment { - public static final String INDEX_NAME = "test-index-information-list"; + @Autowired private ElasticsearchOperations operations; + private IndexOperations indexOperations; - @Autowired protected ElasticsearchOperations operations; + @Autowired protected IndexNameProvider indexNameProvider; @BeforeEach void setUp() { - operations.indexOps(EntityWithSettingsAndMappings.class).delete(); + + indexNameProvider.increment(); + indexOperations = operations.indexOps(EntityWithSettingsAndMappings.class); + indexOperations.createWithMapping(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // #1646, #1718 @DisplayName("should return a list of info for specific index") void shouldReturnInformationList() throws JSONException { - IndexOperations indexOps = operations.indexOps(EntityWithSettingsAndMappings.class); + String indexName = indexNameProvider.indexName(); String aliasName = "testindexinformationindex"; - indexOps.createWithMapping(); - - AliasActionParameters parameters = AliasActionParameters.builder().withAliases(aliasName).withIndices(INDEX_NAME) + AliasActionParameters parameters = AliasActionParameters.builder().withAliases(aliasName).withIndices(indexName) .withIsHidden(false).withIsWriteIndex(false).withRouting("indexrouting").withSearchRouting("searchrouting") .build(); - indexOps.alias(new AliasActions(new AliasAction.Add(parameters))); + indexOperations.alias(new AliasActions(new AliasAction.Add(parameters))); - List indexInformationList = indexOps.getInformation(); + List indexInformationList = indexOperations.getInformation(); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(indexInformationList.size()).isEqualTo(1); IndexInformation indexInformation = indexInformationList.get(0); + softly.assertThat(indexInformation.getName()).isEqualTo(indexName); + Settings settings = indexInformation.getSettings(); + assertThat(settings).isNotNull(); + // old Elasticsearch client returns "1", the new one is typed and returns 1, so we check for Strings here + softly.assertThat(settings.get("index.number_of_shards")).isEqualTo("1"); + softly.assertThat(settings.get("index.number_of_replicas")).isEqualTo("0"); + softly.assertThat(settings.get("index.analysis.analyzer.emailAnalyzer.type")).isEqualTo("custom"); + softly.assertAll(); - assertThat(indexInformationList.size()).isEqualTo(1); - assertThat(indexInformation.getName()).isEqualTo(INDEX_NAME); - assertThat(indexInformation.getSettings().get("index.number_of_shards")).isEqualTo("1"); - assertThat(indexInformation.getSettings().get("index.number_of_replicas")).isEqualTo("0"); - assertThat(indexInformation.getSettings().get("index.analysis.analyzer.emailAnalyzer.type")).isEqualTo("custom"); assertThat(indexInformation.getAliases()).hasSize(1); - AliasData aliasData = indexInformation.getAliases().get(0); - assertThat(aliasData.getAlias()).isEqualTo(aliasName); - assertThat(aliasData.isHidden()).isEqualTo(false); - assertThat(aliasData.isWriteIndex()).isEqualTo(false); - assertThat(aliasData.getIndexRouting()).isEqualTo("indexrouting"); - assertThat(aliasData.getSearchRouting()).isEqualTo("searchrouting"); + softly = new SoftAssertions(); + softly.assertThat(aliasData.getAlias()).isEqualTo(aliasName); + softly.assertThat(aliasData.isHidden()).isEqualTo(false); + softly.assertThat(aliasData.isWriteIndex()).isEqualTo(false); + softly.assertThat(aliasData.getIndexRouting()).isEqualTo("indexrouting"); + softly.assertThat(aliasData.getSearchRouting()).isEqualTo("searchrouting"); + softly.assertAll(); String expectedMappings = "{\n" + // " \"properties\": {\n" + // @@ -94,7 +118,7 @@ public abstract class IndexOperationIntegrationTests { JSONAssert.assertEquals(expectedMappings, indexInformation.getMapping().toJson(), false); } - @Document(indexName = INDEX_NAME) + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(settingPath = "settings/test-settings.json") @Mapping(mappingPath = "mappings/test-mappings.json") protected static class EntityWithSettingsAndMappings { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsELCIntegrationTests.java new file mode 100644 index 000000000..fa981d2c5 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsELCIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.indices; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReactiveIndexOperationsELCIntegrationTests.Config.class }) +public class ReactiveIndexOperationsELCIntegrationTests extends ReactiveIndexOperationsIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-indexoperations"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsERHLCIntegrationTests.java new file mode 100644 index 000000000..01a92ebb7 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsERHLCIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.indices; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReactiveIndexOperationsERHLCIntegrationTests.Config.class }) +public class ReactiveIndexOperationsERHLCIntegrationTests extends ReactiveIndexOperationsIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-indexoperations-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java b/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsIntegrationTests.java similarity index 71% rename from src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java rename to src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsIntegrationTests.java index 1c0697acc..d9d308f20 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/indices/ReactiveIndexOperationsIntegrationTests.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core; +package org.springframework.data.elasticsearch.core.indices; import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; -import org.springframework.data.elasticsearch.core.index.Settings; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -27,13 +26,12 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import org.assertj.core.api.SoftAssertions; import org.json.JSONException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; @@ -41,6 +39,8 @@ 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.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; import org.springframework.data.elasticsearch.core.index.AliasAction; import org.springframework.data.elasticsearch.core.index.AliasActionParameters; import org.springframework.data.elasticsearch.core.index.AliasActions; @@ -49,48 +49,39 @@ import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; +import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ @SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveIndexOperationsTest.Config.class }) -public class ReactiveIndexOperationsTest { - - public static final String TESTINDEX = "reactive-index-operations-testindex"; - - @Configuration - @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) - static class Config {} +public abstract class ReactiveIndexOperationsIntegrationTests { @Autowired private ReactiveElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; + private ReactiveIndexOperations indexOperations; @BeforeEach void setUp() { - deleteIndices(); + indexNameProvider.increment(); + indexOperations = operations.indexOps(IndexCoordinates.of(indexNameProvider.indexName())); } - @AfterEach - void tearDown() { - deleteIndices(); - } - - private void deleteIndices() { - operations.indexOps(IndexCoordinates.of(TESTINDEX + "*")).delete().block(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); } @Test // DATAES-678 void shouldCreateIndexOpsForIndexCoordinates() { - IndexCoordinates indexCoordinates = IndexCoordinates.of(TESTINDEX); - - ReactiveIndexOperations indexOperations = operations.indexOps(indexCoordinates); + ReactiveIndexOperations indexOperations = operations.indexOps(IndexCoordinates.of("some-index-name")); assertThat(indexOperations).isNotNull(); } @@ -106,9 +97,7 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldCreateIndexForName() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-create")); - - indexOps.create() // + indexOperations.create() // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -135,20 +124,18 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldCreateIndexWithGivenSettings() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-create")); - org.springframework.data.elasticsearch.core.document.Document requiredSettings = org.springframework.data.elasticsearch.core.document.Document .create(); requiredSettings.put("index.number_of_replicas", 3); requiredSettings.put("index.number_of_shards", 4); requiredSettings.put("index.refresh_interval", "5s"); - indexOps.create(requiredSettings) // + indexOperations.create(requiredSettings) // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); - indexOps.getSettings().as(StepVerifier::create).consumeNextWith(settings -> { + indexOperations.getSettings().as(StepVerifier::create).consumeNextWith(settings -> { assertThat(settings.get("index.number_of_replicas")).isEqualTo("3"); assertThat(settings.get("index.number_of_shards")).isEqualTo("4"); assertThat(settings.get("index.refresh_interval")).isEqualTo("5s"); @@ -168,7 +155,7 @@ public class ReactiveIndexOperationsTest { indexOps.getSettings().as(StepVerifier::create).consumeNextWith(settings -> { assertThat(settings.get("index.number_of_replicas")).isEqualTo("0"); assertThat(settings.get("index.number_of_shards")).isEqualTo("1"); - assertThat(settings.containsKey("index.analysis.analyzer.emailAnalyzer.tokenizer")).isTrue(); + assertThat(settings.get("index.analysis.analyzer.emailAnalyzer.tokenizer")).isNotNull(); assertThat(settings.get("index.analysis.analyzer.emailAnalyzer.tokenizer")).isEqualTo("uax_url_email"); }).verifyComplete(); } @@ -176,15 +163,13 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 public void shouldCreateIndexUsingServerDefaultConfiguration() { - ReactiveIndexOperations indexOps = operations.indexOps(EntityUseServerConfig.class); - - indexOps.create() // + indexOperations.create() // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); // check the settings from the class annotation - indexOps.getSettings().as(StepVerifier::create).consumeNextWith(settings -> { + indexOperations.getSettings().as(StepVerifier::create).consumeNextWith(settings -> { assertThat(settings.get("index.number_of_replicas")).isEqualTo("1"); assertThat(settings.get("index.number_of_shards")).isEqualTo("1"); }).verifyComplete(); @@ -193,10 +178,9 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldDeleteIfItExists() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-delete")); - indexOps.create().block(); + indexOperations.create().block(); - indexOps.delete() // + indexOperations.delete() // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -205,9 +189,7 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldReturnFalseOnDeleteIfItDoesNotExist() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-delete")); - - indexOps.delete() // + indexOperations.delete() // .as(StepVerifier::create) // .expectNext(false) // .verifyComplete(); @@ -216,10 +198,9 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldReturnExistsTrueIfIndexDoesExist() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-exists")); - indexOps.create().block(); + indexOperations.create().block(); - indexOps.exists() // + indexOperations.exists() // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -227,9 +208,8 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldReturnExistsFalseIfIndexDoesNotExist() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-exists")); - indexOps.exists() // + indexOperations.exists() // .as(StepVerifier::create) // .expectNext(false) // .verifyComplete(); @@ -237,7 +217,6 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldCreateMappingForEntityFromProperties() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-mappings")); String expected = "{\n" + // " \"properties\":{\n" + // @@ -251,7 +230,7 @@ public class ReactiveIndexOperationsTest { " }\n" + // "}\n"; // - indexOps.createMapping(Entity.class) // + indexOperations.createMapping(Entity.class) // .as(StepVerifier::create) // .assertNext(document -> { try { @@ -265,7 +244,6 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldCreateMappingForEntityFromMappingAnnotation() { - ReactiveIndexOperations indexOps = operations.indexOps(IndexCoordinates.of(TESTINDEX + "-mappings")); String expected = "{\n" + // " \"properties\": {\n" + // @@ -276,7 +254,7 @@ public class ReactiveIndexOperationsTest { " }\n" + // "}\n"; // - indexOps.createMapping(EntityWithAnnotatedSettingsAndMappings.class) // + indexOperations.createMapping(EntityWithAnnotatedSettingsAndMappings.class) // .as(StepVerifier::create) // .assertNext(document -> { try { @@ -318,6 +296,7 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-678 void shouldPutAndGetMapping() { + ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); String expected = "{\n" + // @@ -348,15 +327,13 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-864 void shouldCreateAlias() { - ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); - AliasActions aliasActions = new AliasActions(); aliasActions.add(new AliasAction.Add(AliasActionParameters.builder() - .withIndices(indexOps.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); + .withIndices(indexOperations.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); - indexOps.create().flatMap(success -> { + indexOperations.create().flatMap(success -> { if (success) { - return indexOps.alias(aliasActions); + return indexOperations.alias(aliasActions); } else { return Mono.just(false); } @@ -366,19 +343,17 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-864 void shouldGetAliasData() { - ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); - AliasActions aliasActions = new AliasActions(); aliasActions.add(new AliasAction.Add(AliasActionParameters.builder() - .withIndices(indexOps.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); + .withIndices(indexOperations.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); - assertThat(indexOps.create().block()).isTrue(); - assertThat(indexOps.alias(aliasActions).block()).isTrue(); + assertThat(indexOperations.create().block()).isTrue(); + assertThat(indexOperations.alias(aliasActions).block()).isTrue(); - indexOps.getAliases("aliasA") // + indexOperations.getAliases("aliasA") // .as(StepVerifier::create) // .assertNext(aliasDatas -> { // - Set aliasData = aliasDatas.get(indexOps.getIndexCoordinates().getIndexName()); + Set aliasData = aliasDatas.get(indexOperations.getIndexCoordinates().getIndexName()); assertThat(aliasData.stream().map(AliasData::getAlias)).containsExactly("aliasA"); }) // .verifyComplete(); @@ -387,11 +362,9 @@ public class ReactiveIndexOperationsTest { @Test // DATAES-612 void shouldPutTemplate() { - ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); - - org.springframework.data.elasticsearch.core.document.Document mapping = indexOps.createMapping(TemplateClass.class) - .block(); - Settings settings = indexOps.createSettings(TemplateClass.class).block(); + org.springframework.data.elasticsearch.core.document.Document mapping = indexOperations + .createMapping(TemplateClass.class).block(); + Settings settings = indexOperations.createSettings(TemplateClass.class).block(); AliasActions aliasActions = new AliasActions( new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); @@ -403,7 +376,7 @@ public class ReactiveIndexOperationsTest { .withVersion(42) // .build(); - Boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + Boolean acknowledged = indexOperations.putTemplate(putTemplateRequest).block(); assertThat(acknowledged).isTrue(); } @@ -411,19 +384,19 @@ public class ReactiveIndexOperationsTest { void shouldReturnNullOnNonExistingGetTemplate() { String templateName = "template" + UUID.randomUUID().toString(); - ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); GetTemplateRequest getTemplateRequest = new GetTemplateRequest(templateName); - indexOps.getTemplate(getTemplateRequest).as(StepVerifier::create).verifyComplete(); + indexOperations.getTemplate(getTemplateRequest) // + .as(StepVerifier::create) // + .verifyComplete(); } @Test // DATAES-612 void shouldGetTemplate() throws JSONException { - ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); - org.springframework.data.elasticsearch.core.document.Document mapping = indexOps.createMapping(TemplateClass.class) - .block(); - Settings settings = indexOps.createSettings(TemplateClass.class).block(); + org.springframework.data.elasticsearch.core.document.Document mapping = indexOperations + .createMapping(TemplateClass.class).block(); + Settings settings = indexOperations.createSettings(TemplateClass.class).block(); AliasActions aliasActions = new AliasActions( new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); @@ -435,35 +408,37 @@ public class ReactiveIndexOperationsTest { .withVersion(42) // .build(); - Boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + Boolean acknowledged = indexOperations.putTemplate(putTemplateRequest).block(); assertThat(acknowledged).isTrue(); GetTemplateRequest getTemplateRequest = new GetTemplateRequest(putTemplateRequest.getName()); - TemplateData templateData = indexOps.getTemplate(getTemplateRequest).block(); + TemplateData templateData = indexOperations.getTemplate(getTemplateRequest).block(); - assertThat(templateData).isNotNull(); - assertThat(templateData.getIndexPatterns()).containsExactlyInAnyOrder(putTemplateRequest.getIndexPatterns()); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(templateData).isNotNull(); + softly.assertThat(templateData.getIndexPatterns()).containsExactlyInAnyOrder(putTemplateRequest.getIndexPatterns()); assertEquals(settings.toJson(), templateData.getSettings().toJson(), false); + assertEquals(mapping.toJson(), templateData.getMapping().toJson(), false); + Map aliases = templateData.getAliases(); - assertThat(aliases).hasSize(2); + softly.assertThat(aliases).hasSize(2); AliasData alias1 = aliases.get("alias1"); - assertThat(alias1.getAlias()).isEqualTo("alias1"); + softly.assertThat(alias1.getAlias()).isEqualTo("alias1"); AliasData alias2 = aliases.get("alias2"); - assertThat(alias2.getAlias()).isEqualTo("alias2"); - assertThat(templateData.getOrder()).isEqualTo(putTemplateRequest.getOrder()); - assertThat(templateData.getVersion()).isEqualTo(putTemplateRequest.getVersion()); + softly.assertThat(alias2.getAlias()).isEqualTo("alias2"); + softly.assertThat(templateData.getOrder()).isEqualTo(putTemplateRequest.getOrder()); + softly.assertThat(templateData.getVersion()).isEqualTo(putTemplateRequest.getVersion()); + softly.assertAll(); } @Test // DATAES-612 - void shouldCheckExists() { - - ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); + void shouldCheckTemplateExists() { String templateName = "template" + UUID.randomUUID().toString(); ExistsTemplateRequest existsTemplateRequest = new ExistsTemplateRequest(templateName); - boolean exists = indexOps.existsTemplate(existsTemplateRequest).block(); + boolean exists = indexOperations.existsTemplate(existsTemplateRequest).block(); assertThat(exists).isFalse(); PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder(templateName, "log-*") // @@ -471,18 +446,16 @@ public class ReactiveIndexOperationsTest { .withVersion(42) // .build(); - boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + boolean acknowledged = indexOperations.putTemplate(putTemplateRequest).block(); assertThat(acknowledged).isTrue(); - exists = indexOps.existsTemplate(existsTemplateRequest).block(); + exists = indexOperations.existsTemplate(existsTemplateRequest).block(); assertThat(exists).isTrue(); } @Test // DATAES-612 void shouldDeleteTemplate() { - ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); - String templateName = "template" + UUID.randomUUID().toString(); ExistsTemplateRequest existsTemplateRequest = new ExistsTemplateRequest(templateName); @@ -491,20 +464,20 @@ public class ReactiveIndexOperationsTest { .withVersion(42) // .build(); - boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + boolean acknowledged = indexOperations.putTemplate(putTemplateRequest).block(); assertThat(acknowledged).isTrue(); - boolean exists = indexOps.existsTemplate(existsTemplateRequest).block(); + boolean exists = indexOperations.existsTemplate(existsTemplateRequest).block(); assertThat(exists).isTrue(); - acknowledged = indexOps.deleteTemplate(new DeleteTemplateRequest(templateName)).block(); + acknowledged = indexOperations.deleteTemplate(new DeleteTemplateRequest(templateName)).block(); assertThat(acknowledged).isTrue(); - exists = indexOps.existsTemplate(existsTemplateRequest).block(); + exists = indexOperations.existsTemplate(existsTemplateRequest).block(); assertThat(exists).isFalse(); } - @Document(indexName = TESTINDEX) + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(shards = 3, replicas = 2, refreshInterval = "4s") static class Entity { @Nullable @@ -543,23 +516,7 @@ public class ReactiveIndexOperationsTest { } } - @Document(indexName = TESTINDEX) - @Setting(useServerConfiguration = true) - static class EntityUseServerConfig { - @Nullable - @Id private String id; - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - } - - @Document(indexName = TESTINDEX) + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(settingPath = "/settings/test-settings.json") @Mapping(mappingPath = "/mappings/test-mappings.json") static class EntityWithAnnotatedSettingsAndMappings { @@ -576,7 +533,7 @@ public class ReactiveIndexOperationsTest { } } - @Document(indexName = "test-template") + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(refreshInterval = "5s") static class TemplateClass { @Id @@ -589,7 +546,7 @@ public class ReactiveIndexOperationsTest { return id; } - public void setId(String id) { + public void setId(@Nullable String id) { this.id = id; } @@ -598,7 +555,7 @@ public class ReactiveIndexOperationsTest { return message; } - public void setMessage(String message) { + public void setMessage(@Nullable String message) { this.message = message; } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionELCIntegrationTests.java new file mode 100644 index 000000000..11fa824a3 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionELCIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.mapping; + +import java.util.Arrays; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { EntityCustomConversionELCIntegrationTests.Config.class }) +public class EntityCustomConversionELCIntegrationTests extends EntityCustomConversionIntegrationTests { + + @Configuration + @Import({ EntityCustomConversionIntegrationTests.Config.class }) + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.core.mapping" }, + considerNestedRepositories = true) + static class Config extends ElasticsearchTemplateConfiguration { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("entity-customconversions-operations"); + } + + @Override + public ElasticsearchCustomConversions elasticsearchCustomConversions() { + return new ElasticsearchCustomConversions(Arrays.asList(new EntityToMapConverter(), new MapToEntityConverter())); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionERHLCIntegrationTests.java new file mode 100644 index 000000000..b19780db0 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionERHLCIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.mapping; + +import java.util.Arrays; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { EntityCustomConversionERHLCIntegrationTests.Config.class }) +public class EntityCustomConversionERHLCIntegrationTests extends EntityCustomConversionIntegrationTests { + + @Configuration + @Import({ EntityCustomConversionIntegrationTests.Config.class }) + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.core.mapping" }, + considerNestedRepositories = true) + static class Config extends ElasticsearchRestTemplateConfiguration { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("entity-customconversions-operations-es7"); + } + + @Override + public ElasticsearchCustomConversions elasticsearchCustomConversions() { + return new ElasticsearchCustomConversions(Arrays.asList(new EntityToMapConverter(), new MapToEntityConverter())); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java index ebd4f5f82..34ed47765 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java @@ -17,13 +17,12 @@ package org.springframework.data.elasticsearch.core.mapping; import static org.assertj.core.api.Assertions.*; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -34,14 +33,12 @@ import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHits; -import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions; import org.springframework.data.elasticsearch.core.geo.GeoJsonPoint; import org.springframework.data.elasticsearch.core.query.Query; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * Test that a whole entity can be converted using custom conversions @@ -49,31 +46,27 @@ import org.springframework.test.context.ContextConfiguration; * @author Peter-Josef Meisch */ @SpringIntegrationTest -@ContextConfiguration(classes = { EntityCustomConversionIntegrationTests.Config.class }) -public class EntityCustomConversionIntegrationTests { +public abstract class EntityCustomConversionIntegrationTests { @Configuration @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.core.mapping" }, considerNestedRepositories = true) - static class Config extends ElasticsearchRestTemplateConfiguration { - @Override - public ElasticsearchCustomConversions elasticsearchCustomConversions() { - return new ElasticsearchCustomConversions(Arrays.asList(new EntityToMapConverter(), new MapToEntityConverter())); - } - } + static class Config {} @Autowired private ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach void setUp() { + indexNameProvider.increment(); IndexOperations indexOps = operations.indexOps(Entity.class); - indexOps.create(); - indexOps.putMapping(); + indexOps.createWithMapping(); } - @AfterEach - void tearDown() { - operations.indexOps(Entity.class).delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // #1667 @@ -109,7 +102,7 @@ public class EntityCustomConversionIntegrationTests { assertThat(foundEntity).isEqualTo(entity); } - @Document(indexName = "entity-with-custom-conversions") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class Entity { @Nullable private String value; @Nullable private GeoJsonPoint location; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyELCIntegrationTests.java new file mode 100644 index 000000000..6099cc893 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyELCIntegrationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.mapping; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.data.mapping.model.FieldNamingStrategy; +import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { FieldNamingStrategyELCIntegrationTests.Config.class }) +public class FieldNamingStrategyELCIntegrationTests extends FieldNamingStrategyIntegrationTests { + + @Configuration + static class Config extends ElasticsearchTemplateConfiguration { + + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("fieldnaming-strategy"); + } + + @Override + protected FieldNamingStrategy fieldNamingStrategy() { + return new SnakeCaseFieldNamingStrategy(); + } + + } + + @Override + protected Query nativeMatchQuery(String fieldName, String value) { + return NativeQuery.builder() // + .withQuery(q -> q.match(mq -> mq.field(fieldName).query(fv -> fv.stringValue(value)))).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyERHLCIntegrationTests.java similarity index 64% rename from src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyERHLCIntegrationTests.java index 886ce920c..5706a7b53 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyERHLCIntegrationTests.java @@ -15,12 +15,14 @@ */ package org.springframework.data.elasticsearch.core.mapping; -import org.elasticsearch.client.RestHighLevelClient; +import static org.elasticsearch.index.query.QueryBuilders.*; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; import org.springframework.test.context.ContextConfiguration; @@ -28,17 +30,15 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { FieldNamingStrategyRestTemplateIntegrationTests.Config.class }) -public class FieldNamingStrategyRestTemplateIntegrationTests extends FieldNamingStrategyIntegrationTests { +@ContextConfiguration(classes = { FieldNamingStrategyERHLCIntegrationTests.Config.class }) +public class FieldNamingStrategyERHLCIntegrationTests extends FieldNamingStrategyIntegrationTests { @Configuration static class Config extends ElasticsearchRestTemplateConfiguration { - @Override @Bean - public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter, - RestHighLevelClient elasticsearchClient) { - return super.elasticsearchOperations(elasticsearchConverter, elasticsearchClient); + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("fieldnaming-strategy-es7"); } @Override @@ -46,4 +46,9 @@ public class FieldNamingStrategyRestTemplateIntegrationTests extends FieldNaming return new SnakeCaseFieldNamingStrategy(); } } + + @Override + protected Query nativeMatchQuery(String fieldName, String value) { + return new NativeSearchQueryBuilder().withQuery(matchQuery(fieldName, value)).build(); + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTests.java index a7dc5558e..2aa644989 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTests.java @@ -16,22 +16,22 @@ package org.springframework.data.elasticsearch.core.mapping; import static org.assertj.core.api.Assertions.*; -import static org.elasticsearch.index.query.QueryBuilders.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; 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.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -41,13 +41,19 @@ import org.springframework.lang.Nullable; public abstract class FieldNamingStrategyIntegrationTests { @Autowired private ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach void setUp() { - IndexOperations indexOps = this.operations.indexOps(Entity.class); - indexOps.delete(); - indexOps.create(); - indexOps.putMapping(); + indexNameProvider.increment(); + IndexOperations indexOps = operations.indexOps(EntityCustomConversionIntegrationTests.Entity.class); + indexOps.createWithMapping(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // #1565 @@ -60,13 +66,15 @@ public abstract class FieldNamingStrategyIntegrationTests { operations.save(entity); // use a native query here to prevent automatic property name matching - Query query = new NativeSearchQueryBuilder().withQuery(matchQuery("some_text", "searched")).build(); + Query query = nativeMatchQuery("some_text", "searched"); SearchHits searchHits = operations.search(query, Entity.class); assertThat(searchHits.getTotalHits()).isEqualTo(1); } - @Document(indexName = "field-naming-strategy-test") + protected abstract Query nativeMatchQuery(String fieldName, String value); + + @Document(indexName = "#{@indexNameProvider.indexName()}") static class Entity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyELCIntegrationTests.java new file mode 100644 index 000000000..a9aa32ec7 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyELCIntegrationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.mapping; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.data.mapping.model.FieldNamingStrategy; +import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReactiveFieldNamingStrategyELCIntegrationTests.Config.class }) +public class ReactiveFieldNamingStrategyELCIntegrationTests extends ReactiveFieldNamingStrategyIntegrationTests { + + @Configuration + static class Config extends ReactiveElasticsearchTemplateConfiguration { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-fieldnaming-strategy"); + } + + @Override + protected FieldNamingStrategy fieldNamingStrategy() { + return new SnakeCaseFieldNamingStrategy(); + } + } + + @Override + protected Query nativeMatchQuery(String fieldName, String value) { + return NativeQuery.builder() // + .withQuery(q -> q.match(mq -> mq.field(fieldName).query(fv -> fv.stringValue(value)))).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyERHLCIntegrationTests.java new file mode 100644 index 000000000..953be18b4 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyERHLCIntegrationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.mapping; + +import static org.elasticsearch.index.query.QueryBuilders.*; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.data.mapping.model.FieldNamingStrategy; +import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReactiveFieldNamingStrategyERHLCIntegrationTests.Config.class }) +public class ReactiveFieldNamingStrategyERHLCIntegrationTests extends ReactiveFieldNamingStrategyIntegrationTests { + @Configuration + static class Config extends ReactiveElasticsearchRestTemplateConfiguration { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-fieldnaming-strategy-es7"); + } + + @Override + protected FieldNamingStrategy fieldNamingStrategy() { + return new SnakeCaseFieldNamingStrategy(); + } + } + + @Override + protected Query nativeMatchQuery(String fieldName, String value) { + return new NativeSearchQueryBuilder().withQuery(matchQuery(fieldName, value)).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyReactiveTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyIntegrationTests.java similarity index 69% rename from src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyReactiveTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyIntegrationTests.java index 4fa1619e7..ef3a1c3ca 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyReactiveTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/ReactiveFieldNamingStrategyIntegrationTests.java @@ -15,55 +15,45 @@ */ package org.springframework.data.elasticsearch.core.mapping; -import static org.elasticsearch.index.query.QueryBuilders.*; - import reactor.test.StepVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; import org.springframework.data.annotation.Id; 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.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; import org.springframework.data.elasticsearch.core.query.Query; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.data.mapping.model.FieldNamingStrategy; -import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ @SpringIntegrationTest -@ContextConfiguration(classes = { FieldNamingStrategyReactiveTemplateIntegrationTests.Config.class }) -public class FieldNamingStrategyReactiveTemplateIntegrationTests { +public abstract class ReactiveFieldNamingStrategyIntegrationTests { @Autowired private ReactiveElasticsearchOperations operations; - - @Configuration - static class Config extends ReactiveElasticsearchRestTemplateConfiguration { - - @Override - protected FieldNamingStrategy fieldNamingStrategy() { - return new SnakeCaseFieldNamingStrategy(); - } - } + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach void setUp() { + indexNameProvider.increment(); ReactiveIndexOperations indexOps = this.operations.indexOps(Entity.class); indexOps.delete() // - .then(indexOps.create()) // - .then(indexOps.putMapping()) // - .block(); + .then(indexOps.createWithMapping()).block(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); } @Test // #1565 @@ -76,14 +66,16 @@ public class FieldNamingStrategyReactiveTemplateIntegrationTests { operations.save(entity).block(); // use a native query here to prevent automatic property name matching - Query query = new NativeSearchQueryBuilder().withQuery(matchQuery("some_text", "searched")).build(); + Query query = nativeMatchQuery("some_text", "searched"); operations.search(query, Entity.class) // .as(StepVerifier::create) // .expectNextCount(1) // .verifyComplete(); } - @Document(indexName = "field-naming-strategy-test") + abstract protected Query nativeMatchQuery(String fieldName, String value); + + @Document(indexName = "#{@indexNameProvider.indexName()}") static class Entity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterELCIntegrationTests.java new file mode 100644 index 000000000..782c584cb --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.paginating; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveSearchAfterELCIntegrationTests.Config.class }) +public class ReactiveSearchAfterELCIntegrationTests extends ReactiveSearchAfterIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-search-after"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterERHLCIntegrationTests.java new file mode 100644 index 000000000..75f20151a --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterERHLCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.paginating; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveSearchAfterERHLCIntegrationTests.Config.class }) +public class ReactiveSearchAfterERHLCIntegrationTests extends ReactiveSearchAfterIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-search-after-es7"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java index be8fefdde..9513cae91 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java @@ -24,7 +24,9 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -35,20 +37,33 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.Query; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ @SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) -public class ReactiveSearchAfterIntegrationTests { +public abstract class ReactiveSearchAfterIntegrationTests { @Autowired private ReactiveElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; + + @BeforeEach + public void beforeEach() { + + indexNameProvider.increment(); + operations.indexOps(Entity.class).createWithMapping().block(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); + } @Test // #1143 @DisplayName("should read pages with search_after") @@ -84,7 +99,7 @@ public class ReactiveSearchAfterIntegrationTests { assertThat(foundEntities).containsExactlyElementsOf(entities); } - @Document(indexName = "test-search-after") + @Document(indexName = "#{@indexNameProvider.indexName()}") private static class Entity { @Nullable @Id private Long id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterELCIntegrationTests.java new file mode 100644 index 000000000..e471cd0ca --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterELCIntegrationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.paginating; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { SearchAfterELCIntegrationTests.Config.class }) +public class SearchAfterELCIntegrationTests extends SearchAfterIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("search-after"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterERHLCIntegrationTests.java new file mode 100644 index 000000000..6712f0d84 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterERHLCIntegrationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.paginating; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { SearchAfterERHLCIntegrationTests.Config.class }) +public class SearchAfterERHLCIntegrationTests extends SearchAfterIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("search-after-es7"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java index 60d1fa63d..3755730c9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java @@ -22,7 +22,9 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -33,8 +35,10 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -44,6 +48,20 @@ import org.springframework.lang.Nullable; public abstract class SearchAfterIntegrationTests { @Autowired private ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; + + @BeforeEach + public void before() { + + indexNameProvider.increment(); + operations.indexOps(Entity.class).createWithMapping(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); + } @Test // #1143 @DisplayName("should read pages with search_after") @@ -79,7 +97,7 @@ public abstract class SearchAfterIntegrationTests { assertThat(foundEntities).containsExactlyElementsOf(entities); } - @Document(indexName = "test-search-after") + @Document(indexName = "#{@indexNameProvider.indexName()}") private static class Entity { @Nullable @Id private Long id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryELCIntegrationTests.java new file mode 100644 index 000000000..62e40cee3 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryELCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.query; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { CriteriaQueryELCIntegrationTests.Config.class }) +public class CriteriaQueryELCIntegrationTests extends CriteriaQueryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("criteria"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryERHLCIntegrationTests.java similarity index 87% rename from src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryERHLCIntegrationTests.java index 1ce806d50..448f5aa4b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryERHLCIntegrationTests.java @@ -25,8 +25,8 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { CriteriaQueryRestTemplateIntegrationTests.Config.class }) -public class CriteriaQueryRestTemplateIntegrationTests extends CriteriaQueryIntegrationTests { +@ContextConfiguration(classes = { CriteriaQueryERHLCIntegrationTests.Config.class }) +public class CriteriaQueryERHLCIntegrationTests extends CriteriaQueryIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java index 5a306b83d..9cb46a727 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java @@ -78,10 +78,10 @@ public abstract class CriteriaQueryIntegrationTests { CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").contains("test").and("message").contains("some")); - SearchHit searchHit = operations.searchOne(criteriaQuery, SampleEntity.class); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - assertThat(searchHit).isNotNull(); - assertThat(searchHit.getId()).isEqualTo(sampleEntity1.id); + assertThat(searchHits.getTotalHits()).isEqualTo(1); + assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(sampleEntity1.id); } @Test // DATAES-706 @@ -170,8 +170,9 @@ public abstract class CriteriaQueryIntegrationTests { SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); // then - assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); + SearchHit searchHit = searchHits.getSearchHit(0); + assertThat(searchHit.getId()).isEqualTo(searchHit.getId()); } @Test @@ -651,40 +652,21 @@ public abstract class CriteriaQueryIntegrationTests { @Test public void shouldPerformBoostOperation() { - List indexQueries = new ArrayList<>(); - // first document String documentId = nextIdAsString(); - SampleEntity sampleEntity1 = new SampleEntity(); - sampleEntity1.setId(documentId); - sampleEntity1.setRate(700); - sampleEntity1.setMessage("bar foo"); - sampleEntity1.setVersion(System.currentTimeMillis()); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setRate(700); + sampleEntity.setMessage("foo"); + sampleEntity.setVersion(System.currentTimeMillis()); - IndexQuery indexQuery1 = new IndexQuery(); - indexQuery1.setId(documentId); - indexQuery1.setObject(sampleEntity1); - indexQueries.add(indexQuery1); + operations.save(sampleEntity); - // second document - String documentId2 = nextIdAsString(); - SampleEntity sampleEntity2 = new SampleEntity(); - sampleEntity2.setId(documentId2); - sampleEntity2.setRate(800); - sampleEntity2.setMessage("foo"); - sampleEntity2.setVersion(System.currentTimeMillis()); - - IndexQuery indexQuery2 = new IndexQuery(); - indexQuery2.setId(documentId2); - indexQuery2.setObject(sampleEntity2); - indexQueries.add(indexQuery2); - - operations.bulkIndex(indexQueries, SampleEntity.class); - - CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").contains("foo").boost(1)); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").contains("foo").boost(2)); SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); + assertThat(searchHits.getTotalHits()).isEqualTo(1); + assertThat(searchHits.getSearchHit(0).getScore()).isEqualTo(2.0f); } @Test diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchPartQueryTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java similarity index 65% rename from src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchPartQueryTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java index 7944f4931..cebba38e2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchPartQueryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/ElasticsearchPartQueryIntegrationTests.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core; +package org.springframework.data.elasticsearch.core.query; -import static org.mockito.Mockito.*; import static org.skyscreamer.jsonassert.JSONAssert.*; import java.lang.reflect.Method; @@ -23,21 +22,15 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.elasticsearch.search.builder.SearchSourceBuilder; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; -import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; -import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; -import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery; import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod; @@ -47,26 +40,22 @@ import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.lang.Nullable; /** - * Tests for {@link ElasticsearchPartQuery}. Resides in the core package, as we need an instance of the - * {@link RequestFactory} class for the tests. The tests make sure that queries are built according to the method - * naming. + * Tests for {@link ElasticsearchPartQuery}. The tests make sure that queries are built according to the method naming. + * Classes implementing this abstract class are in the packages of their request factories and converters as these are + * kept package private. * * @author Peter-Josef Meisch */ -@ExtendWith(MockitoExtension.class) -class ElasticsearchPartQueryTests { +@SpringIntegrationTest +public abstract class ElasticsearchPartQueryIntegrationTests { public static final String BOOK_TITLE = "Title"; public static final int BOOK_PRICE = 42; - private @Mock ElasticsearchOperations operations; - private ElasticsearchConverter converter; + @Autowired protected ElasticsearchOperations operations; @BeforeEach - public void setUp() { - converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()); - when(operations.getElasticsearchConverter()).thenReturn(converter); - } + public void setUp() {} @Test void findByName() throws NoSuchMethodException, JSONException { @@ -74,12 +63,12 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class }; Object[] parameters = new Object[] { BOOK_TITLE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // - " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name\"]}}" + // " ]" + // " }" + // "}}"; // @@ -93,12 +82,12 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class }; Object[] parameters = new Object[] { BOOK_TITLE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must_not\" : [" + // - " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name\"]}}" + // " ]" + // " }" + // "}}"; // @@ -112,13 +101,13 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class }; Object[] parameters = new Object[] { BOOK_TITLE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE - + "*\", \"fields\" : [\"name^1.0\"], \"analyze_wildcard\": true }}" + // + + "*\", \"fields\" : [\"name\"], \"analyze_wildcard\": true }}" + // " ]" + // " }" + // "}}"; // @@ -132,13 +121,13 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class }; Object[] parameters = new Object[] { BOOK_TITLE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE - + "*\", \"fields\" : [\"name^1.0\"], \"analyze_wildcard\": true }}" + // + + "*\", \"fields\" : [\"name\"], \"analyze_wildcard\": true }}" + // " ]" + // " }" + // "}}"; // @@ -152,13 +141,13 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class }; Object[] parameters = new Object[] { BOOK_TITLE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"query_string\" : {\"query\" : \"*" + BOOK_TITLE - + "\", \"fields\" : [\"name^1.0\"], \"analyze_wildcard\": true }}" + // + + "\", \"fields\" : [\"name\"], \"analyze_wildcard\": true }}" + // " ]" + // " }" + // "}}"; // @@ -172,13 +161,13 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class }; Object[] parameters = new Object[] { BOOK_TITLE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"query_string\" : {\"query\" : \"*" + BOOK_TITLE - + "*\", \"fields\" : [\"name^1.0\"], \"analyze_wildcard\": true }}" + // + + "*\", \"fields\" : [\"name\"], \"analyze_wildcard\": true }}" + // " ]" + // " }" + // "}}"; // @@ -195,7 +184,7 @@ class ElasticsearchPartQueryTests { names.add(BOOK_TITLE + "2"); Object[] parameters = new Object[] { names }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\n" + // " \"query\": {\n" + // @@ -205,7 +194,7 @@ class ElasticsearchPartQueryTests { " \"query_string\": {\n" + // " \"query\": \"\\\"Title\\\" \\\"Title2\\\"\",\n" + // " \"fields\": [\n" + // - " \"name^1.0\"\n" + // + " \"name\"\n" + // " ]\n" + // " }\n" + // " }\n" + // @@ -226,7 +215,7 @@ class ElasticsearchPartQueryTests { names.add(BOOK_TITLE + "2"); Object[] parameters = new Object[] { names }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\n" + // " \"query\": {\n" + // @@ -236,7 +225,7 @@ class ElasticsearchPartQueryTests { " \"query_string\": {\n" + // " \"query\": \"NOT(\\\"Title\\\" \\\"Title2\\\")\",\n" + // " \"fields\": [\n" + // - " \"name^1.0\"\n" + // + " \"name\"\n" + // " ]\n" + // " }\n" + // " }\n" + // @@ -254,12 +243,12 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class }; Object[] parameters = new Object[] { 42 }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // - " {\"query_string\" : {\"query\" : \"" + BOOK_PRICE + "\", \"fields\" : [\"price^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"" + BOOK_PRICE + "\", \"fields\" : [\"price\"]}}" + // " ]" + // " }" + // "}}"; // @@ -273,13 +262,13 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class, Integer.class }; Object[] parameters = new Object[] { BOOK_TITLE, BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // - " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name^1.0\"]}}," + // - " {\"query_string\" : {\"query\" : \"" + BOOK_PRICE + "\", \"fields\" : [\"price^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name\"]}}," + // + " {\"query_string\" : {\"query\" : \"" + BOOK_PRICE + "\", \"fields\" : [\"price\"]}}" + // " ]" + // " }" + // "}}"; // @@ -293,13 +282,13 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { String.class, Integer.class }; Object[] parameters = new Object[] { BOOK_TITLE, BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"should\" : [" + // - " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name^1.0\"]}}," + // - " {\"query_string\" : {\"query\" : \"" + BOOK_PRICE + "\", \"fields\" : [\"price^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"" + BOOK_TITLE + "\", \"fields\" : [\"name\"]}}," + // + " {\"query_string\" : {\"query\" : \"" + BOOK_PRICE + "\", \"fields\" : [\"price\"]}}" + // " ]" + // " }" + // "}}"; // @@ -313,9 +302,9 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class, Integer.class }; Object[] parameters = new Object[] { BOOK_PRICE - 10, BOOK_PRICE + 10 }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); - String expected = "{\"query\": {" + // + String expectedERHLC = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"range\" : {\"price\" : {\"from\" : 32, \"to\" : 52, \"include_lower\" : true, \"include_upper\" : true } } }" @@ -324,7 +313,27 @@ class ElasticsearchPartQueryTests { " }" + // "}}"; // - assertEquals(expected, query, false); + String expectedELC = "{\n" + // + " \"query\": {\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"range\": {\n" + // + " \"price\": {\n" + // + " \"gte\": 32,\n" + // + " \"lte\": 52\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + assertAny( // + () -> assertEquals(expectedERHLC, query, false) // + , () -> assertEquals(expectedELC, query, false) // + ); } @Test @@ -333,9 +342,9 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class }; Object[] parameters = new Object[] { BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); - String expected = "{\"query\": {" + // + String expectedERHLC = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"range\" : {\"price\" : {\"from\" : null, \"to\" : 42, \"include_lower\" : true, \"include_upper\" : false } } }" @@ -344,7 +353,26 @@ class ElasticsearchPartQueryTests { " }" + // "}}"; // - assertEquals(expected, query, false); + String expectedELC = "{\n" + // + " \"query\": {\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"range\": {\n" + // + " \"price\": {\n" + // + " \"lt\": 42\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + "}"; // + + assertAny( // + () -> assertEquals(expectedERHLC, query, false) // + , () -> assertEquals(expectedELC, query, false) // + ); } @Test @@ -353,9 +381,9 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class }; Object[] parameters = new Object[] { BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); - String expected = "{\"query\": {" + // + String expectedERHLC = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"range\" : {\"price\" : {\"from\" : null, \"to\" : 42, \"include_lower\" : true, \"include_upper\" : true } } }" @@ -364,7 +392,26 @@ class ElasticsearchPartQueryTests { " }" + // "}}"; // - assertEquals(expected, query, false); + String expectedELC = "{\n" + // + " \"query\": {\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"range\": {\n" + // + " \"price\": {\n" + // + " \"lte\": 42\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + "}"; // + + assertAny( // + () -> assertEquals(expectedERHLC, query, false) // + , () -> assertEquals(expectedELC, query, false) // + ); } @Test @@ -373,9 +420,9 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class }; Object[] parameters = new Object[] { BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); - String expected = "{\"query\": {" + // + String expectedERHLC = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"range\" : {\"price\" : {\"from\" : 42, \"to\" : null, \"include_lower\" : false, \"include_upper\" : true } } }" @@ -384,7 +431,26 @@ class ElasticsearchPartQueryTests { " }" + // "}}"; // - assertEquals(expected, query, false); + String expectedELC = "{\n" + // + " \"query\": {\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"range\": {\n" + // + " \"price\": {\n" + // + " \"gt\": 42\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + "}"; // + + assertAny( // + () -> assertEquals(expectedERHLC, query, false) // + , () -> assertEquals(expectedELC, query, false) // + ); } @Test @@ -393,9 +459,9 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class }; Object[] parameters = new Object[] { BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); - String expected = "{\"query\": {" + // + String expectedERHLC = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"range\" : {\"price\" : {\"from\" : 42, \"to\" : null, \"include_lower\" : true, \"include_upper\" : true } } }" @@ -404,7 +470,26 @@ class ElasticsearchPartQueryTests { " }" + // "}}"; // - assertEquals(expected, query, false); + String expectedELC = "{\n" + // + " \"query\": {\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"range\": {\n" + // + " \"price\": {\n" + // + " \"gte\": 42\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + "}"; // + + assertAny( // + () -> assertEquals(expectedERHLC, query, false) // + , () -> assertEquals(expectedELC, query, false) // + ); } @Test @@ -413,9 +498,9 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class }; Object[] parameters = new Object[] { BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); - String expected = "{\"query\": {" + // + String expectedERHLC = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"range\" : {\"price\" : {\"from\" : null, \"to\" : 42, \"include_lower\" : true, \"include_upper\" : true } } }" @@ -424,7 +509,26 @@ class ElasticsearchPartQueryTests { " }" + // "}}"; // - assertEquals(expected, query, false); + String expectedELC = "{\n" + // + " \"query\": {\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"range\": {\n" + // + " \"price\": {\n" + // + " \"lte\": 42\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + "}"; // + + assertAny( // + () -> assertEquals(expectedERHLC, query, false) // + , () -> assertEquals(expectedELC, query, false) // + ); } @Test @@ -433,9 +537,9 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] { Integer.class }; Object[] parameters = new Object[] { BOOK_PRICE }; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); - String expected = "{\"query\": {" + // + String expectedERHLC = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // " {\"range\" : {\"price\" : {\"from\" : 42, \"to\" : null, \"include_lower\" : true, \"include_upper\" : true } } }" @@ -444,7 +548,26 @@ class ElasticsearchPartQueryTests { " }" + // "}}"; // - assertEquals(expected, query, false); + String expectedELC = "{\n" + // + " \"query\": {\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"range\": {\n" + // + " \"price\": {\n" + // + " \"gte\": 42\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + "}"; // + + assertAny( // + () -> assertEquals(expectedERHLC, query, false) // + , () -> assertEquals(expectedELC, query, false) // + ); } @Test @@ -453,12 +576,12 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] {}; Object[] parameters = new Object[] {}; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // - " {\"query_string\" : {\"query\" : \"true\", \"fields\" : [\"available^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"true\", \"fields\" : [\"available\"]}}" + // " ]" + // " }" + // "}}"; // @@ -472,12 +595,12 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] {}; Object[] parameters = new Object[] {}; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // - " {\"query_string\" : {\"query\" : \"false\", \"fields\" : [\"available^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"false\", \"fields\" : [\"available\"]}}" + // " ]" + // " }" + // "}}"; // @@ -491,12 +614,12 @@ class ElasticsearchPartQueryTests { Class[] parameterClasses = new Class[] {}; Object[] parameters = new Object[] {}; - String query = getQueryBuilder(methodName, parameterClasses, parameters); + String query = getQueryString(methodName, parameterClasses, parameters); String expected = "{\"query\": {" + // " \"bool\" : {" + // " \"must\" : [" + // - " {\"query_string\" : {\"query\" : \"true\", \"fields\" : [\"available^1.0\"]}}" + // + " {\"query_string\" : {\"query\" : \"true\", \"fields\" : [\"available\"]}}" + // " ]" + // " }" + // "}," + // @@ -506,19 +629,48 @@ class ElasticsearchPartQueryTests { assertEquals(expected, query, false); } - private String getQueryBuilder(String methodName, Class[] parameterClasses, Object[] parameters) + private String getQueryString(String methodName, Class[] parameterClasses, Object[] parameters) throws NoSuchMethodException { Method method = SampleRepository.class.getMethod(methodName, parameterClasses); ElasticsearchQueryMethod queryMethod = new ElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), new SpelAwareProxyProjectionFactory(), - converter.getMappingContext()); + operations.getElasticsearchConverter().getMappingContext()); ElasticsearchPartQuery partQuery = new ElasticsearchPartQuery(queryMethod, operations); CriteriaQuery criteriaQuery = partQuery .createQuery(new ParametersParameterAccessor(queryMethod.getParameters(), parameters)); - SearchSourceBuilder source = new RequestFactory(converter) - .searchRequest(criteriaQuery, Book.class, IndexCoordinates.of("dummy")).source(); - return source.toString(); + return buildQueryString(criteriaQuery, Book.class); + } + + /** + * builds the query String that would be sent to Elasticsearch + * + * @param criteriaQuery the {@link CriteriaQuery} + * @param clazz the entity class + * @return the created query string + */ + abstract protected String buildQueryString(CriteriaQuery criteriaQuery, Class clazz); + + @FunctionalInterface + interface AssertFunction { + void get() throws JSONException; + } + + private void assertAny(AssertFunction... funcs) { + + int failed = 0; + + for (AssertFunction func : funcs) { + try { + func.get(); + } catch (AssertionError | JSONException ignored) { + failed++; + } + } + + if (failed == funcs.length) { + throw new AssertionError("none of the assertions held"); + } } private interface SampleRepository extends ElasticsearchRepository { @@ -566,7 +718,7 @@ class ElasticsearchPartQueryTests { } - static class Book { + public static class Book { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingELCIntegrationTests.java new file mode 100644 index 000000000..549d4a17b --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingELCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.routing; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveRoutingELCIntegrationTests.Config.class }) +public class ReactiveRoutingELCIntegrationTests extends ReactiveRoutingTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-routing"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingERHLCIntegrationTests.java new file mode 100644 index 000000000..6f642cb19 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingERHLCIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.routing; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ReactiveRoutingERHLCIntegrationTests.Config.class }) +public class ReactiveRoutingERHLCIntegrationTests extends ReactiveRoutingTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-routing-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingTests.java similarity index 83% rename from src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingTests.java index 11e60d53a..a2f028d53 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveRoutingTests.java @@ -23,6 +23,7 @@ import java.util.function.Function; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -30,29 +31,25 @@ import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Routing; import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; -import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.Query; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ @SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) -public class ReactiveElasticsearchOperationsRoutingTests { +public abstract class ReactiveRoutingTests { - private static final String INDEX = "routing-test"; private static final String ID_1 = "id1"; private static final String ID_2 = "id2"; private static final String ID_3 = "id3"; @Autowired ReactiveElasticsearchOperations operations; - @Nullable private ReactiveIndexOperations indexOps; + @Autowired private IndexNameProvider indexNameProvider; @BeforeAll static void beforeAll() { @@ -72,8 +69,14 @@ public class ReactiveElasticsearchOperationsRoutingTests { @BeforeEach void setUp() { - indexOps = operations.indexOps(RoutingEntity.class); - indexOps.delete().then(indexOps.create()).then(indexOps.putMapping()).block(); + indexNameProvider.increment(); + operations.indexOps(RoutingEntity.class).createWithMapping().block(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); } @Test // #1218 @@ -81,7 +84,7 @@ public class ReactiveElasticsearchOperationsRoutingTests { void shouldStoreDataWithDifferentRoutingAndBeAbleToGetIt() { RoutingEntity entity = new RoutingEntity(ID_1, ID_2); - operations.save(entity).then(indexOps.refresh()).block(); + operations.save(entity).block(); RoutingEntity savedEntity = operations.withRouting(RoutingResolver.just(ID_2)).get(entity.id, RoutingEntity.class) .block(); @@ -94,10 +97,10 @@ public class ReactiveElasticsearchOperationsRoutingTests { void shouldStoreDataWithDifferentRoutingAndBeAbleToDeleteIt() { RoutingEntity entity = new RoutingEntity(ID_1, ID_2); - operations.save(entity).then(indexOps.refresh()).block(); + operations.save(entity).block(); - String deletedId = operations.withRouting(RoutingResolver.just(ID_2)).delete(entity.id, IndexCoordinates.of(INDEX)) - .block(); + String deletedId = operations.withRouting(RoutingResolver.just(ID_2)) + .delete(entity.id, IndexCoordinates.of(indexNameProvider.indexName())).block(); assertThat(deletedId).isEqualTo(entity.getId()); } @@ -107,7 +110,7 @@ public class ReactiveElasticsearchOperationsRoutingTests { void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() { RoutingEntity entity = new RoutingEntity(ID_1, ID_2); - operations.save(entity).then(indexOps.refresh()).block(); + operations.save(entity).block(); List> searchHits = operations.search(Query.findAll(), RoutingEntity.class).collectList() .block(); @@ -116,11 +119,12 @@ public class ReactiveElasticsearchOperationsRoutingTests { assertThat(searchHits.get(0).getRouting()).isEqualTo(ID_2); } - @Document(indexName = INDEX) + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(shards = 5) @Routing("routing") static class RoutingEntity { - @Nullable @Id private String id; + @Nullable + @Id private String id; @Nullable private String routing; public RoutingEntity(@Nullable String id, @Nullable String routing) { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingELCIntegrationTests.java new file mode 100644 index 000000000..6571b90a9 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.routing; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { RoutingELCIntegrationTests.Config.class }) +public class RoutingELCIntegrationTests extends RoutingIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("routing"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingERHLCIntegrationTests.java new file mode 100644 index 000000000..652b1b785 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingERHLCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.routing; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { RoutingERHLCIntegrationTests.Config.class }) +public class RoutingERHLCIntegrationTests extends RoutingIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("routing-es7"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingIntegrationTests.java index 39b76cfdd..dbe5b8c69 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/RoutingIntegrationTests.java @@ -27,18 +27,23 @@ import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Routing; import org.springframework.data.elasticsearch.annotations.Setting; -import org.springframework.data.elasticsearch.core.*; +import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.MultiGetItem; +import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.BaseQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -49,14 +54,13 @@ import org.springframework.lang.Nullable; @SpringIntegrationTest public abstract class RoutingIntegrationTests { - private static final String INDEX = "routing-test"; private static final String ID_0 = "id0"; private static final String ID_1 = "id1"; private static final String ID_2 = "id2"; private static final String ID_3 = "id3"; @Autowired ElasticsearchOperations operations; - @Nullable private IndexOperations indexOps; + @Autowired private IndexNameProvider indexNameProvider; @BeforeAll static void beforeAll() { @@ -81,10 +85,14 @@ public abstract class RoutingIntegrationTests { @BeforeEach void setUp() { - indexOps = operations.indexOps(RoutingEntity.class); - indexOps.delete(); - indexOps.create(); - indexOps.putMapping(); + indexNameProvider.increment(); + operations.indexOps(RoutingEntity.class).createWithMapping(); + } + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // #1218 @@ -106,7 +114,8 @@ public abstract class RoutingIntegrationTests { RoutingEntity entity = new RoutingEntity(ID_1, ID_2); operations.save(entity); - String deletedId = operations.withRouting(RoutingResolver.just(ID_2)).delete(entity.id, IndexCoordinates.of(INDEX)); + String deletedId = operations.withRouting(RoutingResolver.just(ID_2)).delete(entity.id, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(deletedId).isEqualTo(entity.getId()); } @@ -157,24 +166,25 @@ public abstract class RoutingIntegrationTests { @Test void shouldCreateACopyOfTheClientWithRefreshPolicy() { - //given + // given AbstractElasticsearchTemplate sourceTemplate = (AbstractElasticsearchTemplate) operations; SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext(); DefaultRoutingResolver defaultRoutingResolver = new DefaultRoutingResolver(mappingContext); - //when + // when ElasticsearchOperations operationsCopy = this.operations.withRouting(defaultRoutingResolver); AbstractElasticsearchTemplate copyTemplate = (AbstractElasticsearchTemplate) operationsCopy; - //then + // then assertThat(sourceTemplate.getRefreshPolicy()).isEqualTo(copyTemplate.getRefreshPolicy()); } - @Document(indexName = INDEX) + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(shards = 7) @Routing("routing") static class RoutingEntity { - @Nullable @Id private String id; + @Nullable + @Id private String id; @Nullable private String routing; public RoutingEntity(@Nullable String id, @Nullable String routing) { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionELCIntegrationTests.java new file mode 100644 index 000000000..00b675833 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionELCIntegrationTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.suggest; + +import co.elastic.clients.elasticsearch.core.search.FieldSuggester; +import co.elastic.clients.elasticsearch.core.search.SuggestFuzziness; +import co.elastic.clients.elasticsearch.core.search.Suggester; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { CompletionELCIntegrationTests.Config.class }) +public class CompletionELCIntegrationTests extends CompletionIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("completion"); + } + } + + @Override + public boolean usesNewElasticsearchClient() { + return true; + } + + @Override + protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) { + return NativeQuery.builder() // + .withSuggester(Suggester.of(s -> s // + .suggesters(suggestionName, FieldSuggester.of(fs -> fs // + .completion(cs -> cs // + .field(fieldName) // + .prefix(prefix) // + .fuzzy(SuggestFuzziness.of(f -> f // + // NOTE we currently need to set all these values to their default as the client code does not + // have them nullable + .fuzziness("AUTO") // + .minLength(3) // + .prefixLength(1) // + .transpositions(true) // + .unicodeAware(false))))// + ))) // + ).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionERHLCIntegrationTests.java new file mode 100644 index 000000000..515ba2846 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionERHLCIntegrationTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.suggest; + +import org.elasticsearch.common.unit.Fuzziness; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.search.suggest.SuggestBuilders; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { CompletionERHLCIntegrationTests.Config.class }) +public class CompletionERHLCIntegrationTests extends CompletionIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("completion-es7"); + } + } + + @Override + protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) { + return new NativeSearchQueryBuilder() // + .withSuggestBuilder(new SuggestBuilder() // + .addSuggestion(suggestionName, // + SuggestBuilders.completionSuggestion(fieldName) // + .prefix(prefix, Fuzziness.AUTO))) // + .build(); // + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionIntegrationTests.java index e4896c8a1..9f9f96c39 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionIntegrationTests.java @@ -20,26 +20,24 @@ import static org.assertj.core.api.Assertions.*; import java.util.ArrayList; import java.util.List; -import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.search.suggest.SuggestBuilder; -import org.elasticsearch.search.suggest.SuggestBuilders; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.NewElasticsearchClientDevelopment; import org.springframework.data.elasticsearch.annotations.CompletionField; import org.springframework.data.elasticsearch.annotations.Document; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion; import org.springframework.data.elasticsearch.core.suggest.response.Suggest; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -51,20 +49,22 @@ import org.springframework.lang.Nullable; * @author Peter-Josef Meisch */ @SpringIntegrationTest -public abstract class CompletionIntegrationTests { +public abstract class CompletionIntegrationTests implements NewElasticsearchClientDevelopment { @Autowired private ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach private void setup() { - IndexInitializer.init(operations.indexOps(CompletionEntity.class)); - IndexInitializer.init(operations.indexOps(AnnotatedCompletionEntity.class)); + indexNameProvider.increment(); + operations.indexOps(CompletionEntity.class).createWithMapping(); + operations.indexOps(AnnotatedCompletionEntity.class).createWithMapping(); } - @AfterEach - void after() { - operations.indexOps(CompletionEntity.class).delete(); - operations.indexOps(AnnotatedCompletionEntity.class).delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } private void loadCompletionObjectEntities() { @@ -79,9 +79,7 @@ public abstract class CompletionIntegrationTests { indexQueries.add(new CompletionEntityBuilder("4").name("Artur Konczak").suggest(new String[] { "Artur", "Konczak" }) .buildIndex()); - IndexCoordinates index = IndexCoordinates.of("test-index-core-completion"); - operations.bulkIndex(indexQueries, index); - operations.indexOps(CompletionEntity.class).refresh(); + operations.bulkIndex(indexQueries, CompletionEntity.class); } private void loadAnnotatedCompletionObjectEntities() { @@ -100,8 +98,7 @@ public abstract class CompletionIntegrationTests { indexQueries.add(new AnnotatedCompletionEntityBuilder("4").name("Artur Konczak") .suggest(new String[] { "Artur", "Konczak" }).buildIndex()); - operations.bulkIndex(indexQueries, IndexCoordinates.of("test-index-annotated-completion")); - operations.indexOps(AnnotatedCompletionEntity.class).refresh(); + operations.bulkIndex(indexQueries, AnnotatedCompletionEntity.class); } private void loadAnnotatedCompletionObjectEntitiesWithWeights() { @@ -116,17 +113,15 @@ public abstract class CompletionIntegrationTests { indexQueries.add(new AnnotatedCompletionEntityBuilder("4").name("Mewes Kochheim4") .suggest(new String[] { "Mewes Kochheim4" }, Integer.MAX_VALUE).buildIndex()); - operations.bulkIndex(indexQueries, IndexCoordinates.of("test-index-annotated-completion")); - operations.indexOps(AnnotatedCompletionEntity.class).refresh(); + operations.bulkIndex(indexQueries, AnnotatedCompletionEntity.class); } + @DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150 @Test public void shouldFindSuggestionsForGivenCriteriaQueryUsingCompletionEntity() { loadCompletionObjectEntities(); - NativeSearchQuery query = new NativeSearchQueryBuilder().withSuggestBuilder(new SuggestBuilder() - .addSuggestion("test-suggest", SuggestBuilders.completionSuggestion("suggest").prefix("m", Fuzziness.AUTO))) - .build(); + Query query = getSuggestQuery("test-suggest", "suggest", "m"); SearchHits searchHits = operations.search(query, CompletionEntity.class); @@ -145,20 +140,20 @@ public abstract class CompletionIntegrationTests { assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin"); } + protected abstract Query getSuggestQuery(String suggestionName, String fieldName, String prefix); + @Test // DATAES-754 void shouldRetrieveEntityWithCompletion() { loadCompletionObjectEntities(); - IndexCoordinates index = IndexCoordinates.of("test-index-core-completion"); - operations.get("1", CompletionEntity.class, index); + operations.get("1", CompletionEntity.class); } + @DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150 @Test public void shouldFindSuggestionsForGivenCriteriaQueryUsingAnnotatedCompletionEntity() { loadAnnotatedCompletionObjectEntities(); - NativeSearchQuery query = new NativeSearchQueryBuilder().withSuggestBuilder(new SuggestBuilder() - .addSuggestion("test-suggest", SuggestBuilders.completionSuggestion("suggest").prefix("m", Fuzziness.AUTO))) - .build(); + Query query = getSuggestQuery("test-suggest", "suggest", "m"); SearchHits searchHits = operations.search(query, AnnotatedCompletionEntity.class); @@ -177,13 +172,12 @@ public abstract class CompletionIntegrationTests { assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin"); } + @DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150 @Test public void shouldFindSuggestionsWithWeightsForGivenCriteriaQueryUsingAnnotatedCompletionEntity() { loadAnnotatedCompletionObjectEntitiesWithWeights(); - NativeSearchQuery query = new NativeSearchQueryBuilder().withSuggestBuilder(new SuggestBuilder() - .addSuggestion("test-suggest", SuggestBuilders.completionSuggestion("suggest").prefix("m", Fuzziness.AUTO))) - .build(); + Query query = getSuggestQuery("test-suggest", "suggest", "m"); SearchHits searchHits = operations.search(query, AnnotatedCompletionEntity.class); @@ -246,7 +240,7 @@ public abstract class CompletionIntegrationTests { } } - @Document(indexName = "test-index-core-completion") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class CompletionEntity { @Nullable @@ -328,10 +322,7 @@ public abstract class CompletionIntegrationTests { } } - /** - * @author Mewes Kochheim - */ - @Document(indexName = "test-index-annotated-completion") + @Document(indexName = "#{@indexNameProvider.indexName()}-annotated") static class AnnotatedCompletionEntity { @Nullable diff --git a/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionRestTemplateIntegrationTests.java deleted file mode 100644 index 9be6d955c..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/suggest/CompletionRestTemplateIntegrationTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.core.suggest; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { CompletionRestTemplateIntegrationTests.Config.class }) -public class CompletionRestTemplateIntegrationTests extends CompletionIntegrationTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestELCIntegrationTests.java new file mode 100644 index 000000000..b15dac6ea --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestELCIntegrationTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.suggest; + +import co.elastic.clients.elasticsearch.core.search.FieldSuggester; +import co.elastic.clients.elasticsearch.core.search.SuggestFuzziness; +import co.elastic.clients.elasticsearch.core.search.Suggester; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveSuggestELCIntegrationTests.Config.class }) +public class ReactiveSuggestELCIntegrationTests extends ReactiveSuggestIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-template-suggest"); + } + } + + @Override + public boolean usesNewElasticsearchClient() { + return true; + } + + @Override + protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) { + return NativeQuery.builder() // + .withSuggester(Suggester.of(s -> s // + .suggesters(suggestionName, FieldSuggester.of(fs -> fs // + .completion(cs -> cs // + .field(fieldName) // + .prefix(prefix) // + .fuzzy(SuggestFuzziness.of(f -> f // + // NOTE we currently need to set all these values to their default as the client code does not + // have them nullable + .fuzziness("AUTO") // + .minLength(3) // + .prefixLength(1) // + .transpositions(true) // + .unicodeAware(false))))// + ))) // + ).build(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestERHLCIntegrationTests.java new file mode 100644 index 000000000..a6c7c1d97 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestERHLCIntegrationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.suggest; + +import org.elasticsearch.common.unit.Fuzziness; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.search.suggest.SuggestBuilders; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveSuggestERHLCIntegrationTests.Config.class }) +public class ReactiveSuggestERHLCIntegrationTests extends ReactiveSuggestIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-template-suggest-es7"); + } + } + + @Override + protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) { + return new NativeSearchQueryBuilder() // + .withSuggestBuilder(new SuggestBuilder() // + .addSuggestion(suggestionName, // + SuggestBuilders.completionSuggestion(fieldName) // + .prefix(prefix, Fuzziness.AUTO))) // + .build(); // + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/suggest/SuggestReactiveTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestIntegrationTests.java similarity index 81% rename from src/test/java/org/springframework/data/elasticsearch/core/suggest/SuggestReactiveTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestIntegrationTests.java index e693ac773..3c4e03046 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/suggest/SuggestReactiveTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/suggest/ReactiveSuggestIntegrationTests.java @@ -24,27 +24,22 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.search.suggest.SuggestBuilder; -import org.elasticsearch.search.suggest.SuggestBuilders; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.NewElasticsearchClientDevelopment; import org.springframework.data.elasticsearch.annotations.CompletionField; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion; import org.springframework.data.elasticsearch.core.suggest.response.Suggest; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; @@ -52,21 +47,10 @@ import org.springframework.lang.Nullable; /** * @author Peter-Josef Meisch */ -@SuppressWarnings("SpringJavaAutowiredMembersInspection") @SpringIntegrationTest -public class SuggestReactiveTemplateIntegrationTests { - - @Configuration - @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) - static class Config { - @Bean - IndexNameProvider indexNameProvider() { - return new IndexNameProvider("reactive-template-suggest"); - } - } +public abstract class ReactiveSuggestIntegrationTests implements NewElasticsearchClientDevelopment { @Autowired private ReactiveElasticsearchOperations operations; - @Autowired private IndexNameProvider indexNameProvider; // region Setup @@ -77,13 +61,13 @@ public class SuggestReactiveTemplateIntegrationTests { operations.indexOps(CompletionEntity.class).createWithMapping().block(); } - @Test // #1302 - @DisplayName("should do some test") - void shouldDoSomeTest() { - - assertThat(operations).isNotNull(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block(); } + @DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150 @Test // #1302 @DisplayName("should find suggestions for given prefix completion") void shouldFindSuggestionsForGivenPrefixCompletion() { @@ -105,18 +89,12 @@ public class SuggestReactiveTemplateIntegrationTests { assertThat(options).hasSize(2); assertThat(options.get(0).getText()).isIn("Marchand", "Mohsin"); assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin"); + }) // .verifyComplete(); } - protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) { - return new NativeSearchQueryBuilder() // - .withSuggestBuilder(new SuggestBuilder() // - .addSuggestion(suggestionName, // - SuggestBuilders.completionSuggestion(fieldName) // - .prefix(prefix, Fuzziness.AUTO))) // - .build(); // - } + protected abstract Query getSuggestQuery(String suggestionName, String fieldName, String prefix); // region helper functions private Mono loadCompletionObjectEntities() { diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..99dd0da66 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryELCIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.immutable; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = ImmutableRepositoryELCIntegrationTests.Config.class) +public class ImmutableRepositoryELCIntegrationTests extends ImmutableRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.immutable" }, + considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("immutable"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryERHLCIntegrationTests.java new file mode 100644 index 000000000..daf085bd0 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryERHLCIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.immutable; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = ImmutableRepositoryERHLCIntegrationTests.Config.class) +public class ImmutableRepositoryERHLCIntegrationTests extends ImmutableRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.immutable" }, + considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("immutable-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryIntegrationTests.java similarity index 63% rename from src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java rename to src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryIntegrationTests.java index cf8cc3eaf..f1f714b17 100644 --- a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableRepositoryIntegrationTests.java @@ -19,21 +19,17 @@ import static org.assertj.core.api.Assertions.*; import java.util.Optional; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; 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.annotation.PersistenceCreator; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.repository.CrudRepository; -import org.springframework.test.context.ContextConfiguration; /** * @author Young Gu @@ -43,30 +39,22 @@ import org.springframework.test.context.ContextConfiguration; * @author Peter-Josef Meisch */ @SpringIntegrationTest -@ContextConfiguration(classes = { ImmutableElasticsearchRepositoryTests.Config.class }) -public class ImmutableElasticsearchRepositoryTests { - - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.immutable" }, - considerNestedRepositories = true) - static class Config {} +public abstract class ImmutableRepositoryIntegrationTests { @Autowired ImmutableElasticsearchRepository repository; @Autowired ElasticsearchOperations operations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - IndexOperations indexOperations = operations.indexOps(ImmutableEntity.class); - indexOperations.delete(); - indexOperations.create(); - indexOperations.refresh(); + indexNameProvider.increment(); + operations.indexOps(ImmutableEntity.class).createWithMapping(); } - @AfterEach - void tearDown() { - IndexOperations indexOperations = operations.indexOps(ImmutableEntity.class); - indexOperations.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // DATAES-281 @@ -88,15 +76,11 @@ public class ImmutableElasticsearchRepositoryTests { }); } - /** - * @author Young Gu - * @author Oliver Gierke - */ - @Document(indexName = "test-index-immutable") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class ImmutableEntity { private final String id, name; - @PersistenceConstructor + @PersistenceCreator public ImmutableEntity(String id, String name) { this.id = id; this.name = name; @@ -115,10 +99,6 @@ public class ImmutableElasticsearchRepositoryTests { } } - /** - * @author Young Gu - * @author Oliver Gierke - */ public interface ImmutableElasticsearchRepository extends CrudRepository {} } diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchTemplateConfiguration.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchTemplateConfiguration.java new file mode 100644 index 000000000..c3b8cf5e6 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchTemplateConfiguration.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.junit.jupiter; + +import static org.springframework.util.StringUtils.*; + +import java.time.Duration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.RefreshPolicy; + +/** + * Configuration for Spring Data Elasticsearch tests using an {@link ElasticsearchTemplate}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@Configuration +public class ElasticsearchTemplateConfiguration extends ElasticsearchConfiguration { + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + @Autowired private ClusterConnectionInfo clusterConnectionInfo; + + @Override + public ClientConfiguration clientConfiguration() { + String elasticsearchHostPort = clusterConnectionInfo.getHost() + ':' + clusterConnectionInfo.getHttpPort(); + + ClientConfiguration.TerminalClientConfigurationBuilder configurationBuilder = ClientConfiguration.builder() + .connectedTo(elasticsearchHostPort); + + String proxy = System.getenv("DATAES_ELASTICSEARCH_PROXY"); + + if (proxy != null) { + configurationBuilder = configurationBuilder.withProxy(proxy); + } + + if (clusterConnectionInfo.isUseSsl()) { + configurationBuilder = ((ClientConfiguration.MaybeSecureClientConfigurationBuilder) configurationBuilder) + .usingSsl(); + } + + String user = System.getenv("DATAES_ELASTICSEARCH_USER"); + String password = System.getenv("DATAES_ELASTICSEARCH_PASSWORD"); + + if (hasText(user) && hasText(password)) { + configurationBuilder.withBasicAuth(user, password); + } + + // noinspection UnnecessaryLocalVariable + ClientConfiguration clientConfiguration = configurationBuilder // + .withConnectTimeout(Duration.ofSeconds(20)) // + .withSocketTimeout(Duration.ofSeconds(20)) // + .build(); + + return clientConfiguration; + } + + @Override + protected RefreshPolicy refreshPolicy() { + return RefreshPolicy.IMMEDIATE; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchRestTemplateConfiguration.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchRestTemplateConfiguration.java index 10254e3ae..9248dfcfe 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchRestTemplateConfiguration.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchRestTemplateConfiguration.java @@ -28,14 +28,14 @@ import org.springframework.data.elasticsearch.config.AbstractReactiveElasticsear import org.springframework.data.elasticsearch.core.RefreshPolicy; /** - * Configuration for Spring Data Elasticsearch Integration Tests using - * {@link org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations} + * Configuration for Spring Data Elasticsearch Integration Tests using {@link ReactiveElasticsearchClient} * * @author Peter-Josef Meisch */ @Configuration public class ReactiveElasticsearchRestTemplateConfiguration extends AbstractReactiveElasticsearchConfiguration { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Autowired private ClusterConnectionInfo clusterConnectionInfo; @Override diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchTemplateConfiguration.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchTemplateConfiguration.java new file mode 100644 index 000000000..dca5a95ee --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ReactiveElasticsearchTemplateConfiguration.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.junit.jupiter; + +import static org.springframework.util.StringUtils.*; + +import java.time.Duration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration; +import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.RefreshPolicy; + +/** + * Configuration for Spring Data Elasticsearch tests using an {@link ReactiveElasticsearchTemplate}. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@Configuration +public class ReactiveElasticsearchTemplateConfiguration extends ReactiveElasticsearchConfiguration { + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + @Autowired private ClusterConnectionInfo clusterConnectionInfo; + + @Override + public ClientConfiguration clientConfiguration() { + String elasticsearchHostPort = clusterConnectionInfo.getHost() + ':' + clusterConnectionInfo.getHttpPort(); + + ClientConfiguration.TerminalClientConfigurationBuilder configurationBuilder = ClientConfiguration.builder() // + .connectedTo(elasticsearchHostPort); + + String proxy = System.getenv("DATAES_ELASTICSEARCH_PROXY"); + + if (proxy != null) { + configurationBuilder = configurationBuilder.withProxy(proxy); + } + if (clusterConnectionInfo.isUseSsl()) { + configurationBuilder = ((ClientConfiguration.MaybeSecureClientConfigurationBuilder) configurationBuilder) + .usingSsl(); + } + + String user = System.getenv("DATAES_ELASTICSEARCH_USER"); + String password = System.getenv("DATAES_ELASTICSEARCH_PASSWORD"); + + if (hasText(user) && hasText(password)) { + configurationBuilder.withBasicAuth(user, password); + } + + // noinspection UnnecessaryLocalVariable + ClientConfiguration clientConfiguration = configurationBuilder // + .withConnectTimeout(Duration.ofSeconds(20)) // + .withSocketTimeout(Duration.ofSeconds(20)) // + .build(); + + return clientConfiguration; + } + + @Override + protected RefreshPolicy refreshPolicy() { + return RefreshPolicy.IMMEDIATE; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java index 514b41704..7fbdfc81a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java @@ -37,6 +37,8 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.InnerField; import org.springframework.data.elasticsearch.annotations.MultiField; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.IntegrationTest; import org.springframework.lang.Nullable; @@ -49,9 +51,11 @@ import org.springframework.lang.Nullable; @IntegrationTest public class CdiRepositoryTests { + public static final String PERSON_INDEX = "test-index-person-cdi-repository"; + public static final String PRODUCT_INDEX = "test-index-product-cdi-repository"; + @Nullable private static SeContainer container; - // @Nullable private static CdiTestContainer cdiContainer; private CdiProductRepository repository; private SamplePersonRepository personRepository; private QualifiedProductRepository qualifiedProductRepository; @@ -69,6 +73,12 @@ public class CdiRepositoryTests { public static void shutdown() throws Exception { if (container != null) { + ElasticsearchOperations operations = container.select(ElasticsearchOperations.class).get(); + + if (operations != null) { + operations.indexOps(IndexCoordinates.of(PERSON_INDEX)).delete(); + operations.indexOps(IndexCoordinates.of(PRODUCT_INDEX)).delete(); + } container.close(); } } @@ -153,7 +163,7 @@ public class CdiRepositoryTests { assertThat(personRepository.returnOne()).isEqualTo(1); } - @Document(indexName = "test-index-product-cdi-repository") + @Document(indexName = PRODUCT_INDEX) static class Product { @Nullable @Id private String id; @@ -278,7 +288,7 @@ public class CdiRepositoryTests { } } - @Document(indexName = "test-index-person-cdi-repository") + @Document(indexName = PERSON_INDEX) static class Person { @Id private String id; @@ -291,7 +301,6 @@ public class CdiRepositoryTests { } - @Document(indexName = "test-index-book-cdi-repository") static class Book { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/ElasticsearchOperationsProducer.java b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/ElasticsearchOperationsProducer.java index e5e634bfa..772f4d145 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/ElasticsearchOperationsProducer.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/ElasticsearchOperationsProducer.java @@ -36,6 +36,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ClusterConnectionInf @ApplicationScoped class ElasticsearchOperationsProducer { + // todo #1973 switch to use the new client @Produces public ElasticsearchOperations createElasticsearchTemplate(RestHighLevelClient restHighLevelClient) { return new ElasticsearchRestTemplate(restHighLevelClient); diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..5bbf35fb9 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryELCIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.complex.custommethod.autowiring; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ComplexCustomMethodRepositoryELCIntegrationTests.Config.class }) +public class ComplexCustomMethodRepositoryELCIntegrationTests extends ComplexCustomMethodRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("complex-custom-method"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryERHLCIntegrationTests.java similarity index 86% rename from src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryERHLCIntegrationTests.java index 54090f850..388e7d609 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryERHLCIntegrationTests.java @@ -26,16 +26,17 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { ComplexCustomMethodRepositoryRestTemplateIntegrationTests.Config.class }) -public class ComplexCustomMethodRepositoryRestTemplateIntegrationTests +@ContextConfiguration(classes = { ComplexCustomMethodRepositoryERHLCIntegrationTests.Config.class }) +public class ComplexCustomMethodRepositoryERHLCIntegrationTests extends ComplexCustomMethodRepositoryIntegrationTests { + @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) static class Config { @Bean IndexNameProvider indexNameProvider() { - return new IndexNameProvider("complex-custom-method"); + return new IndexNameProvider("complex-custom-method-es7"); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringELCIntegrationTests.java new file mode 100644 index 000000000..188a7af0d --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringELCIntegrationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.complex.custommethod.manualwiring; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ComplexCustomMethodRepositoryManualWiringELCIntegrationTests.Config.class }) +public class ComplexCustomMethodRepositoryManualWiringELCIntegrationTests + extends ComplexCustomMethodRepositoryManualWiringIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("complex-custom-method-es7"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringERHLCIntegrationTests.java similarity index 89% rename from src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringERHLCIntegrationTests.java index 70200b836..56441ff69 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringERHLCIntegrationTests.java @@ -26,8 +26,8 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { ComplexCustomMethodRepositoryManualWiringRestTemplateIntegrationTests.Config.class }) -public class ComplexCustomMethodRepositoryManualWiringRestTemplateIntegrationTests +@ContextConfiguration(classes = { ComplexCustomMethodRepositoryManualWiringERHLCIntegrationTests.Config.class }) +public class ComplexCustomMethodRepositoryManualWiringERHLCIntegrationTests extends ComplexCustomMethodRepositoryManualWiringIntegrationTests { @Configuration @@ -36,7 +36,7 @@ public class ComplexCustomMethodRepositoryManualWiringRestTemplateIntegrationTes static class Config { @Bean IndexNameProvider indexNameProvider() { - return new IndexNameProvider("complex-custom-method"); + return new IndexNameProvider("complex-custom-method-es7"); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..68c51bd29 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryELCIntegrationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.custommethod; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { CustomMethodRepositoryELCIntegrationTests.Config.class }) +public class CustomMethodRepositoryELCIntegrationTests extends CustomMethodRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories( + basePackages = { "org.springframework.data.elasticsearch.repositories.custommethod" }, + considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("custom-method-repository"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryERHLCIntegrationTests.java similarity index 85% rename from src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryERHLCIntegrationTests.java index 2f22db45f..92051ec99 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryERHLCIntegrationTests.java @@ -28,8 +28,8 @@ import org.springframework.test.context.ContextConfiguration; * @author Mark Paluch * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { CustomMethodRepositoryRestTemplateIntegrationTests.Config.class }) -public class CustomMethodRepositoryRestTemplateIntegrationTests extends CustomMethodRepositoryIntegrationTests { +@ContextConfiguration(classes = { CustomMethodRepositoryERHLCIntegrationTests.Config.class }) +public class CustomMethodRepositoryERHLCIntegrationTests extends CustomMethodRepositoryIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @@ -39,7 +39,7 @@ public class CustomMethodRepositoryRestTemplateIntegrationTests extends CustomMe static class Config { @Bean IndexNameProvider indexNameProvider() { - return new IndexNameProvider("custom-method-repository"); + return new IndexNameProvider("custom-method-repository-es7"); } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..53459b94c --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryELCIntegrationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.doubleid; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { DoubleIDRepositoryELCIntegrationTests.Config.class }) +public class DoubleIDRepositoryELCIntegrationTests extends DoubleIDRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("doubleid-repository"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryERHLCIntegrationTests.java similarity index 84% rename from src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryERHLCIntegrationTests.java index ccf349e60..f5371421d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryERHLCIntegrationTests.java @@ -26,8 +26,8 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { DoubleIDRepositoryRestTemplateIntegrationTests.Config.class }) -public class DoubleIDRepositoryRestTemplateIntegrationTests extends DoubleIDRepositoryIntegrationTests { +@ContextConfiguration(classes = { DoubleIDRepositoryERHLCIntegrationTests.Config.class }) +public class DoubleIDRepositoryERHLCIntegrationTests extends DoubleIDRepositoryIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @@ -35,7 +35,7 @@ public class DoubleIDRepositoryRestTemplateIntegrationTests extends DoubleIDRepo static class Config { @Bean IndexNameProvider indexNameProvider() { - return new IndexNameProvider("doubleid-reository-es7"); + return new IndexNameProvider("doubleid-repository-es7"); } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityELCIntegrationTests.java similarity index 63% rename from src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityELCIntegrationTests.java index 46c1423f9..d02676358 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityELCIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,21 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.repositories.geo; +package org.springframework.data.elasticsearch.repositories.dynamicindex; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch + * @since 4.4 */ -@ContextConfiguration(classes = { GeoRepositoryRestTemplateIntegrationTests.Config.class }) -public class GeoRepositoryRestTemplateIntegrationTests extends GeoRepositoryIntegrationTests { +@ContextConfiguration(classes = { DynamicIndexEntityELCIntegrationTests.Config.class }) +public class DynamicIndexEntityELCIntegrationTests extends DynamicIndexEntityIntegrationTests { @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) + @Import({ ElasticsearchTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + public IndexNameProvider indexNameProvider() { + return new IndexNameProvider(); + } + } + } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityERHLCIntegrationTests.java similarity index 87% rename from src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityERHLCIntegrationTests.java index d9ee6c805..12f94b346 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityERHLCIntegrationTests.java @@ -25,9 +25,8 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { DynamicIndexEntityRestTemplateIntegrationTests.Config.class }) - -public class DynamicIndexEntityRestTemplateIntegrationTests extends DynamicIndexEntityIntegrationTests { +@ContextConfiguration(classes = { DynamicIndexEntityERHLCIntegrationTests.Config.class }) +public class DynamicIndexEntityERHLCIntegrationTests extends DynamicIndexEntityIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityIntegrationTests.java index a77261a84..01e625708 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/dynamicindex/DynamicIndexEntityIntegrationTests.java @@ -30,7 +30,9 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTes import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; /** - * DynamicIndexEntityIntegrationTests + * DynamicIndexEntityIntegration. Not: this does not use the normal + * {@link org.springframework.data.elasticsearch.utils.IndexNameProvider} b ut testes this functionality with a custom + * one. * * @author Sylvain Laurent * @author Peter-Josef Meisch diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..2e51763b6 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.geo; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { GeoRepositoryELCIntegrationTests.Config.class }) +public class GeoRepositoryELCIntegrationTests extends GeoRepositoryIntegrationTests { + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("doubleid-repository"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryERHLCIntegrationTests.java new file mode 100644 index 000000000..6bdf09035 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryERHLCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.geo; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { GeoRepositoryERHLCIntegrationTests.Config.class }) +public class GeoRepositoryERHLCIntegrationTests extends GeoRepositoryIntegrationTests { + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("geo-repository-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryIntegrationTests.java index d44405262..b1dd0655d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/GeoRepositoryIntegrationTests.java @@ -20,8 +20,8 @@ import static org.assertj.core.api.Assertions.*; import java.util.Locale; import java.util.Optional; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -30,9 +30,10 @@ import org.springframework.data.elasticsearch.annotations.GeoPointField; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.geo.Box; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Point; @@ -49,18 +50,20 @@ public abstract class GeoRepositoryIntegrationTests { @Autowired ElasticsearchOperations operations; private IndexOperations indexOperations; + @Autowired IndexNameProvider indexNameProvider; @Autowired SpringDataGeoRepository repository; @BeforeEach public void init() { - indexOperations = operations.indexOps(GeoEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(GeoEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + public void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -99,7 +102,7 @@ public abstract class GeoRepositoryIntegrationTests { return new double[] { point.getX(), point.getY() }; } - @Document(indexName = "test-index-geo-repository") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class GeoEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..8b3ea4fa1 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryELCIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.integer; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { IntegerIDRepositoryELCIntegrationTests.Config.class }) +public class IntegerIDRepositoryELCIntegrationTests extends IntegerIDRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("integerid-repository"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryERHLCIntegrationTests.java new file mode 100644 index 000000000..b8c76a6e6 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryERHLCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.integer; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { IntegerIDRepositoryERHLCIntegrationTests.Config.class }) +public class IntegerIDRepositoryERHLCIntegrationTests extends IntegerIDRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("integerid-repository-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryIntegrationTests.java index 070d4cd34..c55da1354 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryIntegrationTests.java @@ -21,18 +21,18 @@ import static org.springframework.data.elasticsearch.utils.IdGenerator.*; import java.util.Arrays; import java.util.Optional; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; /** * @author Rizwan Idrees @@ -47,18 +47,18 @@ public abstract class IntegerIDRepositoryIntegrationTests { @Autowired private IntegerIDRepository repository; @Autowired ElasticsearchOperations operations; - - private IndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(IntegerIDEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(IntegerIDEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + public void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -106,12 +106,7 @@ public abstract class IntegerIDRepositoryIntegrationTests { assertThat(entityFromElasticSearch).isPresent(); } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ - - @Document(indexName = "test-index-integer-keyed-entity") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class IntegerIDEntity { @Id private Integer id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryRestTemplateIntegrationTests.java deleted file mode 100644 index ca0085358..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryRestTemplateIntegrationTests.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.repositories.integer; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { IntegerIDRepositoryRestTemplateIntegrationTests.Config.class }) -public class IntegerIDRepositoryRestTemplateIntegrationTests extends IntegerIDRepositoryIntegrationTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} - -} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectELCIntegrationTests.java new file mode 100644 index 000000000..8e7439f2b --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectELCIntegrationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright + * 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.nestedobject; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { InnerObjectELCIntegrationTests.Config.class }) +public class InnerObjectELCIntegrationTests extends InnerObjectIntegrationTests { + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("inner-object"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectERHLCIntegrationTests.java new file mode 100644 index 000000000..cae8c3794 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectERHLCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.nestedobject; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { InnerObjectERHLCIntegrationTests.Config.class }) +public class InnerObjectERHLCIntegrationTests extends InnerObjectIntegrationTests { + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("inner-object-es7"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectIntegrationTests.java index fe3477742..13abceea9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectIntegrationTests.java @@ -23,8 +23,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -34,10 +34,10 @@ import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.InnerField; import org.springframework.data.elasticsearch.annotations.MultiField; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -51,18 +51,18 @@ public abstract class InnerObjectIntegrationTests { @Autowired private SampleElasticSearchBookRepository bookRepository; @Autowired ElasticsearchOperations operations; - - private IndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(Book.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(Book.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + public void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -85,7 +85,7 @@ public abstract class InnerObjectIntegrationTests { assertThat(bookRepository.findById(id)).isNotNull(); } - @Document(indexName = "test-index-book") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class Book { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectRestTemplateIntegrationTests.java deleted file mode 100644 index 0a5dc8136..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectRestTemplateIntegrationTests.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.repositories.nestedobject; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { InnerObjectRestTemplateIntegrationTests.Config.class }) -public class InnerObjectRestTemplateIntegrationTests extends InnerObjectIntegrationTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} - -} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..75b71ec43 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryELCIntegrationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.setting.dynamic; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { DynamicSettingAndMappingEntityRepositoryELCIntegrationTests.Config.class }) +public class DynamicSettingAndMappingEntityRepositoryELCIntegrationTests + extends DynamicSettingAndMappingEntityRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("dynamic-setting-and-mapping="); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryERHLCIntegrationTests.java similarity index 88% rename from src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryERHLCIntegrationTests.java index a3a2ddc34..f5dd8af95 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryERHLCIntegrationTests.java @@ -26,8 +26,8 @@ import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { DynamicSettingAndMappingEntityRepositoryRestTemplateIntegrationTests.Config.class }) -public class DynamicSettingAndMappingEntityRepositoryRestTemplateIntegrationTests +@ContextConfiguration(classes = { DynamicSettingAndMappingEntityRepositoryERHLCIntegrationTests.Config.class }) +public class DynamicSettingAndMappingEntityRepositoryERHLCIntegrationTests extends DynamicSettingAndMappingEntityRepositoryIntegrationTests { @Configuration @@ -36,7 +36,7 @@ public class DynamicSettingAndMappingEntityRepositoryRestTemplateIntegrationTest static class Config { @Bean IndexNameProvider indexNameProvider() { - return new IndexNameProvider("dynamic-setting-and-mapping-3s7"); + return new IndexNameProvider("dynamic-setting-and-mapping-es7"); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..d184c7303 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryELCIntegrationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.setting.fielddynamic; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { FieldDynamicMappingEntityRepositoryELCIntegrationTests.Config.class }) +public class FieldDynamicMappingEntityRepositoryELCIntegrationTests + extends FieldDynamicMappingEntityRepositoryIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("field-dynamic-mapping"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryERHLCIntegrationTests.java similarity index 78% rename from src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryERHLCIntegrationTests.java index e91d55df8..0be50164f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryERHLCIntegrationTests.java @@ -15,21 +15,29 @@ */ package org.springframework.data.elasticsearch.repositories.setting.fielddynamic; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { FieldDynamicMappingEntityRepositoryRestTemplateIntegrationTests.Config.class }) -public class FieldDynamicMappingEntityRepositoryRestTemplateIntegrationTests +@ContextConfiguration(classes = { FieldDynamicMappingEntityRepositoryERHLCIntegrationTests.Config.class }) +public class FieldDynamicMappingEntityRepositoryERHLCIntegrationTests extends FieldDynamicMappingEntityRepositoryIntegrationTests { + @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("field-dynamic-mapping-es7"); + } + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryIntegrationTests.java index 5d72b2abc..9223ead26 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/fielddynamic/FieldDynamicMappingEntityRepositoryIntegrationTests.java @@ -19,18 +19,18 @@ import static org.assertj.core.api.Assertions.*; import java.util.Map; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Mapping; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; /** * FieldDynamicMappingEntityRepositoryIntegrationTests @@ -42,17 +42,18 @@ import org.springframework.data.elasticsearch.utils.IndexInitializer; public abstract class FieldDynamicMappingEntityRepositoryIntegrationTests { @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(FieldDynamicMappingEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(FieldDynamicMappingEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test // DATAES-209 @@ -85,10 +86,7 @@ public abstract class FieldDynamicMappingEntityRepositoryIntegrationTests { assertThat((Boolean) content.get("store")).isTrue(); } - /** - * @author Ted Liang - */ - @Document(indexName = "test-index-field-dynamic-mapping") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class FieldDynamicMappingEntity { @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityELCIntegrationTests.java new file mode 100644 index 000000000..e5d0de642 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.spel; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { SpELEntityELCIntegrationTests.Config.class }) +public class SpELEntityELCIntegrationTests extends SpELEntityIntegrationTests { + @Configuration + @Import(ElasticsearchTemplateConfiguration.class) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("spel-entity"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityERHLCIntegrationTests.java similarity index 74% rename from src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityRestTemplateIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityERHLCIntegrationTests.java index 3b9fe0e7b..8847fd1e8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityRestTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityERHLCIntegrationTests.java @@ -15,20 +15,27 @@ */ package org.springframework.data.elasticsearch.repositories.spel; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@ContextConfiguration(classes = { SpELEntityRestTemplateIntegrationTests.Config.class }) -public class SpELEntityRestTemplateIntegrationTests extends SpELEntityIntegrationTests { +@ContextConfiguration(classes = { SpELEntityERHLCIntegrationTests.Config.class }) +public class SpELEntityERHLCIntegrationTests extends SpELEntityIntegrationTests { @Configuration @Import(ElasticsearchRestTemplateConfiguration.class) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("spel-entity-es7"); + } + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityIntegrationTests.java index 4fc197f6f..bba163e4f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityIntegrationTests.java @@ -17,18 +17,17 @@ package org.springframework.data.elasticsearch.repositories.spel; import static org.assertj.core.api.Assertions.*; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; /** * SpELEntityTest @@ -40,19 +39,19 @@ import org.springframework.data.elasticsearch.utils.IndexInitializer; public abstract class SpELEntityIntegrationTests { @Autowired private SpELRepository repository; - @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(SpELEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(SpELEntity.class).createWithMapping(); } - @AfterEach - void after() { - operations.indexOps(IndexCoordinates.of("test-index-abz-*")).delete(); + @Test + @Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -65,7 +64,7 @@ public abstract class SpELEntityIntegrationTests { // when // then - long count = operations.count(operations.matchAllQuery(), IndexCoordinates.of("test-index-abz-entity")); + long count = operations.count(operations.matchAllQuery(), SpELEntity.class); assertThat(count).isEqualTo(2); } @@ -79,11 +78,11 @@ public abstract class SpELEntityIntegrationTests { // when // then - long count = operations.count(operations.matchAllQuery(), IndexCoordinates.of("test-index-abz-entity")); + long count = operations.count(operations.matchAllQuery(), SpELEntity.class); assertThat(count).isEqualTo(1); } - @Document(indexName = "#{'test-index-abz'+'-'+'entity'}") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SpELEntity { @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..e92a3d497 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.synonym; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { SynonymRepositoryELCIntegrationTests.Config.class }) +public class SynonymRepositoryELCIntegrationTests extends SynonymRepositoryIntegrationTests { + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("sysnonym-entity"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryERHLCIntegrationTests.java new file mode 100644 index 000000000..4cc6750e0 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryERHLCIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.synonym; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { SynonymRepositoryERHLCIntegrationTests.Config.class }) +public class SynonymRepositoryERHLCIntegrationTests extends SynonymRepositoryIntegrationTests { + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("sysnonym-entity-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryIntegrationTests.java similarity index 82% rename from src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryTests.java rename to src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryIntegrationTests.java index dd3538b68..8bfd49d65 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryIntegrationTests.java @@ -17,8 +17,8 @@ package org.springframework.data.elasticsearch.repositories.synonym; import static org.assertj.core.api.Assertions.*; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -26,39 +26,39 @@ import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Mapping; import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** - * SynonymRepositoryTests + * SynonymRepositoryIntegrationTests * * @author Artur Konczak * @author Peter-Josef Meisch */ @SpringIntegrationTest -public abstract class SynonymRepositoryTests { +public abstract class SynonymRepositoryIntegrationTests { @Autowired private SynonymRepository repository; - @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(SynonymEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(SynonymEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -76,7 +76,7 @@ public abstract class SynonymRepositoryTests { assertThat(synonymEntities).hasSize(1); } - @Document(indexName = "test-index-synonym") + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(settingPath = "/synonyms/settings.json") @Mapping(mappingPath = "/synonyms/mappings.json") static class SynonymEntity { diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryRestTemplateTests.java deleted file mode 100644 index ac0890ef0..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryRestTemplateTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.repositories.synonym; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { SynonymRepositoryRestTemplateTests.Config.class }) -public class SynonymRepositoryRestTemplateTests extends SynonymRepositoryTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} -} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryELCIntegrationTests.java new file mode 100644 index 000000000..4888c0368 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryELCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.uuidkeyed; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { UUIDElasticsearchRepositoryELCIntegrationTests.Config.class }) +public class UUIDElasticsearchRepositoryELCIntegrationTests extends UUIDElasticsearchRepositoryIntegrationTests { + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("uuid-keyed"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryERHLCIntegrationTests.java new file mode 100644 index 000000000..ab4a74872 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryERHLCIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repositories.uuidkeyed; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { UUIDElasticsearchRepositoryERHLCIntegrationTests.Config.class }) +public class UUIDElasticsearchRepositoryERHLCIntegrationTests extends UUIDElasticsearchRepositoryIntegrationTests { + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("uuid-keyed-es7"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryIntegrationTests.java index 16c01604a..b071264b8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryIntegrationTests.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -38,14 +37,15 @@ 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.ScriptedField; -import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -64,16 +64,19 @@ public abstract class UUIDElasticsearchRepositoryIntegrationTests { @Autowired ElasticsearchOperations operations; private IndexOperations indexOperations; + @Autowired IndexNameProvider indexNameProvider; @BeforeEach public void before() { + indexNameProvider.increment(); indexOperations = operations.indexOps(SampleEntityUUIDKeyed.class); - IndexInitializer.init(indexOperations); + indexOperations.createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @org.junit.jupiter.api.Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -279,7 +282,6 @@ public abstract class UUIDElasticsearchRepositoryIntegrationTests { repository.deleteAll(); // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); Iterable sampleEntities = repository.findAll(); assertThat(sampleEntities).isEmpty(); } @@ -534,7 +536,7 @@ public abstract class UUIDElasticsearchRepositoryIntegrationTests { return sampleEntities; } - @Document(indexName = "test-index-uuid-keyed") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntityUUIDKeyed { @Nullable @Id private UUID id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryRestTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryRestTemplateIntegrationTests.java deleted file mode 100644 index 049c18b9d..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryRestTemplateIntegrationTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.repositories.uuidkeyed; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { UUIDElasticsearchRepositoryRestTemplateIntegrationTests.Config.class }) -public class UUIDElasticsearchRepositoryRestTemplateIntegrationTests - extends UUIDElasticsearchRepositoryIntegrationTests { - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} - -} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsELCIntegrationTests.java new file mode 100644 index 000000000..e773fa715 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsELCIntegrationTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repository.query.keywords; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * {@link QueryKeywordsIntegrationTests} using a Repository backed by an ElasticsearchTemplate. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { QueryKeywordsELCIntegrationTests.Config.class }) +public class QueryKeywordsELCIntegrationTests extends QueryKeywordsIntegrationTests { + + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("query-keywords"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsERHLCIntegrationTests.java new file mode 100644 index 000000000..0fb02e7b3 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsERHLCIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repository.query.keywords; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * {@link QueryKeywordsIntegrationTests} using a Repository backed by an ElasticsearchTemplate. + * + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { QueryKeywordsERHLCIntegrationTests.Config.class }) +public class QueryKeywordsERHLCIntegrationTests extends QueryKeywordsIntegrationTests { + + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + @EnableElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("query-keywords-es7"); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsIntegrationTests.java similarity index 95% rename from src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java rename to src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsIntegrationTests.java index dc9574ff2..3307b2cf2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsIntegrationTests.java @@ -22,9 +22,9 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -32,11 +32,11 @@ 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.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; /** @@ -48,17 +48,16 @@ import org.springframework.lang.Nullable; * @author Peter-Josef Meisch */ @SpringIntegrationTest -abstract class QueryKeywordsTests { +abstract class QueryKeywordsIntegrationTests { @Autowired private ProductRepository repository; - @Autowired ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(Product.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(Product.class).createWithMapping(); Product product1 = new Product("1", "Sugar", "Cane sugar", 1.0f, false, "sort5"); Product product2 = new Product("2", "Sugar", "Cane sugar", 1.2f, true, "sort4"); @@ -71,9 +70,10 @@ abstract class QueryKeywordsTests { repository.saveAll(Arrays.asList(product1, product2, product3, product4, product5, product6, product7)); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete(); } @Test @@ -311,7 +311,7 @@ abstract class QueryKeywordsTests { } @SuppressWarnings("unused") - @Document(indexName = "test-index-product-query-keywords") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class Product { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsRepositoryRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsRepositoryRestTemplateTests.java deleted file mode 100644 index ea47b43d0..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsRepositoryRestTemplateTests.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.repository.query.keywords; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.test.context.ContextConfiguration; - -/** - * {@link QueryKeywordsTests} using a Repository backed by an ElasticsearchTemplate. - * - * @author Peter-Josef Meisch - */ -@ContextConfiguration(classes = { QueryKeywordsRepositoryRestTemplateTests.Config.class }) -public class QueryKeywordsRepositoryRestTemplateTests extends QueryKeywordsTests { - - @Configuration - @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} -} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsELCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsELCIntegrationTests.java new file mode 100644 index 000000000..de14ed1b8 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsELCIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repository.query.keywords; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveQueryKeywordsELCIntegrationTests.Config.class }) +public class ReactiveQueryKeywordsELCIntegrationTests extends ReactiveQueryKeywordsIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchTemplateConfiguration.class }) + @EnableReactiveElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-query-keywords"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsERHLCIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsERHLCIntegrationTests.java new file mode 100644 index 000000000..dc4670936 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsERHLCIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repository.query.keywords; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + * @since 4.4 + */ +@ContextConfiguration(classes = { ReactiveQueryKeywordsERHLCIntegrationTests.Config.class }) +public class ReactiveQueryKeywordsERHLCIntegrationTests extends ReactiveQueryKeywordsIntegrationTests { + + @Configuration + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) + @EnableReactiveElasticsearchRepositories(considerNestedRepositories = true) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-query-keywords-es7"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsIntegrationTests.java index 316db649a..63857df87 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/ReactiveQueryKeywordsIntegrationTests.java @@ -26,40 +26,22 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository; -import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveQueryKeywordsIntegrationTests.Config.class }) -public class ReactiveQueryKeywordsIntegrationTests { - - @Configuration - @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) - @EnableReactiveElasticsearchRepositories(considerNestedRepositories = true) - static class Config { - @Bean - IndexNameProvider indexNameProvider() { - return new IndexNameProvider("reactive-template"); - } - } +public abstract class ReactiveQueryKeywordsIntegrationTests { @Autowired private IndexNameProvider indexNameProvider; @Autowired private ReactiveElasticsearchOperations operations; diff --git a/src/test/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMapUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMapUnitTests.java new file mode 100644 index 000000000..27317503c --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMapUnitTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.support; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author Peter-Josef Meisch + */ +class DefaultStringObjectMapUnitTests { + + private final SOM stringObjectMap = new SOM(); + + @BeforeEach + void setUp() { + String json = "{\n" + // + " \"index\": {\n" + // + " \"some\": {\n" + // + " \"deeply\": {\n" + // + " \"nested\": {\n" + // + " \"answer\": 42\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + "}\n"; + stringObjectMap.fromJson(json); + } + + @Test + @DisplayName("should parse key path") + void shouldParseKeyPath() { + assertThat(stringObjectMap.path("index.some.deeply.nested.answer")).isEqualTo(42); + } + + @Test + @DisplayName("should return null on non existing path") + void shouldReturnNullOnNonExistingPath() { + assertThat(stringObjectMap.path("index.some.deeply.nested.question")).isNull(); + } + + @Test + @DisplayName("should return map object on partial path") + void shouldReturnMapObjectOnPartialPath() { + Object object = stringObjectMap.path("index.some.deeply.nested"); + assertThat(object).isNotNull().isInstanceOf(Map.class); + // noinspection unchecked + Map map = (Map) object; + assertThat(map.get("answer")).isEqualTo(42); + } + + static class SOM extends DefaultStringObjectMap {} +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/BitUtil.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/BitUtil.java new file mode 100644 index 000000000..870be5fea --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/BitUtil.java @@ -0,0 +1,71 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +/** + * /** Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/utils/BitUtil.java + *
+ *
+ * Utilities for common Bit twiddling methods. Borrowed heavily from Lucene (org.apache.lucene.util.BitUtil). + */ +public class BitUtil { // magic numbers for bit interleaving + private static final long MAGIC[] = { 0x5555555555555555L, 0x3333333333333333L, 0x0F0F0F0F0F0F0F0FL, + 0x00FF00FF00FF00FFL, 0x0000FFFF0000FFFFL, 0x00000000FFFFFFFFL, 0xAAAAAAAAAAAAAAAAL }; + // shift values for bit interleaving + private static final short SHIFT[] = { 1, 2, 4, 8, 16 }; + + /** + * Interleaves the first 32 bits of each long value Adapted from: + * http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN + */ + public static long interleave(int even, int odd) { + long v1 = 0x00000000FFFFFFFFL & even; + long v2 = 0x00000000FFFFFFFFL & odd; + v1 = (v1 | (v1 << SHIFT[4])) & MAGIC[4]; + v1 = (v1 | (v1 << SHIFT[3])) & MAGIC[3]; + v1 = (v1 | (v1 << SHIFT[2])) & MAGIC[2]; + v1 = (v1 | (v1 << SHIFT[1])) & MAGIC[1]; + v1 = (v1 | (v1 << SHIFT[0])) & MAGIC[0]; + v2 = (v2 | (v2 << SHIFT[4])) & MAGIC[4]; + v2 = (v2 | (v2 << SHIFT[3])) & MAGIC[3]; + v2 = (v2 | (v2 << SHIFT[2])) & MAGIC[2]; + v2 = (v2 | (v2 << SHIFT[1])) & MAGIC[1]; + v2 = (v2 | (v2 << SHIFT[0])) & MAGIC[0]; + + return (v2 << 1) | v1; + } + + /** + * Extract just the even-bits value as a long from the bit-interleaved value + */ + public static long deinterleave(long b) { + b &= MAGIC[0]; + b = (b ^ (b >>> SHIFT[0])) & MAGIC[1]; + b = (b ^ (b >>> SHIFT[1])) & MAGIC[2]; + b = (b ^ (b >>> SHIFT[2])) & MAGIC[3]; + b = (b ^ (b >>> SHIFT[3])) & MAGIC[4]; + b = (b ^ (b >>> SHIFT[4])) & MAGIC[5]; + return b; + } + + /** + * flip flops odd with even bits + */ + public static final long flipFlop(final long b) { + return ((b & MAGIC[6]) >>> 1) | ((b & MAGIC[0]) << 1); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geohash.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geohash.java new file mode 100644 index 000000000..4303f3547 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geohash.java @@ -0,0 +1,379 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/utils/Geohash.java + *
+ *
+ * Utilities for converting to/from the GeoHash standard The geohash long format is represented as lon/lat (x/y) + * interleaved with the 4 least significant bits representing the level (1-12) [xyxy...xyxyllll] This differs from a + * morton encoded value which interleaves lat/lon (y/x). NOTE: this will replace + * {@code org.elasticsearch.common.geo.GeoHashUtils} + */ +public class Geohash { + private static final char[] BASE_32 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + + private static final String BASE_32_STRING = new String(BASE_32); + /** maximum precision for geohash strings */ + public static final int PRECISION = 12; + /** number of bits used for quantizing latitude and longitude values */ + private static final short BITS = 32; + private static final double LAT_SCALE = (0x1L << (BITS - 1)) / 180.0D; + private static final double LAT_DECODE = 180.0D / (0x1L << BITS); + private static final double LON_SCALE = (0x1L << (BITS - 1)) / 360.0D; + private static final double LON_DECODE = 360.0D / (0x1L << BITS); + + private static final short MORTON_OFFSET = (BITS << 1) - (PRECISION * 5); + /** Bit encoded representation of the latitude of north pole */ + private static final long MAX_LAT_BITS = (0x1L << (PRECISION * 5 / 2)) - 1; + + // Below code is adapted from the spatial4j library (GeohashUtils.java) Apache 2.0 Licensed + private static final double[] precisionToLatHeight, precisionToLonWidth; + static { + precisionToLatHeight = new double[PRECISION + 1]; + precisionToLonWidth = new double[PRECISION + 1]; + precisionToLatHeight[0] = 90 * 2; + precisionToLonWidth[0] = 180 * 2; + boolean even = false; + for (int i = 1; i <= PRECISION; i++) { + precisionToLatHeight[i] = precisionToLatHeight[i - 1] / (even ? 8 : 4); + precisionToLonWidth[i] = precisionToLonWidth[i - 1] / (even ? 4 : 8); + even = !even; + } + } + + // no instance: + private Geohash() {} + + /** Returns a {@link Point} instance from a geohash string */ + public static Point toPoint(final String geohash) throws IllegalArgumentException { + final long hash = mortonEncode(geohash); + return new Point(decodeLongitude(hash), decodeLatitude(hash)); + } + + /** + * Computes the bounding box coordinates from a given geohash + * + * @param geohash Geohash of the defined cell + * @return GeoRect rectangle defining the bounding box + */ + public static Rectangle toBoundingBox(final String geohash) { + // bottom left is the coordinate + Point bottomLeft = toPoint(geohash); + int len = Math.min(12, geohash.length()); + long ghLong = longEncode(geohash, len); + // shift away the level + ghLong >>>= 4; + // deinterleave + long lon = BitUtil.deinterleave(ghLong >>> 1); + long lat = BitUtil.deinterleave(ghLong); + final int shift = (12 - len) * 5 + 2; + if (lat < MAX_LAT_BITS) { + // add 1 to lat and lon to get topRight + ghLong = BitUtil.interleave((int) (lat + 1), (int) (lon + 1)) << 4 | len; + final long mortonHash = BitUtil.flipFlop((ghLong >>> 4) << shift); + Point topRight = new Point(decodeLongitude(mortonHash), decodeLatitude(mortonHash)); + return new Rectangle(bottomLeft.getX(), topRight.getX(), topRight.getY(), bottomLeft.getY()); + } else { + // We cannot go north of north pole, so just using 90 degrees instead of calculating it using + // add 1 to lon to get lon of topRight, we are going to use 90 for lat + ghLong = BitUtil.interleave((int) lat, (int) (lon + 1)) << 4 | len; + final long mortonHash = BitUtil.flipFlop((ghLong >>> 4) << shift); + Point topRight = new Point(decodeLongitude(mortonHash), decodeLatitude(mortonHash)); + return new Rectangle(bottomLeft.getX(), topRight.getX(), 90D, bottomLeft.getY()); + } + } + + /** Array of geohashes one level below the baseGeohash. Sorted. */ + public static String[] getSubGeohashes(String baseGeohash) { + String[] hashes = new String[BASE_32.length]; + for (int i = 0; i < BASE_32.length; i++) {// note: already sorted + char c = BASE_32[i]; + hashes[i] = baseGeohash + c; + } + return hashes; + } + + /** + * Calculate all neighbors of a given geohash cell. + * + * @param geohash Geohash of the defined cell + * @return geohashes of all neighbor cells + */ + public static Collection getNeighbors(String geohash) { + return addNeighborsAtLevel(geohash, geohash.length(), new ArrayList(8)); + } + + /** + * Add all geohashes of the cells next to a given geohash to a list. + * + * @param geohash Geohash of a specified cell + * @param neighbors list to add the neighbors to + * @return the given list + */ + public static final > E addNeighbors(String geohash, E neighbors) { + return addNeighborsAtLevel(geohash, geohash.length(), neighbors); + } + + /** + * Add all geohashes of the cells next to a given geohash to a list. + * + * @param geohash Geohash of a specified cell + * @param level level of the given geohash + * @param neighbors list to add the neighbors to + * @return the given list + */ + public static final > E addNeighborsAtLevel(String geohash, int level, + E neighbors) { + String south = getNeighbor(geohash, level, 0, -1); + String north = getNeighbor(geohash, level, 0, +1); + if (north != null) { + neighbors.add(getNeighbor(north, level, -1, 0)); + neighbors.add(north); + neighbors.add(getNeighbor(north, level, +1, 0)); + } + + neighbors.add(getNeighbor(geohash, level, -1, 0)); + neighbors.add(getNeighbor(geohash, level, +1, 0)); + + if (south != null) { + neighbors.add(getNeighbor(south, level, -1, 0)); + neighbors.add(south); + neighbors.add(getNeighbor(south, level, +1, 0)); + } + + return neighbors; + } + + /** + * Calculate the geohash of a neighbor of a geohash + * + * @param geohash the geohash of a cell + * @param level level of the geohash + * @param dx delta of the first grid coordinate (must be -1, 0 or +1) + * @param dy delta of the second grid coordinate (must be -1, 0 or +1) + * @return geohash of the defined cell + */ + public static final String getNeighbor(String geohash, int level, int dx, int dy) { + int cell = BASE_32_STRING.indexOf(geohash.charAt(level - 1)); + + // Decoding the Geohash bit pattern to determine grid coordinates + int x0 = cell & 1; // first bit of x + int y0 = cell & 2; // first bit of y + int x1 = cell & 4; // second bit of x + int y1 = cell & 8; // second bit of y + int x2 = cell & 16; // third bit of x + + // combine the bitpattern to grid coordinates. + // note that the semantics of x and y are swapping + // on each level + int x = x0 + (x1 / 2) + (x2 / 4); + int y = (y0 / 2) + (y1 / 4); + + if (level == 1) { + // Root cells at north (namely "bcfguvyz") or at + // south (namely "0145hjnp") do not have neighbors + // in north/south direction + if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) { + return null; + } else { + return Character.toString(encodeBase32(x + dx, y + dy)); + } + } else { + // define grid coordinates for next level + final int nx = ((level % 2) == 1) ? (x + dx) : (x + dy); + final int ny = ((level % 2) == 1) ? (y + dy) : (y + dx); + + // if the defined neighbor has the same parent a the current cell + // encode the cell directly. Otherwise find the cell next to this + // cell recursively. Since encoding wraps around within a cell + // it can be encoded here. + // xLimit and YLimit must always be respectively 7 and 3 + // since x and y semantics are swapping on each level. + if (nx >= 0 && nx <= 7 && ny >= 0 && ny <= 3) { + return geohash.substring(0, level - 1) + encodeBase32(nx, ny); + } else { + String neighbor = getNeighbor(geohash, level - 1, dx, dy); + return (neighbor != null) ? neighbor + encodeBase32(nx, ny) : neighbor; + } + } + } + + /** + * Encode a string geohash to the geohash based long format (lon/lat interleaved, 4 least significant bits = level) + */ + public static final long longEncode(String hash) { + return longEncode(hash, hash.length()); + } + + /** + * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level) + */ + public static final long longEncode(final double lon, final double lat, final int level) { + // shift to appropriate level + final short msf = (short) (((12 - level) * 5) + (MORTON_OFFSET - 2)); + return ((encodeLatLon(lat, lon) >>> msf) << 4) | level; + } + + /** + * Encode to a geohash string from full resolution longitude, latitude) + */ + public static final String stringEncode(final double lon, final double lat) { + return stringEncode(lon, lat, 12); + } + + /** + * Encode to a level specific geohash string from full resolution longitude, latitude + */ + public static final String stringEncode(final double lon, final double lat, final int level) { + // convert to geohashlong + long interleaved = encodeLatLon(lat, lon); + interleaved >>>= (((PRECISION - level) * 5) + (MORTON_OFFSET - 2)); + final long geohash = (interleaved << 4) | level; + return stringEncode(geohash); + } + + /** + * Encode to a geohash string from the geohash based long format + */ + public static final String stringEncode(long geoHashLong) { + int level = (int) geoHashLong & 15; + geoHashLong >>>= 4; + char[] chars = new char[level]; + do { + chars[--level] = BASE_32[(int) (geoHashLong & 31L)]; + geoHashLong >>>= 5; + } while (level > 0); + + return new String(chars); + } + + /** base32 encode at the given grid coordinate */ + private static char encodeBase32(int x, int y) { + return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32]; + } + + /** + * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level) + */ + private static long longEncode(final String hash, int length) { + int level = length - 1; + long b; + long l = 0L; + for (char c : hash.toCharArray()) { + b = (long) (BASE_32_STRING.indexOf(c)); + l |= (b << (level-- * 5)); + if (level < 0) { + // We cannot handle more than 12 levels + break; + } + } + return (l << 4) | length; + } + + /** + * Encode to a morton long value from a given geohash string + */ + public static long mortonEncode(final String hash) { + if (hash.isEmpty()) { + throw new IllegalArgumentException("empty geohash"); + } + int level = 11; + long b; + long l = 0L; + for (char c : hash.toCharArray()) { + b = (long) (BASE_32_STRING.indexOf(c)); + if (b < 0) { + throw new IllegalArgumentException("unsupported symbol [" + c + "] in geohash [" + hash + "]"); + } + l |= (b << ((level-- * 5) + (MORTON_OFFSET - 2))); + if (level < 0) { + // We cannot handle more than 12 levels + break; + } + } + return BitUtil.flipFlop(l); + } + + /** approximate width of geohash tile for a specific precision in degrees */ + public static double lonWidthInDegrees(int precision) { + return precisionToLonWidth[precision]; + } + + /** approximate height of geohash tile for a specific precision in degrees */ + public static double latHeightInDegrees(int precision) { + return precisionToLatHeight[precision]; + } + + private static long encodeLatLon(final double lat, final double lon) { + // encode lat/lon flipping the sign bit so negative ints sort before positive ints + final int latEnc = encodeLatitude(lat) ^ 0x80000000; + final int lonEnc = encodeLongitude(lon) ^ 0x80000000; + return BitUtil.interleave(latEnc, lonEnc) >>> 2; + } + + /** encode latitude to integer */ + public static int encodeLatitude(double latitude) { + // the maximum possible value cannot be encoded without overflow + if (latitude == 90.0D) { + latitude = Math.nextDown(latitude); + } + return (int) Math.floor(latitude / LAT_DECODE); + } + + /** encode longitude to integer */ + public static int encodeLongitude(double longitude) { + // the maximum possible value cannot be encoded without overflow + if (longitude == 180.0D) { + longitude = Math.nextDown(longitude); + } + return (int) Math.floor(longitude / LON_DECODE); + } + + /** returns the latitude value from the string based geohash */ + public static final double decodeLatitude(final String geohash) { + return decodeLatitude(Geohash.mortonEncode(geohash)); + } + + /** returns the latitude value from the string based geohash */ + public static final double decodeLongitude(final String geohash) { + return decodeLongitude(Geohash.mortonEncode(geohash)); + } + + /** decode longitude value from morton encoded geo point */ + public static double decodeLongitude(final long hash) { + return unscaleLon(BitUtil.deinterleave(hash)); + } + + /** decode latitude value from morton encoded geo point */ + public static double decodeLatitude(final long hash) { + return unscaleLat(BitUtil.deinterleave(hash >>> 1)); + } + + private static double unscaleLon(final long val) { + return (val / LON_SCALE) - 180; + } + + private static double unscaleLat(final long val) { + return (val / LAT_SCALE) - 90; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geometry.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geometry.java new file mode 100644 index 000000000..5d7831392 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Geometry.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +/** + * Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/Geometry.java + *
+ *
+ * Base class for all Geometry objects supported by elasticsearch + */ +public interface Geometry { + + ShapeType type(); + + T visit(GeometryVisitor visitor) throws E; + + boolean isEmpty(); + + default boolean hasZ() { + return false; + } + + default boolean hasAlt() { + return hasZ(); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryValidator.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryValidator.java new file mode 100644 index 000000000..35b46bb40 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryValidator.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +/** + * /** Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/utils/GeometryValidator.java + *
+ *
+ * Generic geometry validator that can be used by the parser to verify the validity of the parsed geometry + */ +public interface GeometryValidator { + + /** + * Validates the geometry and throws IllegalArgumentException if the geometry is not valid + */ + void validate(Geometry geometry); + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryVisitor.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryVisitor.java new file mode 100644 index 000000000..7a0c2053a --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/GeometryVisitor.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +/** + * Support class for creating Geometry Visitors. + *

+ * This is an implementation of the Visitor pattern. The basic idea is to simplify adding new operations on Geometries, + * without constantly modifying and adding new functionality to the Geometry hierarchy and keeping it as lightweight as + * possible. + *

+ * It is a more object-oriented alternative to structures like this: + * + *

+ * if (obj instanceof This) {
+ *   doThis((This) obj);
+ * } elseif (obj instanceof That) {
+ *   doThat((That) obj);
+ * ...
+ * } else {
+ *   throw new IllegalArgumentException("Unknown object " + obj);
+ * }
+ * 
+ *

+ * The Visitor Pattern replaces this structure with Interface inheritance making it easier to identify all places that + * are using this structure, and making a shape a compile-time failure instead of runtime. + *

+ * See {@link WellKnownText#toWKT(Geometry, StringBuilder)} for an example of how this interface is used. + * + * @see Visitor Pattern + */ +public interface GeometryVisitor { + + /* + T visit(Circle circle) throws E; + + T visit(GeometryCollection collection) throws E; + + T visit(Line line) throws E; + + T visit(LinearRing ring) throws E; + + T visit(MultiLine multiLine) throws E; + + T visit(MultiPoint multiPoint) throws E; + + T visit(MultiPolygon multiPolygon) throws E; + */ + T visit(Point point) throws E; + + /* + T visit(Polygon polygon) throws E; + */ + T visit(Rectangle rectangle) throws E; +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Point.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Point.java new file mode 100644 index 000000000..336d314d9 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Point.java @@ -0,0 +1,129 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +/** + * /** Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/Point.java
+ *
+ * Represents a Point on the earth's surface in decimal degrees and optional altitude in meters. + */ +public class Point implements Geometry { + public static final Point EMPTY = new Point(); + + private final double y; + private final double x; + private final double z; + private final boolean empty; + + private Point() { + y = 0; + x = 0; + z = Double.NaN; + empty = true; + } + + public Point(double x, double y) { + this(x, y, Double.NaN); + } + + public Point(double x, double y, double z) { + this.y = y; + this.x = x; + this.z = z; + this.empty = false; + } + + @Override + public ShapeType type() { + return ShapeType.POINT; + } + + public double getY() { + return y; + } + + public double getX() { + return x; + } + + public double getZ() { + return z; + } + + public double getLat() { + return y; + } + + public double getLon() { + return x; + } + + public double getAlt() { + return z; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Point point = (Point) o; + if (point.empty != empty) + return false; + if (Double.compare(point.y, y) != 0) + return false; + if (Double.compare(point.x, x) != 0) + return false; + return Double.compare(point.z, z) == 0; + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(y); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(x); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(z); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public T visit(GeometryVisitor visitor) throws E { + return visitor.visit(this); + } + + @Override + public boolean isEmpty() { + return empty; + } + + @Override + public boolean hasZ() { + return Double.isNaN(z) == false; + } + + @Override + public String toString() { + return WellKnownText.INSTANCE.toWKT(this); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Rectangle.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Rectangle.java new file mode 100644 index 000000000..d94b435ad --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/Rectangle.java @@ -0,0 +1,204 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +/** + * Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/Rectangle.java + *
+ *
+ * Represents a lat/lon rectangle in decimal degrees and optional altitude in meters. + */ +public class Rectangle implements Geometry { + public static final Rectangle EMPTY = new Rectangle(); + /** + * minimum latitude value (in degrees) + */ + private final double minY; + /** + * minimum longitude value (in degrees) + */ + private final double minX; + /** + * maximum altitude value (in meters) + */ + private final double minZ; + /** + * maximum latitude value (in degrees) + */ + private final double maxY; + /** + * minimum longitude value (in degrees) + */ + private final double maxX; + /** + * minimum altitude value (in meters) + */ + private final double maxZ; + + private final boolean empty; + + private Rectangle() { + minY = 0; + minX = 0; + maxY = 0; + maxX = 0; + minZ = Double.NaN; + maxZ = Double.NaN; + empty = true; + } + + /** + * Constructs a bounding box by first validating the provided latitude and longitude coordinates + */ + public Rectangle(double minX, double maxX, double maxY, double minY) { + this(minX, maxX, maxY, minY, Double.NaN, Double.NaN); + } + + /** + * Constructs a bounding box by first validating the provided latitude and longitude coordinates + */ + public Rectangle(double minX, double maxX, double maxY, double minY, double minZ, double maxZ) { + this.minX = minX; + this.maxX = maxX; + this.minY = minY; + this.maxY = maxY; + this.minZ = minZ; + this.maxZ = maxZ; + empty = false; + if (maxY < minY) { + throw new IllegalArgumentException("max y cannot be less than min x"); + } + if (Double.isNaN(minZ) != Double.isNaN(maxZ)) { + throw new IllegalArgumentException("only one z value is specified"); + } + } + + public double getMinY() { + return minY; + } + + public double getMinX() { + return minX; + } + + public double getMinZ() { + return minZ; + } + + public double getMaxY() { + return maxY; + } + + public double getMaxX() { + return maxX; + } + + public double getMaxZ() { + return maxZ; + } + + public double getMinLat() { + return minY; + } + + public double getMinLon() { + return minX; + } + + public double getMinAlt() { + return minZ; + } + + public double getMaxLat() { + return maxY; + } + + public double getMaxLon() { + return maxX; + } + + public double getMaxAlt() { + return maxZ; + } + + @Override + public ShapeType type() { + return ShapeType.ENVELOPE; + } + + @Override + public String toString() { + return WellKnownText.INSTANCE.toWKT(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Rectangle rectangle = (Rectangle) o; + + if (Double.compare(rectangle.minY, minY) != 0) + return false; + if (Double.compare(rectangle.minX, minX) != 0) + return false; + if (Double.compare(rectangle.maxY, maxY) != 0) + return false; + if (Double.compare(rectangle.maxX, maxX) != 0) + return false; + if (Double.compare(rectangle.minZ, minZ) != 0) + return false; + return Double.compare(rectangle.maxZ, maxZ) == 0; + + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(minY); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(minX); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(maxY); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(maxX); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(minZ); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(maxZ); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public T visit(GeometryVisitor visitor) throws E { + return visitor.visit(this); + } + + @Override + public boolean isEmpty() { + return empty; + } + + @Override + public boolean hasZ() { + return Double.isNaN(maxZ) == false; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/ShapeType.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/ShapeType.java new file mode 100644 index 000000000..7e3a0394d --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/ShapeType.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +import java.util.Locale; + +/** + * Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/ShapeType.java + *
+ *
+ * Shape types supported by elasticsearch + */ +public enum ShapeType { + POINT, // + MULTIPOINT, // + LINESTRING, // + MULTILINESTRING, // + POLYGON, // + MULTIPOLYGON, // + GEOMETRYCOLLECTION, // + LINEARRING, // not serialized by itself in WKT or WKB + ENVELOPE, // not part of the actual WKB spec + CIRCLE; // not part of the actual WKB spec + + public static ShapeType forName(String shapeName) { + return ShapeType.valueOf(shapeName.toUpperCase(Locale.ROOT)); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/StandardValidator.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/StandardValidator.java new file mode 100644 index 000000000..3b233ce3d --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/StandardValidator.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +/** + * Code copied from Elasticsearch 7.10, Apache License V2 + * https://github.com/elastic/elasticsearch/blob/7.10/libs/geo/src/main/java/org/elasticsearch/geometry/utils/StandardValidator.java + *
+ *
+ * Validator that only checks that altitude only shows up if ignoreZValue is set to true. + */ +public class StandardValidator implements GeometryValidator { + + private final boolean ignoreZValue; + + public StandardValidator(boolean ignoreZValue) { + this.ignoreZValue = ignoreZValue; + } + + protected void checkZ(double zValue) { + if (ignoreZValue == false && Double.isNaN(zValue) == false) { + throw new IllegalArgumentException( + "found Z value [" + zValue + "] but [ignore_z_value] " + "parameter is [" + ignoreZValue + "]"); + } + } + + @Override + public void validate(Geometry geometry) { + if (ignoreZValue == false) { + geometry.visit(new GeometryVisitor() { + + @Override + public Void visit(Point point) throws RuntimeException { + checkZ(point.getZ()); + return null; + } + + @Override + public Void visit(Rectangle rectangle) throws RuntimeException { + checkZ(rectangle.getMinZ()); + checkZ(rectangle.getMaxZ()); + return null; + } + }); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/WellKnownText.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/WellKnownText.java new file mode 100644 index 000000000..e6cb4ef30 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/WellKnownText.java @@ -0,0 +1,311 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils.geohash; + +import java.io.IOException; +import java.io.StreamTokenizer; +import java.io.StringReader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Locale; + +/** + * Utility class for converting to and from WKT + */ +public class WellKnownText { + /* The instance of WKT serializer that coerces values and accepts Z component */ + public static final WellKnownText INSTANCE = new WellKnownText(true, new StandardValidator(true)); + + public static final String EMPTY = "EMPTY"; + public static final String SPACE = " "; + public static final String LPAREN = "("; + public static final String RPAREN = ")"; + public static final String COMMA = ","; + public static final String NAN = "NaN"; + + private final String NUMBER = ""; + private final String EOF = "END-OF-STREAM"; + private final String EOL = "END-OF-LINE"; + + private final boolean coerce; + private final GeometryValidator validator; + + public WellKnownText(boolean coerce, GeometryValidator validator) { + this.coerce = coerce; + this.validator = validator; + } + + public String toWKT(Geometry geometry) { + StringBuilder builder = new StringBuilder(); + toWKT(geometry, builder); + return builder.toString(); + } + + public void toWKT(Geometry geometry, StringBuilder sb) { + sb.append(getWKTName(geometry)); + sb.append(SPACE); + if (geometry.isEmpty()) { + sb.append(EMPTY); + } else { + geometry.visit(new GeometryVisitor() { + + @Override + public Void visit(Point point) { + if (point.isEmpty()) { + sb.append(EMPTY); + } else { + sb.append(LPAREN); + visitPoint(point.getX(), point.getY(), point.getZ()); + sb.append(RPAREN); + } + return null; + } + + private void visitPoint(double lon, double lat, double alt) { + sb.append(lon).append(SPACE).append(lat); + if (Double.isNaN(alt) == false) { + sb.append(SPACE).append(alt); + } + } + + @Override + public Void visit(Rectangle rectangle) { + sb.append(LPAREN); + // minX, maxX, maxY, minY + sb.append(rectangle.getMinX()); + sb.append(COMMA); + sb.append(SPACE); + sb.append(rectangle.getMaxX()); + sb.append(COMMA); + sb.append(SPACE); + sb.append(rectangle.getMaxY()); + sb.append(COMMA); + sb.append(SPACE); + sb.append(rectangle.getMinY()); + if (rectangle.hasZ()) { + sb.append(COMMA); + sb.append(SPACE); + sb.append(rectangle.getMinZ()); + sb.append(COMMA); + sb.append(SPACE); + sb.append(rectangle.getMaxZ()); + } + sb.append(RPAREN); + return null; + } + }); + } + } + + public Geometry fromWKT(String wkt) throws IOException, ParseException { + StringReader reader = new StringReader(wkt); + try { + // setup the tokenizer; configured to read words w/o numbers + StreamTokenizer tokenizer = new StreamTokenizer(reader); + tokenizer.resetSyntax(); + tokenizer.wordChars('a', 'z'); + tokenizer.wordChars('A', 'Z'); + tokenizer.wordChars(128 + 32, 255); + tokenizer.wordChars('0', '9'); + tokenizer.wordChars('-', '-'); + tokenizer.wordChars('+', '+'); + tokenizer.wordChars('.', '.'); + tokenizer.whitespaceChars(' ', ' '); + tokenizer.whitespaceChars('\t', '\t'); + tokenizer.whitespaceChars('\r', '\r'); + tokenizer.whitespaceChars('\n', '\n'); + tokenizer.commentChar('#'); + Geometry geometry = parseGeometry(tokenizer); + validator.validate(geometry); + return geometry; + } finally { + reader.close(); + } + } + + /** + * parse geometry from the stream tokenizer + */ + private Geometry parseGeometry(StreamTokenizer stream) throws IOException, ParseException { + final String type = nextWord(stream).toLowerCase(Locale.ROOT); + switch (type) { + case "point": + return parsePoint(stream); + case "bbox": + return parseBBox(stream); + } + throw new IllegalArgumentException("Unknown geometry type: " + type); + } + + private Point parsePoint(StreamTokenizer stream) throws IOException, ParseException { + if (nextEmptyOrOpen(stream).equals(EMPTY)) { + return Point.EMPTY; + } + double lon = nextNumber(stream); + double lat = nextNumber(stream); + Point pt; + if (isNumberNext(stream)) { + pt = new Point(lon, lat, nextNumber(stream)); + } else { + pt = new Point(lon, lat); + } + nextCloser(stream); + return pt; + } + + private void parseCoordinates(StreamTokenizer stream, ArrayList lats, ArrayList lons, + ArrayList alts) throws IOException, ParseException { + parseCoordinate(stream, lats, lons, alts); + while (nextCloserOrComma(stream).equals(COMMA)) { + parseCoordinate(stream, lats, lons, alts); + } + } + + private void parseCoordinate(StreamTokenizer stream, ArrayList lats, ArrayList lons, + ArrayList alts) throws IOException, ParseException { + lons.add(nextNumber(stream)); + lats.add(nextNumber(stream)); + if (isNumberNext(stream)) { + alts.add(nextNumber(stream)); + } + if (alts.isEmpty() == false && alts.size() != lons.size()) { + throw new ParseException("coordinate dimensions do not match: " + tokenString(stream), stream.lineno()); + } + } + + private Rectangle parseBBox(StreamTokenizer stream) throws IOException, ParseException { + if (nextEmptyOrOpen(stream).equals(EMPTY)) { + return Rectangle.EMPTY; + } + // TODO: Add 3D support + double minLon = nextNumber(stream); + nextComma(stream); + double maxLon = nextNumber(stream); + nextComma(stream); + double maxLat = nextNumber(stream); + nextComma(stream); + double minLat = nextNumber(stream); + nextCloser(stream); + return new Rectangle(minLon, maxLon, maxLat, minLat); + } + + /** + * next word in the stream + */ + private String nextWord(StreamTokenizer stream) throws ParseException, IOException { + switch (stream.nextToken()) { + case StreamTokenizer.TT_WORD: + final String word = stream.sval; + return word.equalsIgnoreCase(EMPTY) ? EMPTY : word; + case '(': + return LPAREN; + case ')': + return RPAREN; + case ',': + return COMMA; + } + throw new ParseException("expected word but found: " + tokenString(stream), stream.lineno()); + } + + private double nextNumber(StreamTokenizer stream) throws IOException, ParseException { + if (stream.nextToken() == StreamTokenizer.TT_WORD) { + if (stream.sval.equalsIgnoreCase(NAN)) { + return Double.NaN; + } else { + try { + return Double.parseDouble(stream.sval); + } catch (NumberFormatException e) { + throw new ParseException("invalid number found: " + stream.sval, stream.lineno()); + } + } + } + throw new ParseException("expected number but found: " + tokenString(stream), stream.lineno()); + } + + private String tokenString(StreamTokenizer stream) { + switch (stream.ttype) { + case StreamTokenizer.TT_WORD: + return stream.sval; + case StreamTokenizer.TT_EOF: + return EOF; + case StreamTokenizer.TT_EOL: + return EOL; + case StreamTokenizer.TT_NUMBER: + return NUMBER; + } + return "'" + (char) stream.ttype + "'"; + } + + private boolean isNumberNext(StreamTokenizer stream) throws IOException { + final int type = stream.nextToken(); + stream.pushBack(); + return type == StreamTokenizer.TT_WORD; + } + + private String nextEmptyOrOpen(StreamTokenizer stream) throws IOException, ParseException { + final String next = nextWord(stream); + if (next.equals(EMPTY) || next.equals(LPAREN)) { + return next; + } + throw new ParseException("expected " + EMPTY + " or " + LPAREN + " but found: " + tokenString(stream), + stream.lineno()); + } + + private String nextCloser(StreamTokenizer stream) throws IOException, ParseException { + if (nextWord(stream).equals(RPAREN)) { + return RPAREN; + } + throw new ParseException("expected " + RPAREN + " but found: " + tokenString(stream), stream.lineno()); + } + + private String nextComma(StreamTokenizer stream) throws IOException, ParseException { + if (nextWord(stream).equals(COMMA)) { + return COMMA; + } + throw new ParseException("expected " + COMMA + " but found: " + tokenString(stream), stream.lineno()); + } + + private String nextOpener(StreamTokenizer stream) throws IOException, ParseException { + if (nextWord(stream).equals(LPAREN)) { + return LPAREN; + } + throw new ParseException("expected " + LPAREN + " but found: " + tokenString(stream), stream.lineno()); + } + + private String nextCloserOrComma(StreamTokenizer stream) throws IOException, ParseException { + String token = nextWord(stream); + if (token.equals(COMMA) || token.equals(RPAREN)) { + return token; + } + throw new ParseException("expected " + COMMA + " or " + RPAREN + " but found: " + tokenString(stream), + stream.lineno()); + } + + private static String getWKTName(Geometry geometry) { + return geometry.visit(new GeometryVisitor() { + + @Override + public String visit(Point point) { + return "POINT"; + } + + @Override + public String visit(Rectangle rectangle) { + return "BBOX"; + } + }); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/geohash/package-info.java b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/package-info.java new file mode 100644 index 000000000..52735ca2c --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/geohash/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * package collecting classes needed for GeoHash calculations. Copied from Elasticsearch 7.10 code which was the last + * one under the Apache license V2. + * + * @author Peter-Josef Meisch + * @since 4.4 + */ +package org.springframework.data.elasticsearch.utils.geohash; diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 000000000..7afec02a9 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/synonyms/settings.json b/src/test/resources/synonyms/settings.json index 1587fc6b6..a4c380e08 100644 --- a/src/test/resources/synonyms/settings.json +++ b/src/test/resources/synonyms/settings.json @@ -5,6 +5,7 @@ "analysis": { "analyzer": { "synonym_analyzer": { + "type": "custom", "tokenizer": "whitespace", "filter": [ "synonym_filter" @@ -14,7 +15,7 @@ "filter": { "synonym_filter": { "type": "synonym", - "lenient" : true, + "lenient": true, "synonyms": [ "british,english", "queen,monarch" diff --git a/src/test/resources/testcontainers-elasticsearch.properties b/src/test/resources/testcontainers-elasticsearch.properties index d40140cab..37932da82 100644 --- a/src/test/resources/testcontainers-elasticsearch.properties +++ b/src/test/resources/testcontainers-elasticsearch.properties @@ -14,8 +14,6 @@ # limitations under the License. # # -# properties defining the image, these are not passed to the container on startup -# sde.testcontainers.image-name=docker.elastic.co/elasticsearch/elasticsearch sde.testcontainers.image-version=7.17.1 # @@ -23,3 +21,4 @@ sde.testcontainers.image-version=7.17.1 # needed as we do a DELETE /* at the end of the tests, will be required from 8.0 on, produces a warning since 7.13 # action.destructive_requires_name=false +reindex.remote.whitelist=localhost:9200