1
0
mirror of synced 2026-07-07 18:50:00 +00:00

Compare commits

...

207 Commits

Author SHA1 Message Date
Mark Paluch 2bbe88f49a DATAES-808 - Release version 4.0 GA (Neumann). 2020-05-12 12:28:23 +02:00
Mark Paluch f86231353f DATAES-808 - Prepare 4.0 GA (Neumann). 2020-05-12 12:27:59 +02:00
Mark Paluch 5483176be4 DATAES-808 - Updated changelog. 2020-05-12 12:27:58 +02:00
Peter-Josef Meisch 8118713706 DATAES-822 - Convert Reactive Server Exceptions to ElasticsearchStatusException. 2020-05-12 06:22:13 +02:00
Mark Paluch cea8c93616 DATAES-822 - Convert Reactive Client exceptions to ElasticsearchStatusException.
We now use ElasticsearchStatusException instead of HttpClientErrorException to simplify exception translation so that ElasticsearchExceptionTranslator does no longer depend on spring-web.
2020-05-11 17:56:19 +02:00
Peter-Josef Meisch 5100fe04cc DATES-821 - Fix code for adding an alias.
Original PR: #451
2020-05-10 19:45:15 +02:00
Peter-Josef Meisch a4fd008a24 DATAES-819 - Test refactorings.
Original PR: #450
2020-05-09 19:48:26 +02:00
Peter-Josef Meisch ec7414c356 DATAES-817 - StreamQueries does only delete the last scrollid. (#449)
Original PR: #449
2020-05-07 22:11:18 +02:00
Peter-Josef Meisch 3c9b0a7b2f DATAES-814 - Fix documentation.
Original PR: #448
2020-05-06 08:19:48 +02:00
Peter-Josef Meisch 4deea9d667 DATAES-812 - IndexOperations should use SpEL index name from entity.
Original PR: #447
2020-05-05 21:39:06 +02:00
Greg Turnquist 97cb075afe DATAES-811 - Remove Travis CI. 2020-05-04 15:20:28 -05:00
Peter-Josef Meisch e605cad688 DATAES-767 - Fix ReactiveElasticsearch handling of 4xx HTTP responses.
Original PR: #445
2020-05-02 20:08:25 +02:00
Peter-Josef Meisch 07ee01f435 DATAES-750 - Migration guide and documentation update.
Original PR: #444
2020-05-01 22:04:01 +02:00
Peter-Josef Meisch b278bf9819 DATAES-809 - Creation of test data may lead to duplicate values.
Original PR: #443
2020-04-30 08:18:29 +02:00
Peter-Josef Meisch 15f51c5151 DATAES-799 - Polishing. 2020-04-29 20:30:09 +02:00
Roman Puchkovskiy 9b620b31bd DATAES-799 - Support optimistic locking for full update scenario using seq_no + primary_term.
Original PR: #441
2020-04-29 20:11:14 +02:00
Mark Paluch 853980cdfd DATAES-774 - After release cleanups. 2020-04-28 15:11:43 +02:00
Mark Paluch 2e346a4e4a DATAES-774 - Prepare next development iteration. 2020-04-28 15:11:42 +02:00
Mark Paluch 6277330017 DATAES-774 - Release version 4.0 RC2 (Neumann). 2020-04-28 15:03:53 +02:00
Mark Paluch 1d4adadaa7 DATAES-774 - Prepare 4.0 RC2 (Neumann). 2020-04-28 15:03:33 +02:00
Mark Paluch 051af10402 DATAES-774 - Updated changelog. 2020-04-28 15:03:32 +02:00
Mark Paluch 5beaf561df DATAES-770 - Updated changelog. 2020-04-28 14:46:50 +02:00
Mark Paluch 04b739e637 DATAES-755 - Updated changelog. 2020-04-28 11:59:01 +02:00
Peter-Josef Meisch efbe7237f2 DATAES-802 - Polishing. 2020-04-26 17:45:40 +02:00
JBodkin 21ade25fbb DATAES-802 - Update documentation for using scroll API with repository methods.
Original PR: #440
2020-04-26 17:40:30 +02:00
Peter-Josef Meisch a4ec819e7d DATAES-801 - Implement callback to enable adding custom headers in the REST HTTP request.
Original PR: #442
2020-04-26 17:30:46 +02:00
Peter-Josef Meisch 65f89f9480 DATAES-803 - Move count request setup from reactive template to reactive client.
Original PR: #439
2020-04-24 18:00:29 +02:00
Peter-Josef Meisch da31b978bc DATAES-800 - Delombok production code.
Original PR: #438
2020-04-23 23:23:58 +02:00
Peter-Josef Meisch 4876a0e3ab DATAES-797 - Fix MappingElasticsearchConverter recursive descent when reading Map objects.
Original PR: #437
2020-04-23 08:01:46 +02:00
Peter-Josef Meisch 60cbb67877 DATAES-785 - Polishing. 2020-04-21 21:57:31 +02:00
Roman Puchkovskiy 7501c19be4 DATAES-785 - Various entity callbacks implementation improvements.
Original PR: #431
2020-04-21 21:24:22 +02:00
kkonrad 5019793f17 Fix MappingElasticsearchConverter conversion from Document into Map<String, Object>.
Original PR: #436
2020-04-21 20:39:03 +02:00
Peter-Josef Meisch 16d8cc22d1 DATAES-794 - MappingBuilder must not write empty mapping properties.
Original PR: #434
2020-04-20 22:42:04 +02:00
Peter-Josef Meisch 91f442bd2f DATAES-567 - Polishing. 2020-04-20 18:56:11 +02:00
amordleq c2eec8c74a DATAES-567 - Add aggregation support to reactive client. (#430)
Original PR: #430
2020-04-20 18:33:35 +02:00
Peter-Josef Meisch 0afa37c8ea DATAES-792 - Fix tests with timezone.
Original PR: #433
2020-04-19 23:34:44 +02:00
Peter-Josef Meisch e49f140233 DATAES-792 - Add java.util.Date to the supported types for Field annotation date times.
Original PR: #432
2020-04-19 22:58:02 +02:00
Peter-Josef Meisch f6a37f4601 DATAES-772 - Polishing. 2020-04-17 21:40:54 +02:00
Roman Puchkovskiy 76e91c3366 DATAES-772 - Add after-convert entity callbacks support.
Original PR: #422
2020-04-17 21:29:39 +02:00
Peter-Josef Meisch 539c1ee6e7 DATAES-778 - Fix SSL setup in the reactive client.
Original PR: #429
2020-04-16 21:44:53 +02:00
Peter-Josef Meisch 99bf2fc0cb DATAES-786 - Polishing. 2020-04-16 07:06:19 +02:00
Roman Puchkovskiy ff08d06c45 DATAES-786 - Move the creation of SearchHit(s) from ElasticsearchConverter closer to ElasticsearchTemplate..
Original PR: #427
2020-04-16 06:50:16 +02:00
Peter-Josef Meisch 39fb25cbb9 DATAES-791 - DocumentOperations.multiGet() implementations must return null values for not found entities.
Original PR: #428
2020-04-14 22:12:19 +02:00
alesharik 41fffc0fa5 DATAES-789 - Make ElasticsearchRestTemplate.ClientCallback public.
Original PR: #425
2020-04-14 22:02:16 +02:00
Peter-Josef Meisch 771d8fb5e7 DATAES-790 - Deprecate noRefresh repository method.
Original PR: #426
2020-04-12 07:40:52 +02:00
Peter-Josef Meisch 89944b66c4 DATAES-788 - Add missing path mapping to completion context.
Original PR: #424
2020-04-11 11:50:31 +02:00
Peter-Josef Meisch bbc9ec213a DATAES-784 - MappingBuilder should use @Field annotation with custom value objects.
Original PR: #423
2020-04-07 14:40:41 +02:00
Peter-Josef Meisch 3afd6fd427 DATAES-782 - Make underlying TransportClient accessible.
Original PR: #421
2020-04-04 19:19:26 +02:00
Peter-Josef Meisch e449944e3b DATAES-781 - Upgrade to Elasticsearch 7.6.2.
Original PR: #419
2020-04-04 11:10:36 +02:00
Peter-Josef Meisch b434b05215 DATAES-777 - SearchHitsSupport must preserve pageable when unwrapping to AggregatedPage.
Original PR: #418
2020-04-01 21:18:52 +02:00
Peter-Josef Meisch bce9da4343 DATAES-766 - Adapt RestClients class to change in InetSocketAddress class in JDK14.
Original PR: #417
2020-04-01 07:55:04 +02:00
Peter-Josef Meisch 403d9d08e2 DATAES-775 Fix test runner setup.
Original PR: #416
2020-03-31 22:15:30 +02:00
Peter-Josef Meisch 1cd572125e DATAES-773 - Polishing. 2020-03-31 20:36:36 +02:00
alesharik 40752c2235 DATAES-773 - Add search-as-you-type field support.
Original PR: #415
2020-03-31 20:24:44 +02:00
Mark Paluch 40838e187c DATAES-762 - After release cleanups. 2020-03-31 15:08:05 +02:00
Mark Paluch d0cfd498ee DATAES-762 - Prepare next development iteration. 2020-03-31 15:08:04 +02:00
Mark Paluch ed5fe948d1 DATAES-762 - Release version 4.0 RC1 (Neumann). 2020-03-31 14:59:41 +02:00
Mark Paluch 27871899b0 DATAES-762 - Prepare 4.0 RC1 (Neumann). 2020-03-31 14:59:20 +02:00
Mark Paluch 863c54f24f DATAES-762 - Updated changelog. 2020-03-31 14:59:19 +02:00
Peter-Josef Meisch abf886e877 DATAES-771 - Polishing. 2020-03-29 19:54:00 +02:00
Roman Puchkovskiy c73d1973bc DATAES-771 - Add after-save entity callbacks support.
Original PR: #414
2020-03-29 19:50:56 +02:00
Peter-Josef Meisch a7cdfb84b0 DATAES-653 - Polishing. 2020-03-29 19:09:53 +02:00
Roman Puchkovskiy 0b378601d9 DATAES-653 - Make it easier to use a custom request converter when extending DefaultReactiveElasticsearchClient.
Original PR: #411
2020-03-29 19:06:56 +02:00
Peter-Josef Meisch 2ec61ab4ff DATAES-435 - Report version mismatch if used with older ElasticSearch-version.
Original PR: #413
2020-03-28 11:09:06 +01:00
Peter-Josef Meisch 21406a5e7d DATAES-766 - Replace CloseableIterator with SearchHitsIterator in stream operations.
Original PR: #412

fix documentation
2020-03-27 17:19:56 +01:00
Mark Paluch fd711093b1 DATAES-756 - Updated changelog. 2020-03-25 10:59:47 +01:00
Peter-Josef Meisch f8630a09df Dataes 768 add missing query parameters for an update query.
Original PR: #410
2020-03-23 20:59:30 +01:00
xhaggi f354f986ca DATAES-766 - Replace CloseableIterator with SearchHitsIterator in stream operations.
Original pull request: #407
2020-03-22 09:52:35 +01:00
Peter-Josef Meisch db28d93676 DATAES-763 - Polishing. 2020-03-22 08:46:57 +01:00
gsrinivas10 a92970236c DATAES-763 - Allow map properties in entity with null values.
Original PR: #405
2020-03-22 08:29:32 +01:00
Peter-Josef Meisch 745f9e9d79 DATAES-765 - Pageable.unpaged() is not used to build a query returning all documents.
Original PR: #408
2020-03-20 18:27:50 +01:00
Sascha Woo f103bdb9d8 DATAES-764 - StreamQueries#streamResults does not clear scroll context when finished.
Original PR: #406
2020-03-18 20:37:05 +01:00
Peter-Josef Meisch 300eb313dd DATAES-68 - Add support for auditing annotations.
Original PR: #400
2020-03-11 18:39:11 +01:00
Peter-Josef Meisch 0b0c8027a3 DATAES-751 - Introduce ClientCallback for the rest client.
Original PR: #401
2020-03-11 12:34:56 +01:00
Jens Schauder ab0c6a8f66 DATAES-744 - After release cleanups. 2020-03-11 09:57:43 +01:00
Jens Schauder 9e81d39194 DATAES-744 - Prepare next development iteration. 2020-03-11 09:57:42 +01:00
Jens Schauder 8a6f56a89e DATAES-744 - Release version 4.0 M4 (Neumann). 2020-03-11 09:47:07 +01:00
Jens Schauder 3a528c7039 DATAES-744 - Prepare 4.0 M4 (Neumann). 2020-03-11 09:46:29 +01:00
Jens Schauder 31ea4548d7 DATAES-744 - Updated changelog. 2020-03-11 09:46:27 +01:00
Peter-Josef Meisch 9258cdde84 DATAES-759 - Update to Elasticsearch 7.6.1.
OriginalPR: #404
2020-03-07 23:00:19 +01:00
Peter-Josef Meisch ec214a009e DATAES-758 - Fix documentation for @Query annotation.
Original PR: #403
2020-03-06 19:08:49 +01:00
Peter-Josef Meisch 251adc1eec DATAES-754 - Completion field deserialization is failing due to class cast error.
Original PR: #399
2020-02-26 22:34:44 +01:00
Peter-Josef Meisch bf51de3805 DATAES-753 - Reactive Elasticsearch repository: Bulk update fails on empty entity list.
Original PR: #398
2020-02-26 21:50:42 +01:00
Peter-Josef Meisch 6a4a7483aa DATAES-749 - Introduce SearchPage as return type for repository methods.
Original PR: #397
2020-02-26 21:13:30 +01:00
Mark Paluch dc795eb7ee DATAES-730 - Updated changelog. 2020-02-26 11:55:05 +01:00
Mark Paluch f541133569 DATAES-729 - Updated changelog. 2020-02-26 11:31:48 +01:00
Peter-Josef Meisch 96ebc72dd1 Dataes 745 consolidate operations api
Original PR: #396
2020-02-25 22:01:40 +01:00
Roman Puchkovskiy 1f56a9b9fe DATAES-741 / Tests fail due to Elasticsearch cluster 'blocks' on nearly-full file-systems.
Original PR: #393
2020-02-15 16:22:40 +01:00
Peter-Josef Meisch 9696f418fd DATAES-747 - ElasticsearchConfigurationSupport does not set customConversions into the MappingElasticsearchConverter.
Original PR: #395
2020-02-15 14:45:41 +01:00
Peter-Josef Meisch 36a3d5943a DATAES-746 - Add store converters to convert binary data.
Original PR: #394
2020-02-14 21:24:51 +01:00
Mark Paluch 05df3c3805 DATAES-732 - After release cleanups. 2020-02-12 15:04:22 +01:00
Mark Paluch ead2cb13b7 DATAES-732 - Prepare next development iteration. 2020-02-12 15:04:21 +01:00
Mark Paluch a9295947c6 DATAES-732 - Release version 4.0 M3 (Neumann). 2020-02-12 14:47:16 +01:00
Mark Paluch c85c20e039 DATAES-732 - Prepare 4.0 M3 (Neumann). 2020-02-12 14:46:56 +01:00
Mark Paluch 94f8623349 DATAES-732 - Updated changelog. 2020-02-12 14:46:55 +01:00
Peter-Josef Meisch 77f43ce64c DATAES-738 - Removing one save method as it does not fit the naming scheme. 2020-02-11 19:03:00 +01:00
Peter-Josef Meisch 9c64dd9c39 DATAES-743 - Revert geo converters to back to store converters.
Original PR: #390
2020-02-11 18:33:58 +01:00
Peter-Josef Meisch b2ffc236bf DATAES-738 - Add entity related save methods to DocumentOperations.
Original PR: #389
2020-02-10 22:09:49 +01:00
Peter-Josef Meisch 0c15eef858 DATAES-740 - Adapt to spring-data-commons changes.
Original PR: #386
2020-02-09 19:31:16 +01:00
Peter-Josef Meisch 936de20421 DATAES-739 - Introduce nullable annotations for API validation.
Original PR: #387
2020-02-09 13:00:25 +01:00
Peter-Josef Meisch 4fc4d91b74 DATAES-735 - Update to Elasticsearch 7.5.2.
Original PR: #384
2020-01-25 21:36:34 +01:00
Peter-Josef Meisch 13761b073a DATAES-449 - Pass route parameter to created search request.
Original PR: #383
2020-01-25 18:54:51 +01:00
Peter-Josef Meisch 37f15853c0 DATAES-734 - Add Sort implementation that allows geo distance sorts.
Original PR: #382
2020-01-23 18:03:37 +01:00
Mark Paluch bf13ed919f DATAES-731 - After release cleanups. 2020-01-17 09:47:50 +01:00
Mark Paluch 9e3712e41e DATAES-731 - Prepare next development iteration. 2020-01-17 09:47:49 +01:00
Mark Paluch 033a6b21dc DATAES-731 - Release version 4.0 M2 (Neumann). 2020-01-17 09:39:03 +01:00
Mark Paluch 1f19ac0313 DATAES-731 - Prepare 4.0 M2 (Neumann). 2020-01-17 09:38:43 +01:00
Mark Paluch 2b5619f172 DATAES-731 - Updated changelog. 2020-01-17 09:38:42 +01:00
Mark Paluch bf75f0fab2 DATAES-663 - After release cleanups. 2020-01-16 16:11:55 +01:00
Mark Paluch 86c621de35 DATAES-663 - Prepare next development iteration. 2020-01-16 16:11:54 +01:00
Mark Paluch 191ce92743 DATAES-663 - Release version 4.0 M1 (Neumann). 2020-01-16 16:05:23 +01:00
Mark Paluch fe8d2fe1a2 DATAES-663 - Prepare 4.0 M1 (Neumann). 2020-01-16 16:05:03 +01:00
Mark Paluch 2677e39bb1 DATAES-663 - Updated changelog. 2020-01-16 16:05:02 +01:00
Mark Paluch fe0359f40b DATAES-704 - Updated changelog. 2020-01-15 12:51:13 +01:00
Mark Paluch 28d31fbb07 DATAES-703 - Updated changelog. 2020-01-15 10:36:36 +01:00
Mark Paluch 9e5a828342 DATAES-690 - Polishing.
Revert parent pom version to 2.3.0.BUILD-SNAPSHOT.
2020-01-14 14:50:04 +01:00
Peter-Josef Meisch e5ec8fdab3 DATAES-623 - Polishing. 2020-01-13 20:35:03 +01:00
alesharik d7262e4370 DATAES-623 - Add bulk operations for ReactiveElasticsearchRepository.
Original PR:#376
2020-01-13 20:26:19 +01:00
Peter-Josef Meisch e670a88772 DATAES-724 - Provide IndexOperations bean.
Original PR: #381
2020-01-12 18:11:40 +01:00
Peter-Josef Meisch 513741bcf6 DATAES-701 - Enable proxy support for the reactive rest client.
Original PR: #380
2020-01-11 21:13:42 +01:00
Peter-Josef Meisch d55cb00d45 DATAES-727 - Use track_total_hits parameter for count queries.
Original PR: #379
2020-01-10 15:52:03 +01:00
Peter-Josef Meisch 7294accaca DATAES-661 - Added track_total_hits request parameter to Query.
Original PR: #378
2020-01-09 23:29:34 +01:00
Greg Turnquist 0843481cc5 DATAES-690 - Enable JDK 11+ builds. 2020-01-09 10:10:50 -06:00
Mark Paluch 8472c296f6 DATAES-725 - Update copyright years to 2020. 2020-01-07 09:08:10 +01:00
Peter-Josef Meisch 0693923798 DATAES-372 - Support highlighting via annotation.
Original PR: #377
2020-01-06 22:25:54 +01:00
Peter-Josef Meisch d3c624c28a DATAES-461 - Polishing. 2020-01-05 10:40:59 +01:00
alesharik 5eb83464a1 DATAES-461 - Make deleteAll(entities) more performant.
Original PR: #375
2020-01-05 10:36:14 +01:00
Peter-Josef Meisch 9fea1365ac DATAES-718 - Deprecate @Score and scoreProperty.
original PR: #374
2020-01-04 22:19:01 +01:00
Peter-Josef Meisch 16e0f566cd DATAES-723 - Cleanup ElasticsearchRepository interface.
Original PR: #373
2020-01-04 17:44:19 +01:00
Peter-Josef Meisch 0d272fe9bf DATAES-717 - Enable Repositories to return a SearchHits instance instead of a list.
Original PR: #372
2020-01-03 23:20:17 +01:00
Peter-Josef Meisch e2d4ed96c8 DATAES-278 - Fix documentation for created queries from repository method names.
Original PR: #371
2020-01-02 13:27:32 +01:00
Peter-Josef Meisch bd047776e2 DATAES-711 - Update to Elasticsearch 7.5.1.
Original PR: #370
2019-12-31 19:39:20 +01:00
Peter-Josef Meisch 5c862e80bf DATAES-722 - Return total count relation in the SearchHits object.
Original PR: #369
2019-12-31 13:55:56 +01:00
Peter-Josef Meisch 90d29994f1 DATAES-715 - Highlight results should be returned in the SearchHits.
Original PR: #368
2019-12-30 17:08:52 +01:00
Peter-Josef Meisch d026884c12 DATAES-721 - Deprecation and Warnings cleanup.
Original PR: #367
2019-12-29 21:46:58 +01:00
Peter-Josef Meisch a68c6ba5d7 Dataes 716 - Add value mapping to the ElasticsearchMappingConverter.
Original PR: #366
2019-12-28 19:25:25 +01:00
Peter-Josef Meisch d2b7df87f4 DATAES-713 - Add returned aggregations from the AggregatedPage to the SearchHits.
Original PR: #365
2019-12-26 20:14:04 +01:00
Peter-Josef Meisch b634f318ab DATAES-720 - SimpleReactiveElasticsearchRepository findAll() returns only 10 elements.
Original PR: #364
2019-12-25 10:40:10 +01:00
Peter-Josef Meisch f7a14c1135 DATAES-719 - Add customization hook for reactive WebClient.
Original PR: #363
2019-12-25 08:35:48 +01:00
Peter-Josef Meisch 6dfeee3ba9 DATAES-676 - Fix documentation to reflect the changes in API restructuring.
Original PR: #362
2019-12-24 09:23:23 +01:00
Peter-Josef Meisch d19e699b32 DATAES-714 - Sort results should be returned in the SearchHits.
Original PR: #361
2019-12-22 14:25:43 +01:00
Peter-Josef Meisch e55bae725e DATAES-672 - Introduce SearchHit and SearchHits types to enrich search results.
Original PR: #359
2019-12-22 12:15:46 +01:00
Peter-Josef Meisch 2bb3fdfa8b DATAES-314 - NodeClientFactoryBean doesn't shutdown ES on destroy life-cycle.
Original PR: #360
2019-12-15 15:53:52 +01:00
Sascha Woo 47d0104295 DATAES-709 - Add parameter to include defaults on get settings.
Original PR: #357
2019-12-15 09:44:37 +01:00
Peter-Josef Meisch dec5231a05 DATAES-239 - Query for null values.
Original PR: #355
2019-12-11 22:09:02 +01:00
Peter-Josef Meisch 11a6430a90 DATAES-634 - Rearrange methods in Template API.
Original PR: #352
2019-12-10 19:04:37 +01:00
Sascha Woo ff381c63b6 DATAES-187 - Use Pageable#getOffset to set from offset in SearchRequest.
Original PR: #353
2019-12-06 13:15:15 +01:00
Jens Schauder 9d4fe6d4af DATAES-692 - Updated changelog. 2019-12-04 14:32:47 +01:00
Jens Schauder e882dae3af DATAES-691 - Updated changelog. 2019-12-04 12:09:50 +01:00
Sascha Woo aa4ae525f4 DATAES-702 - Travis CI builds currently broken.
Original PR: #351
2019-12-03 12:16:52 +01:00
Peter-Josef Meisch c3714dd6e5 DATAES-675 - Migrate tests to JUnit 5.
Original PR: #350
2019-12-01 18:31:51 +01:00
Peter-Josef Meisch 03fd603909 DATAES-700 - Enable proxy support for RestClient.
Original PR: #348
2019-11-30 23:00:19 +01:00
Peter-Josef Meisch 35e9b691bd DATAES-697_-_Query-refactoring-cleanup.
Original PR: #347
2019-11-30 15:49:14 +01:00
Peter-Josef Meisch 2cd18178e3 DATAES-631 - Consolidate query objects.
Original PR: #340
2019-11-28 23:41:13 +01:00
MassimilianoPoggi 79d75f814c DATAES-693 - Support for source fetching in update operations.
Original PR: #346
2019-11-18 20:31:35 +01:00
Mark Paluch eba23eeb17 DATAES-685 - Updated changelog. 2019-11-18 12:43:26 +01:00
Mark Paluch 8e5f0dd341 DATAES-683 - Updated changelog. 2019-11-18 12:16:27 +01:00
Peter-Josef Meisch 3f82108734 DATAES-675 - Migrate tests to JUnit 5.
OriginalPR: #345
2019-11-09 21:40:48 +01:00
Peter-Josef Meisch 8bc133d955 DATAES-148 - Dynamic mapping setting.
Original PR: #344
2019-11-09 20:54:44 +01:00
Peter-Josef Meisch 26ab706e87 DATAES-688 - Remove unneeded SearchQuery subinterface.
Original PR: #343
2019-11-08 16:44:52 +01:00
Peter-Josef Meisch 62385edaa5 DATAES-680 - ReactiveElasticsearchTemplate-should-use-the-count-API.
Original PR: #341
2019-11-06 19:14:47 +01:00
Peter-Josef Meisch 24751972a8 DATAES-684 - Polishing. 2019-11-05 10:05:14 +01:00
Henrique Amaral 6ae424428c DATAES-684 Implement bulk request from reactive client
Original PR: #342 

* DATAES-684 Implement bulk request from reactive client

* Update src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java

Co-Authored-By: Peter-Josef Meisch <pj.meisch@sothawo.com>

* DATAES-684 Implement bulk request from reactive client

Added author
2019-11-05 09:57:13 +01:00
Christoph Strobl a4b9a764c9 DATAES-662 - Updated changelog. 2019-11-04 15:39:57 +01:00
Christoph Strobl 4f66a9bcda DATAES-660 - Updated changelog. 2019-11-04 10:34:52 +01:00
Peter-Josef Meisch c46a6f4c64 DATAES-675 - Migrate tests to JUnit5.
Original PR: #338
2019-10-28 21:24:08 +01:00
Peter-Josef Meisch c50af32fc3 DATAES-677 - Update to Elasticsearch 7.4.1.
Original PR: #337
2019-10-27 12:23:42 +01:00
Peter-Josef Meisch a438b38f05 DATAES-675 - Migrate tests to JUnit5.
Original PR: #336
2019-10-25 23:27:33 +02:00
Peter-Josef Meisch 4e7f1ccb4b DATAES-630 - Remove GetResultMapper and friends from core package.
Original PR: #331
2019-10-25 22:05:58 +02:00
Peter-Josef Meisch d4f23a50e7 DATAES-316 - Revert: NodeClientFactoryBean doesn't shutdown ES on destroy life-cycle.
Original PR: #335
2019-10-25 21:38:46 +02:00
Peter-Josef Meisch 29f34b05d3 DATAES-314 - NodeClientFactoryBean doesn't shutdown ES on destroy life-cycle.
Original PR: #333
2019-10-22 19:44:02 +02:00
Henrique Amaral f7f103d331 DATAES-673 Create a Ssl Rest Client using SslContext and HostnameVerifier
Original PR: #334
2019-10-22 19:02:19 +02:00
Peter-Josef Meisch 38353e5bdd DATAES-654 - Add JUnit5 support. 2019-10-21 18:06:49 +02:00
Peter-Josef Meisch 58920af37a DATAES-654 - Add Junit5 support.
Original PR: #329
2019-10-20 20:05:33 +02:00
Gyula Csörögi 4682636333 DATAES-671 - Missing indicesOptions support for scrolling queries.
Original PR: #332
2019-10-16 06:35:34 +02:00
Mark Paluch ba76986172 DATAES-619 - Migrate remaining tests to AssertJ. 2019-10-14 14:21:23 +02:00
Peter-Josef Meisch c4bb59e545 DATAES-670 - Fix version compatibility matrix in documentation.
Original PR: #330
2019-10-12 10:46:13 +02:00
Peter-Josef Meisch d72ee18944 DATES-666 - Rebase 4.0.x onto master and merge. 2019-10-01 20:49:00 +02:00
Peter-Josef Meisch d3295a6416 DATAES-659 - Move MappingElasticsearchConverter to correct package.
Original PR: #328
2019-10-01 20:11:39 +02:00
Peter-Josef Meisch 1b12d81b81 DATAES-685 - update to ES 7.3.2.
Original PR: #326
2019-10-01 20:11:39 +02:00
Peter-Josef Meisch 4b9bcfb410 DATAES-650 - Polishing. 2019-10-01 20:11:06 +02:00
Huw 948d29ba65 DATAES-650 - Add support for pathPrefix to configuration classes.
Original pull request: #320.
2019-10-01 20:11:06 +02:00
Peter-Josef Meisch b820c9a422 DATAES-629 - ElasticsearchEntityMapper refactoring.
Original PR: #309
2019-10-01 20:11:06 +02:00
Peter-Josef Meisch 7a4aebf9f4 DATAES-651 - Fix regression from escaping query parameters.
Original PR: #321
2019-10-01 20:04:29 +02:00
Peter-Josef Meisch 5364642c84 DATAES-213 - Polishing. 2019-10-01 20:04:29 +02:00
JBodkin 0b062a4a52 DATAES-213 - Escape text in queries.
Original PR:  #318
2019-10-01 20:04:29 +02:00
Peter-Josef Meisch 40d0ec2801 DATAES-647 - Polishing. 2019-10-01 20:04:29 +02:00
rasmusfaber 486a112046 DATAES-647: Use terms-query instead of multiple should-queries for In and NotIn.
Original PR: #315
2019-10-01 20:04:29 +02:00
Mark Paluch 5efe47f868 DATAES-646 - Adapt to Spring Framework 5.2 RC2.
Implement missing methods in RawActionResponse. Consistent versions actross netty dependencies.
2019-10-01 20:04:29 +02:00
Peter-Josef Meisch f0117e7b6a DATAES-621 - Add missing fieldtypes and parameters to mapping.
Original PR: #311
2019-10-01 19:22:33 +02:00
Peter-Josef Meisch 490347f286 DATAES-290 - Make joda-time optional dependency.
Original PR: #308 and #213
2019-10-01 19:22:33 +02:00
Peter-Josef Meisch d864ff1292 DATAES-176 - firstN repository queries.
Original PR: #305
2019-10-01 19:22:33 +02:00
Peter-Josef Meisch b4ebca7ab5 DATAES-142 - AbstractRepository.saveAll can process empty list.
Original PR: #307
2019-10-01 19:22:33 +02:00
Peter-Josef Meisch 4bb7b5ef56 DATAES-495 - Deprecate TransportClient support.
Original PR: #306
2019-10-01 19:22:33 +02:00
P.J. Meisch 165e02d6e7 DATAES-639 - Polishing. 2019-10-01 19:19:21 +02:00
Xiao Yu a9c3058bef DATAES-639 - Add ignore_above mapping parameter support.
Original pull request: #304.
2019-10-01 19:19:21 +02:00
P.J. Meisch 2712a1e021 DATAES-628 - documentation, assertions, ES7 adaption. 2019-10-01 19:19:21 +02:00
Mark Paluch ba2d8594a1 Update src/main/java/org/springframework/data/elasticsearch/core/DocumentAdapters.java
Co-Authored-By: Peter-Josef Meisch <pj.meisch@sothawo.com>
2019-10-01 19:19:21 +02:00
Mark Paluch 7a612f3ba8 DATAES-628 - Adapt mappers to Document API. 2019-10-01 19:19:21 +02:00
Mark Paluch 29ecd484c5 DATAES-628 - Introduce Document API.
We now expose a Document API to encapsulate data interchanged with Elasticsearch (get responses, search hits, arbitrary maps) for a consistent API.
2019-10-01 19:19:21 +02:00
Peter-Josef Meisch ce686b1f03 DATAES-637 - Change branch 4.0. to use Elasticsearch 7.3.
Original PR: #302
2019-10-01 19:19:20 +02:00
P.J. Meisch 9cb9c72acc DATAES-638 - Polishing. 2019-10-01 19:17:12 +02:00
Xiao Yu 1e96f7f3b4 DATAES-638 - Remove redundant public modifiers in @MultiField.
Original PR: #303
2019-10-01 19:17:12 +02:00
Christoph Strobl 7725689606 DATAES-635 - Create branch 4.0.x and integrate the changes from the existing 4.x branch. 2019-10-01 19:17:12 +02:00
Mark Paluch 84380cef0b DATAES-665 - Fix plugin ordering to deploy Javadoc. 2019-10-01 08:38:48 +02:00
Mark Paluch be1a81e40f DATAES-625 - After release cleanups. 2019-09-30 16:17:29 +02:00
Mark Paluch 4bc5189092 DATAES-625 - Prepare next development iteration. 2019-09-30 16:17:28 +02:00
520 changed files with 28569 additions and 15270 deletions
-22
View File
@@ -1,22 +0,0 @@
language: java
jdk:
- oraclejdk8
before_install:
- curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}.deb && sudo dpkg -i --force-confnew elasticsearch-${ES_VERSION}.deb && sudo service elasticsearch restart
- sleep 10
env:
global:
- ES_VERSION=6.5.1
addons:
apt:
packages:
- oracle-java8-set-default
- oracle-java8-installer
sudo: true
script: "mvn clean dependency:list test -Dsort"
Vendored
+41 -4
View File
@@ -12,7 +12,28 @@ pipeline {
}
stages {
stage("Test") {
stage("test: baseline (jdk8)") {
when {
anyOf {
branch 'master'
not { triggeredBy 'UpstreamCause' }
}
}
agent {
docker {
image 'adoptopenjdk/openjdk8:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
}
options { timeout(time: 30, unit: 'MINUTES') }
steps {
sh 'rm -rf ?'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
}
}
stage("Test other configurations") {
when {
anyOf {
branch 'master'
@@ -20,10 +41,10 @@ pipeline {
}
}
parallel {
stage("test: baseline") {
stage("test: baseline (jdk11)") {
agent {
docker {
image 'adoptopenjdk/openjdk8:latest'
image 'adoptopenjdk/openjdk11:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
@@ -31,11 +52,27 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
steps {
sh 'rm -rf ?'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Dsort -U -B'
}
}
stage("test: baseline (jdk12)") {
agent {
docker {
image 'adoptopenjdk/openjdk12:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
}
options { timeout(time: 30, unit: 'MINUTES') }
steps {
sh 'rm -rf ?'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Dsort -U -B'
}
}
}
}
stage('Release to artifactory') {
when {
anyOf {
+39 -34
View File
@@ -11,10 +11,10 @@ This project is lead and maintained by the community.
== Features
* Spring configuration support using Java based `@Configuration` classes or an XML namespace for a ES clients instances.
* `ElasticsearchTemplate` helper class that increases productivity performing common ES operations. Includes integrated object mapping between documents and POJOs.
* `ElasticsearchRestTemplate` helper class that increases productivity performing common ES operations. Includes integrated object mapping between documents and POJOs.
* Feature Rich Object Mapping integrated with Springs Conversion Service
* Annotation based mapping metadata but extensible to support other metadata formats
* Automatic implementation of `Repository` interfaces including support for custom finder methods.
* Annotation based mapping metadata
* Automatic implementation of `Repository` interfaces including support for custom search methods.
* CDI support for repositories
== Code of Conduct
@@ -53,51 +53,56 @@ public class MyService {
repository.save(person);
List<Person> lastNameResults = repository.findByLastname("Gierke");
List<Person> firstNameResults = repository.findByFirstnameLike("Oli*");
List<Person> firstNameResults = repository.findByFirstnameLike("Oli");
}
}
----
Using Node Client
=== Using Transport Client
[source,xml]
NOTE: Usage of the TransportClient is deprecated as of version 4.0, use RestClient instead.
[source,java]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
<elasticsearch:node-client id="client" local="true"/>
@Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
return client;
}
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
return new ElasticsearchTemplate(elasticsearchClient());
}
}
----
Using Transport Client
=== Using the RestClient
[source,xml]
Provide a configuration like this:
[source,java]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
<elasticsearch:repositories base-package="com.xyz.acme"/>
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
<elasticsearch:transport-client id="client" cluster-nodes="ip:9300,ip:9300" cluster-name="elasticsearch" />
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
return RestClients.create(clientConfiguration).rest();
}
}
----
=== Maven configuration
@@ -190,7 +195,7 @@ You also need JDK 1.8.
If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.5.0 or above].
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributors Agreement] before your first non-trivial change._
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributors Agreement] before submitting your first pull request._
=== Building reference documentation
+60 -16
View File
@@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>3.2.0.RELEASE</version>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.0.0.RELEASE</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.2.0.RELEASE</version>
<version>2.3.0.RELEASE</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -19,9 +19,9 @@
<properties>
<commonslang>2.6</commonslang>
<elasticsearch>6.8.1</elasticsearch>
<elasticsearch>7.6.2</elasticsearch>
<log4j>2.9.1</log4j>
<springdata.commons>2.2.0.RELEASE</springdata.commons>
<springdata.commons>2.3.0.RELEASE</springdata.commons>
<netty>4.1.39.Final</netty>
<java-module-name>spring.data.elasticsearch</java-module-name>
</properties>
@@ -144,6 +144,7 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime}</version>
<optional>true</optional>
</dependency>
<!-- Elasticsearch -->
@@ -248,6 +249,24 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.26.3</version>
<scope>test</scope>
<exclusions>
<!-- these exclusions are needed because of Elasticsearch JarHell-->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Upgrade xbean to 4.5 to prevent incompatibilities due to ASM versions -->
<dependency>
<groupId>org.apache.xbean</groupId>
@@ -258,23 +277,39 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/versions.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/versions.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
</plugin>
<!--
please do not remove this configuration for surefire - we need that to avoid issue with jar hell
-->
@@ -286,12 +321,21 @@
<useFile>false</useFile>
<includes>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
<systemPropertyVariables>
<es.set.netty.runtime.available.processors>false</es.set.netty.runtime.available.processors>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
+10 -1
View File
@@ -5,13 +5,14 @@ BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Stro
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
(C) 2013-2019 The original author(s).
(C) 2013-2020 The original author(s).
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
toc::[]
include::preface.adoc[]
:leveloffset: +1
include::{spring-data-commons-docs}/repositories.adoc[]
:leveloffset: -1
@@ -23,7 +24,15 @@ include::{spring-data-commons-docs}/repositories.adoc[]
include::reference/elasticsearch-clients.adoc[]
include::reference/elasticsearch-object-mapping.adoc[]
include::reference/elasticsearch-operations.adoc[]
include::reference/elasticsearch-repositories.adoc[]
include::{spring-data-commons-docs}/auditing.adoc[]
include::reference/elasticsearch-auditing.adoc[]
include::{spring-data-commons-docs}/entity-callbacks.adoc[]
include::reference/elasticsearch-entity-callbacks.adoc[leveloffset=+1]
include::reference/elasticsearch-misc.adoc[]
:leveloffset: -1
+8 -6
View File
@@ -3,12 +3,13 @@
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. It provides:
* _Templates_ as a high-level abstraction for storing, querying, sorting and faceting documents.
* _Templates_ as a high-level abstraction for storing, searching, sorting documents and building aggregations.
* _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>).
You will notice similarities to the Spring data solr and mongodb support in the Spring Framework.
include::reference/elasticsearch-new.adoc[leveloffset=+1]
include::reference/elasticsearch-migration-guide-3.2-4.0.adoc[leveloffset=+1]
[[preface.metadata]]
== Project Metadata
@@ -34,11 +35,12 @@ Requires an installation of https://www.elastic.co/products/elasticsearch[Elasti
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of Spring Data Elasticsearch included in that, as well as the Spring Boot versions refering to that particular Spring Data release train:
[cols="^,^,^,^",options="header"]
|===
|Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
|Moorefootnote:cdv[Currently in development] |3.2.xfootnote:cdv[]|6.8.1 / 7.xfootnote:hrc[via the <<elasticsearch.clients.rest,high-level REST client>>]|2.2.0footnote:cdv[]
|Lovelace |3.1.x |6.2.2 / 7.xfootnote:hrc[]|2.1.x
|Kayfootnote:oom[Out of maintenance]|3.0.xfootnote:oom[] |5.5.0 |2.0.xfootnote:oom[]
|Ingallsfootnote:oom[]|2.1.xfootnote:oom[] |2.4.0 |1.5.xfootnote:oom[]
| Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
| Neumannfootnote:cdv[Currently in development] |4.0.xfootnote:cdv[]|7.6.2 |2.3.xfootnote:cdv[]
| Moore | 3.2.x |6.8.6 | 2.2.x
| Lovelace | 3.1.x | 6.2.2 |2.1.x
| Kayfootnote:oom[Out of maintenance] | 3.0.xfootnote:oom[] | 5.5.0 | 2.0.xfootnote:oom[]
| Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 1.5.xfootnote:oom[]
|===
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the <<elasticsearch.clients.rest,high-level REST client>>.
@@ -0,0 +1,68 @@
[[elasticsearch.auditing]]
== Elasticsearch Auditing
=== Preparing entities
In order for the auditing code to be able to decide wether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
[source,java]
----
package org.springframework.data.domain;
import org.springframework.lang.Nullable;
public interface Persistable<ID> {
@Nullable
ID getId();
boolean isNew();
}
----
As the existence of an Id is not a sufficient criterion to determine if an enitity is new in Elasticsearch, additional information is necessary. One way is to use the creation-relevant auditing fields for this decision:
A `Person` entity might look as follows - omitting getter and setter methods for brevity:
[source,java]
----
@Document(indexName = "person")
public class Person implements Persistable<Long> {
@Id private Long id;
private String lastName;
private String firstName;
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
private Instant createdDate;
private String createdBy
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
private Instant lastModifiedDate;
private String lastModifiedBy;
public Long getId() { <1>
return id;
}
@Override
public boolean isNew() {
return id == null || (createdDate == null && createdBy == null); <2>
}
}
----
<1> the getter also is the required implementation from the interface
<2> an object is new if it either has no `id` or none of fields containing creation attributes are set.
=== Activating auditing
After the entities have been set up and providing the `AuditorAware` the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
[source,java]
----
@Configuration
@EnableElasticsearchRepositories
@EnableElasticsearchAuditing
class MyConfiguration {
// configuration code
}
----
If your code contains more than one `AuditorAware` bean for different types, you must provide the name of the bean to use as an argument to the `auditorAwareRef` parameter of the
`@EnableElasticsearchAuditing` annotation.
@@ -3,13 +3,13 @@
This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
Spring data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.transport]]
== Transport Client
WARNING: The well known `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used
Elasticsearch <<elasticsearch.versions,version>>.
Elasticsearch <<elasticsearch.versions,version>> but has deprecated the classes using it since version 4.0.
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
@@ -17,18 +17,21 @@ We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `
====
[source,java]
----
static class Config {
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean
Client client() {
Settings settings = Settings.builder()
.put("cluster.name", "elasticsearch") <1>
.build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1")
, 9300)); <2>
return client;
}
@Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build(); <1>
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); <2>
return client;
}
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
return new ElasticsearchTemplate(elasticsearchClient());
}
}
// ...
@@ -46,7 +49,7 @@ IndexResponse response = client.index(request);
[[elasticsearch.clients.rest]]
== High Level REST Client
The Java High Level REST Client now is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns
The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns
the very same request/response objects and therefore depends on the Elasticsearch core project.
Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done.
@@ -54,18 +57,19 @@ Asynchronous calls are operated upon a client managed thread pool and require a
====
[source,java]
----
import org.springframework.beans.factory.annotation.Autowired;@Configuration
static class Config {
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Bean
RestHighLevelClient client() {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
.connectedTo("localhost:9200", "localhost:9201")
.build();
final ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest(); <2>
}
return RestClients.create(clientConfiguration).rest(); <2>
}
}
// ...
@@ -73,7 +77,7 @@ static class Config {
@Autowired
RestHighLevelClient highLevelClient;
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
// ...
@@ -104,8 +108,15 @@ static class Config {
@Bean
ReactiveElasticsearchClient client() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
.connectedTo("localhost:9200", "localhost:9291")
.withWebClientConfigurer(webClient -> { <2>
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs()
.maxInMemorySize(-1))
.build();
return webClient.mutate().exchangeStrategies(exchangeStrategies).build();
})
.build();
return ReactiveRestClients.create(clientConfiguration);
@@ -124,6 +135,7 @@ Mono<IndexResponse> response = client.index(request ->
);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> when configuring a reactive client, the `withWebClientConfigurer` hook can be used to customize the WebClient.
====
NOTE: The ReactiveClient response, especially for search operations, is bound to the `from` (offset) & `size` (limit) options of the request.
@@ -131,36 +143,47 @@ NOTE: The ReactiveClient response, especially for search operations, is bound to
[[elasticsearch.clients.configuration]]
== Client Configuration
Client behaviour can be changed via the `ClientConfiguration` that allows to set options for SSL, connect and socket timeouts.
Client behaviour can be changed via the `ClientConfiguration` that allows to set options for SSL, connect and socket timeouts, headers and other parameters.
.Client Configuration
====
[source,java]
----
// optional if Basic Auhtentication is needed
HttpHeaders defaultHeaders = new HttpHeaders();
defaultHeaders.setBasicAuth(USER_NAME, USER_PASS); <1>
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("some-header", "on every request") <1>
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291") <2>
.withConnectTimeout(Duration.ofSeconds(5)) <3>
.withSocketTimeout(Duration.ofSeconds(3)) <4>
.useSsl() <5>
.withDefaultHeaders(defaultHeaders) <6>
.withBasicAuth(username, password) <7>
.useSsl() <3>
.withProxy("localhost:8888") <4>
.withPathPrefix("ela") <5>
.withConnectTimeout(Duration.ofSeconds(5)) <6>
.withSocketTimeout(Duration.ofSeconds(3)) <7>
.withDefaultHeaders(defaultHeaders) <8>
.withBasicAuth(username, password) <9>
.withHeaders(() -> { <10>
HttpHeaders headers = new HttpHeaders();
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
. // ... other options
.build();
----
<1> Define default headers, if they need to be customized
<2> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<3> Set the connection timeout. Default is 10 sec.
<4> Set the socket timeout. Default is 5 sec.
<5> Optionally enable SSL.
<6> Optionally set headers.
<7> Add basic authentication.
<3> Optionally enable SSL.
<4> Optionally set a proxy.
<5> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<6> Set the connection timeout. Default is 10 sec.
<7> Set the socket timeout. Default is 5 sec.
<8> Optionally set headers.
<9> Add basic authentication.
<10> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens. If this is used in the reactive setup, the supplier function *must not* block!
[[elasticsearch.clients.logging]]
== Client Logging
@@ -0,0 +1,35 @@
[[elasticsearch.entity-callbacks]]
= Elasticsearch EntityCallbacks
Spring Data Elasticsearch uses the `EntityCallback` API internally for its auditing support and reacts on the following callbacks:
.Supported Entity Callbacks
[%header,cols="4"]
|===
| Callback
| Method
| Description
| Order
| Reactive/BeforeConvertCallback
| `onBeforeConvert(T entity, IndexCoordinates index)`
| Invoked before a domain object is converted to `org.springframework.data.elasticsearch.core.document.Document`. Can return the `entity` or a modified entity which then will be converted.
| `Ordered.LOWEST_PRECEDENCE`
| Reactive/AfterConvertCallback
| `onAfterConvert(T entity, Document document, IndexCoordinates indexCoordinates)`
| Invoked after a domain object is converted from `org.springframework.data.elasticsearch.core.document.Document` on reading result data from Elasticsearch.
| `Ordered.LOWEST_PRECEDENCE`
| Reactive/AuditingEntityCallback
| `onBeforeConvert(Object entity, IndexCoordinates index)`
| Marks an auditable entity _created_ or _modified_
| 100
| Reactive/AfterSaveCallback
| `T onAfterSave(T entity, IndexCoordinates index)`
| Invoked after a domain object is saved.
| `Ordered.LOWEST_PRECEDENCE`
|===
@@ -0,0 +1,133 @@
[[elasticsearch-migration-guide-3.2-4.0]]
== Upgrading from 3.2.x to 4.0.x
This section describes breaking changes from version 3.2.x to 4.0.x and how removed features can be replaced by new introduced features.
=== Removal of the used Jackson Mapper.
One of the changes in version 4.0.x is that Spring Data Elasticsearch does not use the Jackson Mapper anymore to map an entity to the JSON representation needed for Elasticsearch (see <<elasticsearch.mapping>>). In version 3.2.x the Jackson Mapper was the default that was used. It was possible to switch to the meta-model based converter (named `ElasticsearchEntityMapper`) by explicitly configuring it (<<elasticsearch.mapping.meta-model>>).
In version 4.0.x the meta-model based converter is the only one that is available and does not need to be configured explicitly. If you had a custom configuration to enable the meta-model converter by providing a bean like this:
[code,java]
----
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService()
);
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
----
You now have to remove this bean, the `ElasticsearchEntityMapper` interface has been removed.
.Entity configuration
Some users had custom Jackson annotations on the entity class, for example in order to define a custom name for the mapped document in Elasticsearch or to configure date conversions. These are not taken into account anymore. The needed functionality is now provided with Spring Data Elasticsearch's `@Field` annotation. Please see <<elasticsearch.mapping.meta-model.annotations>> for detailed information.
=== Removal of implicit index name from query objects
In 3.2.x the different query classes like `IndexQuery` or `SearchQuery` had properties that were taking the index name or index names that they were operating upon. If these were not set, the passed in entity was inspected to retrieve the index name that was set in the `@Document` annotation. +
In 4.0.x the index name(s) must now be provided in an additional parameter of type `IndexCoordinates`. By separating this, it now is possible to use one query object against different indices.
So for example the following code:
[code,java]
----
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(person.getId().toString())
.withObject(person)
.build();
String documentId = elasticsearchOperations.index(indexQuery);
----
must be changed to:
[code,java]
----
IndexCoordinates indexCoordinates = elasticsearchOperations.getIndexCoordinatesFor(person.getClass());
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(person.getId().toString())
.withObject(person)
.build();
String documentId = elasticsearchOperations.index(indexQuery, indexCoordinates);
----
To make it easier to work with entities and use the index name that is contained in the entitie's `@Document` annotation, new methods have been added like `DocumentOperations.save(T entity)`;
=== The new Operations interfaces
In version 3.2 there was the `ElasticsearchOperations` interface that defined all the methods for the `ElasticsearchTemplate` class. In version 4 the functions have been split into different interfaces, aligning these interfaces with the Elasticsearch API:
* `DocumentOperations` are the functions related documents like saving, or deleting
* `SearchOperations` contains the functions to search in Elasticsearch
* `IndexOperations` define the functions to operate on indexes, like index creation or mappings creation.
`ElasticsearchOperations` now extends `DocumentOperations` and `SearchOperations` and has methods get access to an `IndexOperations` instance.
NOTE: All the functions from the `ElasticsearchOperations` interface in version 3.2 that are now moved to the `IndexOperations` interface are still available, they are marked as deprecated and have default implementations that delegate to the new implementation:
[code,java]
----
/**
* Create an index for given indexName .
*
* @param indexName the name of the index
* @return {@literal true} if the index was created
* @deprecated since 4.0, use {@link IndexOperations#create()}
*/
@Deprecated
default boolean createIndex(String indexName) {
return indexOps(IndexCoordinates.of(indexName)).create();
}
----
=== Deprecations
==== Methods and classes
Many functions and classes have been deprecated. These functions still work, but the Javadocs show with what they should be replaced.
.Example from ElasticsearchOperations
[code,java]
----
/**
* Retrieves an object from an index.
*
* @param query the query defining the id of the object to get
* @param clazz the type of the object to be returned
* @return the found object
* @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
*/
@Deprecated
@Nullable
<T> T queryForObject(GetQuery query, Class<T> clazz);
----
==== Elasticsearch deprecations
Since version 7 the Elasticsearch `TransportClient` is deprecated, it will be removed with Elasticsearch version 8. Spring Data Elasticsearch deprecates the `ElasticsearchTemplate` class which uses the `TransportClient` in version 4.0.
Mapping types were removed from Elasticsearch 7, they still exist as deprecated values in the Spring Data `@Document` annotation and the `IndexCoordinates` class but they are not used anymore internally.
=== Removals
* As already described, the `ElasticsearchEntityMapper` interface has been removed.
* The `SearchQuery` interface has been merged into it's base interface `Query`, so it's occurrences can just be replaced with `Query`.
* The method `org.springframework.data.elasticsearch.core.ElasticsearchOperations.query(SearchQuery query, ResultsExtractor<T> resultsExtractor);` and the `org.springframework.data.elasticsearch.core.ResultsExtractor` interface have been removed. These could be used to parse the result from Elasticsearch for cases in which the response mapping done with the Jackson based mapper was not enough. Since version 4.0, there are the new <<elasticsearch.operations.searchresulttypes>> to return the information from an Elasticsearch response, so there is no need to expose this low level functionality.
* The low level methods `startScroll`, `continueScroll` and `clearScroll` have been removed from the `ElasticsearchOperations` interface. For low level scroll API access, there now are `searchScrollStart`, `searchScrollContinue` and `searchScrollClear` methods on the `ElasticsearchRestTemplate` class.
@@ -11,27 +11,28 @@ Filter Builder improves query speed.
====
[source,java]
----
private ElasticsearchTemplate elasticsearchTemplate;
private ElasticsearchOperations operations;
IndexCoordinates index = IndexCoordinates.of("sample-index");
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFilter(boolFilter().must(termFilter("id", documentId)))
.build();
Page<SampleEntity> sampleEntities =
elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, SampleEntity.class, index);
----
====
[[elasticsearch.scroll]]
== Using Scroll For Big Result Set
Elasticsearch has a scroll API for getting big result set in chunks. `ElasticsearchTemplate` has startScroll and continueScroll methods that can be used as below.
Elasticsearch has a scroll API for getting big result set in chunks. This is internally used by Spring Data Elasticsearch to provide the implementations of the `<T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index)` method.
.Using startScroll and continueScroll
====
[source,java]
----
IndexCoordinates index = IndexCoordinates.of("sample-index");
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
@@ -40,38 +41,66 @@ SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withPageable(PageRequest.of(0, 10))
.build();
ScrolledPage<SampleEntity> scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class);
String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
while (scroll.hasContent()) {
sampleEntities.addAll(scroll.getContent());
scrollId = scroll.getScrollId();
scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class);
}
elasticsearchTemplate.clearScroll(scrollId);
----
====
`ElasticsearchTemplate` additionally has the stream method which wraps the scan and scroll operations into a CloseableIterator.
.Using stream
====
[source,java]
----
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
CloseableIterator<SampleEntity> stream = elasticsearchTemplate.stream(searchQuery, SampleEntity.class);
SearchHitsIterator<SampleEntity> stream = elasticsearchTemplate.searchForStream(searchQuery, SampleEntity.class, index);
List<SampleEntity> sampleEntities = new ArrayList<>();
while (stream.hasNext()) {
sampleEntities.add(stream.next());
}
stream.close();
----
====
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `ElasticsearchRestTemplate` can be used:
[source,java]
----
@Autowired ElasticsearchRestTemplate template;
IndexCoordinates index = IndexCoordinates.of("sample-index");
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, searchQuery, SampleEntity.class, index);
String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
while (scroll.hasSearchHits()) {
sampleEntities.addAll(scroll.getSearchHits());
scrollId = scroll.getScrollId();
scroll = template.searchScrollContinue(scrollId, 1000, SampleEntity.class);
}
template.searchScrollClear(scrollId);
----
To use the Scroll API with repository methods, the return type must defined as `Stream` in the Elasticsearch Repository. The implementation of the method will then use the scroll methods from the ElasticsearchTemplate.
[source,java]
----
interface SampleEntityRepository extends Repository<SampleEntity, String> {
Stream<SampleEntity> findBy();
}
----
[[elasticsearch.misc.sorts]]
== Sort options
In addition to the default sort options described <<repositories.paging-and-sorting>> Spring Data Elasticsearch has a `GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
[source,java]
----
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
----
@@ -1,6 +1,23 @@
[[new-features]]
= What's new
[[new-features.4-0-0]]
== New in Spring Data Elasticsearch 4.0
* Uses Spring 5.2.
* Upgrade to Elasticsearch 7.6.2.
* Deprecation of `TransportClient` usage.
* Implements most of the mapping-types available for the index mappings.
* Removal of the Jackson `ObjectMapper`, now using the <<elasticsearch.mapping.meta-model,MappingElasticsearchConverter>>
* Cleanup of the API in the `*Operations` interfaces, grouping and renaming methods so that they match the Elasticsearch API, deprecating the old methods, aligning with other Spring Data modules.
* Introduction of `SearchHit<T>` class to represent a found document together with the relevant result metadata for this document (i.e. _sortValues_).
* Introduction of the `SearchHits<T>` class to represent a whole search result together with the
metadata for the complete search result (i.e. _max_score_).
* Introduction of `SearchPage<T>` class to represent a paged result containing a `SearchHits<T>` instance.
* Introduction of the `GeoDistanceOrder` class to be able to create sorting by geographical distance
* Implementation of Auditing Support
* Implementation of lifecycle entity callbacks
[[new-features.3-2-0]]
== New in Spring Data Elasticsearch 3.2
@@ -1,36 +1,18 @@
[[elasticsearch.mapping]]
= Elasticsearch Object Mapping
Spring Data Elasticsearch allows to choose between two mapping implementations abstracted via the `EntityMapper` interface:
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
* <<elasticsearch.mapping.jackson2>>
* <<elasticsearch.mapping.meta-model>>
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>. As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
[[elasticsearch.mapping.jackson2]]
== Jackson Object Mapping
The main reasons for the removal of the Jackson based mapper are:
The Jackson2 based approach (used by default) utilizes a customized `ObjectMapper` instance with spring data specific modules.
Extensions to the actual mapping need to be customized via Jackson annotations like `@JsonInclude`.
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`. This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
* Custom field types and formats also need to be stored into the Elasticsearch index mappings. The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
.Jackson2 Object Mapping Configuration
====
[source,java]
----
@Configuration
public class Config extends AbstractElasticsearchConfiguration { <1>
Using the `MappingElasticsearchConverter` now covers all these cases.
@Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
}
}
----
<1> `AbstractElasticsearchConfiguration` already defines a Jackson2 based `entityMapper` via `ElasticsearchConfigurationSupport`.
====
[WARNING]
`CustomConversions`, `@ReadingConverter` & `@WritingConverter` cannot be applied when using the Jackson based `EntityMapper`. +
Setting the name of a mapped field with `@Field(name="custom-name")` also cannot be used with this Mapper.
[[elasticsearch.mapping.meta-model]]
== Meta Model Object Mapping
@@ -38,64 +20,34 @@ Setting the name of a mapped field with `@Field(name="custom-name")` also cannot
The Metamodel based approach uses domain type information for reading/writing from/to Elasticsearch.
This allows to register `Converter` instances for specific domain type mapping.
.Meta Model Object Mapping Configuration
====
[source,java]
----
@Configuration
public class Config extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest()
}
@Bean
@Override
public EntityMapper entityMapper() { <1>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService() <2>
);
entityMapper.setConversions(elasticsearchCustomConversions()); <3>
return entityMapper;
}
}
----
<1> Overwrite the default `EntityMapper` from `ElasticsearchConfigurationSupport` and expose it as bean.
<2> Use the provided `SimpleElasticsearchMappingContext` to avoid inconsistencies and provide a `GenericConversionService`
for `Converter` registration.
<3> Optionally set `CustomConversions` if applicable.
====
[[elasticsearch.mapping.meta-model.annotations]]
=== Mapping Annotation Overview
The `ElasticsearchEntityMapper` can use metadata to drive the mapping of objects to documents. The following annotations are available:
The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents. The metadata is taken from the entity's properties which can be annotated.
The following annotations are available:
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. The most important attributes are:
** `indexName`: the name of the index to store this entity in
** `type`: the mapping type. If not set, the lowercased simple name of the class is used.
** `type`: [line-through]#the mapping type. If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
** `shards`: the number of shards for the index.
** `replicas`: the number of replicas for the index.
** `refreshIntervall`: Refresh interval for the index. Used for index creation. Default value is _"1s"_.
** `indexStoreType`: Index storage type for the index. Used for index creation. Default value is _"fs"_.
** `createIndex`: Configuration whether to create an index on repository bootstrapping. Default value is _true_.
** `versionType`: Configuration of version management. Default value is _EXTERNAL_.
* `@Transient`: By default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions:
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used.
** `type`: the field type, can be one of _Text, Integer, Long, Date, Float, Double, Boolean, Object, Auto, Nested, Ip, Attachment, Keyword_.
** `type`: the field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
** `format` and `pattern` custom definitions for the _Date_ type.
** `store`: Flag wether the original field value should be store in Elasticsearch, default value is _false_.
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom custom analyzers and normalizer.
** `copy_to`: the target field to copy multiple document fields to.
* `@GeoPoint`: marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class.
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
[[elasticsearch.mapping.meta-model.rules]]
@@ -257,25 +209,14 @@ public class Config extends AbstractElasticsearchConfiguration {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
}
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions()); <1>
return entityMapper;
}
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
return new ElasticsearchCustomConversions(
Arrays.asList(new AddressToMap(), new MapToAddress())); <2>
Arrays.asList(new AddressToMap(), new MapToAddress())); <1>
}
@WritingConverter <3>
@WritingConverter <2>
static class AddressToMap implements Converter<Address, Map<String, Object>> {
@Override
@@ -289,7 +230,7 @@ public class Config extends AbstractElasticsearchConfiguration {
}
}
@ReadingConverter <4>
@ReadingConverter <3>
static class MapToAddress implements Converter<Map<String, Object>, Address> {
@Override
@@ -309,8 +250,7 @@ public class Config extends AbstractElasticsearchConfiguration {
"localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
}
----
<1> Register `ElasticsearchCustomConversions` with the `EntityMapper`.
<2> Add `Converter` implementations.
<3> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<4> Set up the `Converter` used for reading `DomainType` from search result.
<1> Add `Converter` implementations.
<2> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<3> Set up the `Converter` used for reading `DomainType` from search result.
====
@@ -1,10 +1,18 @@
[[elasticsearch.operations]]
= Elasticsearch Operations
Spring Data Elasticsearch uses two interfaces to define the operations that can be called against an Elasticsearch index. These are `ElasticsearchOperations` and `ReactiveElasticsearchOperations`. Whereas the first is used with the classic synchronous implementations, the second one uses reactive infrastructure.
Spring Data Elasticsearch uses several interfaces to define the operations that can be called against an Elasticsearch index (for a description of the reactive interfaces see <<elasticsearch.reactive.operations>>).
* `IndexOperations` defines actions on index level like creating or deleting an index.
* `DocumentOperations` defines actions to store, update and retrieve entities based on their id.
* `SearchOperations` define the actions to search for multiple entities using queries
* `ElasticsearchOperations` combines the `DocumentOperations` and `SearchOperations` interfaces.
These interfaces correspond to the structuring of the https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html[Elasticsearch API].
The default implementations of the interfaces offer:
* index management functionality.
* Read/Write mapping support for domain types.
* A rich query and criteria api.
* Resource management and Exception translation.
@@ -12,6 +20,8 @@ The default implementations of the interfaces offer:
[[elasticsearch.operations.template]]
== ElasticsearchTemplate
NOTE: Usage of the ElasticsearchTemplate is deprecated as of version 4.0, use ElasticsearchRestTemplate instead.
The `ElasticsearchTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.transport>>.
.ElasticsearchTemplate configuration
@@ -31,23 +41,12 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { <2>
return new ElasticsearchTemplate(elasticsearchClient(), entityMapper());
}
// use the ElasticsearchEntityMapper
@Bean
@Override
public EntityMapper entityMapper() { <3>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
return new ElasticsearchTemplate(elasticsearchClient());
}
}
----
<1> Setting up the <<elasticsearch.clients.transport>>.
<1> Setting up the <<elasticsearch.clients.transport>>. Deprecated as of version 4.0.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
<3> Using the <<elasticsearch.mapping.meta-model>> ElasticsearchMapper.
====
[[elasticsearch.operations.resttemplate]]
@@ -67,29 +66,16 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
}
// no special bean creation needed <2>
// use the ElasticsearchEntityMapper
@Bean
@Override
public EntityMapper entityMapper() { <3>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
}
----
<1> Setting up the <<elasticsearch.clients.rest>>.
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
<3> Using the <<elasticsearch.mapping.meta-model>> ElasticsearchMapper.
====
[[elasticsearch.operations.usage]]
== Usage examples
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the
corresponding Bean with one of the configurations shown above.
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above.
.ElasticsearchOperations usage
====
@@ -133,3 +119,38 @@ public class TestController {
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.
include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
[[elasticsearch.operations.searchresulttypes]]
== Search Result Types
When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned. When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information. These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations. The following classes and interfaces are now available:
.SearchHit<T>
Contains the following information:
* Id
* Score
* Sort Values
* Highlight fields
* The retrieved entity of type <T>
.SearchHits<T>
Contains the following information:
* Number of total hits
* Total hits relation
* Maximum score
* A list of `SearchHit<T>` objects
* Returned aggregations
.SearchPage<T>
Defines a Spring Data `Page` that contains a `SearchHits<T>` element and can be used for paging access using repository methods.
.SearchScrollHits<T>
Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, it enriches a `SearchHits<T>` with the Elasticsearch scroll id.
.SearchHitsIterator<T>
An Iterator returned by the streaming functions of the `SearchOperations` interface.
@@ -8,7 +8,7 @@ The Elasticsearch module supports all basic query building feature as string que
=== Declared queries
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names. In this case one might make either use of `@Query` annotation (see <<elasticsearch.query-methods.at-query>> ).
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names. In this case one might make use of the `@Query` annotation (see <<elasticsearch.query-methods.at-query>> ).
[[elasticsearch.query-methods.criterions]]
== Query creation
@@ -29,12 +29,14 @@ The method name above will be translated into the following Elasticsearch json q
[source]
----
{ "bool" :
{ "must" :
[
{ "field" : {"name" : "?"} },
{ "field" : {"price" : "?"} }
]
{
"query": {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}
}
----
@@ -48,80 +50,184 @@ A list of supported keywords for Elasticsearch is shown below.
| Sample
| Elasticsearch Query String| `And`
| `findByNameAndPrice`
| `{"bool" : {"must" : [ {"field" : {"name" : "?"}},
{"field" : {"price" : "?"}} ]}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}}`
| `Or`
| `findByNameOrPrice`
| `{"bool" : {"should" : [ {"field" : {"name" : "?"}},
{"field" : {"price" : "?"}} ]}}`
| `{ "query" : {
"bool" : {
"should" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}}`
| `Is`
| `findByName`
| `{"bool" : {"must" : {"field" : {"name" : "?"}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
]
}
}}`
| `Not`
| `findByNameNot`
| `{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}`
| `{ "query" : {
"bool" : {
"must_not" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
]
}
}}`
| `Between`
| `findByPriceBetween`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `LessThan`
| `findByPriceLessThan`
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } }
]
}
}}`
| `LessThanEqual`
| `findByPriceLessThan`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
null,"to" : ?,"include_lower" : true,"include_upper" :
true}}}}}`
| `findByPriceLessThanEqual`
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `GreaterThan`
| `findByPriceGreaterThan`
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } }
]
}
}}`
| `GreaterThanEqual`
| `findByPriceGreaterThan`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
?,"to" : null,"include_lower" : true,"include_upper" :
true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `Before`
| `findByPriceBefore`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
null,"to" : ?,"include_lower" : true,"include_upper" :
true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `After`
| `findByPriceAfter`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
?,"to" : null,"include_lower" : true,"include_upper" :
true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `Like`
| `findByNameLike`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"?*","analyze_wildcard" : true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `StartingWith`
| `findByNameStartingWith`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"?*","analyze_wildcard" : true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `EndingWith`
| `findByNameEndingWith`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"*?","analyze_wildcard" : true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `Contains/Containing`
| `findByNameContaining`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"*?*","analyze_wildcard" : true}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "\*?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `In`
| `findByNameIn(Collection<String>names)`
| `{"bool" : {"must" : {"bool" : {"should" : [ {"field" :
{"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{"bool" : {"must" : [
{"terms" : {"name" : ["?","?"]}}
]
}
}
]
}
}}`
| `NotIn`
| `findByNameNotIn(Collection<String>names)`
| `{"bool" : {"must_not" : {"bool" : {"should" : {"field" :
{"name" : "?"}}}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{"bool" : {"must_not" : [
{"terms" : {"name" : ["?","?"]}}
]
}
}
]
}
}}`
| `Near`
| `findByStoreNear`
@@ -129,18 +235,48 @@ A list of supported keywords for Elasticsearch is shown below.
| `True`
| `findByAvailableTrue`
| `{"bool" : {"must" : {"field" : {"available" : true}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
]
}
}}`
| `False`
| `findByAvailableFalse`
| `{"bool" : {"must" : {"field" : {"available" : false}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "false", "fields" : [ "available" ] } }
]
}
}}`
| `OrderBy`
| `findByAvailableTrueOrderByNameDesc`
| `{"sort" : [{ "name" : {"order" : "desc"} }],"bool" :
{"must" : {"field" : {"available" : true}}}}`
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
]
}
}, "sort":[{"name":{"order":"desc"}}]
}`
|===
== Method return types
Repository methods can be defined to have the following return types for returning multiple Elements:
* `List<T>`
* `Stream<T>`
* `SearchHits<T>`
* `List<SearchHit<T>>`
* `Stream<SearchHit<T>>`
* `SearchPage<T>`
[[elasticsearch.query-methods.at-query]]
== Using @Query Annotation
@@ -149,8 +285,21 @@ A list of supported keywords for Elasticsearch is shown below.
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}")
@Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
Page<Book> findByName(String name,Pageable pageable);
}
----
The String that is set as the annotation argument must be a valid Elasticsearch JSON query. It will be sent to Easticsearch as value of the query element; if for example the function is called with the parameter _John_, it would produce the following query body:
[source,json]
----
{
"query": {
"match": {
"name": {
"query": "John"
}
}
}
}
----
====
@@ -82,7 +82,7 @@ Consider the following:
====
[source,java]
----
@Document(indexName = "marvel", type = "characters")
@Document(indexName = "marvel")
public class Person {
private @Id String id;
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -17,15 +17,20 @@ package org.springframework.data.elasticsearch;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* ElasticsearchException
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @deprecated since 4.0, use {@link org.springframework.dao.UncategorizedDataAccessException}
*/
@Deprecated
public class ElasticsearchException extends RuntimeException {
private Map<String, String> failedDocuments;
@Nullable private Map<String, String> failedDocuments;
public ElasticsearchException(String message) {
super(message);
@@ -45,6 +50,7 @@ public class ElasticsearchException extends RuntimeException {
this.failedDocuments = failedDocuments;
}
@Nullable
public Map<String, String> getFailedDocuments() {
return failedDocuments;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-2020 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.
@@ -0,0 +1,28 @@
/*
* Copyright 2020 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 org.springframework.dao.UncategorizedDataAccessException;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public class UncategorizedElasticsearchException extends UncategorizedDataAccessException {
public UncategorizedElasticsearchException(String msg, Throwable cause) {
super(msg, cause);
}
}
@@ -1,7 +1,5 @@
package org.springframework.data.elasticsearch.annotations;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
@@ -9,6 +7,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
/**
* Based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
*
@@ -26,4 +26,5 @@ public @interface CompletionContext {
String precision() default "";
String path() default "";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -22,7 +22,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.index.VersionType;
import org.springframework.data.annotation.Persistent;
/**
@@ -33,6 +32,7 @@ import org.springframework.data.annotation.Persistent;
* @author Mason Chan
* @author Ivan Greene
* @author Mark Paluch
* @author Peter-Josef Meisch
*/
@Persistent
@Inherited
@@ -54,8 +54,14 @@ public @interface Document {
String indexName();
/**
* Mapping type name.
* Mapping type name. <br/>
* deprecated as Elasticsearch does not support this anymore
* (@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/removal-of-types.html">Elastisearch removal of types documentation</a>) and will remove it in
* Elasticsearch 8.
*
* @deprecated since 4.0
*/
@Deprecated
String type() default "";
/**
@@ -64,9 +70,11 @@ public @interface Document {
boolean useServerConfiguration() default false;
/**
* Number of shards for the index {@link #indexName()}. Used for index creation.
* Number of shards for the index {@link #indexName()}. Used for index creation. <br/>
* With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of
* Elasticsearch which changed to 1 as well in Elasticsearch 7.0.
*/
short shards() default 5;
short shards() default 1;
/**
* Number of replicas for the index {@link #indexName()}. Used for index creation.
@@ -0,0 +1,37 @@
/*
* Copyright 2019-2020 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.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to set the dynamic mapping mode
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
*
* @author Peter-Josef Meisch
* @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
@Documented
public @interface DynamicMapping {
DynamicMappingValue value() default DynamicMappingValue.True;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2019-2020 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,15 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.facet.request;
package org.springframework.data.elasticsearch.annotations;
/**
* @author Artur Konczak
* @author Mohsin Husen
* values for the {@link DynamicMapping annotation}
*
* @author Peter-Josef Meisch
* @since 4.0
*/
@Deprecated
public enum TermFacetOrder {
ascTerm, descTerm, ascCount, descCount;
public enum DynamicMappingValue {
True, False, Strict
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
@@ -24,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
@@ -32,6 +32,8 @@ import java.lang.annotation.Target;
* @author Jakub Vavrik
* @author Kevin Leturc
* @author Peter-Josef Meisch
* @author Xiao Yu
* @author Aleksei Arsenev
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -41,6 +43,7 @@ public @interface Field {
/**
* Alias for {@link #name}.
*
* @since 3.2
*/
@AliasFor("name")
@@ -48,7 +51,9 @@ public @interface Field {
/**
* The <em>name</em> to be used to store the field inside the document.
* <p>If not set, the name of the annotated property is used.
* <p>
* √5 If not set, the name of the annotated property is used.
*
* @since 3.2
*/
@AliasFor("value")
@@ -77,4 +82,76 @@ public @interface Field {
boolean includeInParent() default false;
String[] copyTo() default {};
/**
* @since 4.0
*/
int ignoreAbove() default -1;
/**
* @since 4.0
*/
boolean coerce() default true;
/**
* @since 4.0
*/
boolean docValues() default true;
/**
* @since 4.0
*/
boolean ignoreMalformed() default false;
/**
* @since 4.0
*/
IndexOptions indexOptions() default IndexOptions.none;
/**
* @since 4.0
*/
boolean indexPhrases() default false;
/**
* implemented as array to enable the empty default value
*
* @since 4.0
*/
IndexPrefixes[] indexPrefixes() default {};
/**
* @since 4.0
*/
boolean norms() default true;
/**
* @since 4.0
*/
String nullValue() default "";
/**
* @since 4.0
*/
int positionIncrementGap() default -1;
/**
* @since 4.0
*/
Similarity similarity() default Similarity.Default;
/**
* @since 4.0
*/
TermVector termVector() default TermVector.none;
/**
* @since 4.0
*/
double scalingFactor() default 1;
/**
* @since 4.0
*/
int maxShingleSize() default -1;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -20,22 +20,36 @@ package org.springframework.data.elasticsearch.annotations;
* @author Mohsin Husen
* @author Artur Konczak
* @author Zeng Zetang
* @author Peter-Josef Meisch
* @author Aleksei Arsenev
*/
public enum FieldType {
Text,
Byte,
Short,
Integer,
Long,
Date,
Half_Float,
Float,
Double,
Boolean,
Object,
Auto,
Nested,
Ip,
Attachment,
Keyword
Auto, //
Text, //
Keyword, //
Long, //
Integer, //
Short, //
Byte, //
Double, //
Float, //
Half_Float, //
Scaled_Float, //
Date, //
Date_Nanos, //
Boolean, //
Binary, //
Integer_Range, //
Float_Range, //
Long_Range, //
Double_Range, //
Date_Range, //
Ip_Range, //
Object, //
Nested, //
Ip, //
TokenCount, //
Percolator, //
Flattened, //
Search_As_You_Type //
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -0,0 +1,36 @@
/*
* Copyright 2020-2020 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.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Highlight {
HighlightParameters parameters() default @HighlightParameters;
HighlightField[] fields();
}
@@ -0,0 +1,37 @@
/*
* Copyright 2020-2020 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.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HighlightField {
/**
* The name of the field to apply highlighting to. This must be the field name of the entity's property, not the name
* of the field in the index mappings.
*/
String name() default "";
HighlightParameters parameters() default @HighlightParameters;
}
@@ -0,0 +1,78 @@
/*
* Copyright 2020-2020 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.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HighlightParameters {
String boundaryChars() default "";
int boundaryMaxScan() default -1;
String boundaryScanner() default "";
String boundaryScannerLocale() default "";
/**
* only used for {@link Highlight}s.
*/
String encoder() default "";
boolean forceSource() default false;
String fragmenter() default "";
/**
* only used for {@link HighlightField}s.
*/
int fragmentOffset() default -1;
int fragmentSize() default -1;
/**
* only used for {@link HighlightField}s.
*/
String[] matchedFields() default {};
int noMatchSize() default -1;
int numberOfFragments() default -1;
String order() default "";
int phraseLimit() default -1;
String[] preTags() default {};
String[] postTags() default {};
boolean requireFieldMatch() default true;
/**
* only used for {@link Highlight}s.
*/
String tagsSchema() default "";
String type() default "";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2019-2020 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,15 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.facet;
package org.springframework.data.elasticsearch.annotations;
/**
* @author Artur Konczak
* @author Petar Tahchiev
* @author Peter-Josef Meisch
* @since 4.0
*/
@Deprecated
public enum FacetType {
term, range, histogram, statistical
public enum IndexOptions {
none, docs, freqs, positions, offsets
}
@@ -0,0 +1,29 @@
/*
* Copyright 2019-2020 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.annotations;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public @interface IndexPrefixes {
int MIN_DEFAULT = 2;
int MAX_DEFAULT = 2;
int minChars() default MIN_DEFAULT;
int maxChars() default MAX_DEFAULT;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -24,6 +24,9 @@ import java.lang.annotation.Target;
* @author Artur Konczak
* @author Mohsin Husen
* @author Sascha Woo
* @author Xiao Yu
* @author Peter-Josef Meisch
* @author Aleksei Arsenev
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -48,4 +51,76 @@ public @interface InnerField {
String analyzer() default "";
String normalizer() default "";
/**
* @since 4.0
*/
int ignoreAbove() default -1;
/**
* @since 4.0
*/
boolean coerce() default true;
/**
* @since 4.0
*/
boolean docValues() default true;
/**
* @since 4.0
*/
boolean ignoreMalformed() default false;
/**
* @since 4.0
*/
IndexOptions indexOptions() default IndexOptions.none;
/**
* @since 4.0
*/
boolean indexPhrases() default false;
/**
* implemented as array to enable the empty default value
*
* @since 4.0
*/
IndexPrefixes[] indexPrefixes() default {};
/**
* @since 4.0
*/
boolean norms() default true;
/**
* @since 4.0
*/
String nullValue() default "";
/**
* @since 4.0
*/
int positionIncrementGap() default -1;
/**
* @since 4.0
*/
Similarity similarity() default Similarity.Default;
/**
* @since 4.0
*/
TermVector termVector() default TermVector.none;
/**
* @since 4.0
*/
double scalingFactor() default 1;
/**
* @since 4.0
*/
int maxShingleSize() default -1;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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,20 +15,25 @@
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
* @author Xiao Yu
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface MultiField {
public Field mainField();
Field mainField();
public InnerField[] otherFields() default {};
InnerField[] otherFields() default {};
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -8,16 +8,21 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.elasticsearch.core.SearchHit;
/**
* Specifies that this field is used for storing the document score.
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @since 3.1
* @deprecated since 4.0, use {@link SearchHit#getScore()} instead
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
@ReadOnlyProperty
public @interface Score {}
@Deprecated
public @interface Score {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2019-2020 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,32 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.facet.result;
package org.springframework.data.elasticsearch.annotations;
/**
* Single term
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
* @author Peter-Josef Meisch
* @since 4.0
*/
@Deprecated
public class Term {
public enum Similarity {
Default("default"), BM25("BM25"), classic("classic"), Boolean("boolean");
private String term;
private long count;
// need to use a custom name because 'boolean' can't be used as enum name
private final String toStringName;
public Term(String term, long count) {
this.term = term;
this.count = count;
Similarity(String name) {
this.toStringName = name;
}
public String getTerm() {
return term;
}
public long getCount() {
return count;
@Override
public String toString() {
return toStringName;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2019-2020 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,12 @@
* 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.annotations;
/**
* EsClient interface. Specify what client an ElasticSearchTemplate will return from getClient().
*
* @author Don Wellington
* @param <C>
* @author Peter-Josef Meisch
* @since 4.0
*/
public interface EsClient<C> {
C getClient();
public enum TermVector {
none, no, yes, with_positions, with_offsets, woth_positions_offsets, with_positions_payloads, with_positions_offets_payloads
}
@@ -0,0 +1,3 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.annotations;
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -20,16 +20,23 @@ import java.net.SocketAddress;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Configuration interface exposing common client configuration properties for Elasticsearch clients.
*
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @since 3.2
*/
public interface ClientConfiguration {
@@ -118,6 +125,13 @@ public interface ClientConfiguration {
*/
Optional<SSLContext> getSslContext();
/**
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
*
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
*/
Optional<HostnameVerifier> getHostNameVerifier();
/**
* Returns the {@link java.time.Duration connect timeout}.
*
@@ -135,6 +149,33 @@ public interface ClientConfiguration {
*/
Duration getSocketTimeout();
/**
* Returns the path prefix that should be prepended to HTTP(s) requests for Elasticsearch behind a proxy.
*
* @return the path prefix.
* @since 4.0
*/
@Nullable
String getPathPrefix();
/**
* returns an optionally set proxy in the form host:port
*
* @return the optional proxy
* @since 4.0
*/
Optional<String> getProxy();
/**
* @return the function for configuring a WebClient.
*/
Function<WebClient, WebClient> getWebClientConfigurer();
/**
* @return the supplier for custom headers.
*/
Supplier<HttpHeaders> getHeadersSupplier();
/**
* @author Christoph Strobl
*/
@@ -201,6 +242,16 @@ public interface ClientConfiguration {
* @return the {@link TerminalClientConfigurationBuilder}.
*/
TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext);
/**
* Connect via {@literal https} using the givens {@link SSLContext} and HostnameVerifier {@link HostnameVerifier}
* .<br />
* <strong>NOTE</strong> You need to leave out the protocol in
* {@link ClientConfigurationBuilderWithRequiredEndpoint#connectedTo(String)}.
*
* @return the {@link TerminalClientConfigurationBuilder}.
*/
TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext, HostnameVerifier hostnameVerifier);
}
/**
@@ -267,6 +318,42 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder withBasicAuth(String username, String password);
/**
* Configure the path prefix that will be prepended to any HTTP(s) requests
*
* @param pathPrefix the pathPrefix.
* @return the {@link TerminalClientConfigurationBuilder}
* @since 4.0
*/
TerminalClientConfigurationBuilder withPathPrefix(String pathPrefix);
/**
* @param proxy a proxy formatted as String {@literal host:port}.
* @return the {@link TerminalClientConfigurationBuilder}.
*/
TerminalClientConfigurationBuilder withProxy(String proxy);
/**
* set customization hook in case of a reactive configuration
*
* @param webClientConfigurer function to configure the WebClient
* @return the {@link TerminalClientConfigurationBuilder}.
*/
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
/**
* set a supplier for custom headers. This is invoked for every HTTP request to Elasticsearch to retrieve headers
* that should be sent with the request. A common use case is passing in authentication headers that may change.
* <br/>
* Note: When used in a reactive environment, the calling of {@link Supplier#get()} function must not do any
* blocking operations. It may return {@literal null}.
*
* @param headers supplier function for headers, must not be {@literal null}
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.0
*/
TerminalClientConfigurationBuilder withHeaders(Supplier<HttpHeaders> headers);
/**
* Build the {@link ClientConfiguration} object.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -20,8 +20,11 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
@@ -30,6 +33,7 @@ import org.springframework.data.elasticsearch.client.ClientConfiguration.Termina
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Default builder implementation for {@link ClientConfiguration}.
@@ -37,6 +41,8 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @since 3.2
*/
class ClientConfigurationBuilder
@@ -46,10 +52,15 @@ class ClientConfigurationBuilder
private HttpHeaders headers = HttpHeaders.EMPTY;
private boolean useSsl;
private @Nullable SSLContext sslContext;
private @Nullable HostnameVerifier hostnameVerifier;
private Duration connectTimeout = Duration.ofSeconds(10);
private Duration soTimeout = Duration.ofSeconds(5);
private String username;
private String password;
private @Nullable String username;
private @Nullable String password;
private @Nullable String pathPrefix;
private @Nullable String proxy;
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
/*
* (non-Javadoc)
@@ -78,6 +89,13 @@ class ClientConfigurationBuilder
return this;
}
@Override
public MaybeSecureClientConfigurationBuilder withProxy(String proxy) {
Assert.hasLength(proxy, "proxy must not be null or empty");
this.proxy = proxy;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl()
@@ -103,6 +121,22 @@ class ClientConfigurationBuilder
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl(javax.net.ssl.SSLContext, javax.net.ssl.HostnameVerifier)
*/
@Override
public TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext, HostnameVerifier hostnameVerifier) {
Assert.notNull(sslContext, "SSL Context must not be null");
Assert.notNull(hostnameVerifier, "Host Name Verifier must not be null");
this.useSsl = true;
this.sslContext = sslContext;
this.hostnameVerifier = hostnameVerifier;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder#withDefaultHeaders(org.springframework.http.HttpHeaders)
@@ -156,6 +190,32 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder withPathPrefix(String pathPrefix) {
this.pathPrefix = pathPrefix;
return this;
}
@Override
public TerminalClientConfigurationBuilder withWebClientConfigurer(
Function<WebClient, WebClient> webClientConfigurer) {
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
this.webClientConfigurer = webClientConfigurer;
return this;
}
@Override
public TerminalClientConfigurationBuilder withHeaders(Supplier<HttpHeaders> headers) {
Assert.notNull(headers, "headersSupplier must not be null");
this.headersSupplier = headers;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithOptionalDefaultHeaders#build()
@@ -170,8 +230,8 @@ class ClientConfigurationBuilder
headers.setBasicAuth(username, password);
}
return new DefaultClientConfiguration(this.hosts, this.headers, this.useSsl, this.sslContext, this.soTimeout,
this.connectTimeout);
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, webClientConfigurer, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -20,6 +20,7 @@ import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
@@ -90,7 +91,7 @@ public abstract class ClientLogger {
* @param logId the correlation Id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
*/
public static void logRawResponse(String logId, HttpStatus statusCode) {
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode) {
if (isEnabled()) {
WIRE_LOGGER.trace("[{}] Received raw response: {}", logId, statusCode);
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -66,7 +66,7 @@ class ClusterNodes implements Streamable<TransportAddress> {
Assert.hasText(host, () -> String.format("No host name given cluster node %s!", node));
Assert.hasText(port, () -> String.format("No port given in cluster node %s!", node));
return new TransportAddress(toInetAddress(host), Integer.valueOf(port));
return new TransportAddress(toInetAddress(host), Integer.parseInt(port));
}).collect(Collectors.toList());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -21,17 +21,23 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Default {@link ClientConfiguration} implementation.
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Huw Ayling-Miller
* @author Peter-Josef Meisch
* @since 3.2
*/
class DefaultClientConfiguration implements ClientConfiguration {
@@ -42,9 +48,16 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final @Nullable SSLContext sslContext;
private final Duration soTimeout;
private final Duration connectTimeout;
private final @Nullable String pathPrefix;
private final @Nullable HostnameVerifier hostnameVerifier;
private final @Nullable String proxy;
private final Function<WebClient, WebClient> webClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout) {
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix,
@Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
Function<WebClient, WebClient> webClientConfigurer, Supplier<HttpHeaders> headersSupplier) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
@@ -52,60 +65,66 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.sslContext = sslContext;
this.soTimeout = soTimeout;
this.connectTimeout = connectTimeout;
this.pathPrefix = pathPrefix;
this.hostnameVerifier = hostnameVerifier;
this.proxy = proxy;
this.webClientConfigurer = webClientConfigurer;
this.headersSupplier = headersSupplier;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getEndpoints()
*/
@Override
public List<InetSocketAddress> getEndpoints() {
return this.hosts;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getDefaultHeaders()
*/
@Override
public HttpHeaders getDefaultHeaders() {
return this.headers;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#useSsl()
*/
@Override
public boolean useSsl() {
return this.useSsl;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getSslContext()
*/
@Override
public Optional<SSLContext> getSslContext() {
return Optional.ofNullable(this.sslContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getConnectTimeout()
*/
@Override
public Optional<HostnameVerifier> getHostNameVerifier() {
return Optional.ofNullable(this.hostnameVerifier);
}
@Override
public Duration getConnectTimeout() {
return this.connectTimeout;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getSocketTimeout()
*/
@Override
public Duration getSocketTimeout() {
return this.soTimeout;
}
@Nullable
@Override
public String getPathPrefix() {
return this.pathPrefix;
}
@Override
public Optional<String> getProxy() {
return Optional.ofNullable(proxy);
}
@Override
public Function<WebClient, WebClient> getWebClientConfigurer() {
return webClientConfigurer;
}
@Override
public Supplier<HttpHeaders> getHeadersSupplier() {
return headersSupplier;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -104,7 +104,7 @@ public class ElasticsearchHost {
@Override
public String toString() {
return "ElasticsearchHost(" + endpoint + ", " + state.name() + ")";
return "ElasticsearchHost(" + endpoint + ", " + state.name() + ')';
}
public enum State {
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 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,11 +15,10 @@
*/
package org.springframework.data.elasticsearch.client;
import static java.util.Arrays.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.node.NodeClient;
@@ -33,7 +32,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
@@ -42,28 +43,33 @@ import org.springframework.util.StringUtils;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Ilkang Na
* @author Peter-Josef Meisch
*/
public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(NodeClientFactoryBean.class);
private boolean local;
private boolean enableHttp;
private String clusterName;
private NodeClient nodeClient;
private String pathData;
private String pathHome;
private String pathConfiguration;
private @Nullable String clusterName;
private @Nullable Node node;
private @Nullable NodeClient nodeClient;
private @Nullable String pathData;
private @Nullable String pathHome;
private @Nullable String pathConfiguration;
public static class TestNode extends Node {
private static final String DEFAULT_NODE_NAME = "spring-data-elasticsearch-nodeclientfactorybean-test";
public TestNode(Settings preparedSettings, Collection<Class<? extends Plugin>> classpathPlugins) {
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, null), classpathPlugins, false);
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null,
() -> DEFAULT_NODE_NAME), classpathPlugins, false);
}
protected void registerDerivedNodeNameWithLogger(String nodeName) {
try {
LogConfigurator.setNodeName(nodeName);
LogConfigurator.setNodeName(nodeName);
} catch (Exception e) {
// nagh - just forget about it
}
@@ -77,7 +83,12 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
}
@Override
public NodeClient getObject() throws Exception {
public NodeClient getObject() {
if (nodeClient == null) {
throw new FactoryBeanNotInitializedException();
}
return nodeClient;
}
@@ -94,10 +105,17 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
@Override
public void afterPropertiesSet() throws Exception {
nodeClient = (NodeClient) new TestNode(Settings.builder().put(loadConfig()).put("transport.type", "netty4")
.put("http.type", "netty4").put("path.home", this.pathHome).put("path.data", this.pathData)
.put("cluster.name", this.clusterName).put("node.max_local_storage_nodes", 100).build(),
asList(Netty4Plugin.class)).start().client();
Settings settings = Settings.builder() //
.put(loadConfig()) //
.put("transport.type", "netty4") //
.put("http.type", "netty4") //
.put("path.home", this.pathHome) //
.put("path.data", this.pathData) //
.put("cluster.name", this.clusterName) //
.put("node.max_local_storage_nodes", 100) //
.build();
node = new TestNode(settings, Collections.singletonList(Netty4Plugin.class));
nodeClient = (NodeClient) node.start().client();
}
private Settings loadConfig() throws IOException {
@@ -137,11 +155,14 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
}
@Override
public void destroy() throws Exception {
public void destroy() {
try {
logger.info("Closing elasticSearch client");
if (nodeClient != null) {
nodeClient.close();
// NodeClient.close() is a noop, no need to call it here
nodeClient = null;
logger.info("Closing elasticSearch node");
if (node != null) {
node.close();
node = null;
}
} catch (final Exception e) {
logger.error("Error closing ElasticSearch client: ", e);
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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,40 +15,44 @@
*/
package org.springframework.data.elasticsearch.client;
import lombok.extern.slf4j.Slf4j;
import java.net.URL;
import java.util.ArrayList;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* RestClientFactoryBean
*
*
* @author Don Wellington
* @author Peter-Josef Meisch
*/
@Slf4j
public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
private RestHighLevelClient client;
private static final Logger LOGGER = LoggerFactory.getLogger(RestClientFactoryBean.class);
private @Nullable RestHighLevelClient client;
private String hosts = "http://localhost:9200";
static final String COMMA = ",";
@Override
public void destroy() throws Exception {
public void destroy() {
try {
log.info("Closing elasticSearch client");
LOGGER.info("Closing elasticSearch client");
if (client != null) {
client.close();
}
} catch (final Exception e) {
log.error("Error closing ElasticSearch client: ", e);
LOGGER.error("Error closing ElasticSearch client: ", e);
}
}
@@ -58,7 +62,12 @@ public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>,
}
@Override
public RestHighLevelClient getObject() throws Exception {
public RestHighLevelClient getObject() {
if (client == null) {
throw new FactoryBeanNotInitializedException();
}
return client;
}
@@ -75,7 +84,8 @@ public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>,
protected void buildClient() throws Exception {
Assert.hasText(hosts, "[Assertion Failed] At least one host must be set.");
ArrayList<HttpHost> httpHosts = new ArrayList<HttpHost>();
ArrayList<HttpHost> httpHosts = new ArrayList<>();
for (String host : hosts.split(COMMA)) {
URL hostUrl = new URL(host);
httpHosts.add(new HttpHost(hostUrl.getHost(), hostUrl.getPort(), hostUrl.getProtocol()));
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -20,12 +20,11 @@ import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -52,6 +51,9 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @author Peter-Josef Meisch
* @since 3.2
*/
public final class RestClients {
@@ -75,32 +77,31 @@ public final class RestClients {
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()) {
Header[] httpHeaders = headers.toSingleValueMap().entrySet().stream()
.map(it -> new BasicHeader(it.getKey(), it.getValue())).toArray(Header[]::new);
builder.setDefaultHeaders(httpHeaders);
builder.setDefaultHeaders(toHeaderArray(headers));
}
builder.setHttpClientConfigCallback(clientBuilder -> {
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
sslContext.ifPresent(clientBuilder::setSSLContext);
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);
}
Duration connectTimeout = clientConfiguration.getConnectTimeout();
Duration timeout = clientConfiguration.getSocketTimeout();
Builder requestConfigBuilder = RequestConfig.custom();
Duration connectTimeout = clientConfiguration.getConnectTimeout();
if (!connectTimeout.isNegative()) {
@@ -108,12 +109,16 @@ public final class RestClients {
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
}
Duration timeout = clientConfiguration.getSocketTimeout();
if (!timeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
}
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
return clientBuilder;
});
@@ -121,8 +126,16 @@ public final class RestClients {
return () -> client;
}
private static Header[] toHeaderArray(HttpHeaders headers) {
return headers.entrySet().stream() //
.flatMap(entry -> entry.getValue().stream() //
.map(value -> new BasicHeader(entry.getKey(), value))) //
.toArray(Header[]::new);
}
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it).collect(Collectors.toList());
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ":" + it.getPort())
.collect(Collectors.toList());
}
/**
@@ -167,7 +180,6 @@ public final class RestClients {
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
if (logId == null) {
logId = ClientLogger.newLogId();
context.setAttribute(RestClients.LOG_ID_ATTRIBUTE, logId);
}
@@ -192,10 +204,31 @@ public final class RestClients {
@Override
public void process(HttpResponse response, HttpContext context) {
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()));
}
}
/**
* Interceptor to inject custom supplied headers.
*
* @since 4.0
*/
private static class CustomHeaderInjector implements HttpRequestInterceptor {
public CustomHeaderInjector(Supplier<HttpHeaders> headersSupplier) {
this.headersSupplier = headersSupplier;
}
private final Supplier<HttpHeaders> 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);
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -24,7 +24,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
/**
* TransportClientFactoryBean
@@ -35,7 +37,10 @@ import org.springframework.beans.factory.InitializingBean;
* @author Piotr Betkier
* @author Ilkang Na
* @author Oliver Gierke
* @author Peter-Josef Meisch
* @deprecated as of 4.0
*/
@Deprecated
public class TransportClientFactoryBean implements FactoryBean<TransportClient>, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(TransportClientFactoryBean.class);
@@ -45,11 +50,11 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
private Boolean clientIgnoreClusterName = Boolean.FALSE;
private String clientPingTimeout = "5s";
private String clientNodesSamplerInterval = "5s";
private TransportClient client;
private Properties properties;
private @Nullable TransportClient client;
private @Nullable Properties properties;
@Override
public void destroy() throws Exception {
public void destroy() {
try {
logger.info("Closing elasticSearch client");
if (client != null) {
@@ -61,7 +66,11 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
}
@Override
public TransportClient getObject() throws Exception {
public TransportClient getObject() {
if (clientTransportSniff == null) {
throw new FactoryBeanNotInitializedException();
}
return client;
}
@@ -80,14 +89,14 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
buildClient();
}
protected void buildClient() throws Exception {
protected void buildClient() {
client = new PreBuiltTransportClient(settings());
clusterNodes.stream() //
.peek(it -> logger.info("Adding transport node : " + it.toString())) //
.forEach(client::addTransportAddress);
client.connectedNodes();
}
@@ -101,13 +110,10 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
return builder.build();
}
return Settings.builder()
.put("cluster.name", clusterName)
.put("client.transport.sniff", clientTransportSniff)
return Settings.builder().put("cluster.name", clusterName).put("client.transport.sniff", clientTransportSniff)
.put("client.transport.ignore_cluster_name", clientIgnoreClusterName)
.put("client.transport.ping_timeout", clientPingTimeout)
.put("client.transport.nodes_sampler_interval", clientNodesSamplerInterval)
.build();
.put("client.transport.nodes_sampler_interval", clientNodesSamplerInterval).build();
}
public void setClusterNodes(String clusterNodes) {
@@ -0,0 +1,3 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.client;
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -16,7 +16,9 @@
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;
@@ -25,6 +27,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.ProxyProvider;
import reactor.netty.tcp.TcpClient;
import java.io.IOException;
@@ -33,21 +36,19 @@ import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
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;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
@@ -58,6 +59,8 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
@@ -87,18 +90,18 @@ import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.reactivestreams.Publisher;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.NoReachableHostException;
import org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices;
import org.springframework.data.elasticsearch.client.util.RequestConverters;
import org.springframework.data.elasticsearch.client.util.NamedXContents;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -109,8 +112,6 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
@@ -122,6 +123,11 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @author Roman Puchkovskiy
* @author Russell Parry
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
@@ -129,6 +135,8 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices {
private final HostProvider hostProvider;
private final RequestCreator requestCreator;
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
/**
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
@@ -137,10 +145,30 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
* @param hostProvider must not be {@literal null}.
*/
public DefaultReactiveElasticsearchClient(HostProvider hostProvider) {
this(hostProvider, new DefaultRequestCreator());
}
/**
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
* connections and the given {@link RequestCreator}.
*
* @param hostProvider must not be {@literal null}.
* @param requestCreator must not be {@literal null}.
*/
public DefaultReactiveElasticsearchClient(HostProvider hostProvider, RequestCreator requestCreator) {
Assert.notNull(hostProvider, "HostProvider must not be null");
Assert.notNull(requestCreator, "RequestCreator must not be null");
this.hostProvider = hostProvider;
this.requestCreator = requestCreator;
}
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
Assert.notNull(headersSupplier, "headersSupplier must not be null");
this.headersSupplier = headersSupplier;
}
/**
@@ -171,14 +199,35 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
* @return new instance of {@link DefaultReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration) {
return create(clientConfiguration, new DefaultRequestCreator());
}
/**
* Create a new {@link DefaultReactiveElasticsearchClient} given {@link ClientConfiguration} and
* {@link RequestCreator}. <br />
* <strong>NOTE</strong> If the cluster requires authentication be sure to provide the according {@link HttpHeaders}
* correctly.
*
* @param clientConfiguration Client configuration. Must not be {@literal null}.
* @param requestCreator Request creator. Must not be {@literal null}.
* @return new instance of {@link DefaultReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration,
RequestCreator requestCreator) {
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null");
Assert.notNull(requestCreator, "RequestCreator must not be null");
WebClientProvider provider = getWebClientProvider(clientConfiguration);
HostProvider hostProvider = HostProvider.provider(provider,
HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
return new DefaultReactiveElasticsearchClient(hostProvider);
DefaultReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator);
client.setHeadersSupplier(clientConfiguration.getHeadersSupplier());
return client;
}
private static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) {
@@ -193,22 +242,37 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
}
if (!soTimeout.isNegative()) {
tcpClient = tcpClient.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\"");
}
tcpClient = tcpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
.port(Integer.parseInt(hostPort[1])));
}
String scheme = "http";
HttpClient httpClient = HttpClient.from(tcpClient);
if (clientConfiguration.useSsl()) {
httpClient = httpClient.secure(sslConfig -> {
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
sslContext.ifPresent(it -> sslConfig.sslContext(new JdkSslContext(it, true, ClientAuth.NONE)));
});
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";
}
@@ -216,7 +280,13 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
WebClientProvider provider = WebClientProvider.create(scheme, connector);
return provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders());
if (clientConfiguration.getPathPrefix() != null) {
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
}
provider = provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer());
return provider;
}
/*
@@ -226,7 +296,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Boolean> ping(HttpHeaders headers) {
return sendRequest(new MainRequest(), RequestCreator.ping(), RawActionResponse.class, headers) //
return sendRequest(new MainRequest(), requestCreator.ping(), RawActionResponse.class, headers) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.onErrorResume(NoReachableHostException.class, error -> Mono.just(false)).next();
}
@@ -238,7 +308,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<MainResponse> info(HttpHeaders headers) {
return sendRequest(new MainRequest(), RequestCreator.info(), MainResponse.class, headers) //
return sendRequest(new MainRequest(), requestCreator.info(), MainResponse.class, headers) //
.next();
}
@@ -249,7 +319,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<GetResult> get(HttpHeaders headers, GetRequest getRequest) {
return sendRequest(getRequest, RequestCreator.get(), GetResponse.class, headers) //
return sendRequest(getRequest, requestCreator.get(), GetResponse.class, headers) //
.filter(GetResponse::isExists) //
.map(DefaultReactiveElasticsearchClient::getResponseToGetResult) //
.next();
@@ -262,7 +332,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Flux<GetResult> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) {
return sendRequest(multiGetRequest, RequestCreator.multiGet(), MultiGetResponse.class, headers)
return sendRequest(multiGetRequest, requestCreator.multiGet(), MultiGetResponse.class, headers)
.map(MultiGetResponse::getResponses) //
.flatMap(Flux::fromArray) //
.filter(it -> !it.isFailed() && it.getResponse().isExists()) //
@@ -276,7 +346,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest) {
return sendRequest(getRequest, RequestCreator.exists(), RawActionResponse.class, headers) //
return sendRequest(getRequest, requestCreator.exists(), RawActionResponse.class, headers) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.next();
}
@@ -287,7 +357,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
*/
@Override
public Mono<IndexResponse> index(HttpHeaders headers, IndexRequest indexRequest) {
return sendRequest(indexRequest, RequestCreator.index(), IndexResponse.class, headers).publishNext();
return sendRequest(indexRequest, requestCreator.index(), IndexResponse.class, headers).publishNext();
}
/*
@@ -305,7 +375,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
*/
@Override
public Mono<UpdateResponse> update(HttpHeaders headers, UpdateRequest updateRequest) {
return sendRequest(updateRequest, RequestCreator.update(), UpdateResponse.class, headers).publishNext();
return sendRequest(updateRequest, requestCreator.update(), UpdateResponse.class, headers).publishNext();
}
/*
@@ -315,10 +385,25 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<DeleteResponse> delete(HttpHeaders headers, DeleteRequest deleteRequest) {
return sendRequest(deleteRequest, RequestCreator.delete(), DeleteResponse.class, headers) //
return sendRequest(deleteRequest, requestCreator.delete(), DeleteResponse.class, headers) //
.publishNext();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#count(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Mono<Long> count(HttpHeaders headers, SearchRequest searchRequest) {
searchRequest.source().trackTotalHits(true);
searchRequest.source().size(0);
searchRequest.source().fetchSource(false);
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
.map(SearchResponse::getHits) //
.map(searchHits -> searchHits.getTotalHits().value) //
.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
@@ -326,11 +411,29 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest) {
return sendRequest(searchRequest, RequestCreator.search(), SearchResponse.class, headers) //
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
.map(SearchResponse::getHits) //
.flatMap(Flux::fromIterable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#aggregate(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Flux<Aggregation> aggregate(HttpHeaders headers, SearchRequest searchRequest) {
Assert.notNull(headers, "headers must not be null");
Assert.notNull(searchRequest, "searchRequest must not be null");
searchRequest.source().size(0);
searchRequest.source().trackTotalHits(false);
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
.map(SearchResponse::getAggregations) //
.flatMap(Flux::fromIterable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#scroll(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
@@ -353,11 +456,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
Flux<SearchResponse> exchange = outbound.startWith(searchRequest).flatMap(it -> {
if (it instanceof SearchRequest) {
return sendRequest((SearchRequest) it, RequestCreator.search(), SearchResponse.class, headers);
return sendRequest((SearchRequest) it, requestCreator.search(), SearchResponse.class, headers);
} else if (it instanceof SearchScrollRequest) {
return sendRequest((SearchScrollRequest) it, RequestCreator.scroll(), SearchResponse.class, headers);
return sendRequest((SearchScrollRequest) it, requestCreator.scroll(), SearchResponse.class, headers);
} else if (it instanceof ClearScrollRequest) {
return sendRequest((ClearScrollRequest) it, RequestCreator.clearScroll(), ClearScrollResponse.class, headers)
return sendRequest((ClearScrollRequest) it, requestCreator.clearScroll(), ClearScrollResponse.class, headers)
.flatMap(discard -> Flux.empty());
}
@@ -410,16 +513,27 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
clearScrollRequest.scrollIds(state.getScrollIds());
// just send the request, resources get cleaned up anyways after scrollTimeout has been reached.
return sendRequest(clearScrollRequest, RequestCreator.clearScroll(), ClearScrollResponse.class, headers);
return sendRequest(clearScrollRequest, requestCreator.clearScroll(), ClearScrollResponse.class, headers);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.index.reindex.DeleteByQueryRequest)
*/
@Override
public Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest) {
return sendRequest(deleteRequest, RequestCreator.deleteByQuery(), BulkByScrollResponse.class, headers) //
return sendRequest(deleteRequest, requestCreator.deleteByQuery(), BulkByScrollResponse.class, headers) //
.publishNext();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#bulk(org.springframework.http.HttpHeaders, org.elasticsearch.action.bulk.BulkRequest)
*/
@Override
public Mono<BulkResponse> bulk(HttpHeaders headers, BulkRequest bulkRequest) {
return sendRequest(bulkRequest, requestCreator.bulk(), BulkResponse.class, headers) //
.publishNext();
}
@@ -432,7 +546,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
return sendRequest(request, RequestCreator.indexExists(), RawActionResponse.class, headers) //
return sendRequest(request, requestCreator.indexExists(), RawActionResponse.class, headers) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.next();
}
@@ -444,7 +558,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
return sendRequest(request, RequestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
return sendRequest(request, requestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -455,7 +569,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
return sendRequest(createIndexRequest, RequestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
return sendRequest(createIndexRequest, requestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -466,7 +580,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest request) {
return sendRequest(request, RequestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
return sendRequest(request, requestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -477,7 +591,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest) {
return sendRequest(closeIndexRequest, RequestCreator.indexClose(), AcknowledgedResponse.class, headers) //
return sendRequest(closeIndexRequest, requestCreator.indexClose(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -488,7 +602,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest) {
return sendRequest(refreshRequest, RequestCreator.indexRefresh(), RefreshResponse.class, headers) //
return sendRequest(refreshRequest, requestCreator.indexRefresh(), RefreshResponse.class, headers) //
.then();
}
@@ -499,7 +613,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
return sendRequest(putMappingRequest, RequestCreator.putMapping(), AcknowledgedResponse.class, headers) //
return sendRequest(putMappingRequest, requestCreator.putMapping(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -510,7 +624,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest) {
return sendRequest(flushRequest, RequestCreator.flushIndex(), FlushResponse.class, headers) //
return sendRequest(flushRequest, requestCreator.flushIndex(), FlushResponse.class, headers) //
.then();
}
@@ -548,18 +662,17 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return new GetResult(response.getIndex(), response.getType(), response.getId(), response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion(), response.isExists(), response.getSourceAsBytesRef(),
response.getFields());
response.getFields(), null);
}
// -->
private <Req extends ActionRequest, Resp extends ActionResponse> Flux<Resp> sendRequest(Req request,
Function<Req, Request> converter, Class<Resp> responseType, HttpHeaders headers) {
private <Req extends ActionRequest, Resp> Flux<Resp> sendRequest(Req request, Function<Req, Request> converter,
Class<Resp> responseType, HttpHeaders headers) {
return sendRequest(converter.apply(request), responseType, headers);
}
private <AR extends ActionResponse> Flux<AR> sendRequest(Request request, Class<AR> responseType,
HttpHeaders headers) {
private <Resp> Flux<Resp> sendRequest(Request request, Class<Resp> responseType, HttpHeaders headers) {
String logId = ClientLogger.newLogId();
@@ -594,6 +707,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
request.getOptions().getHeaders().forEach(it -> theHeaders.add(it.getName(), it.getValue()));
}
}
// plus the ones from the supplier
HttpHeaders suppliedHeaders = headersSupplier.get();
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
theHeaders.addAll(suppliedHeaders);
}
});
if (request.getEntity() != null) {
@@ -604,7 +723,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
body::get);
requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()));
requestBodySpec.body(Mono.fromSupplier(body::get), String.class);
requestBodySpec.body(Mono.fromSupplier(body), String.class);
} else {
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters());
}
@@ -641,6 +760,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return handleServerError(request, response);
}
if (response.statusCode().is4xxClientError()) {
ClientLogger.logRawResponse(logId, response.statusCode());
return handleClientError(logId, request, response, responseType);
}
return response.body(BodyExtractors.toMono(byte[].class)) //
.map(it -> new String(it, StandardCharsets.UTF_8)) //
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) //
@@ -671,113 +796,74 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
}
private static XContentParser createParser(String mediaType, String content) throws IOException {
return XContentType.fromMediaTypeOrFormat(mediaType) //
.xContent() //
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
.createParser(new NamedXContentRegistry(NamedXContents.getDefaultNamedXContents()),
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
}
private static <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
private <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
return Mono.error(
new HttpServerErrorException(response.statusCode(), String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), response.statusCode().value())));
RestStatus status = RestStatus.fromCode(response.statusCode().value());
return Mono.error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), response.statusCode().value()), status));
}
static class RequestCreator {
static Function<SearchRequest, Request> search() {
return RequestConverters::search;
}
static Function<SearchScrollRequest, Request> scroll() {
return RequestConverters::searchScroll;
}
static Function<ClearScrollRequest, Request> clearScroll() {
return RequestConverters::clearScroll;
}
static Function<IndexRequest, Request> index() {
return RequestConverters::index;
}
static Function<GetRequest, Request> get() {
return RequestConverters::get;
}
static Function<MainRequest, Request> ping() {
return (request) -> RequestConverters.ping();
}
static Function<MainRequest, Request> info() {
return (request) -> RequestConverters.info();
}
static Function<MultiGetRequest, Request> multiGet() {
return RequestConverters::multiGet;
}
static Function<GetRequest, Request> exists() {
return RequestConverters::exists;
}
static Function<UpdateRequest, Request> update() {
return RequestConverters::update;
}
static Function<DeleteRequest, Request> delete() {
return RequestConverters::delete;
}
static Function<DeleteByQueryRequest, Request> deleteByQuery() {
return request -> {
try {
return RequestConverters.deleteByQuery(request);
} catch (IOException e) {
throw new ElasticsearchException("Could not parse request", e);
}
};
}
// --> INDICES
static Function<GetIndexRequest, Request> indexExists() {
return RequestConverters::indexExists;
}
static Function<DeleteIndexRequest, Request> indexDelete() {
return RequestConverters::indexDelete;
}
static Function<CreateIndexRequest, Request> indexCreate() {
return RequestConverters::indexCreate;
}
static Function<OpenIndexRequest, Request> indexOpen() {
return RequestConverters::indexOpen;
}
static Function<CloseIndexRequest, Request> indexClose() {
return RequestConverters::indexClose;
}
static Function<RefreshRequest, Request> indexRefresh() {
return RequestConverters::indexRefresh;
}
static Function<PutMappingRequest, Request> putMapping() {
return RequestConverters::putMapping;
}
static Function<FlushRequest, Request> flushIndex() {
return RequestConverters::flushIndex;
}
private <T> Publisher<? extends T> handleClientError(String logId, Request request, ClientResponse response,
Class<T> responseType) {
return response.body(BodyExtractors.toMono(byte[].class)) //
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
.flatMap(content -> {
String mediaType = response.headers().contentType().map(MediaType::toString)
.orElse(XContentType.JSON.mediaType());
RestStatus status = RestStatus.fromCode(response.statusCode().value());
try {
ElasticsearchException exception = getElasticsearchException(response, content, mediaType);
if (exception != null) {
StringBuilder sb = new StringBuilder();
buildExceptionMessages(sb, exception);
return Mono.error(new ElasticsearchStatusException(sb.toString(), status, exception));
}
} catch (Exception e) {
return Mono.error(new ElasticsearchStatusException(content, status));
}
return Mono.just(content);
}).doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) //
.flatMap(content -> doDecode(response, responseType, content));
}
// region ElasticsearchException helper
@Nullable
private ElasticsearchException getElasticsearchException(ClientResponse response, String content, String mediaType)
throws IOException {
XContentParser parser = createParser(mediaType, content);
// we have a JSON object with an error and a status field
XContentParser.Token token = parser.nextToken(); // Skip START_OBJECT
do {
token = parser.nextToken();
if (parser.currentName().equals("error")) {
return ElasticsearchException.failureFromXContent(parser);
}
} while (token == XContentParser.Token.FIELD_NAME);
return null;
}
private static void buildExceptionMessages(StringBuilder sb, Throwable t) {
sb.append(t.getMessage());
for (Throwable throwable : t.getSuppressed()) {
sb.append(", ");
buildExceptionMessages(sb, throwable);
}
}
// endregion
// region internal classes
/**
* Reactive client {@link ReactiveElasticsearchClient.Status} implementation.
*
@@ -801,40 +887,5 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
}
}
/**
* Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)}
*
* @author Christoph Strobl
* @since 3.2
*/
private static class ScrollState {
private final Object lock = new Object();
private final List<String> pastIds = new ArrayList<>(1);
private String scrollId;
String getScrollId() {
return scrollId;
}
List<String> getScrollIds() {
synchronized (lock) {
return Collections.unmodifiableList(new ArrayList<>(pastIds));
}
}
void updateScrollId(String scrollId) {
if (StringUtils.hasText(scrollId)) {
synchronized (lock) {
this.scrollId = scrollId;
pastIds.add(scrollId);
}
}
}
}
// endregion
}
@@ -0,0 +1,7 @@
package org.springframework.data.elasticsearch.client.reactive;
/**
* @author Roman Puchkovskiy
* @since 4.0
*/
class DefaultRequestCreator implements RequestCreator {}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -19,6 +19,7 @@ import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ClientHttpConnector;
@@ -32,6 +33,8 @@ import org.springframework.web.reactive.function.client.WebClient.Builder;
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Huw Ayling-Miller
* @author Peter-Josef Meisch
* @since 3.2
*/
class DefaultWebClientProvider implements WebClientProvider {
@@ -42,6 +45,8 @@ class DefaultWebClientProvider implements WebClientProvider {
private final @Nullable ClientHttpConnector connector;
private final Consumer<Throwable> errorListener;
private final HttpHeaders headers;
private final String pathPrefix;
private final Function<WebClient, WebClient> webClientConfigurer;
/**
* Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}.
@@ -50,35 +55,38 @@ class DefaultWebClientProvider implements WebClientProvider {
* @param connector can be {@literal null}.
*/
DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector) {
this(scheme, connector, e -> {}, HttpHeaders.EMPTY);
this(scheme, connector, e -> {}, HttpHeaders.EMPTY, null, Function.identity());
}
/**
* Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}.
*
*
* @param pathPrefix can be {@literal null}
* @param scheme must not be {@literal null}.
* @param connector can be {@literal null}.
* @param errorListener must not be {@literal null}.
* @param headers must not be {@literal null}.
* @param webClientConfigurer must not be {@literal null}.
*/
private DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector,
Consumer<Throwable> errorListener, HttpHeaders headers) {
Consumer<Throwable> errorListener, HttpHeaders headers, @Nullable String pathPrefix,
Function<WebClient, WebClient> webClientConfigurer) {
Assert.notNull(scheme, "Scheme must not be null! A common scheme would be 'http'.");
Assert.notNull(errorListener, "ErrorListener must not be null! You may want use a no-op one 'e -> {}' instead.");
Assert.notNull(errorListener, "errorListener must not be null! You may want use a no-op one 'e -> {}' instead.");
Assert.notNull(headers, "headers must not be null! Think about using 'HttpHeaders.EMPTY' as an alternative.");
Assert.notNull(webClientConfigurer,
"webClientConfigurer must not be null! You may want use a no-op one 'Function.identity()' instead.");
this.cachedClients = new ConcurrentHashMap<>();
this.scheme = scheme;
this.connector = connector;
this.errorListener = errorListener;
this.headers = headers;
this.pathPrefix = pathPrefix;
this.webClientConfigurer = webClientConfigurer;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#get(java.net.InetSocketAddress)
*/
@Override
public WebClient get(InetSocketAddress endpoint) {
@@ -87,19 +95,21 @@ class DefaultWebClientProvider implements WebClientProvider {
return this.cachedClients.computeIfAbsent(endpoint, this::createWebClientForSocketAddress);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#getDefaultHeaders()
*/
@Override
public HttpHeaders getDefaultHeaders() {
return headers;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#withDefaultHeaders(org.springframework.http.HttpHeaders)
*/
@Override
public Consumer<Throwable> getErrorListener() {
return this.errorListener;
}
@Override
public String getPathPrefix() {
return pathPrefix;
}
@Override
public WebClientProvider withDefaultHeaders(HttpHeaders headers) {
@@ -109,31 +119,33 @@ class DefaultWebClientProvider implements WebClientProvider {
merged.addAll(this.headers);
merged.addAll(headers);
return new DefaultWebClientProvider(this.scheme, this.connector, errorListener, merged);
return new DefaultWebClientProvider(scheme, connector, errorListener, merged, pathPrefix, webClientConfigurer);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#getErrorListener()
*/
@Override
public Consumer<Throwable> getErrorListener() {
return this.errorListener;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#withErrorListener(java.util.function.Consumer)
*/
@Override
public WebClientProvider withErrorListener(Consumer<Throwable> errorListener) {
Assert.notNull(errorListener, "Error listener must not be null.");
Consumer<Throwable> listener = this.errorListener.andThen(errorListener);
return new DefaultWebClientProvider(this.scheme, this.connector, listener, this.headers);
return new DefaultWebClientProvider(scheme, this.connector, listener, headers, pathPrefix, webClientConfigurer);
}
@Override
public WebClientProvider withPathPrefix(String pathPrefix) {
Assert.notNull(pathPrefix, "pathPrefix must not be null.");
return new DefaultWebClientProvider(this.scheme, this.connector, this.errorListener, this.headers, pathPrefix,
webClientConfigurer);
}
@Override
public WebClientProvider withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer) {
return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer);
}
protected WebClient createWebClientForSocketAddress(InetSocketAddress socketAddress) {
Builder builder = WebClient.builder().defaultHeaders(it -> it.addAll(getDefaultHeaders()));
@@ -142,7 +154,9 @@ class DefaultWebClientProvider implements WebClientProvider {
builder = builder.clientConnector(connector);
}
String baseUrl = String.format("%s://%s:%d", this.scheme, socketAddress.getHostString(), socketAddress.getPort());
return builder.baseUrl(baseUrl).filter((request, next) -> next.exchange(request).doOnError(errorListener)).build();
String baseUrl = String.format("%s://%s:%d%s", this.scheme, socketAddress.getHostString(), socketAddress.getPort(),
pathPrefix == null ? "" : '/' + pathPrefix);
WebClient webClient = builder.baseUrl(baseUrl).filter((request, next) -> next.exchange(request).doOnError(errorListener)).build();
return webClientConfigurer.apply(webClient);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -20,9 +20,11 @@ import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.NoReachableHostException;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
@@ -40,18 +42,20 @@ public interface HostProvider {
* Create a new {@link HostProvider} best suited for the given {@link WebClientProvider} and number of hosts.
*
* @param clientProvider must not be {@literal null} .
* @param headersSupplier to supply custom headers, must not be {@literal null}
* @param endpoints must not be {@literal null} nor empty.
* @return new instance of {@link HostProvider}.
*/
static HostProvider provider(WebClientProvider clientProvider, InetSocketAddress... endpoints) {
static HostProvider provider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier,
InetSocketAddress... endpoints) {
Assert.notNull(clientProvider, "WebClientProvider must not be null");
Assert.notEmpty(endpoints, "Please provide at least one endpoint to connect to.");
if (endpoints.length == 1) {
return new SingleNodeHostProvider(clientProvider, endpoints[0]);
return new SingleNodeHostProvider(clientProvider, headersSupplier, endpoints[0]);
} else {
return new MultiNodeHostProvider(clientProvider, endpoints);
return new MultiNodeHostProvider(clientProvider,headersSupplier, endpoints);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -27,6 +28,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
@@ -45,11 +47,13 @@ import org.springframework.web.reactive.function.client.WebClient;
class MultiNodeHostProvider implements HostProvider {
private final WebClientProvider clientProvider;
private final Supplier<HttpHeaders> headersSupplier;
private final Map<InetSocketAddress, ElasticsearchHost> hosts;
MultiNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress... endpoints) {
MultiNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, InetSocketAddress... endpoints) {
this.clientProvider = clientProvider;
this.headersSupplier = headersSupplier;
this.hosts = new ConcurrentHashMap<>();
for (InetSocketAddress endpoint : endpoints) {
this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN));
@@ -60,6 +64,7 @@ class MultiNodeHostProvider implements HostProvider {
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#clusterInfo()
*/
@Override
public Mono<ClusterInformation> clusterInfo() {
return nodes(null).map(this::updateNodeState).buffer(hosts.size())
.then(Mono.just(new ClusterInformation(new LinkedHashSet<>(this.hosts.values()))));
@@ -132,8 +137,9 @@ class MultiNodeHostProvider implements HostProvider {
.flatMap(host -> {
Mono<ClientResponse> exchange = createWebClient(host) //
.head().uri("/").exchange().doOnError(throwable -> {
.head().uri("/") //
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
.exchange().doOnError(throwable -> {
hosts.put(host, new ElasticsearchHost(host, State.OFFLINE));
clientProvider.getErrorListener().accept(throwable);
});
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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,6 +15,10 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.springframework.http.HttpStatus;
@@ -61,4 +65,12 @@ class RawActionResponse extends ActionResponse {
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
return delegate.body(extractor);
}
/*
* (non-Javadoc)
* until Elasticsearch 7.4 this empty implementation was available in the abstract base class
*/
@Override
public void writeTo(StreamOutput out) throws IOException {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -30,6 +30,8 @@ import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
@@ -45,10 +47,11 @@ import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
@@ -58,6 +61,8 @@ import org.springframework.web.reactive.function.client.WebClient;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Henrique Amaral
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
@@ -329,6 +334,47 @@ public interface ReactiveElasticsearchClient {
*/
Mono<DeleteResponse> delete(HttpHeaders headers, DeleteRequest deleteRequest);
/**
* Execute a {@link SearchRequest} against the {@literal count} API.
*
* @param consumer new {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
* elastic.co</a>
* @return the {@link Mono} emitting the count result.
* @since 4.0
*/
default Mono<Long> count(Consumer<SearchRequest> consumer) {
SearchRequest searchRequest = new SearchRequest();
consumer.accept(searchRequest);
return count(searchRequest);
}
/**
* Execute a {@link SearchRequest} against the {@literal search} API.
*
* @param searchRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
* elastic.co</a>
* @return the {@link Mono} emitting the count result.
* @since 4.0
*/
default Mono<Long> count(SearchRequest searchRequest) {
return count(HttpHeaders.EMPTY, searchRequest);
}
/**
* Execute a {@link SearchRequest} against the {@literal search} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param searchRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
* elastic.co</a>
* @return the {@link Mono} emitting the count result.
* @since 4.0
*/
Mono<Long> count(HttpHeaders headers, SearchRequest searchRequest);
/**
* Execute a {@link SearchRequest} against the {@literal search} API.
*
@@ -367,6 +413,50 @@ public interface ReactiveElasticsearchClient {
*/
Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest);
/**
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
*
* @param consumer
* never {@literal null}.
* @return the {@link Flux} emitting {@link Aggregation} one by one.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
* elastic.co</a>
* @since 4.0
*/
default Flux<Aggregation> aggregate(Consumer<SearchRequest> consumer) {
Assert.notNull(consumer, "consumer must not be null");
SearchRequest request = new SearchRequest();
consumer.accept(request);
return aggregate(request);
}
/**
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
*
* @param searchRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
* elastic.co</a>
* @return the {@link Flux} emitting {@link Aggregation} one by one.
* @since 4.0
*/
default Flux<Aggregation> aggregate(SearchRequest searchRequest) {
return aggregate(HttpHeaders.EMPTY, searchRequest);
}
/**
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param searchRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
* elastic.co</a>
* @return the {@link Flux} emitting {@link Aggregation} one by one.
* @since 4.0
*/
Flux<Aggregation> aggregate(HttpHeaders headers, SearchRequest searchRequest);
/**
* Execute the given {@link SearchRequest} against the {@literal search scroll} API.
*
@@ -430,6 +520,44 @@ public interface ReactiveElasticsearchClient {
*/
Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest);
/**
* Execute a {@link BulkRequest} against the {@literal bulk} API.
*
* @param consumer never {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on
* elastic.co</a>
* @return a {@link Mono} emitting the emitting operation response.
*/
default Mono<BulkResponse> bulk(Consumer<BulkRequest> consumer) {
BulkRequest request = new BulkRequest();
consumer.accept(request);
return bulk(request);
}
/**
* Execute a {@link BulkRequest} against the {@literal bulk} API.
*
* @param bulkRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on
* elastic.co</a>
* @return a {@link Mono} emitting the emitting operation response.
*/
default Mono<BulkResponse> bulk(BulkRequest bulkRequest) {
return bulk(HttpHeaders.EMPTY, bulkRequest);
}
/**
* Execute a {@link BulkRequest} against the {@literal bulk} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param bulkRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on
* elastic.co</a>
* @return a {@link Mono} emitting operation response.
*/
Mono<BulkResponse> bulk(HttpHeaders headers, BulkRequest bulkRequest);
/**
* Compose the actual command/s to run against Elasticsearch using the underlying {@link WebClient connection}.
* {@link #execute(ReactiveElasticsearchClientCallback) Execute} selects an active server from the available ones and
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -24,6 +24,7 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Roman Puchkovskiy
* @since 3.2
*/
public final class ReactiveRestClients {
@@ -33,6 +34,8 @@ public final class ReactiveRestClients {
/**
* Start here to create a new client tailored to your needs.
*
* @param clientConfiguration client configuration to use for building {@link ReactiveElasticsearchClient}; must not
* be {@literal null}.
* @return new instance of {@link ReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration) {
@@ -41,4 +44,21 @@ public final class ReactiveRestClients {
return DefaultReactiveElasticsearchClient.create(clientConfiguration);
}
/**
* Start here to create a new client tailored to your needs.
*
* @param clientConfiguration client configuration to use for building {@link ReactiveElasticsearchClient}; must not
* be {@literal null}.
* @param requestCreator request creator to use in the client; must not be {@literal null}.
* @return new instance of {@link ReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration,
RequestCreator requestCreator) {
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!");
Assert.notNull(requestCreator, "RequestCreator must not be null!");
return DefaultReactiveElasticsearchClient.create(clientConfiguration, requestCreator);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -0,0 +1,134 @@
package org.springframework.data.elasticsearch.client.reactive;
import java.io.IOException;
import java.util.function.Function;
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.main.MainRequest;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.client.util.RequestConverters;
/**
* @author Roman Puchkovskiy
* @since 4.0
*/
public interface RequestCreator {
default Function<SearchRequest, Request> search() {
return RequestConverters::search;
}
default Function<SearchScrollRequest, Request> scroll() {
return RequestConverters::searchScroll;
}
default Function<ClearScrollRequest, Request> clearScroll() {
return RequestConverters::clearScroll;
}
default Function<IndexRequest, Request> index() {
return RequestConverters::index;
}
default Function<GetRequest, Request> get() {
return RequestConverters::get;
}
default Function<MainRequest, Request> ping() {
return (request) -> RequestConverters.ping();
}
default Function<MainRequest, Request> info() {
return (request) -> RequestConverters.info();
}
default Function<MultiGetRequest, Request> multiGet() {
return RequestConverters::multiGet;
}
default Function<GetRequest, Request> exists() {
return RequestConverters::exists;
}
default Function<UpdateRequest, Request> update() {
return RequestConverters::update;
}
default Function<DeleteRequest, Request> delete() {
return RequestConverters::delete;
}
default Function<DeleteByQueryRequest, Request> deleteByQuery() {
return RequestConverters::deleteByQuery;
}
default Function<BulkRequest, Request> bulk() {
return request -> {
try {
return RequestConverters.bulk(request);
} catch (IOException e) {
throw new ElasticsearchException("Could not parse request", e);
}
};
}
// --> INDICES
default Function<GetIndexRequest, Request> indexExists() {
return RequestConverters::indexExists;
}
default Function<DeleteIndexRequest, Request> indexDelete() {
return RequestConverters::indexDelete;
}
default Function<CreateIndexRequest, Request> indexCreate() {
return RequestConverters::indexCreate;
}
default Function<OpenIndexRequest, Request> indexOpen() {
return RequestConverters::indexOpen;
}
default Function<CloseIndexRequest, Request> indexClose() {
return RequestConverters::indexClose;
}
default Function<RefreshRequest, Request> indexRefresh() {
return RequestConverters::indexRefresh;
}
default Function<PutMappingRequest, Request> putMapping() {
return RequestConverters::putMapping;
}
default Function<FlushRequest, Request> flushIndex() {
return RequestConverters::flushIndex;
}
default Function<CountRequest, Request> count() {
return RequestConverters::count;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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,10 +15,12 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
@@ -35,12 +37,14 @@ import org.springframework.web.reactive.function.client.WebClient;
class SingleNodeHostProvider implements HostProvider {
private final WebClientProvider clientProvider;
private final Supplier<HttpHeaders> headersSupplier;
private final InetSocketAddress endpoint;
private volatile ElasticsearchHost state;
SingleNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress endpoint) {
SingleNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, InetSocketAddress endpoint) {
this.clientProvider = clientProvider;
this.headersSupplier = headersSupplier;
this.endpoint = endpoint;
this.state = new ElasticsearchHost(this.endpoint, State.UNKNOWN);
}
@@ -53,9 +57,10 @@ class SingleNodeHostProvider implements HostProvider {
public Mono<ClusterInformation> clusterInfo() {
return createWebClient(endpoint) //
.head().uri("/").exchange() //
.head().uri("/")
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
.exchange() //
.flatMap(it -> {
if (it.statusCode().isError()) {
state = ElasticsearchHost.offline(endpoint);
} else {
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.client.reactive;
import java.net.InetSocketAddress;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ClientHttpConnector;
@@ -34,6 +35,8 @@ import org.springframework.web.reactive.function.client.WebClient;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Huw Ayling-Miller
* @author Peter-Josef Meisch
* @since 3.2
*/
public interface WebClientProvider {
@@ -96,6 +99,15 @@ public interface WebClientProvider {
*/
Consumer<Throwable> getErrorListener();
/**
* Obtain the {@link String pathPrefix} to be used.
*
* @return the pathPrefix if set.
* @since 4.0
*/
@Nullable
String getPathPrefix();
/**
* Create a new instance of {@link WebClientProvider} applying the given headers by default.
*
@@ -111,4 +123,23 @@ public interface WebClientProvider {
* @return new instance of {@link WebClientProvider}.
*/
WebClientProvider withErrorListener(Consumer<Throwable> errorListener);
/**
* Create a new instance of {@link WebClientProvider} where HTTP requests are called with the given path prefix.
*
* @param pathPrefix Path prefix to add to requests
* @return new instance of {@link WebClientProvider}
* @since 4.0
*/
WebClientProvider withPathPrefix(String pathPrefix);
/**
* Create a new instance of {@link WebClientProvider} calling the given {@link Function} to configure the
* {@link WebClient}.
*
* @param webClientConfigurer configuration function
* @return new instance of {@link WebClientProvider}
* @since 4.0
*/
WebClientProvider withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
}
@@ -1,6 +1,3 @@
/**
* Everything required for a Reactive Elasticsearch client.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.client.reactive;
@@ -0,0 +1,169 @@
/*
* Copyright 2020 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.util;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ContextParser;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.bucket.adjacency.AdjacencyMatrixAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.adjacency.ParsedAdjacencyMatrix;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.ParsedComposite;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilters;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoHashGrid;
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoTileGrid;
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.global.ParsedGlobal;
import org.elasticsearch.search.aggregations.bucket.histogram.AutoDateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedAutoDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedHistogram;
import org.elasticsearch.search.aggregations.bucket.missing.MissingAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.missing.ParsedMissing;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedReverseNested;
import org.elasticsearch.search.aggregations.bucket.nested.ReverseNestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.DateRangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.GeoDistanceAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.IpRangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.ParsedBinaryRange;
import org.elasticsearch.search.aggregations.bucket.range.ParsedDateRange;
import org.elasticsearch.search.aggregations.bucket.range.ParsedGeoDistance;
import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.sampler.InternalSampler;
import org.elasticsearch.search.aggregations.bucket.sampler.ParsedSampler;
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantLongTerms;
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantStringTerms;
import org.elasticsearch.search.aggregations.bucket.significant.SignificantLongTerms;
import org.elasticsearch.search.aggregations.bucket.significant.SignificantStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.metrics.*;
import org.elasticsearch.search.aggregations.pipeline.*;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestion;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
/**
* <p>
* Original implementation source {@link org.elasticsearch.client.RestHighLevelClient#getDefaultNamedXContents()} by
* {@literal Elasticsearch} (<a href="https://www.elastic.co">https://www.elastic.co</a>) licensed under the Apache
* License, Version 2.0.
* </p>
* Modified for usage with {@link ReactiveElasticsearchClient}.
* <p>
* Only intended for internal use.
*
* @author Russell Parry
* @since 4.0
*/
public class NamedXContents {
private NamedXContents() {
// contains only utility methods
}
public static List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
Map<String, ContextParser<Object, ? extends Aggregation>> map = new HashMap<>();
map.put(CardinalityAggregationBuilder.NAME, (p, c) -> ParsedCardinality.fromXContent(p, (String) c));
map.put(InternalHDRPercentiles.NAME, (p, c) -> ParsedHDRPercentiles.fromXContent(p, (String) c));
map.put(InternalHDRPercentileRanks.NAME, (p, c) -> ParsedHDRPercentileRanks.fromXContent(p, (String) c));
map.put(InternalTDigestPercentiles.NAME, (p, c) -> ParsedTDigestPercentiles.fromXContent(p, (String) c));
map.put(InternalTDigestPercentileRanks.NAME, (p, c) -> ParsedTDigestPercentileRanks.fromXContent(p, (String) c));
map.put(PercentilesBucketPipelineAggregationBuilder.NAME,
(p, c) -> ParsedPercentilesBucket.fromXContent(p, (String) c));
map.put(MedianAbsoluteDeviationAggregationBuilder.NAME,
(p, c) -> ParsedMedianAbsoluteDeviation.fromXContent(p, (String) c));
map.put(MinAggregationBuilder.NAME, (p, c) -> ParsedMin.fromXContent(p, (String) c));
map.put(MaxAggregationBuilder.NAME, (p, c) -> ParsedMax.fromXContent(p, (String) c));
map.put(SumAggregationBuilder.NAME, (p, c) -> ParsedSum.fromXContent(p, (String) c));
map.put(AvgAggregationBuilder.NAME, (p, c) -> ParsedAvg.fromXContent(p, (String) c));
map.put(WeightedAvgAggregationBuilder.NAME, (p, c) -> ParsedWeightedAvg.fromXContent(p, (String) c));
map.put(ValueCountAggregationBuilder.NAME, (p, c) -> ParsedValueCount.fromXContent(p, (String) c));
map.put(InternalSimpleValue.NAME, (p, c) -> ParsedSimpleValue.fromXContent(p, (String) c));
map.put(DerivativePipelineAggregationBuilder.NAME, (p, c) -> ParsedDerivative.fromXContent(p, (String) c));
map.put(InternalBucketMetricValue.NAME, (p, c) -> ParsedBucketMetricValue.fromXContent(p, (String) c));
map.put(StatsAggregationBuilder.NAME, (p, c) -> ParsedStats.fromXContent(p, (String) c));
map.put(StatsBucketPipelineAggregationBuilder.NAME, (p, c) -> ParsedStatsBucket.fromXContent(p, (String) c));
map.put(ExtendedStatsAggregationBuilder.NAME, (p, c) -> ParsedExtendedStats.fromXContent(p, (String) c));
map.put(ExtendedStatsBucketPipelineAggregationBuilder.NAME,
(p, c) -> ParsedExtendedStatsBucket.fromXContent(p, (String) c));
map.put(GeoBoundsAggregationBuilder.NAME, (p, c) -> ParsedGeoBounds.fromXContent(p, (String) c));
map.put(GeoCentroidAggregationBuilder.NAME, (p, c) -> ParsedGeoCentroid.fromXContent(p, (String) c));
map.put(HistogramAggregationBuilder.NAME, (p, c) -> ParsedHistogram.fromXContent(p, (String) c));
map.put(DateHistogramAggregationBuilder.NAME, (p, c) -> ParsedDateHistogram.fromXContent(p, (String) c));
map.put(AutoDateHistogramAggregationBuilder.NAME, (p, c) -> ParsedAutoDateHistogram.fromXContent(p, (String) c));
map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
map.put(LongTerms.NAME, (p, c) -> ParsedLongTerms.fromXContent(p, (String) c));
map.put(DoubleTerms.NAME, (p, c) -> ParsedDoubleTerms.fromXContent(p, (String) c));
map.put(MissingAggregationBuilder.NAME, (p, c) -> ParsedMissing.fromXContent(p, (String) c));
map.put(NestedAggregationBuilder.NAME, (p, c) -> ParsedNested.fromXContent(p, (String) c));
map.put(ReverseNestedAggregationBuilder.NAME, (p, c) -> ParsedReverseNested.fromXContent(p, (String) c));
map.put(GlobalAggregationBuilder.NAME, (p, c) -> ParsedGlobal.fromXContent(p, (String) c));
map.put(FilterAggregationBuilder.NAME, (p, c) -> ParsedFilter.fromXContent(p, (String) c));
map.put(InternalSampler.PARSER_NAME, (p, c) -> ParsedSampler.fromXContent(p, (String) c));
map.put(GeoHashGridAggregationBuilder.NAME, (p, c) -> ParsedGeoHashGrid.fromXContent(p, (String) c));
map.put(GeoTileGridAggregationBuilder.NAME, (p, c) -> ParsedGeoTileGrid.fromXContent(p, (String) c));
map.put(RangeAggregationBuilder.NAME, (p, c) -> ParsedRange.fromXContent(p, (String) c));
map.put(DateRangeAggregationBuilder.NAME, (p, c) -> ParsedDateRange.fromXContent(p, (String) c));
map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c));
map.put(FiltersAggregationBuilder.NAME, (p, c) -> ParsedFilters.fromXContent(p, (String) c));
map.put(AdjacencyMatrixAggregationBuilder.NAME, (p, c) -> ParsedAdjacencyMatrix.fromXContent(p, (String) c));
map.put(SignificantLongTerms.NAME, (p, c) -> ParsedSignificantLongTerms.fromXContent(p, (String) c));
map.put(SignificantStringTerms.NAME, (p, c) -> ParsedSignificantStringTerms.fromXContent(p, (String) c));
map.put(ScriptedMetricAggregationBuilder.NAME, (p, c) -> ParsedScriptedMetric.fromXContent(p, (String) c));
map.put(IpRangeAggregationBuilder.NAME, (p, c) -> ParsedBinaryRange.fromXContent(p, (String) c));
map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
map.put(CompositeAggregationBuilder.NAME, (p, c) -> ParsedComposite.fromXContent(p, (String) c));
List<NamedXContentRegistry.Entry> entries = map.entrySet().stream().map(
entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
.collect(Collectors.toList());
entries.add(
new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> TermSuggestion.fromXContent(parser, (String) context)));
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class,
new ParseField(PhraseSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> PhraseSuggestion.fromXContent(parser, (String) context)));
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class,
new ParseField(CompletionSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> CompletionSuggestion.fromXContent(parser, (String) context)));
return entries;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -33,7 +33,6 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
@@ -60,6 +59,8 @@ import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.RethrottleRequest;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
@@ -76,6 +77,7 @@ import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.rankeval.RankEvalRequest;
import org.elasticsearch.index.reindex.AbstractBulkByScrollRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
@@ -98,8 +100,11 @@ import org.springframework.lang.Nullable;
* <p>
* Only intended for internal use.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
@SuppressWarnings("JavadocReference")
public class RequestConverters {
private static final XContentType REQUEST_BODY_CONTENT_TYPE = XContentType.JSON;
@@ -179,7 +184,9 @@ public class RequestConverters {
metadata.field("_index", action.index());
}
if (Strings.hasLength(action.type())) {
metadata.field("_type", action.type());
if (MapperService.SINGLE_MAPPING_NAME.equals(action.type()) == false) {
metadata.field("_type", action.type());
}
}
if (Strings.hasLength(action.id())) {
metadata.field("_id", action.id());
@@ -197,11 +204,14 @@ public class RequestConverters {
metadata.field("version_type", "external");
} else if (versionType == VersionType.EXTERNAL_GTE) {
metadata.field("version_type", "external_gte");
} else if (versionType == VersionType.FORCE) {
metadata.field("version_type", "force");
}
}
if (action.ifSeqNo() != SequenceNumbers.UNASSIGNED_SEQ_NO) {
metadata.field("if_seq_no", action.ifSeqNo());
metadata.field("if_primary_term", action.ifPrimaryTerm());
}
if (opType == DocWriteRequest.OpType.INDEX || opType == DocWriteRequest.OpType.CREATE) {
IndexRequest indexRequest = (IndexRequest) action;
if (Strings.hasLength(indexRequest.getPipeline())) {
@@ -361,7 +371,7 @@ public class RequestConverters {
XContentType upsertContentType = updateRequest.upsertRequest().getContentType();
if ((xContentType != null) && (xContentType != upsertContentType)) {
throw new IllegalStateException("Update request cannot have different content types for doc [" + xContentType
+ "]" + " and upsert [" + upsertContentType + "] documents");
+ ']' + " and upsert [" + upsertContentType + "] documents");
} else {
xContentType = upsertContentType;
}
@@ -386,6 +396,32 @@ public class RequestConverters {
return request;
}
/**
* Creates a count request.
*
* @param countRequest the search defining the data to be counted
* @return Elasticsearch count request
* @since 4.0
*/
public static Request count(CountRequest countRequest) {
Request request = new Request(HttpMethod.POST.name(),
endpoint(countRequest.indices(), countRequest.types(), "_count"));
Params params = new Params(request);
addCountRequestParams(params, countRequest);
if (countRequest.source() != null) {
request.setEntity(createEntity(countRequest.source(), REQUEST_BODY_CONTENT_TYPE));
}
return request;
}
private static void addCountRequestParams(Params params, CountRequest countRequest) {
params.withRouting(countRequest.routing());
params.withPreference(countRequest.preference());
params.withIndicesOptions(countRequest.indicesOptions());
}
private static void addSearchRequestParams(Params params, SearchRequest searchRequest) {
params.putParam("typed_keys", "true");
params.withRouting(searchRequest.routing());
@@ -431,7 +467,7 @@ public class RequestConverters {
return request;
}
public static Request explain(ExplainRequest explainRequest) throws IOException {
public static Request explain(ExplainRequest explainRequest) {
Request request = new Request(HttpMethod.GET.name(),
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
@@ -453,7 +489,7 @@ public class RequestConverters {
return request;
}
public static Request rankEval(RankEvalRequest rankEvalRequest) throws IOException {
public static Request rankEval(RankEvalRequest rankEvalRequest) {
Request request = new Request(HttpMethod.GET.name(),
endpoint(rankEvalRequest.indices(), Strings.EMPTY_ARRAY, "_rank_eval"));
@@ -472,8 +508,7 @@ public class RequestConverters {
return prepareReindexRequest(reindexRequest, false);
}
private static Request prepareReindexRequest(ReindexRequest reindexRequest, boolean waitForCompletion)
throws IOException {
private static Request prepareReindexRequest(ReindexRequest reindexRequest, boolean waitForCompletion) {
String endpoint = new EndpointBuilder().addPathPart("_reindex").build();
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withWaitForCompletion(waitForCompletion).withRefresh(reindexRequest.isRefresh())
@@ -487,7 +522,7 @@ public class RequestConverters {
return request;
}
public static Request updateByQuery(UpdateByQueryRequest updateByQueryRequest) throws IOException {
public static Request updateByQuery(UpdateByQueryRequest updateByQueryRequest) {
String endpoint = endpoint(updateByQueryRequest.indices(), updateByQueryRequest.getDocTypes(), "_update_by_query");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withRouting(updateByQueryRequest.getRouting())
@@ -512,7 +547,7 @@ public class RequestConverters {
return request;
}
public static Request deleteByQuery(DeleteByQueryRequest deleteByQueryRequest) throws IOException {
public static Request deleteByQuery(DeleteByQueryRequest deleteByQueryRequest) {
String endpoint = endpoint(deleteByQueryRequest.indices(), deleteByQueryRequest.getDocTypes(), "_delete_by_query");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withRouting(deleteByQueryRequest.getRouting())
@@ -558,7 +593,7 @@ public class RequestConverters {
return request;
}
public static Request putScript(PutStoredScriptRequest putStoredScriptRequest) throws IOException {
public static Request putScript(PutStoredScriptRequest putStoredScriptRequest) {
String endpoint = new EndpointBuilder().addPathPartAsIs("_scripts").addPathPart(putStoredScriptRequest.id())
.build();
Request request = new Request(HttpMethod.POST.name(), endpoint);
@@ -572,7 +607,7 @@ public class RequestConverters {
return request;
}
public static Request analyze(AnalyzeRequest request) throws IOException {
public static Request analyze(AnalyzeRequest request) {
EndpointBuilder builder = new EndpointBuilder();
String index = request.index();
if (index != null) {
@@ -718,10 +753,10 @@ public class RequestConverters {
Request request = new Request(HttpMethod.PUT.name(),
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping", putMappingRequest.type()));
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withTimeout(putMappingRequest.timeout());
parameters.withMasterTimeout(putMappingRequest.masterNodeTimeout());
RequestConverters.Params parameters = new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(true);
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -942,7 +977,10 @@ public class RequestConverters {
Params withWaitForActiveShards(ActiveShardCount activeShardCount, ActiveShardCount defaultActiveShardCount) {
if (activeShardCount != null && activeShardCount != defaultActiveShardCount) {
return putParam("wait_for_active_shards", activeShardCount.toString().toLowerCase(Locale.ROOT));
// in Elasticsearch 7, "default" cannot be sent anymore, so it needs to be mapped to the default value of 1
String value = activeShardCount == ActiveShardCount.DEFAULT ? "1"
: activeShardCount.toString().toLowerCase(Locale.ROOT);
return putParam("wait_for_active_shards", value);
}
return this;
}
@@ -1082,6 +1120,18 @@ public class RequestConverters {
}
return this;
}
/**
* sets the include_type_name parameter. Needed for Elasticsearch 7 to be used with the mapping types still
* available. Will be removed again when Elasticsearch drops the support for this parameter in Elasticsearch 8.
*
* @deprecated since 4.0
* @since 4.0
*/
@Deprecated
Params withIncludeTypeName(boolean includeTypeName) {
return putParam("include_type_name", String.valueOf(includeTypeName));
}
}
/**
@@ -1101,7 +1151,7 @@ public class RequestConverters {
}
if (requestContentType != xContentType) {
throw new IllegalArgumentException("Mismatching content-type found for request with content-type ["
+ requestContentType + "], previous requests have content-type [" + xContentType + "]");
+ requestContentType + "], previous requests have content-type [" + xContentType + ']');
}
return xContentType;
}
@@ -1150,7 +1200,7 @@ public class RequestConverters {
// encode each part (e.g. index, type and id) separately before merging them into the path
// we prepend "/" to the path part to make this path absolute, otherwise there can be issues with
// paths that start with `-` or contain `:`
URI uri = new URI(null, null, null, -1, "/" + pathPart, null, null);
URI uri = new URI(null, null, null, -1, '/' + pathPart, null, null);
// manually encode any slash that each part may contain
return uri.getRawPath().substring(1).replaceAll("/", "%2F");
} catch (URISyntaxException e) {
@@ -0,0 +1,72 @@
/*
* Copyright 2020 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.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.search.Scroll;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)}
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public class ScrollState {
private final Object lock = new Object();
private final Set<String> pastIds = new LinkedHashSet<>();
@Nullable private String scrollId;
public ScrollState() {}
public ScrollState(String scrollId) {
updateScrollId(scrollId);
}
@Nullable
public String getScrollId() {
return scrollId;
}
public List<String> getScrollIds() {
synchronized (lock) {
return Collections.unmodifiableList(new ArrayList<>(pastIds));
}
}
public void updateScrollId(String scrollId) {
if (StringUtils.hasText(scrollId)) {
synchronized (lock) {
this.scrollId = scrollId;
pastIds.add(scrollId);
}
}
}
}
@@ -0,0 +1,3 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.client.util;
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -19,6 +19,7 @@ import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
/**
* @author Christoph Strobl
@@ -42,8 +43,8 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
*
* @return never {@literal null}.
*/
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
public ElasticsearchOperations elasticsearchOperations() {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter(), resultsMapper());
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -22,10 +22,12 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
* @see ElasticsearchConfigurationSupport
*/
@@ -47,10 +49,10 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic
* @return never {@literal null}.
*/
@Bean
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate() {
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(),
elasticsearchConverter(), resultsMapper());
elasticsearchConverter);
template.setIndicesOptions(indicesOptions());
template.setRefreshPolicy(refreshPolicy());
@@ -0,0 +1,109 @@
/*
* Copyright 2020 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 static org.springframework.data.config.ParsingUtils.*;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.auditing.config.IsNewAwareAuditingHandlerBeanDefinitionParser;
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} to register a {@link AuditingEntityCallback} to transparently set auditing information
* on an entity.
*
* @author Peter-Josef Meisch
*/
public class ElasticsearchAuditingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static String MAPPING_CONTEXT_BEAN_NAME = "simpleElasticsearchMappingContext";
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClass(org.w3c.dom.Element)
*/
@Override
protected Class<?> getBeanClass(Element element) {
return AuditingEntityCallback.class;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#shouldGenerateId()
*/
@Override
protected boolean shouldGenerateId() {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder)
*/
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String mappingContextRef = element.getAttribute("mapping-context-ref");
if (!StringUtils.hasText(mappingContextRef)) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (!registry.containsBeanDefinition(MAPPING_CONTEXT_BEAN_NAME)) {
registry.registerBeanDefinition(MAPPING_CONTEXT_BEAN_NAME,
new RootBeanDefinition(SimpleElasticsearchMappingContext.class));
}
mappingContextRef = MAPPING_CONTEXT_BEAN_NAME;
}
IsNewAwareAuditingHandlerBeanDefinitionParser parser = new IsNewAwareAuditingHandlerBeanDefinitionParser(
mappingContextRef);
parser.parse(element, parserContext);
AbstractBeanDefinition isNewAwareAuditingHandler = getObjectFactoryBeanDefinition(parser.getResolvedBeanName(),
parserContext.extractSource(element));
builder.addConstructorArgValue(isNewAwareAuditingHandler);
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
registerReactiveAuditingEntityCallback(parserContext.getRegistry(), isNewAwareAuditingHandler,
parserContext.extractSource(element));
}
}
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry,
AbstractBeanDefinition isNewAwareAuditingHandler, @Nullable Object source) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(isNewAwareAuditingHandler);
builder.getRawBeanDefinition().setSource(source);
registry.registerBeanDefinition(ReactiveAuditingEntityCallback.class.getName(), builder.getBeanDefinition());
}
}
@@ -0,0 +1,181 @@
/*
* Copyright 2020 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 java.lang.annotation.Annotation;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.util.Assert;
/**
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableElasticsearchAuditing} annotation.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableElasticsearchAuditing.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
*/
@Override
protected String getAuditingHandlerBeanName() {
return "elasticsearchAuditingHandler";
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
super.registerBeanDefinitions(annotationMetadata, registry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
*/
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(ElasticsearchMappingContextLookup.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(AuditingEntityCallback.class);
listenerBeanDefinitionBuilder
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
AuditingEntityCallback.class.getName(), registry);
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
registerReactiveAuditingEntityCallback(registry, auditingHandlerDefinition.getSource());
}
}
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry, Object source) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
builder.getRawBeanDefinition().setSource(source);
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
registry);
}
/**
* Simple helper to be able to wire the {@link MappingContext} from a {@link MappingElasticsearchConverter} bean
* available in the application context.
*
* @author Oliver Gierke
*/
static class ElasticsearchMappingContextLookup implements
FactoryBean<MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>> {
private final MappingElasticsearchConverter converter;
/**
* Creates a new {@link ElasticsearchMappingContextLookup} for the given {@link MappingElasticsearchConverter}.
*
* @param converter must not be {@literal null}.
*/
public ElasticsearchMappingContextLookup(MappingElasticsearchConverter converter) {
this.converter = converter;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getObject()
throws Exception {
return converter.getMappingContext();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@Override
public Class<?> getObjectType() {
return MappingContext.class;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
@Override
public boolean isSingleton() {
return true;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.config;
import lombok.SneakyThrows;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -30,10 +28,6 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.DefaultEntityMapper;
import org.springframework.data.elasticsearch.core.DefaultResultMapper;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.ResultsMapper;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
@@ -43,14 +37,19 @@ import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
@Configuration
public class ElasticsearchConfigurationSupport {
@Bean
public ElasticsearchConverter elasticsearchConverter() {
return new MappingElasticsearchConverter(elasticsearchMappingContext());
public ElasticsearchConverter elasticsearchEntityMapper(
SimpleElasticsearchMappingContext elasticsearchMappingContext) {
MappingElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
elasticsearchMappingContext);
elasticsearchConverter.setConversions(elasticsearchCustomConversions());
return elasticsearchConverter;
}
/**
@@ -61,7 +60,6 @@ public class ElasticsearchConfigurationSupport {
* @return never {@literal null}.
*/
@Bean
@SneakyThrows
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
@@ -71,35 +69,6 @@ public class ElasticsearchConfigurationSupport {
return mappingContext;
}
/**
* Returns the {@link EntityMapper} used for mapping between the source and domain type. <br />
* <strong>Hint</strong>: you can use {@link org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper} as
* an alternative to the {@link DefaultEntityMapper}.
*
* <pre class="code">
* ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
* new DefaultConversionService());
* entityMapper.setConversions(elasticsearchCustomConversions());
* </pre>
*
* @return never {@literal null}.
*/
@Bean
public EntityMapper entityMapper() {
return new DefaultEntityMapper(elasticsearchMappingContext());
}
/**
* Returns the {@link ResultsMapper} to be used for search responses.
*
* @see #entityMapper()
* @return never {@literal null}.
*/
@Bean
public ResultsMapper resultsMapper() {
return new DefaultResultMapper(elasticsearchMappingContext(), entityMapper());
}
/**
* Register custom {@link Converter}s in a {@link ElasticsearchCustomConversions} object if required.
*
@@ -131,9 +100,8 @@ public class ElasticsearchConfigurationSupport {
*
* @see #getMappingBasePackages()
* @return never {@literal null}.
* @throws ClassNotFoundException
*/
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
protected Set<Class<?>> getInitialEntitySet() {
Set<Class<?>> initialEntitySet = new HashSet<>();
@@ -150,27 +118,29 @@ public class ElasticsearchConfigurationSupport {
*
* @param basePackage must not be {@literal null}.
* @return never {@literal null}.
* @throws ClassNotFoundException
*/
protected Set<Class<?>> scanForEntities(String basePackage) throws ClassNotFoundException {
protected Set<Class<?>> scanForEntities(String basePackage) {
if (!StringUtils.hasText(basePackage)) {
return Collections.emptySet();
}
Set<Class<?>> initialEntitySet = new HashSet<Class<?>>();
Set<Class<?>> initialEntitySet = new HashSet<>();
if (StringUtils.hasText(basePackage)) {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
String beanClassName = candidate.getBeanClassName();
initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(),
AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
if (beanClassName != null) {
try {
initialEntitySet.add(
ClassUtils.forName(beanClassName, AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
} catch (ClassNotFoundException | LinkageError ignored) {}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -0,0 +1,70 @@
/*
* Copyright 2020 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 java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
/**
* Annotation to enable auditing in Elasticsearch via annotation configuration.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ElasticsearchAuditingRegistrar.class)
public @interface EnableElasticsearchAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
*
* @return
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
* used for setting creation and modification dates.
*
* @return
*/
String dateTimeProviderRef() default "";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -27,8 +27,9 @@ import org.w3c.dom.Element;
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @deprecated as of 4.0
*/
@Deprecated
public class TransportClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
@@ -0,0 +1,3 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.config;
@@ -0,0 +1,277 @@
/*
* Copyright 2019-2020 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.util.StringUtils.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Base implementation of {@link IndexOperations} common to Transport and Rest based Implementations of IndexOperations.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
*/
abstract class AbstractDefaultIndexOperations implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDefaultIndexOperations.class);
protected final ElasticsearchConverter elasticsearchConverter;
protected final RequestFactory requestFactory;
@Nullable protected final Class<?> boundClass;
private final IndexCoordinates boundIndex;
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
this.boundClass = boundClass;
this.boundIndex = getIndexCoordinatesFor(boundClass);
}
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
this.boundClass = null;
this.boundIndex = boundIndex;
}
protected Class<?> checkForBoundClass() {
if (boundClass == null) {
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
}
return boundClass;
}
// region IndexOperations
@Override
public boolean create() {
if (boundClass != null) {
Class<?> clazz = boundClass;
String indexName = getIndexCoordinates().getIndexName();
if (clazz.isAnnotationPresent(Setting.class)) {
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
if (hasText(settingPath)) {
String settings = ResourceUtil.readFileFromClasspath(settingPath);
if (hasText(settings)) {
return doCreate(indexName, Document.parse(settings));
}
} else {
LOGGER.info("settingPath in @Setting has to be defined. Using default instead.");
}
}
return doCreate(indexName, getDefaultSettings(getRequiredPersistentEntity(clazz)));
}
return doCreate(getIndexCoordinates().getIndexName(), null);
}
@Override
public boolean create(Document settings) {
return doCreate(getIndexCoordinates().getIndexName(), settings);
}
protected abstract boolean doCreate(String indexName, @Nullable Document settings);
@Override
public boolean delete() {
return doDelete(getIndexCoordinates().getIndexName());
}
protected abstract boolean doDelete(String indexName);
@Override
public boolean exists() {
return doExists(getIndexCoordinates().getIndexName());
}
protected abstract boolean doExists(String indexName);
@Override
public boolean putMapping(Document mapping) {
return doPutMapping(getIndexCoordinates(), mapping);
}
protected abstract boolean doPutMapping(IndexCoordinates index, Document mapping);
@Override
public Map<String, Object> getMapping() {
return doGetMapping(getIndexCoordinates());
}
abstract protected Map<String, Object> doGetMapping(IndexCoordinates index);
@Override
public Map<String, Object> getSettings() {
return getSettings(false);
}
@Override
public Map<String, Object> getSettings(boolean includeDefaults) {
return doGetSettings(getIndexCoordinates().getIndexName(), includeDefaults);
}
protected abstract Map<String, Object> doGetSettings(String indexName, boolean includeDefaults);
@Override
public void refresh() {
doRefresh(getIndexCoordinates());
}
protected abstract void doRefresh(IndexCoordinates indexCoordinates);
@Override
public boolean addAlias(AliasQuery query) {
return doAddAlias(query, getIndexCoordinates());
}
protected abstract boolean doAddAlias(AliasQuery query, IndexCoordinates index);
@Override
public List<AliasMetaData> queryForAlias() {
return doQueryForAlias(getIndexCoordinates().getIndexName());
}
protected abstract List<AliasMetaData> doQueryForAlias(String indexName);
@Override
public boolean removeAlias(AliasQuery query) {
return doRemoveAlias(query, getIndexCoordinates());
}
protected abstract boolean doRemoveAlias(AliasQuery query, IndexCoordinates index);
@Override
public Document createMapping() {
return createMapping(checkForBoundClass());
}
@Override
public Document createMapping(Class<?> clazz) {
return buildMapping(clazz);
}
protected Document buildMapping(Class<?> clazz) {
// load mapping specified in Mapping annotation if present
if (clazz.isAnnotationPresent(Mapping.class)) {
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
if (!StringUtils.isEmpty(mappings)) {
return Document.parse(mappings);
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
// build mapping from field annotations
try {
String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz);
return Document.parse(mapping);
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
}
// endregion
// region Helper functions
private <T> Document getDefaultSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
if (persistentEntity.isUseServerConfiguration()) {
return Document.create();
}
Map<String, String> map = new MapBuilder<String, String>()
.put("index.number_of_shards", String.valueOf(persistentEntity.getShards()))
.put("index.number_of_replicas", String.valueOf(persistentEntity.getReplicas()))
.put("index.refresh_interval", persistentEntity.getRefreshInterval())
.put("index.store.type", persistentEntity.getIndexStoreType()).map();
return Document.from(map);
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
}
/**
* get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed
* index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned.
*
* @return IndexCoordinates
*/
protected IndexCoordinates getIndexCoordinates() {
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex;
}
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
protected Map<String, Object> convertSettingsResponseToMap(GetSettingsResponse response, String indexName) {
Map<String, Object> settings = new HashMap<>();
if (!response.getIndexToDefaultSettings().isEmpty()) {
Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName);
for (String key : defaultSettings.keySet()) {
settings.put(key, defaultSettings.get(key));
}
}
if (!response.getIndexToSettings().isEmpty()) {
Settings customSettings = response.getIndexToSettings().get(indexName);
for (String key : customSettings.keySet()) {
settings.put(key, customSettings.get(key));
}
}
return settings;
}
// endregion
}
@@ -1,52 +1,648 @@
/*
* Copyright 2019-2020 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
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.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Mapping;
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.AfterSaveCallback;
import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback;
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.GetQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* AbstractElasticsearchTemplate
*
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
*/
public abstract class AbstractElasticsearchTemplate {
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchTemplate.class);
protected @Nullable ElasticsearchConverter elasticsearchConverter;
protected @Nullable RequestFactory requestFactory;
protected ElasticsearchConverter elasticsearchConverter;
private @Nullable EntityCallbacks entityCallbacks;
public AbstractElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
// region Initialization
protected void initialize(ElasticsearchConverter elasticsearchConverter) {
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
VersionInfo.logVersions(getClusterVersion());
}
protected String buildMapping(Class<?> clazz) {
protected ElasticsearchConverter createElasticsearchConverter() {
MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext());
mappingElasticsearchConverter.afterPropertiesSet();
return mappingElasticsearchConverter;
}
// load mapping specified in Mapping annotation if present
if (clazz.isAnnotationPresent(Mapping.class)) {
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
if (!StringUtils.isEmpty(mappings)) {
return mappings;
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (entityCallbacks == null) {
setEntityCallbacks(EntityCallbacks.create(applicationContext));
}
if (elasticsearchConverter instanceof ApplicationContextAware) {
((ApplicationContextAware) elasticsearchConverter).setApplicationContext(applicationContext);
}
}
/**
* Set the {@link EntityCallbacks} instance to use when invoking {@link EntityCallbacks callbacks} like the
* {@link org.springframework.data.elasticsearch.core.event.BeforeConvertCallback}.
* <p />
* Overrides potentially existing {@link EntityCallbacks}.
*
* @param entityCallbacks must not be {@literal null}.
* @throws IllegalArgumentException if the given instance is {@literal null}.
* @since 4.0
*/
public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
Assert.notNull(entityCallbacks, "entityCallbacks must not be null");
this.entityCallbacks = entityCallbacks;
}
// endregion
// region DocumentOperations
@Override
public <T> T save(T entity) {
Assert.notNull(entity, "entity must not be null");
return save(entity, getIndexCoordinatesFor(entity.getClass()));
}
@Override
public <T> T save(T entity, IndexCoordinates index) {
Assert.notNull(entity, "entity must not be null");
Assert.notNull(index, "index must not be null");
IndexQuery query = getIndexQuery(entity);
index(query, index);
// suppressing because it's either entity itself or something of a correct type returned by an entity callback
@SuppressWarnings("unchecked")
T castResult = (T) query.getObject();
return castResult;
}
@Override
public <T> Iterable<T> save(Iterable<T> entities) {
Assert.notNull(entities, "entities must not be null");
Iterator<T> iterator = entities.iterator();
if (iterator.hasNext()) {
return save(entities, getIndexCoordinatesFor(iterator.next().getClass()));
}
return entities;
}
@Override
public <T> Iterable<T> save(Iterable<T> entities, IndexCoordinates index) {
Assert.notNull(entities, "entities must not be null");
Assert.notNull(index, "index must not be null");
List<IndexQuery> indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery)
.collect(Collectors.toList());
if (!indexQueries.isEmpty()) {
List<String> ids = bulkIndex(indexQueries, index);
Iterator<String> idIterator = ids.iterator();
entities.forEach(entity -> {
setPersistentEntityId(entity, idIterator.next());
});
}
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
}
@Override
public <T> Iterable<T> save(T... entities) {
return save(Arrays.asList(entities));
}
@Override
@Nullable
public <T> T get(String id, Class<T> clazz) {
return get(id, clazz, getIndexCoordinatesFor(clazz));
}
@Override
@Nullable
public <T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index) {
return get(query.getId(), clazz, index);
}
@Override
@Nullable
public <T> T queryForObject(GetQuery query, Class<T> clazz) {
return get(query.getId(), clazz, getIndexCoordinatesFor(clazz));
}
@Override
public boolean exists(String id, Class<?> clazz) {
return exists(id, getIndexCoordinatesFor(clazz));
}
@Override
public boolean exists(String id, IndexCoordinates index) {
return doExists(id, index);
}
abstract protected boolean doExists(String id, IndexCoordinates index);
@Override
public String delete(String id, Class<?> entityType) {
Assert.notNull(id, "id must not be null");
Assert.notNull(entityType, "entityType must not be null");
return this.delete(id, getIndexCoordinatesFor(entityType));
}
@Override
public String delete(Object entity) {
return delete(entity, getIndexCoordinatesFor(entity.getClass()));
}
@Override
public String delete(Object entity, IndexCoordinates index) {
return this.delete(getEntityId(entity), index);
}
// endregion
// region SearchOperations
@Override
public long count(Query query, Class<?> clazz) {
return count(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> CloseableIterator<T> stream(Query query, Class<T> clazz, IndexCoordinates index) {
return (CloseableIterator<T>) SearchHitSupport.unwrapSearchHits(searchForStream(query, clazz, index));
}
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz) {
return searchForStream(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index) {
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
return StreamQueries.streamResults( //
searchScrollStart(scrollTimeInMillis, query, clazz, index), //
scrollId -> searchScrollContinue(scrollId, scrollTimeInMillis, clazz, index), //
this::searchScrollClear);
}
@Override
public <T> SearchHits<T> search(MoreLikeThisQuery query, Class<T> clazz) {
return search(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> SearchHits<T> search(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).build(), clazz, index);
}
@Override
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
MultiSearchRequest request = new MultiSearchRequest();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, clazz, index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse())));
}
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes,
IndexCoordinates index) {
MultiSearchRequest request = new MultiSearchRequest();
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, it.next(), index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response)));
}
return res;
}
@Override
public <T> SearchHits<T> search(Query query, Class<T> clazz) {
return search(query, clazz, getIndexCoordinatesFor(clazz));
}
/*
* internal use only, not for public API
*/
abstract protected <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
IndexCoordinates index);
/*
* internal use only, not for public API
*/
abstract protected <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
Class<T> clazz, IndexCoordinates index);
/*
* internal use only, not for public API
*/
protected void searchScrollClear(String scrollId) {
searchScrollClear(Collections.singletonList(scrollId));
}
/*
* internal use only, not for public API
*/
abstract protected void searchScrollClear(List<String> scrollIds);
abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request);
// endregion
// region Helper methods
@Override
public ElasticsearchConverter getElasticsearchConverter() {
Assert.notNull(elasticsearchConverter, "elasticsearchConverter is not initialized.");
return elasticsearchConverter;
}
/**
* @since 4.0
*/
public RequestFactory getRequestFactory() {
Assert.notNull(requestFactory, "requestfactory not initialized");
return requestFactory;
}
protected static String[] toArray(List<String> values) {
String[] valuesAsArray = new String[values.size()];
return values.toArray(valuesAsArray);
}
/**
* @param clazz the entity class
* @return the IndexCoordinates defined on the entity.
* @since 4.0
*/
@Override
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
/**
* @param bulkResponse
* @return the list of the item id's
*/
protected List<String> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new ElasticsearchException(
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + ']',
failedDocuments);
}
return Stream.of(bulkResponse.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
}
protected void setPersistentEntityId(Object entity, String id) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
persistentEntity.getPropertyAccessor(entity).setProperty(idProperty, id);
}
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
}
@Nullable
private String getEntityId(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
if (idProperty != null) {
return stringIdRepresentation(persistentEntity.getPropertyAccessor(entity).getProperty(idProperty));
}
return null;
}
@Nullable
private Long getEntityVersion(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
if (versionProperty != null) {
Object version = persistentEntity.getPropertyAccessor(entity).getProperty(versionProperty);
if (version != null && Long.class.isAssignableFrom(version.getClass())) {
return ((Long) version);
}
}
// build mapping from field annotations
try {
MappingBuilder mappingBuilder = new MappingBuilder(elasticsearchConverter);
return mappingBuilder.buildPropertyMapping(clazz);
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
return null;
}
@Nullable
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty property = persistentEntity.getSeqNoPrimaryTermProperty();
if (property != null) {
Object seqNoPrimaryTerm = persistentEntity.getPropertyAccessor(entity).getProperty(property);
if (seqNoPrimaryTerm != null && SeqNoPrimaryTerm.class.isAssignableFrom(seqNoPrimaryTerm.getClass())) {
return (SeqNoPrimaryTerm) seqNoPrimaryTerm;
}
}
return null;
}
private <T> IndexQuery getIndexQuery(T entity) {
String id = getEntityId(entity);
if (id != null) {
id = elasticsearchConverter.convertId(id);
}
IndexQueryBuilder builder = new IndexQueryBuilder() //
.withId(id) //
.withObject(entity);
SeqNoPrimaryTerm seqNoPrimaryTerm = getEntitySeqNoPrimaryTerm(entity);
if (seqNoPrimaryTerm != null) {
builder.withSeqNoPrimaryTerm(seqNoPrimaryTerm);
} else {
// version cannot be used together with seq_no and primary_term
builder.withVersion(getEntityVersion(entity));
}
return builder.build();
}
/**
* tries to extract the version of the Elasticsearch cluster
*
* @return the version as string if it can be retrieved
*/
@Nullable
abstract protected String getClusterVersion();
// endregion
// region Entity callbacks
protected <T> T maybeCallbackBeforeConvert(T entity, IndexCoordinates index) {
if (entityCallbacks != null) {
return entityCallbacks.callback(BeforeConvertCallback.class, entity, index);
}
return entity;
}
protected void maybeCallbackBeforeConvertWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
queryObject = maybeCallbackBeforeConvert(queryObject, index);
indexQuery.setObject(queryObject);
}
}
}
// this can be called with either a List<IndexQuery> or a List<UpdateQuery>; these query classes
// don't have a common base class, therefore the List<?> argument
protected void maybeCallbackBeforeConvertWithQueries(List<?> queries, IndexCoordinates index) {
queries.forEach(query -> maybeCallbackBeforeConvertWithQuery(query, index));
}
protected <T> T maybeCallbackAfterSave(T entity, IndexCoordinates index) {
if (entityCallbacks != null) {
return entityCallbacks.callback(AfterSaveCallback.class, entity, index);
}
return entity;
}
protected void maybeCallbackAfterSaveWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
queryObject = maybeCallbackAfterSave(queryObject, index);
indexQuery.setObject(queryObject);
}
}
}
// this can be called with either a List<IndexQuery> or a List<UpdateQuery>; these query classes
// don't have a common base class, therefore the List<?> argument
protected void maybeCallbackAfterSaveWithQueries(List<?> queries, IndexCoordinates index) {
queries.forEach(query -> maybeCallbackAfterSaveWithQuery(query, index));
}
protected <T> T maybeCallbackAfterConvert(T entity, Document document, IndexCoordinates index) {
if (entityCallbacks != null) {
return entityCallbacks.callback(AfterConvertCallback.class, entity, document, index);
}
return entity;
}
// endregion
// region Document callbacks
protected interface DocumentCallback<T> {
@Nullable
T doWith(@Nullable Document document);
}
protected class ReadDocumentCallback<T> implements DocumentCallback<T> {
private final EntityReader<? super T, Document> reader;
private final Class<T> type;
private final IndexCoordinates index;
public ReadDocumentCallback(EntityReader<? super T, Document> reader, Class<T> type, IndexCoordinates index) {
Assert.notNull(reader, "reader is null");
Assert.notNull(type, "type is null");
this.reader = reader;
this.type = type;
this.index = index;
}
@Nullable
public T doWith(@Nullable Document document) {
if (document == null) {
return null;
}
T entity = reader.read(type, document);
return maybeCallbackAfterConvert(entity, document, index);
}
}
protected interface SearchDocumentResponseCallback<T> {
@NonNull
T doWith(@NonNull SearchDocumentResponse response);
}
protected class ReadSearchDocumentResponseCallback<T> implements SearchDocumentResponseCallback<SearchHits<T>> {
private final DocumentCallback<T> delegate;
private final Class<T> type;
public ReadSearchDocumentResponseCallback(Class<T> type, IndexCoordinates index) {
Assert.notNull(type, "type is null");
this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index);
this.type = type;
}
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapHits(response, entities);
}
}
protected class ReadSearchScrollDocumentResponseCallback<T>
implements SearchDocumentResponseCallback<SearchScrollHits<T>> {
private final DocumentCallback<T> delegate;
private final Class<T> type;
public ReadSearchScrollDocumentResponseCallback(Class<T> type, IndexCoordinates index) {
Assert.notNull(type, "type is null");
this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index);
this.type = type;
}
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapScrollHits(response,
entities);
}
}
// endregion
}
@@ -1,73 +0,0 @@
/*
* Copyright 2014-2019 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.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.util.Assert;
/**
* @author Artur Konczak
* @author Christoph Strobl
*/
public abstract class AbstractResultMapper implements ResultsMapper {
private final EntityMapper entityMapper;
private final ProjectionFactory projectionFactory;
/**
* Create a new {@link AbstractResultMapper}.
*
* @param entityMapper must not be {@literal null}.
*/
public AbstractResultMapper(EntityMapper entityMapper) {
this(entityMapper, new SpelAwareProxyProjectionFactory());
}
/**
* Create a new {@link AbstractResultMapper}.
*
* @param entityMapper must not be {@literal null}.
* @param projectionFactory must not be {@literal null}.
* @since 3.2
*/
public AbstractResultMapper(EntityMapper entityMapper, ProjectionFactory projectionFactory) {
Assert.notNull(entityMapper, "EntityMapper must not be null!");
Assert.notNull(projectionFactory, "ProjectionFactory must not be null!");
this.entityMapper = entityMapper;
this.projectionFactory = projectionFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.ResultsMapper#getEntityMapper()
*/
@Override
public EntityMapper getEntityMapper() {
return this.entityMapper;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.ResultsMapper#getProjectionFactory()
*/
@Override
public ProjectionFactory getProjectionFactory() {
return this.projectionFactory;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -20,10 +20,13 @@ import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoBoundingBoxQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.elasticsearch.core.geo.GeoBox;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.Criteria;
@@ -31,6 +34,7 @@ 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.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -39,28 +43,25 @@ import org.springframework.util.Assert;
* @author Franck Marchand
* @author Mohsin Husen
* @author Artur Konczak
*
* @author Peter-Josef Meisch
*/
class CriteriaFilterProcessor {
QueryBuilder createFilterFromCriteria(Criteria criteria) {
List<QueryBuilder> fbList = new LinkedList<>();
QueryBuilder filter = null;
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
while (chainIterator.hasNext()) {
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
QueryBuilder fb = null;
Criteria chainedCriteria = chainIterator.next();
if (chainedCriteria.isOr()) {
fb = QueryBuilders.boolQuery();
for(QueryBuilder f: createFilterFragmentForCriteria(chainedCriteria)){
((BoolQueryBuilder)fb).should(f);
for (QueryBuilder f : createFilterFragmentForCriteria(chainedCriteria)) {
((BoolQueryBuilder) fb).should(f);
}
fbList.add(fb);
} else if (chainedCriteria.isNegating()) {
List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(), criteria.getFilterCriteriaEntries().iterator());
List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries().iterator());
if (!negationFilters.isEmpty()) {
fbList.addAll(negationFilters);
@@ -75,15 +76,14 @@ class CriteriaFilterProcessor {
filter = fbList.get(0);
} else {
filter = QueryBuilders.boolQuery();
for(QueryBuilder f: fbList) {
((BoolQueryBuilder)filter).must(f);
for (QueryBuilder f : fbList) {
((BoolQueryBuilder) filter).must(f);
}
}
}
return filter;
}
private List<QueryBuilder> createFilterFragmentForCriteria(Criteria chainedCriteria) {
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getFilterCriteriaEntries().iterator();
List<QueryBuilder> filterList = new LinkedList<>();
@@ -101,8 +101,9 @@ class CriteriaFilterProcessor {
return filterList;
}
@Nullable
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
if (value == null) {
return null;
}
@@ -116,8 +117,10 @@ class CriteriaFilterProcessor {
Object[] valArray = (Object[]) value;
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point, "First element of a geo distance filter must be a GeoPoint, a Point or a text");
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance, "Second element of a geo distance filter must be a text or a Distance");
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point,
"First element of a geo distance filter must be a GeoPoint, a Point or a text");
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance,
"Second element of a geo distance filter must be a text or a Distance");
StringBuilder dist = new StringBuilder();
@@ -129,15 +132,18 @@ class CriteriaFilterProcessor {
if (valArray[0] instanceof GeoPoint) {
GeoPoint loc = (GeoPoint) valArray[0];
geoDistanceQueryBuilder.point(loc.getLat(),loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
} else if (valArray[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
} else {
String loc = (String) valArray[0];
if (loc.contains(",")) {
String c[] = loc.split(",");
geoDistanceQueryBuilder.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
String[] c = loc.split(",");
geoDistanceQueryBuilder.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
} else {
geoDistanceQueryBuilder.geohash(loc).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
}
@@ -150,20 +156,22 @@ class CriteriaFilterProcessor {
case BBOX: {
filter = QueryBuilders.geoBoundingBoxQuery(fieldName);
Assert.isTrue(value instanceof Object[], "Value of a boundedBy filter should be an array of one or two values.");
Assert.isTrue(value instanceof Object[],
"Value of a boundedBy filter should be an array of one or two values.");
Object[] valArray = (Object[]) value;
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
if (valArray.length == 1) {
//GeoEnvelop
// GeoEnvelop
oneParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray[0]);
} else if (valArray.length == 2) {
//2x GeoPoint
//2x text
// 2x GeoPoint
// 2x text
twoParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray);
} else {
//error
Assert.isTrue(false, "Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
// error
Assert.isTrue(false,
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
}
break;
}
@@ -172,7 +180,6 @@ class CriteriaFilterProcessor {
return filter;
}
/**
* extract the distance string from a {@link org.springframework.data.geo.Distance} object.
*
@@ -196,7 +203,8 @@ class CriteriaFilterProcessor {
}
private void oneParameterBBox(GeoBoundingBoxQueryBuilder filter, Object value) {
Assert.isTrue(value instanceof GeoBox || value instanceof Box, "single-element of boundedBy filter must be type of GeoBox or Box");
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) {
@@ -206,7 +214,8 @@ class CriteriaFilterProcessor {
geoBBox = (GeoBox) value;
}
filter.setCorners(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon(), geoBBox.getBottomRight().getLat(), geoBBox.getBottomRight().getLon());
filter.setCorners(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon(), geoBBox.getBottomRight().getLat(),
geoBBox.getBottomRight().getLon());
}
private static boolean isType(Object[] array, Class clazz) {
@@ -219,7 +228,8 @@ class CriteriaFilterProcessor {
}
private void twoParameterBBox(GeoBoundingBoxQueryBuilder filter, Object[] values) {
Assert.isTrue(isType(values, GeoPoint.class) || isType(values, String.class), " both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)");
Assert.isTrue(isType(values, GeoPoint.class) || isType(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];
@@ -236,7 +246,8 @@ class CriteriaFilterProcessor {
while (it.hasNext()) {
Criteria.CriteriaEntry criteriaEntry = it.next();
QueryBuilder notFilter = QueryBuilders.boolQuery().mustNot(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
QueryBuilder notFilter = QueryBuilders.boolQuery()
.mustNot(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
notFilterList.add(notFilter);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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,18 +15,22 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.index.query.Operator.AND;
import static org.elasticsearch.index.query.Operator.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
import org.elasticsearch.index.query.*;
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -36,13 +40,15 @@ import org.springframework.util.Assert;
* @author Mohsin Husen
* @author Franck Marchand
* @author Artur Konczak
* @author Rasmus Faber-Espensen
* @author James Bodkin
* @author Peter-Josef Meisch
*/
class CriteriaQueryProcessor {
QueryBuilder createQueryFromCriteria(Criteria criteria) {
if (criteria == null)
return null;
Assert.notNull(criteria, "criteria must not be null");
List<QueryBuilder> shouldQueryBuilderList = new LinkedList<>();
List<QueryBuilder> mustNotQueryBuilderList = new LinkedList<>();
@@ -104,7 +110,7 @@ class CriteriaQueryProcessor {
return query;
}
@Nullable
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
if (chainedCriteria.getQueryCriteriaEntries().isEmpty())
return null;
@@ -131,34 +137,39 @@ class CriteriaQueryProcessor {
return query;
}
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry,/* OperationKey key, Object value,*/ String fieldName) {
Object value = entry.getValue();
if (value == null) {
return null;
}
@Nullable
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry, String fieldName) {
OperationKey key = entry.getKey();
Object value = entry.getValue();
if (value == null) {
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
} else {
return null;
}
}
String searchText = QueryParserUtil.escape(value.toString());
QueryBuilder query = null;
String searchText = StringUtils.toString(value);
Iterable<Object> collection = null;
switch (key) {
case EQUALS:
query = queryStringQuery(searchText).field(fieldName).defaultOperator(AND);
break;
case CONTAINS:
query = queryStringQuery("*" + searchText + "*").field(fieldName).analyzeWildcard(true);
query = queryStringQuery('*' + searchText + '*').field(fieldName).analyzeWildcard(true);
break;
case STARTS_WITH:
query = queryStringQuery(searchText + "*").field(fieldName).analyzeWildcard(true);
query = queryStringQuery(searchText + '*').field(fieldName).analyzeWildcard(true);
break;
case ENDS_WITH:
query = queryStringQuery("*" + searchText).field(fieldName).analyzeWildcard(true);
query = queryStringQuery('*' + searchText).field(fieldName).analyzeWildcard(true);
break;
case EXPRESSION:
query = queryStringQuery(searchText).field(fieldName);
query = queryStringQuery(value.toString()).field(fieldName);
break;
case LESS_EQUAL:
query = rangeQuery(fieldName).lte(value);
@@ -180,23 +191,23 @@ class CriteriaQueryProcessor {
query = fuzzyQuery(fieldName, searchText);
break;
case IN:
query = boolQuery();
collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).should(queryStringQuery(item.toString()).field(fieldName));
}
query = boolQuery().must(termsQuery(fieldName, toStringList((Iterable<Object>) value)));
break;
case NOT_IN:
query = boolQuery();
collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).mustNot(queryStringQuery(item.toString()).field(fieldName));
}
query = boolQuery().mustNot(termsQuery(fieldName, toStringList((Iterable<Object>) value)));
break;
}
return query;
}
private static List<String> toStringList(Iterable<?> iterable) {
List<String> list = new ArrayList<>();
for (Object item : iterable) {
list.add(StringUtils.toString(item));
}
return list;
}
private void addBoost(QueryBuilder query, float boost) {
if (Float.isNaN(boost)) {
return;
@@ -1,189 +0,0 @@
/*
* Copyright 2014-2019 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 java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.elasticsearch.core.geo.CustomGeoModule;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
/**
* EntityMapper based on a Jackson {@link ObjectMapper}.
*
* @author Artur Konczak
* @author Petar Tahchiev
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class DefaultEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
/**
* Creates a new {@link DefaultEntityMapper} using the given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public DefaultEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
objectMapper = new ObjectMapper();
objectMapper.registerModule(new SpringDataElasticsearchModule(context));
objectMapper.registerModule(new CustomGeoModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToString(java.lang.Object)
*/
@Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapObject(java.lang.Object)
*/
@Override
public Map<String, Object> mapObject(Object source) {
try {
return objectMapper.readValue(mapToString(source), HashMap.class);
} catch (IOException e) {
throw new MappingException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToObject(java.lang.String, java.lang.Class)
*/
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#readObject(java.util.Map, java.lang.Class)
*/
@Override
public <T> T readObject (Map<String, Object> source, Class<T> targetType) {
try {
return mapToObject(mapToString(source), targetType);
} catch (IOException e) {
throw new MappingException(e.getMessage(), e);
}
}
/**
* A simple Jackson module to register the {@link SpringDataSerializerModifier}.
*
* @author Oliver Gierke
* @since 3.1
*/
private static class SpringDataElasticsearchModule extends SimpleModule {
private static final long serialVersionUID = -9168968092458058966L;
/**
* Creates a new {@link SpringDataElasticsearchModule} using the given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public SpringDataElasticsearchModule(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
setSerializerModifier(new SpringDataSerializerModifier(context));
}
/**
* A {@link BeanSerializerModifier} that will drop properties annotated with {@link ReadOnlyProperty} for
* serialization.
*
* @author Oliver Gierke
* @since 3.1
*/
private static class SpringDataSerializerModifier extends BeanSerializerModifier {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
public SpringDataSerializerModifier(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
this.context = context;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.BeanSerializerModifier#changeProperties(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.BeanDescription, java.util.List)
*/
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription description,
List<BeanPropertyWriter> properties) {
Class<?> type = description.getBeanClass();
ElasticsearchPersistentEntity<?> entity = context.getPersistentEntity(type);
if (entity == null) {
return super.changeProperties(config, description, properties);
}
List<BeanPropertyWriter> result = new ArrayList<>(properties.size());
for (BeanPropertyWriter beanPropertyWriter : properties) {
ElasticsearchPersistentProperty property = entity.getPersistentProperty(beanPropertyWriter.getName());
if (property != null && property.isWritable()) {
result.add(beanPropertyWriter);
}
}
return result;
}
}
}
}
@@ -0,0 +1,231 @@
/*
* Copyright 2019-2020 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.client.Requests.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.client.support.AliasData;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link IndexOperations} implementation using the RestClient.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
*/
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
private ElasticsearchRestTemplate restTemplate;
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
super(restTemplate.getElasticsearchConverter(), boundClass);
this.restTemplate = restTemplate;
}
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
super(restTemplate.getElasticsearchConverter(), boundIndex);
this.restTemplate = restTemplate;
}
@Override
protected boolean doCreate(String indexName, @Nullable Document settings) {
CreateIndexRequest request = requestFactory.createIndexRequest(indexName, settings);
return restTemplate.execute(client -> client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected boolean doDelete(String indexName) {
Assert.notNull(indexName, "No index defined for delete operation");
if (doExists(indexName)) {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
return restTemplate.execute(client -> client.indices().delete(request, RequestOptions.DEFAULT).isAcknowledged());
}
return false;
}
@Override
protected boolean doExists(String indexName) {
GetIndexRequest request = new GetIndexRequest(indexName);
return restTemplate.execute(client -> client.indices().exists(request, RequestOptions.DEFAULT));
}
@Override
protected boolean doPutMapping(IndexCoordinates index, Document mapping) {
Assert.notNull(index, "No index defined for putMapping()");
PutMappingRequest request = requestFactory.putMappingRequest(index, mapping);
return restTemplate
.execute(client -> client.indices().putMapping(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected Map<String, Object> doGetMapping(IndexCoordinates index) {
Assert.notNull(index, "No index defined for getMapping()");
return restTemplate.execute(client -> {
RestClient restClient = client.getLowLevelClient();
Request request = new Request("GET", '/' + index.getIndexName() + "/_mapping");
Response response = restClient.performRequest(request);
return convertMappingResponse(EntityUtils.toString(response.getEntity()));
});
}
@Override
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index);
return restTemplate
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
IndicesAliasesRequest indicesAliasesRequest = requestFactory.indicesRemoveAliasesRequest(query, index);
return restTemplate.execute(
client -> client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected List<AliasMetaData> doQueryForAlias(String indexName) {
List<AliasMetaData> aliases = null;
return restTemplate.execute(client -> {
RestClient restClient = client.getLowLevelClient();
Response response;
String aliasResponse;
response = restClient.performRequest(new Request("GET", '/' + indexName + "/_alias/*"));
aliasResponse = EntityUtils.toString(response.getEntity());
return convertAliasResponse(aliasResponse);
});
}
@Override
protected Map<String, Object> doGetSettings(String indexName, boolean includeDefaults) {
Assert.notNull(indexName, "No index defined for getSettings");
GetSettingsRequest request = new GetSettingsRequest() //
.indices(indexName) //
.includeDefaults(includeDefaults);
//
GetSettingsResponse response = restTemplate.execute(client -> client.indices() //
.getSettings(request, RequestOptions.DEFAULT));
return convertSettingsResponseToMap(response, indexName);
}
@Override
protected void doRefresh(IndexCoordinates index) {
Assert.notNull(index, "No index defined for refresh()");
restTemplate
.execute(client -> client.indices().refresh(refreshRequest(index.getIndexNames()), RequestOptions.DEFAULT));
}
// region Helper methods
private Map<String, Object> convertMappingResponse(String mappingResponse) {
ObjectMapper mapper = new ObjectMapper();
try {
Map<String, Object> result = null;
JsonNode node = mapper.readTree(mappingResponse);
node = node.findValue("mappings");
result = mapper.readValue(mapper.writeValueAsString(node), HashMap.class);
return result;
} catch (IOException e) {
throw new UncategorizedElasticsearchException("Could not map alias response : " + mappingResponse, e);
}
}
/**
* It takes two steps to create a List<AliasMetadata> from the elasticsearch http response because the aliases field
* is actually a Map by alias name, but the alias name is on the AliasMetadata.
*
* @param aliasResponse
* @return
*/
private List<AliasMetaData> convertAliasResponse(String aliasResponse) {
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode node = mapper.readTree(aliasResponse);
node = node.findValue("aliases");
if (node == null) {
return Collections.emptyList();
}
Map<String, AliasData> aliasData = mapper.readValue(mapper.writeValueAsString(node),
new TypeReference<Map<String, AliasData>>() {});
Iterable<Map.Entry<String, AliasData>> aliasIter = aliasData.entrySet();
List<AliasMetaData> aliasMetaDataList = new ArrayList<>();
for (Map.Entry<String, AliasData> aliasentry : aliasIter) {
AliasData data = aliasentry.getValue();
aliasMetaDataList.add(AliasMetaData.newAliasMetaDataBuilder(aliasentry.getKey()).filter(data.getFilter())
.routing(data.getRouting()).searchRouting(data.getSearch_routing()).indexRouting(data.getIndex_routing())
.build());
}
return aliasMetaDataList;
} catch (IOException e) {
throw new UncategorizedElasticsearchException("Could not map alias response : " + aliasResponse, e);
}
}
// endregion
}
@@ -1,253 +0,0 @@
/*
* Copyright 2014-2019 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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.search.SearchHit;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* @author Artur Konczak
* @author Petar Tahchiev
* @author Young Gu
* @author Oliver Gierke
* @author Chris White
* @author Mark Paluch
* @author Ilkang Na
* @author Sascha Woo
* @author Christoph Strobl
* @author Dmitriy Yakovlev
*/
public class DefaultResultMapper extends AbstractResultMapper {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final ConversionService conversionService = new DefaultConversionService();
public DefaultResultMapper() {
this(new SimpleElasticsearchMappingContext());
}
public DefaultResultMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
this(mappingContext, initEntityMapper(mappingContext));
}
public DefaultResultMapper(EntityMapper entityMapper) {
this(new SimpleElasticsearchMappingContext(), entityMapper);
}
public DefaultResultMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
@Nullable EntityMapper entityMapper) {
super(entityMapper != null ? entityMapper : initEntityMapper(mappingContext));
this.mappingContext = mappingContext;
}
private static EntityMapper initEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
Assert.notNull(mappingContext, "MappingContext must not be null!");
return new DefaultEntityMapper(mappingContext);
}
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
long totalHits = response.getHits().getTotalHits();
float maxScore = response.getHits().getMaxScore();
List<T> results = new ArrayList<>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
T result = null;
String hitSourceAsString = hit.getSourceAsString();
if (!StringUtils.isEmpty(hitSourceAsString)) {
result = mapEntity(hitSourceAsString, clazz);
} else {
result = mapEntity(hit.getFields().values(), clazz);
}
setPersistentEntityId(result, hit.getId(), clazz);
setPersistentEntityVersion(result, hit.getVersion(), clazz);
setPersistentEntityScore(result, hit.getScore(), clazz);
populateScriptFields(result, hit);
results.add(result);
}
}
return new AggregatedPageImpl<T>(results, pageable, totalHits, response.getAggregations(), response.getScrollId(),
maxScore);
}
private <T> void populateScriptFields(T result, SearchHit hit) {
if (hit.getFields() != null && !hit.getFields().isEmpty() && result != null) {
for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) {
ScriptedField scriptedField = field.getAnnotation(ScriptedField.class);
if (scriptedField != null) {
String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name();
DocumentField searchHitField = hit.getFields().get(name);
if (searchHitField != null) {
field.setAccessible(true);
try {
field.set(result, searchHitField.getValue());
} catch (IllegalArgumentException e) {
throw new ElasticsearchException(
"failed to set scripted field: " + name + " with value: " + searchHitField.getValue(), e);
} catch (IllegalAccessException e) {
throw new ElasticsearchException("failed to access scripted field: " + name, e);
}
}
}
}
}
}
private <T> T mapEntity(Collection<DocumentField> values, Class<T> clazz) {
return mapEntity(buildJSONFromFields(values), clazz);
}
private String buildJSONFromFields(Collection<DocumentField> values) {
JsonFactory nodeFactory = new JsonFactory();
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonGenerator generator = nodeFactory.createGenerator(stream, JsonEncoding.UTF8);
generator.writeStartObject();
for (DocumentField value : values) {
if (value.getValues().size() > 1) {
generator.writeArrayFieldStart(value.getName());
for (Object val : value.getValues()) {
generator.writeObject(val);
}
generator.writeEndArray();
} else {
generator.writeObjectField(value.getName(), value.getValue());
}
}
generator.writeEndObject();
generator.flush();
return new String(stream.toByteArray(), Charset.forName("UTF-8"));
} catch (IOException e) {
return null;
}
}
@Override
public <T> T mapResult(GetResponse response, Class<T> clazz) {
T result = mapEntity(response.getSourceAsString(), clazz);
if (result != null) {
setPersistentEntityId(result, response.getId(), clazz);
setPersistentEntityVersion(result, response.getVersion(), clazz);
}
return result;
}
@Override
public <T> List<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
List<T> list = new ArrayList<>();
for (MultiGetItemResponse response : responses.getResponses()) {
if (!response.isFailed() && response.getResponse().isExists()) {
T result = mapEntity(response.getResponse().getSourceAsString(), clazz);
setPersistentEntityId(result, response.getResponse().getId(), clazz);
setPersistentEntityVersion(result, response.getResponse().getVersion(), clazz);
list.add(result);
}
}
return list;
}
private <T> void setPersistentEntityId(T result, String id, Class<T> clazz) {
if (clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
PersistentPropertyAccessor<T> accessor = new ConvertingPropertyAccessor<>(
persistentEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
accessor.setProperty(idProperty, id);
}
}
}
private <T> void setPersistentEntityVersion(T result, long version, Class<T> clazz) {
if (clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(clazz);
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// Only deal with Long because ES versions are longs !
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
// check that a version was actually returned in the response, -1 would indicate that
// a search didn't request the version ids in the response, which would be an issue
Assert.isTrue(version != -1, "Version in response is -1");
persistentEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
}
}
}
private <T> void setPersistentEntityScore(T result, float score, Class<T> clazz) {
if (clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(clazz);
if (!entity.hasScoreProperty()) {
return;
}
entity.getPropertyAccessor(result) //
.setProperty(entity.getScoreProperty(), score);
}
}
}
@@ -0,0 +1,158 @@
/*
* Copyright 2019-2020 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.client.Requests.*;
import java.util.List;
import java.util.Map;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link IndexOperations} implementation using the TransportClient.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
*/
class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
private final Client client;
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
Class<?> boundClass) {
super(elasticsearchConverter, boundClass);
this.client = client;
}
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
IndexCoordinates boundIndex) {
super(elasticsearchConverter, boundIndex);
this.client = client;
}
@Override
protected boolean doCreate(String indexName, @Nullable Document settings) {
CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, indexName,
settings);
return createIndexRequestBuilder.execute().actionGet().isAcknowledged();
}
@Override
protected boolean doDelete(String indexName) {
Assert.notNull(indexName, "No index defined for delete operation");
if (doExists(indexName)) {
return client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet().isAcknowledged();
}
return false;
}
@Override
protected boolean doExists(String indexName) {
return client.admin().indices().exists(indicesExistsRequest(indexName)).actionGet().isExists();
}
@Override
protected boolean doPutMapping(IndexCoordinates index, Document mapping) {
Assert.notNull(index, "No index defined for putMapping()");
PutMappingRequestBuilder requestBuilder = requestFactory.putMappingRequestBuilder(client, index, mapping);
return requestBuilder.execute().actionGet().isAcknowledged();
}
@Override
protected Map<String, Object> doGetMapping(IndexCoordinates index) {
Assert.notNull(index, "No index defined for getMapping()");
try {
return client.admin().indices().getMappings( //
new GetMappingsRequest().indices(index.getIndexNames())).actionGet() //
.getMappings().get(index.getIndexName()).get(IndexCoordinates.TYPE) //
.getSourceAsMap();
} catch (Exception e) {
throw new ElasticsearchException("Error while getting mapping for indexName : " + index.getIndexName(), e);
}
}
@Override
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest.AliasActions aliasAction = requestFactory.aliasAction(query, index);
return client.admin().indices().prepareAliases().addAliasAction(aliasAction).execute().actionGet().isAcknowledged();
}
@Override
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
return client.admin().indices().prepareAliases().removeAlias(index.getIndexName(), query.getAliasName()).execute()
.actionGet().isAcknowledged();
}
@Override
protected List<AliasMetaData> doQueryForAlias(String indexName) {
return client.admin().indices().getAliases(new GetAliasesRequest().indices(indexName)).actionGet().getAliases()
.get(indexName);
}
@Override
protected Map<String, Object> doGetSettings(String indexName, boolean includeDefaults) {
Assert.notNull(indexName, "No index defined for getSettings");
GetSettingsRequest request = new GetSettingsRequest() //
.indices(indexName) //
.includeDefaults(includeDefaults);
GetSettingsResponse response = client.admin() //
.indices() //
.getSettings(request) //
.actionGet();
return convertSettingsResponseToMap(response, indexName);
}
@Override
protected void doRefresh(IndexCoordinates index) {
Assert.notNull(index, "No index defined for refresh()");
client.admin().indices().refresh(refreshRequest(index.getIndexNames())).actionGet();
}
}
@@ -0,0 +1,271 @@
/*
* Copyright 2019-2020 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 java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.GetQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.lang.Nullable;
/**
* The operations for the
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html">Elasticsearch Document APIs</a>.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
public interface DocumentOperations {
/**
* Saves an entity to the index specified in the entity's Document annotation
*
* @param entity the entity to save, must not be {@literal null}
* @param <T> the entity type
* @return the saved entity
*/
<T> T save(T entity);
/**
* Saves an entity to the index specified in the entity's Document annotation
*
* @param entity the entity to save, must not be {@literal null}
* @param index the index to save the entity in, must not be {@literal null}
* @param <T> the entity type
* @return the saved entity
*/
<T> T save(T entity, IndexCoordinates index);
/**
* saves the given entities to the index retrieved from the entities' Document annotation
*
* @param entities must not be {@literal null}
* @param <T> the entity type
* @return the saved entites
*/
<T> Iterable<T> save(Iterable<T> entities);
/**
* saves the given entities to the given index
*
* @param entities must not be {@literal null}
* @param index the index to save the entities in, must not be {@literal null}
* @param <T> the entity type
* @return the saved entities
*/
<T> Iterable<T> save(Iterable<T> entities, IndexCoordinates index);
/**
* saves the given entities to the index retrieved from the entities' Document annotation
*
* @param entities must not be {@literal null}
* @param <T> the entity type
* @return the saved entities as Iterable
*/
<T> Iterable<T> save(T... entities);
/**
* Index an object. Will do save or update.
*
* @param query the query defining the object
* @param index the index where the object is stored.
* @return returns the document id
*/
String index(IndexQuery query, IndexCoordinates index);
/**
* Retrieves an object from the index specified in the entity's Document annotation.
*
* @param id the id of the object
* @param clazz the entity class,
* @param <T> the entity type
* @return the entity
*/
@Nullable
<T> T get(String id, Class<T> clazz);
/**
* Retrieves an object from the index specified in the entity's Document annotation.
*
* @param id the id of the object
* @param clazz the entity class,
* @param index the index from which the object is read.
* @return the entity
*/
@Nullable
<T> T get(String id, Class<T> clazz, IndexCoordinates index);
/**
* Execute a multiGet against elasticsearch for the given ids.
*
* @param query the query defining the ids of the objects to get
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return list of objects, contains null values for ids that are not found
*/
<T> List<T> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param clazz the domain type used.
* @return {@literal true} if a matching document exists, {@literal false} otherwise.
*/
boolean exists(String id, Class<?> clazz);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param index the target index, must not be {@literal null}
* @return {@literal true} if a matching document exists, {@literal false} otherwise.
*/
boolean exists(String id, IndexCoordinates index);
/**
* Bulk index all objects. Will do save or update.
*
* @param queries the queries to execute in bulk
* @return the ids of the indexed objects
*/
default List<String> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
return bulkIndex(queries, BulkOptions.defaultOptions(), index);
}
/**
* Bulk index all objects. Will do save or update.
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @return the ids of the indexed objects
*/
List<String> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
/**
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
*/
default void bulkUpdate(List<UpdateQuery> queries, IndexCoordinates index) {
bulkUpdate(queries, BulkOptions.defaultOptions(), index);
}
/**
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
*/
void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
/**
* Delete the one object with provided id.
*
* @param id the document ot delete
* @param index the index from which to delete
* @return documentId of the document deleted
*/
String delete(String id, IndexCoordinates index);
/**
* Delete the one object with provided id.
*
* @param id the document ot delete
* @param entityType must not be {@literal null}.
* @return documentId of the document deleted
*/
String delete(String id, Class<?> entityType);
/**
* Deletes the given entity
*
* @param entity the entity to delete
* @return documentId of the document deleted
*/
String delete(Object entity);
/**
* Deletes the given entity
*
* @param entity the entity to delete
* @param index the index from which to delete
* @return documentId of the document deleted
*/
String delete(Object entity, IndexCoordinates index);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class, must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
*/
void delete(Query query, Class<?> clazz, IndexCoordinates index);
/**
* Partial update of the document.
*
* @param updateQuery query defining the update
* @param index the index where to update the records
* @return the update response
*/
UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index);
// region deprecated
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param index the index where to delete the records
* @deprecated since 4.0, use {@link #delete(Query, Class, IndexCoordinates)}
*/
@Deprecated
void delete(DeleteQuery query, IndexCoordinates index);
/**
* Retrieves an object from an index.
*
* @param query the query defining the id of the object to get
* @param clazz the type of the object to be returned
* @param index the index from which the object is read.
* @return the found object
* @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
*/
@Deprecated
@Nullable
<T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index);
/**
* Retrieves an object from an index.
*
* @param query the query defining the id of the object to get
* @param clazz the type of the object to be returned
* @return the found object
* @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
*/
@Deprecated
@Nullable
<T> T queryForObject(GetQuery query, Class<T> clazz);
// endregion
}
@@ -1,705 +0,0 @@
/*
* Copyright 2019 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 com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchTypeMapper;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Elasticsearch specific {@link EntityReader} & {@link EntityWriter} implementation based on domain type
* {@link ElasticsearchPersistentEntity metadata}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public class ElasticsearchEntityMapper implements
EntityConverter<ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty, Object, Map<String, Object>>,
EntityWriter<Object, Map<String, Object>>, EntityReader<Object, Map<String, Object>>, InitializingBean,
EntityMapper {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final GenericConversionService conversionService;
private final ObjectReader objectReader;
private final ObjectWriter objectWriter;
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
private EntityInstantiators instantiators = new EntityInstantiators();
private ElasticsearchTypeMapper typeMapper;
public ElasticsearchEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
@Nullable GenericConversionService conversionService) {
this.mappingContext = mappingContext;
this.conversionService = conversionService != null ? conversionService : new DefaultConversionService();
this.typeMapper = ElasticsearchTypeMapper.create(mappingContext);
ObjectMapper objectMapper = new ObjectMapper();
objectReader = objectMapper.readerFor(HashMap.class);
objectWriter = objectMapper.writer();
}
// --> GETTERS / SETTERS
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
return mappingContext;
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
/**
* Set the {@link CustomConversions} to be applied during the mapping process. <br />
* Conversions are registered after {@link #afterPropertiesSet() bean initialization}.
*
* @param conversions must not be {@literal null}.
*/
public void setConversions(CustomConversions conversions) {
this.conversions = conversions;
}
/**
* Set the {@link ElasticsearchTypeMapper} to use for reading / writing type hints.
*
* @param typeMapper must not be {@literal null}.
*/
public void setTypeMapper(ElasticsearchTypeMapper typeMapper) {
this.typeMapper = typeMapper;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
DateFormatterRegistrar.addDateConverters(conversionService);
conversions.registerConvertersIn(conversionService);
}
// --> READ
@Override
public <T> T readObject(Map<String, Object> source, Class<T> targetType) {
return read(targetType, source);
}
@SuppressWarnings("unchecked")
@Override
@Nullable
public <R> R read(Class<R> type, Map<String, Object> source) {
return doRead(source, ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type)));
}
@SuppressWarnings("unchecked")
@Nullable
protected <R> R doRead(Map<String, Object> source, TypeInformation<R> typeHint) {
if (source == null) {
return null;
}
typeHint = (TypeInformation<R>) typeMapper.readType(source, typeHint);
if (conversions.hasCustomReadTarget(Map.class, typeHint.getType())) {
return conversionService.convert(source, typeHint.getType());
}
if (typeHint.isMap() || ClassTypeInformation.OBJECT.equals(typeHint)) {
return (R) source;
}
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(typeHint);
return readEntity(entity, source);
}
@SuppressWarnings("unchecked")
protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
ElasticsearchPropertyValueProvider propertyValueProvider = new ElasticsearchPropertyValueProvider(
new MapValueAccessor(source));
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
R instance = (R) instantiator.createInstance(targetEntity,
new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null));
return targetEntity.requiresPropertyPopulation() ? readProperties(targetEntity, instance, propertyValueProvider)
: instance;
}
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
ElasticsearchPropertyValueProvider valueProvider) {
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);
for (ElasticsearchPersistentProperty prop : entity) {
if (entity.isConstructorArgument(prop) || prop.isScoreProperty()) {
continue;
}
Object value = valueProvider.getPropertyValue(prop);
if (value != null) {
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
}
}
return accessor.getBean();
}
@SuppressWarnings("unchecked")
protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Class<R> rawType = targetType.getType();
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
return rawType.cast(conversionService.convert(source, rawType));
} else if (source instanceof List) {
return readCollectionValue((List) source, property, targetType);
} else if (source instanceof Map) {
return readMapValue((Map<String, Object>) source, property, targetType);
}
return (R) readSimpleValue(source, targetType);
}
@SuppressWarnings("unchecked")
private <R> R readMapValue(@Nullable Map<String, Object> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
TypeInformation information = typeMapper.readType(source);
if (property.isEntity() && !property.isMap() || information != null) {
ElasticsearchPersistentEntity<?> targetEntity = information != null
? mappingContext.getRequiredPersistentEntity(information)
: mappingContext.getRequiredPersistentEntity(property);
return readEntity(targetEntity, source);
}
Map<String, Object> target = new LinkedHashMap<>();
for (Entry<String, Object> entry : source.entrySet()) {
if (isSimpleType(entry.getValue())) {
target.put(entry.getKey(),
readSimpleValue(entry.getValue(), targetType.isMap() ? targetType.getComponentType() : targetType));
} else {
ElasticsearchPersistentEntity<?> targetEntity = computeGenericValueTypeForRead(property, entry.getValue());
if (targetEntity.getTypeInformation().isMap()) {
Map<String, Object> valueMap = (Map) entry.getValue();
if (typeMapper.containsTypeInformation(valueMap)) {
target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue()));
} else {
target.put(entry.getKey(), readValue(valueMap, property, targetEntity.getTypeInformation()));
}
} else if (targetEntity.getTypeInformation().isCollectionLike()) {
target.put(entry.getKey(),
readValue(entry.getValue(), property, targetEntity.getTypeInformation().getActualType()));
} else {
target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue()));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Collection<Object> target = createCollectionForValue(targetType, source.size());
for (Object value : source) {
if (isSimpleType(value)) {
target.add(
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
} else {
if (value instanceof List) {
target.add(readValue(value, property, property.getTypeInformation().getActualType()));
} else {
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map) value));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {
Class<?> target = targetType.getType();
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
return value;
}
if (conversions.hasCustomReadTarget(value.getClass(), target)) {
return conversionService.convert(value, target);
}
if (Enum.class.isAssignableFrom(target)) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return conversionService.convert(value, target);
}
// --> WRITE
@Override
public Map<String, Object> mapObject(Object source) {
LinkedHashMap<String, Object> target = new LinkedHashMap<>();
write(source, target);
return target;
}
@SuppressWarnings("unchecked")
@Override
public void write(@Nullable Object source, Map<String, Object> sink) {
if (source == null) {
return;
}
if (source instanceof Map) {
sink.putAll((Map<String, Object>) source);
return;
}
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
TypeInformation<?> type = ClassTypeInformation.from(entityType);
if (requiresTypeHint(type, source.getClass(), null)) {
typeMapper.writeType(source.getClass(), sink);
}
doWrite(source, sink, type);
}
protected void doWrite(@Nullable Object source, Map<String, Object> sink,
@Nullable TypeInformation<? extends Object> typeHint) {
if (source == null) {
return;
}
Class<?> entityType = source.getClass();
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Map.class);
if (customTarget.isPresent()) {
sink.putAll(conversionService.convert(source, Map.class));
return;
}
if (typeHint != null) {
ElasticsearchPersistentEntity<?> entity = typeHint.getType().equals(entityType)
? mappingContext.getRequiredPersistentEntity(typeHint)
: mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
return;
}
// write Entity
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
}
protected void writeEntity(ElasticsearchPersistentEntity<?> entity, Object source, Map<String, Object> sink,
@Nullable TypeInformation containingStructure) {
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
if (requiresTypeHint(entity.getTypeInformation(), source.getClass(), containingStructure)) {
typeMapper.writeType(source.getClass(), sink);
}
writeProperties(entity, accessor, sink);
}
protected void writeProperties(ElasticsearchPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
Map<String, Object> sink) {
for (ElasticsearchPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = accessor.getProperty(property);
if (value == null) {
continue;
}
if (!isSimpleType(value)) {
writeProperty(property, value, sink);
} else {
sink.put(property.getFieldName(), getWriteSimpleValue(value));
}
}
}
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, Map<String, Object> sink) {
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
if (customWriteTarget.isPresent()) {
Class<?> writeTarget = customWriteTarget.get();
sink.put(property.getFieldName(), conversionService.convert(value, writeTarget));
return;
}
TypeInformation<?> typeHint = property.getTypeInformation();
if (typeHint.equals(ClassTypeInformation.OBJECT)) {
if (value instanceof List) {
typeHint = ClassTypeInformation.LIST;
} else if (value instanceof Map) {
typeHint = ClassTypeInformation.MAP;
} else if (value instanceof Set) {
typeHint = ClassTypeInformation.SET;
} else if (value instanceof Collection) {
typeHint = ClassTypeInformation.COLLECTION;
}
}
sink.put(property.getFieldName(), getWriteComplexValue(property, typeHint, value));
}
protected Object getWriteSimpleValue(Object value) {
if (value == null) {
return null;
}
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass());
if (customTarget.isPresent()) {
return conversionService.convert(value, customTarget.get());
}
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
}
@SuppressWarnings("unchecked")
protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation<?> typeHint,
Object value) {
if (typeHint.isCollectionLike() || value instanceof Iterable) {
return writeCollectionValue(value, property, typeHint);
}
if (typeHint.isMap()) {
return writeMapValue((Map<String, Object>) value, property, typeHint);
}
if (property.isEntity() || !isSimpleType(value)) {
return writeEntity(value, property, typeHint);
}
return value;
}
private Object writeEntity(Object value, ElasticsearchPersistentProperty property, TypeInformation<?> typeHint) {
Map<String, Object> target = new LinkedHashMap<>();
writeEntity(mappingContext.getRequiredPersistentEntity(value.getClass()), value, target,
property.getTypeInformation());
return target;
}
private Object writeMapValue(Map<String, Object> value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Map<Object, Object> target = new LinkedHashMap<>();
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
if (!typeHint.getActualType().getType().equals(Object.class)
&& isSimpleType(typeHint.getMapValueType().getType())) {
mapSource.forEach(it -> target.put(it.getKey(), getWriteSimpleValue(it.getValue())));
} else {
mapSource.forEach(it -> {
Object converted = null;
if (it.getValue() != null) {
if (isSimpleType(it.getValue())) {
converted = getWriteSimpleValue(it.getValue());
} else {
converted = getWriteComplexValue(property, ClassTypeInformation.from(it.getValue().getClass()),
it.getValue());
}
}
target.put(it.getKey(), converted);
});
}
return target;
}
private Object writeCollectionValue(Object value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Streamable<?> collectionSource = value instanceof Iterable ? Streamable.of((Iterable<?>) value)
: Streamable.of(ObjectUtils.toObjectArray(value));
List<Object> target = new ArrayList<>();
if (!typeHint.getActualType().getType().equals(Object.class) && isSimpleType(typeHint.getActualType().getType())) {
collectionSource.map(this::getWriteSimpleValue).forEach(target::add);
} else {
collectionSource.map(it -> {
if (it == null) {
return null;
}
if (isSimpleType(it)) {
return getWriteSimpleValue(it);
}
return getWriteComplexValue(property, ClassTypeInformation.from(it.getClass()), it);
}).forEach(target::add);
}
return target;
}
// --> LEGACY
@Override
public String mapToString(Object source) throws IOException {
Map<String, Object> sink = new LinkedHashMap<>();
write(source, sink);
return objectWriter.writeValueAsString(sink);
}
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return read(clazz, objectReader.readValue(source));
}
// --> PRIVATE HELPERS
private boolean requiresTypeHint(TypeInformation<?> type, Class<?> actualType,
@Nullable TypeInformation<?> container) {
if (container != null) {
if (container.isCollectionLike()) {
if (type.equals(container.getActualType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.isMap()) {
if (type.equals(container.getMapValueType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.equals(type) && type.getType().equals(actualType)) {
return false;
}
}
return !conversions.isSimpleType(type.getType()) && !type.isCollectionLike()
&& !conversions.hasCustomWriteTarget(type.getType());
}
/**
* Compute the type to use by checking the given entity against the store type;
*
* @param entity
* @param source
* @return
*/
private ElasticsearchPersistentEntity<?> computeClosestEntity(ElasticsearchPersistentEntity<?> entity,
Map<String, Object> source) {
TypeInformation<?> typeToUse = typeMapper.readType(source);
if (typeToUse == null) {
return entity;
}
if (!entity.getTypeInformation().getType().isInterface() && !entity.getTypeInformation().isCollectionLike()
&& !entity.getTypeInformation().isMap()
&& !ClassUtils.isAssignableValue(entity.getType(), typeToUse.getType())) {
return entity;
}
return mappingContext.getRequiredPersistentEntity(typeToUse);
}
private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
Object value) {
return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
? mappingContext.getRequiredPersistentEntity(value.getClass())
: mappingContext.getRequiredPersistentEntity(property.getTypeInformation().getActualType());
}
private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {
Class<?> collectionType = collectionTypeInformation.isSubTypeOf(Collection.class) //
? collectionTypeInformation.getType() //
: List.class;
TypeInformation<?> componentType = collectionTypeInformation.getComponentType() != null //
? collectionTypeInformation.getComponentType() //
: ClassTypeInformation.OBJECT;
return collectionTypeInformation.getType().isArray() //
? new ArrayList<>(size) //
: CollectionFactory.createCollection(collectionType, componentType.getType(), size);
}
private boolean isSimpleType(Object value) {
return isSimpleType(value.getClass());
}
private boolean isSimpleType(Class<?> type) {
return conversions.isSimpleType(type);
}
// --> OHTER STUFF
@RequiredArgsConstructor
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
final MapValueAccessor mapValueAccessor;
@SuppressWarnings("unchecked")
@Override
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation());
}
}
static class MapValueAccessor {
final Map<String, Object> target;
MapValueAccessor(Map<String, Object> target) {
this.target = target;
}
public Object get(ElasticsearchPersistentProperty property) {
String fieldName = property.getFieldName();
if (!fieldName.contains(".")) {
return target.get(fieldName);
}
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
Map<String, Object> source = target;
Object result = null;
while (source != null && parts.hasNext()) {
result = source.get(parts.next());
if (parts.hasNext()) {
source = getAsMap(result);
}
}
return result;
}
@SuppressWarnings("unchecked")
private Map<String, Object> getAsMap(Object result) {
if (result instanceof Map) {
return (Map) result;
}
throw new IllegalArgumentException(String.format("%s is not a Map.", result));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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,24 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.net.ConnectException;
import java.io.IOException;
import java.util.List;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.rest.RestStatus;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* 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 Christoph Strobl
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
* @author Mark Paluch
* @since 3.2
*/
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
@@ -38,6 +51,10 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
if (isSeqNoConflict(ex)) {
return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex);
}
if (ex instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
@@ -46,22 +63,52 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
ex);
}
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
}
if (ex.getCause() instanceof ConnectException) {
if (ex instanceof ValidationException) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
Throwable cause = ex.getCause();
if (cause instanceof IOException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
return null;
}
private boolean isSeqNoConflict(Exception exception) {
if (exception instanceof ElasticsearchStatusException) {
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
return statusException.status() == RestStatus.CONFLICT && statusException.getMessage() != null
&& statusException.getMessage().contains("type=version_conflict_engine_exception")
&& statusException.getMessage().contains("version conflict, required seqNo");
}
if (exception instanceof VersionConflictEngineException) {
VersionConflictEngineException versionConflictEngineException = (VersionConflictEngineException) exception;
return versionConflictEngineException.getMessage() != null
&& versionConflictEngineException.getMessage().contains("version conflict, required seqNo");
}
return false;
}
private boolean indexAvailable(ElasticsearchException ex) {
List<String> metadata = ex.getMetadata("es.index_uuid");
if (metadata == null) {
if (ex instanceof ElasticsearchStatusException) {
return StringUtils.hasText(ObjectUtils.nullSafeToString(((ElasticsearchStatusException) ex).getIndex()));
return StringUtils.hasText(ObjectUtils.nullSafeToString(ex.getIndex()));
}
return false;
}
return !CollectionUtils.contains(metadata.iterator(), "_na_");
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,57 +0,0 @@
/*
* Copyright 2014-2019 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 java.io.IOException;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* DocumentMapper interface, it will allow to customize how we mapping object to json
*
* @author Artur Konczak
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Christoph Strobl
*/
public interface EntityMapper {
String mapToString(Object object) throws IOException;
<T> T mapToObject(String source, Class<T> clazz) throws IOException;
/**
* Map the given {@literal source} to {@link Map}.
*
* @param source must not be {@literal null}.
* @return never {@literal null}
* @since 3.2
*/
Map<String, Object> mapObject(Object source);
/**
* Map the given {@link Map} into an instance of the {@literal targetType}.
*
* @param source must not be {@literal null}.
* @param targetType must not be {@literal null}.
* @param <T>
* @return can be {@literal null}.
* @since 3.2
*/
@Nullable
<T> T readObject(Map<String, Object> source, Class<T> targetType);
}

Some files were not shown because too many files have changed in this diff Show More