1
0
mirror of synced 2026-07-07 10:40:02 +00:00

Compare commits

..

169 Commits

Author SHA1 Message Date
Jens Schauder 060cab76d5 DATAES-692 - Release version 3.2.3 (Moore SR3). 2019-12-04 14:12:24 +01:00
Jens Schauder afb8a35eac DATAES-692 - Prepare 3.2.3 (Moore SR3). 2019-12-04 14:11:44 +01:00
Jens Schauder 1c0dd71020 DATAES-692 - Updated changelog. 2019-12-04 14:11:42 +01:00
Jens Schauder 1633668d7f DATAES-691 - Updated changelog. 2019-12-04 12:09:51 +01:00
Peter-Josef Meisch 6756f792c8 DATAES-699 - Fix count implementation.
Original PR: #349
2019-12-01 10:03:56 +01:00
Peter-Josef Meisch bae4db8a7f DATAES-700 - Enable proxy support for RestClient. 2019-11-30 23:30:59 +01:00
Mark Paluch c47fd2cfce DATAES-685 - After release cleanups. 2019-11-18 12:42:05 +01:00
Mark Paluch cb08bb7196 DATAES-685 - Prepare next development iteration. 2019-11-18 12:42:04 +01:00
Mark Paluch f3500623ff DATAES-685 - Release version 3.2.2 (Moore SR2). 2019-11-18 12:32:12 +01:00
Mark Paluch 4c4cbed43b DATAES-685 - Prepare 3.2.2 (Moore SR2). 2019-11-18 12:31:53 +01:00
Mark Paluch 23fb5689b7 DATAES-685 - Updated changelog. 2019-11-18 12:31:52 +01:00
Mark Paluch b551466f94 DATAES-683 - Updated changelog. 2019-11-18 12:16:28 +01:00
Peter-Josef Meisch 0a7af69d69 DATAES-680 - Fix missing import. 2019-11-08 17:03:37 +01:00
Peter-Josef Meisch 2cf42a4763 DATAES-680 - ReactiveElasticsearchTemplate-should-use-the-count-API.
Original PR: #341

(cherry picked from commit 62385edaa5)
2019-11-07 17:07:39 +01:00
Peter-Josef Meisch 6f0d1ee9e7 DATAES-684 - Polishing.
(cherry picked from commit 24751972a8)
2019-11-05 10:41:21 +01:00
Henrique Amaral f82dd229d9 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

(cherry picked from commit 6ae424428c)
2019-11-05 10:39:40 +01:00
Christoph Strobl 3b833f6f63 DATAES-662 - After release cleanups. 2019-11-04 15:34:27 +01:00
Christoph Strobl 2517fd5c90 DATAES-662 - Prepare next development iteration. 2019-11-04 15:34:26 +01:00
Christoph Strobl 4e572679dd DATAES-662 - Release version 3.2.1 (Moore SR1). 2019-11-04 14:55:02 +01:00
Christoph Strobl de9c664d1e DATAES-662 - Prepare 3.2.1 (Moore SR1). 2019-11-04 14:54:16 +01:00
Christoph Strobl 8187c5362a DATAES-662 - Updated changelog. 2019-11-04 14:54:15 +01:00
Christoph Strobl a8eb260bbd DATAES-660 - Updated changelog. 2019-11-04 10:34:54 +01:00
Peter-Josef Meisch bb944f595f DATAES-679 - Upgrade_to_Elasticsearch_6.8.4.
Original PR: #339
2019-10-31 17:40:24 +01:00
Henrique Amaral 598626238b DATAES-673 Create a Ssl Rest Client using SslContext and HostnameVerifier 2019-10-23 19:22:34 +02:00
Gyula Csörögi a451f8dca4 DATAES-671 - Missing indicesOptions support for scrolling queries.
Original PR: #332
2019-10-16 09:00:44 +02:00
Peter-Josef Meisch 4fd070c332 DATAES-670 - Fix version compatibility matrix in documentation.
Original PR: #330
2019-10-12 10:42:27 +02:00
Mark Paluch 091413dd69 DATAES-665 - Fix plugin ordering to deploy Javadoc. 2019-10-01 08:39:10 +02:00
Greg Turnquist ff74425132 DATAES-625 - Create CI job. 2019-09-30 14:23:41 -05:00
Mark Paluch 6f4d1dcc57 DATAES-625 - After release cleanups. 2019-09-30 16:17:54 +02:00
Mark Paluch 9713e33fed DATAES-625 - Prepare next development iteration. 2019-09-30 16:17:53 +02:00
Mark Paluch ab4fd17520 DATAES-625 - Release version 3.2 GA (Moore). 2019-09-30 15:51:08 +02:00
Mark Paluch 6045f1e0e3 DATAES-625 - Prepare 3.2 GA (Moore). 2019-09-30 15:50:44 +02:00
Mark Paluch 102d7370bd DATAES-625 - Updated changelog. 2019-09-30 15:50:42 +02:00
Mark Paluch e22ba228a9 DATAES-624 - Updated changelog. 2019-09-30 11:16:16 +02:00
Peter-Josef Meisch 52105fcdcc DATAES-227 - Polishing. 2019-09-27 21:35:28 +02:00
Sascha Woo 8a1242fdbd DATAES-227 - prepareUpdate does not consider UpdateRequest options.
Original PR: #327
2019-09-27 21:22:20 +02:00
xhaggi 5f78ab8e25 DATAES-657 - Sort by _score not supported by ElasticsearchRestTemplate.
Original pull request: #325
2019-09-24 10:52:57 +02:00
Sascha Woo 0899df94eb DATAES-541 - Removing an alias with ElasticseachRestTemplate does not work properly.
Original PR: #324
2019-09-23 20:10:12 +02:00
P.J. Meisch 327e0a207c DATAES-652 - Polishing. 2019-09-20 12:11:14 +02:00
Roman Puchkovskiy 8d044fe3d1 DATAES-652 - Send if_seq_no and if_primary_term parameters when index…
...ing via ReactiveElasticsearchClient

Original PR: #322
2019-09-20 12:05:06 +02:00
xhaggi 642d95b5d1 DATAES-305 - Allow to put mapping built from entity class to different index.
Original pull request: #319
2019-09-13 08:23:42 +02:00
Oliver Drotbohm 6092da6fe7 DATAES-648 - Unified version compatibility matrix into reference docs.
Added information about which Boot version uses which Spring Data Elasticsearch version as well as hints about which versions are still under active maintenance.

Tweaked the readme to rather point to the reference documentation. This needs to be updated to point to the "current" alias once we've reached GA.

Original pull request: #317.
2019-09-11 20:31:23 +02:00
Mark Paluch 4773da32ab DATAES-497 - Polishing.
Fix compatibility matrix in readme.
2019-09-10 11:57:38 +02:00
Mark Paluch 642e42e01e DATAES-626 - After release cleanups. 2019-09-06 10:21:56 +02:00
Mark Paluch 0820ede014 DATAES-626 - Prepare next development iteration. 2019-09-06 10:21:54 +02:00
Mark Paluch fb49aaa2d6 DATAES-626 - Release version 3.2 RC3 (Moore). 2019-09-06 10:10:42 +02:00
Mark Paluch c67b3082cb DATAES-626 - Prepare 3.2 RC3 (Moore). 2019-09-06 10:10:12 +02:00
Mark Paluch 0784cc7226 DATAES-626 - Updated changelog. 2019-09-06 10:10:11 +02:00
P.J. Meisch 2ce1bddce9 DATAES-645 - Polishing. 2019-09-04 22:23:46 +02:00
Gyula Csörögi 48d2cd1461 DATAES-645 Fix missing highlight setting in scroll queries.
Original PR: #312
2019-09-04 22:20:04 +02:00
Mark Paluch bdee49f815 DATAES-646 - Adapt to Spring Framework 5.2 RC2.
Un-implement ClientResponse from RawActionResponse to not require us updating the code whenever ClientResponse introduces new methods. Retain only methods from ClientResponse that are actually used.

Consistent versions actross netty dependencies.
2019-09-04 14:48:11 +02:00
Mark Paluch aae7734bc0 DATAES-646 - Adapt to Spring Framework 5.2 RC2.
Implement missing methods in RawActionResponse. Consistent versions actross netty dependencies.
2019-09-04 12:13:01 +02:00
Mathias Teier 054c02bd41 DATAES-643: Added searchType to prepareSearch() in RestTemplate (#310)
Original PR: #310
2019-08-27 12:11:46 +02:00
Mohsin Husen 4798136074 DATAES-589 - change spring data example link 2019-08-14 15:24:08 +01:00
Mark Paluch 389a5bc5e7 DATAES-627 - Polishing.
Reinstantiate schema links for Elasticsearch 1.0 XSD.
2019-08-07 08:03:01 +02:00
Mark Paluch fea26b5751 DATAES-627 - Add HTTPS entries into spring.schemas.
To resolve XSD files properly from the classpath, their HTTPS reference must be present in the spring.schemas to avoid internet interaction for resolving an XSD file.
2019-08-07 08:01:11 +02:00
Greg Turnquist fd7a380edd DATAES-583 - Force check for updates. 2019-08-05 10:21:32 -05:00
Mark Paluch ff021c1319 DATAES-591 - After release cleanups. 2019-08-05 15:53:03 +02:00
Mark Paluch ae38eab00d DATAES-591 - Prepare next development iteration. 2019-08-05 15:53:02 +02:00
Mark Paluch 61f4581dc4 DATAES-591 - Release version 3.2 RC2 (Moore). 2019-08-05 15:35:35 +02:00
Mark Paluch 11c1381f6c DATAES-591 - Prepare 3.2 RC2 (Moore). 2019-08-05 15:35:05 +02:00
Mark Paluch 8d37e4c8e4 DATAES-591 - Updated changelog. 2019-08-05 15:35:03 +02:00
Mark Paluch a83073f3ee DATAES-590 - Updated changelog. 2019-08-05 11:34:27 +02:00
Mark Paluch e47d7525c5 DATAES-581 - Updated changelog. 2019-08-05 11:08:58 +02:00
Peter-Josef Meisch 530a1f8a4b DATAES-405 - use org.springframework.lang.Nullable.
Original PR: #300
2019-07-31 22:19:30 +02:00
Peter-Josef Meisch 9e93dd08aa DATES-615 - Use annotated field name on repository order by clause.
Original PR: #298
2019-07-30 12:40:31 +02:00
Mark Paluch d1aa604fe5 DATAES-619 - Polishing.
Use consistently SpringRunner.
2019-07-29 15:53:37 +02:00
Mark Paluch 2c4ca358f3 DATAES-619 - Migrate remaining tests to AssertJ. 2019-07-29 15:48:09 +02:00
Mark Paluch e23f6f1d33 DATAES-603 - Polishing.
Add nullable annotations to nullable properties in BulkOptions. Add assertions to non-nullable method arguments. Reformat code. Make methods static where possible.

Original pull request: #296.
2019-07-26 12:03:47 +02:00
P.J.Meisch 8c7d5d47b1 DATAES-603 - Add support for bulk options in Elasticsearch-Template classes.
Original pull request: #296.
2019-07-26 12:03:29 +02:00
Mark Paluch 4c743ee5b9 DATAES-616 - Polishing.
Remove superfluous SuppressWarnings. Add Javadoc override for consistency.
2019-07-26 09:29:19 +02:00
Peter-Josef Meisch 085dd9ad95 DATAES-616 - Fix implementations of ParameterAccessor interface.
Original PR: #297
2019-07-26 00:03:07 +02:00
Greg Turnquist 63172b8a69 DATAES-583 - Publish documentation for main branch. 2019-07-19 11:59:52 -05:00
P.J.Meisch ccfade12b0 DATAES-611 - Adapt to API changes in Spring Webflux.
Original pull request: #295.
2019-07-16 09:55:12 +02:00
Mark Paluch cf7fc61f7b DATAES-607 - Polishing.
Ensure copying HttpHeaders before using these as default to prevent modifications of the original object. Adapt tests. Reuse header constants.

Original pull request: #293.
2019-07-16 09:49:01 +02:00
Mark Paluch c8c3cb7909 DATAES-497 - Polishing.
Consistently add anchors to headings. Tiny syntax tweaks. Remove superfluous newlines.

Original pull request: #291.
2019-07-16 09:32:54 +02:00
P.J.Meisch ef01211639 DATAES-497 - Update reference documentation.
Original pull request: #291.
2019-07-16 09:32:47 +02:00
Peter-Josef Meisch 4816902f9c DATAES-610 - Clarify inclusion of rc and snapshot builds in readme.adoc.
Original PR: #294
2019-07-15 20:48:29 +02:00
Peter-Josef Meisch eec55e273e DATAES-607 - Client-security-configuration.
Original PR: #293
2019-07-13 10:33:23 +02:00
Johnny Lim f5b4722b6b DATAES-609 - Fix TransportClientFactoryBean.isSingleton().
Original PR: #153
2019-07-12 19:42:55 +02:00
P.J.Meisch a9d57936c3 DATAES-606 - update 3.2.x to use ES 6.8.1. 2019-07-10 23:30:03 +02:00
P.J.Meisch 45015cd7ee DATAES-605 - Polishing. 2019-07-10 22:55:43 +02:00
rasmusfaber 216fc89fd2 DATAES-605 - Make batch size for streamQuery configurable.
Original PR: #292
2019-07-10 22:42:14 +02:00
Mark Paluch 7fd9986c6e DATAES-604 - Fix typo. 2019-07-10 09:57:46 +02:00
Mark Paluch 305f207731 DATAES-583 - Cleanup release profile.
Reuse inherited configuration from parent pom.
2019-07-09 12:11:12 +02:00
Mark Paluch ed666823a4 DATAES-604 - Revise readme for a consistent structure. 2019-07-09 12:11:12 +02:00
Tony Zeng 8dd2f47b7c DATAES-415 - More Field Types Support.
Original PR: #193
2019-07-04 21:29:43 +02:00
Greg Turnquist 1e4dcdff7c DATAES-583 - Use labeled agents for CI jobs. 2019-07-03 14:15:43 -05:00
Greg Turnquist 802c91f324 DATAES-583 - Use parent 'artifactory' profile to release snapshots. 2019-07-03 12:41:31 -05:00
Greg Turnquist 18ae3e194a DATAES-583 - Only build main branch when triggered upstream. 2019-06-28 16:33:55 -05:00
Oliver Drotbohm e3003f3268 DATAES-601 - Fixed NoHTTP errors. 2019-06-27 15:54:04 +02:00
P.J.Meisch 03567d4281 DATAES-456 - Polishing. 2019-06-27 08:10:05 +02:00
Sylvain LAURENT bf4948239e DATAES-456 - re-enable dynamic index name evaluation.
Original PR: #221
2019-06-27 07:55:21 +02:00
P.J.Meisch 57ba74e543 DATAES-593 - Polishing. 2019-06-24 20:37:57 +02:00
Greg Turnquist 6567d17f23 DATAES-583 - Configure user.name and user.home for CI jobs. 2019-06-24 13:26:56 -05:00
Martin Choraine 0d1c57a874 DATAES-593 - Add support for field collapse function.
Original PR: #290
2019-06-24 20:18:38 +02:00
P.J.Meisch f1aa75a94f DATAES-595 - Polishing. 2019-06-21 22:51:15 +02:00
Azaza 40ea40a763 DATAES-595 - Support for setting preference parameter in query.
Original PR: #288
2019-06-21 22:41:40 +02:00
quxinyong 7ea0a43162 DATAES-598: Add Elasticsearch authentication code example to readme.
Original PR: #289
2019-06-21 22:19:35 +02:00
P.J.Meisch 6a3042c437 DATAES-594 - Polishing. 2019-06-20 12:11:19 +02:00
murali_ch d22b12874d DATAES-594 Added support for index without refresh.
Original PR: #286
2019-06-20 11:57:42 +02:00
Johnny Lim 9a6172b4fe DATAES-597- Fix code formatting in readme.
Original PR: #111
2019-06-19 10:03:53 +02:00
Roman Puchkovskiy 159489c7da DATAES-596 - Fix usage of deprecated Rest(HighLevel)Client API. (#287)
Switch to methods taking RequestOptions as they are more universal
Original-PR: #287
2019-06-19 09:12:35 +02:00
Mark Paluch 24b0f09323 DATAES-592 - Integrate nohttp tooling into CI build profile. 2019-06-18 10:36:49 +02:00
Christoph Strobl 00d6ff90ea DATAES-560 - After release cleanups. 2019-06-14 15:12:53 +02:00
Christoph Strobl 8bd2e8aacf DATAES-560 - Prepare next development iteration. 2019-06-14 15:12:51 +02:00
Christoph Strobl 6bf091be68 DATAES-560 - Release version 3.2 RC1 (Moore). 2019-06-14 14:44:26 +02:00
Christoph Strobl 3b8ccff089 DATAES-560 - Prepare 3.2 RC1 (Moore). 2019-06-14 14:43:17 +02:00
Christoph Strobl cd428b28a7 DATAES-560 - Updated changelog. 2019-06-14 14:43:16 +02:00
Christoph Strobl d7b7e1f7dd DATAES-580 - Updated changelog. 2019-06-14 13:27:17 +02:00
Peter-Josef Meisch 2194096ad8 DATAES-589 - Improve readme file.
Original PR: #285, contains change from #268
2019-06-11 22:46:44 +02:00
Mark Paluch 42803ebd55 DATAES-586 - Create security policy readme. 2019-05-31 16:11:21 +02:00
Mark Paluch 92433b79aa DATAES-579 - Reformat test sources.
Original pull request: #283.
2019-05-31 11:46:32 +02:00
Mark Paluch 198ad0e02b DATAES-579 - Polishing.
Use Lombok's Data where possible. Extract duplicate code into ResourceUtil and StreamQueries. Generalize multiGet to return List instead of LinkedList. Reorder methods.

Reduce properties usage by removing unused properties from test entities.

Remove final keyword usage in methods. Formatting.

Original pull request: #283.
2019-05-31 11:46:29 +02:00
P.J.Meisch 4c206f7de5 DATAES-579 - Test code cleanup.
Original pull request: #283.
2019-05-31 11:46:22 +02:00
Greg Turnquist 287aa45b98 DATAES-583 - Introduce Jenkins. 2019-05-28 15:02:29 -05:00
Mark Paluch 14a7ad03bd DATAES-578 - Updated changelog. 2019-05-13 18:19:11 +02:00
Mark Paluch e577a80811 DATAES-577 - Updated changelog. 2019-05-13 14:59:29 +02:00
Mark Paluch efbc0d97f2 DATAES-564 - After release cleanups. 2019-05-13 12:17:54 +02:00
Mark Paluch 7f7364054a DATAES-564 - Prepare next development iteration. 2019-05-13 12:17:53 +02:00
Mark Paluch 8dc8428abd DATAES-564 - Release version 3.2 M4 (Moore). 2019-05-13 11:59:50 +02:00
Mark Paluch aba55b1d67 DATAES-564 - Prepare 3.2 M4 (Moore). 2019-05-13 11:59:04 +02:00
Mark Paluch e66dac0cb1 DATAES-564 - Updated changelog. 2019-05-13 11:59:02 +02:00
Oliver Drotbohm e9197b2c2e DATAES-555 - Updated changelog. 2019-05-10 14:18:14 +02:00
Oliver Drotbohm 04b3e468e6 DATAES-557 - Updated changelog. 2019-05-10 12:57:28 +02:00
Mark Paluch 9a867fb35f DATAES-575 - Polishing.
Remove unused OSGi manifest template.
2019-05-07 16:51:15 +02:00
Mark Paluch 36f90a15cb DATAES-575 - Upgrade to Elasticsearch 6.7.2. 2019-05-07 16:45:40 +02:00
Mark Paluch 7e56c3f9f9 DATAES-569 - Polishing.
Slightly tweak javadoc. Tweak StepVerifier usage. Add missing assertion. Fix generics.

Original pull request: #277.
2019-05-07 16:45:15 +02:00
Christoph Strobl 2e709a72b7 DATAES-569 - Add index operations to reactive client.
Original pull request: #277.
2019-05-07 16:45:08 +02:00
Mark Paluch 807cfe78bc DATAES-576 - Fix CustomMethodRepositoryRestTests.
Register custom template bean as workaround to delete the index before repository bootstrapping.
2019-05-06 21:59:36 +02:00
Mark Paluch 1f04e6453c DATAES-576 - Fix ElasticsearchTemplateTests.
We now delete documents with a type query instead of a term query. Also, added index cleanup to ReactiveElasticsearchTemplateTests.
2019-05-06 21:21:16 +02:00
Mark Paluch b6fa4c8a74 DATAES-568 - Polishing.
Add package-info and nullability annotations to org.springframework.data.elasticsearch.core.mapping.
Extract method to avoid excessive nesting.

Add ticket references/convert old references to test methods. Move test models to inner classes. Use static imports for JSON Assert. Formatting.

Original pull request: #281.
2019-05-06 20:00:24 +02:00
P.J. Meisch 66b77ecb75 DATAES-568 - MappingBuilder must use the @Field annotation's name attribute.
Original pull request: #281.
2019-05-06 15:59:01 +02:00
xhaggi e5c514e385 DATAES-570 - Use Delete By Query API for delete by query operations.
Original pull request: #280
2019-04-29 11:37:57 +02:00
Dmitriy Yakovlev be34ff8703 DATAES-459 - Return ScrolledPage instead of Page while using startScroll and continueScroll methods.
Original pull request: #274
2019-04-26 14:47:58 +02:00
xhaggi 9abe0f28c5 DATAES-572 - Indices created in ElasticsearchTemplateTests are retained after tests.
Original pull request: #279
2019-04-26 10:22:25 +02:00
Christoph Strobl b60c9af35c DATAES-565 - Alter test setup to assure a clean start.
Make sure required resources are cleaned before running tests. Some leftovers from other tests may still be lingering around and we need to get rid of those.
2019-04-25 13:25:52 +02:00
Dmitriy Yakovlev eb15c114b8 DATAES-457 - ElasticsearchTemplate.prepareScroll() does not add sorting.
Original pull request: #273
2019-04-24 15:39:19 +02:00
Dmitriy Yakovlev 4ab8af5116 DATAES-566 - Eliminate double SearchHit source to string conversion in DefaultResultMapper.
Original pull request: #275
2019-04-24 15:30:27 +02:00
P.J.Meisch e7857e874f DATAES-562 - Custom name attribute for @Field mapping in ElasticsearchEntityMapper.
Original pull request: #270
2019-04-24 15:19:55 +02:00
Mark Paluch 17d9022181 DATAES-563 - Polishing.
Merge test methods. Add author name. Formatting, javadoc.

Original pull request: #271.
2019-04-23 16:05:39 +02:00
P.J. Meisch deefeb462e DATAES-563 - Add elasticsearchTemplate bean alias to AbstractElasticsearchConfiguration.
We now register ElasticsearchOperations through AbstractElasticsearchConfiguration with an additional bean alias to retain compatibility with existing code and to not require additional configuration when using @EnableElasticsearchRepositories.

EnableElasticsearchRepositories defaults to a bean named elasticsearchTemplate while AbstractElasticsearchConfiguration exposed the template bean named elasticsearchOperations.

Original pull request: #271.
2019-04-23 16:02:37 +02:00
Dmitriy Yakovlev f0ae72141f DATAES-565 - prepareScroll doesn't respect SourceFilter from the Query.
Original pull request: #272
2019-04-18 12:46:31 +02:00
Dimitri 7e0d0ea2c0 DATAES-546 - Escaped double quotes in code snippet.
Original pull request: #246
2019-04-17 10:16:37 +02:00
P.J. Meisch 676567910d DATAES-561 - Reuse ObjectMapper in ElasticsearchEntityMapper
Original pull request: #269
2019-04-17 09:40:43 +02:00
Christoph Strobl dff7d4e11b DATAES-542 - After release cleanups. 2019-04-11 12:00:21 +02:00
Christoph Strobl 5c9fea307b DATAES-542 - Prepare next development iteration. 2019-04-11 12:00:20 +02:00
Christoph Strobl 2f2aa263fe DATAES-542 - Release version 3.2 M3 (Moore). 2019-04-11 11:24:32 +02:00
Christoph Strobl 6d980cb767 DATAES-542 - Prepare 3.2 M3 (Moore). 2019-04-11 11:23:56 +02:00
Christoph Strobl f5b29aeba7 DATAES-542 - Updated changelog. 2019-04-11 11:23:55 +02:00
Jens Schauder 0ccc54da34 DATAES-558 - Upgrade Elastic Search version to 6.6.2. 2019-04-11 11:07:07 +02:00
Christoph Strobl 3ea7dea5cb DATAES-552 - Polishing.
Update lookup to replace all matches & add some tests.

Original Pull Request: #267
2019-04-10 10:20:12 +02:00
Taylor b93bd07a3a DATAES-552 - @Query annotation fail when passing over 10 parameters
Updated the replacePlaceholders methods to use replaceFirst to prevent duplication substitutions.

Original Pull Request: #267
2019-04-10 10:19:25 +02:00
Christoph Strobl 16bf8450f0 DATAES-547 - Polishing.
Add test and directly use SearchHit to pass on the index name.
Fix minor flaw in Exception translation for non existing indices along the way.

Original Pull Request: #257
2019-04-09 15:40:49 +02:00
Spyna 082988955b DATAES-547 - ElasticSearchTemplate.delete(DeleteQuery, Class) does not delete documents.
Original Pull Request: #257
2019-04-09 15:40:34 +02:00
Oliver Drotbohm 3b99aa04ef DATAES-538 - Updated changelog. 2019-04-01 20:56:32 +02:00
Oliver Drotbohm 18910f457a DATAES-528 - Updated changelog. 2019-04-01 19:37:03 +02:00
Oliver Drotbohm e4825d52a7 DATAES-554 - Updated changelog. 2019-04-01 18:52:21 +02:00
Oliver Drotbohm 27b33e019e DATAES-527 - Updated changelog. 2019-04-01 18:49:24 +02:00
Spring Operator 9818ed0af9 DATAES-549 - URL Cleanup.
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* [ ] http://www.apache.org/licenses/ with 1 occurrences migrated to:
  https://www.apache.org/licenses/ ([https](https://www.apache.org/licenses/) result 200).
* [ ] http://www.apache.org/licenses/LICENSE-2.0 with 317 occurrences migrated to:
  https://www.apache.org/licenses/LICENSE-2.0 ([https](https://www.apache.org/licenses/LICENSE-2.0) result 200).

Original Pull Request: #266
2019-03-22 08:20:19 +01:00
Spring Operator cacab0a478 DATAES-549 - URL Cleanup.
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed But Review Recommended
These URLs were fixed, but the https status was not OK. However, the https status was the same as the http request or http redirected to an https URL, so they were migrated. Your review is recommended.

* [ ] http://www.elasticsearch.org/ (301) with 1 occurrences migrated to:
  https://www.elastic.co/ ([https](https://www.elasticsearch.org/) result SSLHandshakeException).
* [ ] http://www.elasticsearch.org/download/ (301) with 1 occurrences migrated to:
  https://www.elastic.co/downloads/elasticsearch ([https](https://www.elasticsearch.org/download/) result SSLHandshakeException).
* [ ] http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html (301) with 2 occurrences migrated to:
  https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html ([https](https://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html) result SSLHandshakeException).
* [ ] http://www.elasticsearch.org/guide/reference/mapping/date-format/ (301) with 1 occurrences migrated to:
  https://www.elastic.co/guide/reference/mapping/date-format/ ([https](https://www.elasticsearch.org/guide/reference/mapping/date-format/) result SSLHandshakeException).

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* [ ] http://asciidoctor.org with 1 occurrences migrated to:
  https://asciidoctor.org ([https](https://asciidoctor.org) result 200).
* [ ] http://stackoverflow.com/questions/tagged/spring-data-elasticsearch with 1 occurrences migrated to:
  https://stackoverflow.com/questions/tagged/spring-data-elasticsearch ([https](https://stackoverflow.com/questions/tagged/spring-data-elasticsearch) result 200).
* [ ] http://www.springframework.org/schema/beans/spring-beans.xsd with 2 occurrences migrated to:
  https://www.springframework.org/schema/beans/spring-beans.xsd ([https](https://www.springframework.org/schema/beans/spring-beans.xsd) result 200).
* [ ] http://www.springframework.org/schema/data/repository/spring-repository.xsd with 2 occurrences migrated to:
  https://www.springframework.org/schema/data/repository/spring-repository.xsd ([https](https://www.springframework.org/schema/data/repository/spring-repository.xsd) result 200).
* [ ] http://contributor-covenant.org with 1 occurrences migrated to:
  https://contributor-covenant.org ([https](https://contributor-covenant.org) result 301).
* [ ] http://contributor-covenant.org/version/1/3/0/ with 1 occurrences migrated to:
  https://contributor-covenant.org/version/1/3/0/ ([https](https://contributor-covenant.org/version/1/3/0/) result 301).
* [ ] http://docs.spring.io/spring-data/elasticsearch/docs/current/api/ with 1 occurrences migrated to:
  https://docs.spring.io/spring-data/elasticsearch/docs/current/api/ ([https](https://docs.spring.io/spring-data/elasticsearch/docs/current/api/) result 301).
* [ ] http://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/ with 1 occurrences migrated to:
  https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/ ([https](https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/) result 301).
* [ ] http://help.github.com/forking/ with 1 occurrences migrated to:
  https://help.github.com/forking/ ([https](https://help.github.com/forking/) result 301).
* [ ] http://projects.spring.io/spring-data with 1 occurrences migrated to:
  https://projects.spring.io/spring-data ([https](https://projects.spring.io/spring-data) result 301).
* [ ] http://www.springframework.org/schema/beans/spring-beans-3.1.xsd with 3 occurrences migrated to:
  https://www.springframework.org/schema/beans/spring-beans-3.1.xsd ([https](https://www.springframework.org/schema/beans/spring-beans-3.1.xsd) result 301).
* [ ] http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd with 3 occurrences migrated to:
  https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd ([https](https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd) result 301).
* [ ] http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd with 2 occurrences migrated to:
  https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd ([https](https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd) result 301).
* [ ] http://repo.spring.io/libs-snapshot with 1 occurrences migrated to:
  https://repo.spring.io/libs-snapshot ([https](https://repo.spring.io/libs-snapshot) result 302).
* [ ] http://www.springsource.org/download with 1 occurrences migrated to:
  https://www.springsource.org/download ([https](https://www.springsource.org/download) result 302).
* [ ] http://www.springsource.org/node/feed with 1 occurrences migrated to:
  https://www.springsource.org/node/feed ([https](https://www.springsource.org/node/feed) result 302).

# Ignored
These URLs were intentionally ignored.

* http://127.0.0.1:9200 with 1 occurrences
* http://localhost:9200 with 3 occurrences
* http://www.springframework.org/schema/beans with 14 occurrences
* http://www.springframework.org/schema/data/elasticsearch with 14 occurrences
* http://www.springframework.org/schema/data/repository with 4 occurrences
* http://www.springframework.org/schema/tool with 4 occurrences
* http://www.w3.org/2001/XMLSchema with 2 occurrences
* http://www.w3.org/2001/XMLSchema-instance with 5 occurrences

Original Pull Request: #259
2019-03-21 08:00:05 +01:00
Spring Operator 75b039a477 DATAES-549 - URL Cleanup.
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* http://maven.apache.org/xsd/maven-4.0.0.xsd with 1 occurrences migrated to:
  https://maven.apache.org/xsd/maven-4.0.0.xsd ([https](https://maven.apache.org/xsd/maven-4.0.0.xsd) result 200).
* http://www.springframework.org/schema/beans/spring-beans-3.1.xsd with 19 occurrences migrated to:
  https://www.springframework.org/schema/beans/spring-beans-3.1.xsd ([https](https://www.springframework.org/schema/beans/spring-beans-3.1.xsd) result 200).
* http://www.springframework.org/schema/beans/spring-beans.xsd with 3 occurrences migrated to:
  https://www.springframework.org/schema/beans/spring-beans.xsd ([https](https://www.springframework.org/schema/beans/spring-beans.xsd) result 200).
* http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd with 20 occurrences migrated to:
  https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd ([https](https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd) result 200).
* http://java.sun.com/xml/ns/javaee/beans_1_0.xsd with 1 occurrences migrated to:
  https://java.sun.com/xml/ns/javaee/beans_1_0.xsd ([https](https://java.sun.com/xml/ns/javaee/beans_1_0.xsd) result 302).

# Ignored
These URLs were intentionally ignored.

* http://java.sun.com/xml/ns/javaee with 2 occurrences
* http://maven.apache.org/POM/4.0.0 with 2 occurrences
* http://www.springframework.org/schema/beans with 44 occurrences
* http://www.springframework.org/schema/data/elasticsearch with 40 occurrences
* http://www.w3.org/2001/XMLSchema-instance with 24 occurrences

Original Pull Request: #258
2019-03-20 12:38:23 +01:00
Spring Operator 3b834b6c60 DATAES-549 - URL Cleanup.
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed Success
These URLs were fixed successfully.

* http://www.apache.org/licenses/LICENSE-2.0 migrated to:
  https://www.apache.org/licenses/LICENSE-2.0 ([https](https://www.apache.org/licenses/LICENSE-2.0) result 200).
* http://www.pivotal.io migrated to:
  https://www.pivotal.io ([https](https://www.pivotal.io) result 301).

# Ignored
These URLs were intentionally ignored.

* http://maven.apache.org/POM/4.0.0
* http://maven.apache.org/xsd/maven-4.0.0.xsd
* http://www.w3.org/2001/XMLSchema-instance

Original pull request: #244
2019-03-19 13:04:32 -05:00
Oliver Drotbohm 13d369de20 DATAES-545 - Move off deprecations in Spring Data Commons.
Related ticket: DATACMNS-1496.
2019-03-13 19:20:40 +01:00
Petr Kukrál 11ba4c55ad DATAES-535 - Add mapping annotation @DynamicTemplates.
Original pull request: #238
2019-03-13 14:16:12 +01:00
xhaggi efb5d9e021 DATAES-536 - Polishing
* remove unused field xContentBuilder
* add missing generics
* add XContentType to rawValue
* organize imports
2019-03-13 13:46:13 +01:00
Robert Gründler 092690d1b9 DATAES-536 - Add support for context suggester
Original pull request: #241
2019-03-13 10:27:11 +01:00
Christoph Strobl 16f2139742 DATAES-517 - After release cleanups. 2019-03-07 10:07:30 +01:00
Christoph Strobl 437f2e9cde DATAES-517 - Prepare next development iteration. 2019-03-07 10:07:29 +01:00
436 changed files with 13961 additions and 9489 deletions
-110
View File
@@ -1,110 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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
http://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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL =
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: : " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
+43
View File
@@ -0,0 +1,43 @@
= Continuous Integration
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=Moore%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F3.1.x&subject=Lovelace%20(3.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F2.1.x&subject=Ingalls%20(2.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
== Running CI tasks locally
Since this pipeline is purely Docker-based, it's easy to:
* Debug what went wrong on your local machine.
* Test out a a tweak to your `test.sh` script before sending it out.
* Experiment against a new image before submitting your pull request.
All of these use cases are great reasons to essentially run what the CI server does on your local machine.
IMPORTANT: To do this you must have Docker installed on your machine.
1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-elasticsearch-github adoptopenjdk/openjdk8:latest /bin/bash`
+
This will launch the Docker image and mount your source code at `spring-data-elasticsearch-github`.
+
2. `cd spring-data-elasticsearch-github`
+
Next, run your tests from inside the container:
+
3. `./mvnw clean dependency:list test -Dsort` (or whatever profile you need to test out)
Since the container is binding to your source, you can make edits from your IDE and continue to run build jobs.
If you need to package things up, do this:
1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-elasticsearch-github adoptopenjdk/openjdk8:latest /bin/bash`
+
This will launch the Docker image and mount your source code at `spring-data-elasticsearch-github`.
+
2. `cd spring-data-elasticsearch-github`
+
Next, try to package everything up from inside the container:
+
3. `./mvnw -Pci,snapshot -Dmaven.test.skip=true clean package`
NOTE: Docker containers can eat up disk space fast! From time to time, run `docker system prune` to clean out old images.
+1 -1
View File
@@ -24,4 +24,4 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor
All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
This Code of Conduct is adapted from the http://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at http://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/].
This Code of Conduct is adapted from the https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/].
Vendored
+114
View File
@@ -0,0 +1,114 @@
pipeline {
agent none
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/2.2.x", threshold: hudson.model.Result.SUCCESS)
}
options {
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '14'))
}
stages {
stage("Test") {
when {
anyOf {
branch '3.2.x'
not { triggeredBy 'UpstreamCause' }
}
}
parallel {
stage("test: baseline") {
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('Release to artifactory') {
when {
anyOf {
branch '3.2.x'
not { triggeredBy 'UpstreamCause' }
}
}
agent {
docker {
image 'adoptopenjdk/openjdk8:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
}
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
sh 'rm -rf ?'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.staging-repository=libs-snapshot-local " +
"-Dartifactory.build-name=spring-data-elasticsearch " +
"-Dartifactory.build-number=${BUILD_NUMBER} " +
'-Dmaven.test.skip=true clean deploy -U -B'
}
}
stage('Publish documentation') {
when {
branch '3.2.x'
}
agent {
docker {
image 'adoptopenjdk/openjdk8:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
}
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.distribution-repository=temp-private-local " +
'-Dmaven.test.skip=true clean deploy -U -B'
}
}
}
post {
changed {
script {
slackSend(
color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
channel: '#spring-data-dev',
message: "${currentBuild.fullDisplayName} - `${currentBuild.currentResult}`\n${env.BUILD_URL}")
emailext(
subject: "[${currentBuild.fullDisplayName}] ${currentBuild.currentResult}",
mimeType: 'text/html',
recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']],
body: "<a href=\"${env.BUILD_URL}\">${currentBuild.fullDisplayName} is reported as ${currentBuild.currentResult}</a>")
}
}
}
}
+212
View File
@@ -0,0 +1,212 @@
image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] image:https://spring.io/badges/spring-data-elasticsearch/snapshot.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start]
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The Spring Data Elasticsearch project provides integration with the https://www.elastic.co/[Elasticsearch] search engine. Key functional areas of Spring Data Elasticsearch are a POJO centric model for interacting with a Elasticsearch Documents and easily writing a Repository style data access layer.
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.
* 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.
* CDI support for repositories
== Code of Conduct
This project is governed by the link:CODE_OF_CONDUCT.adoc[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
== Getting Started
Here is a quick teaser of an application using Spring Data Repositories in Java:
[source,java]
----
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByLastname(String lastname);
List<Person> findByFirstnameLike(String firstname);
}
@Service
public class MyService {
private final PersonRepository repository;
public MyService(PersonRepository repository) {
this.repository = repository;
}
public void doWork() {
repository.deleteAll();
Person person = new Person();
person.setFirstname("Oliver");
person.setLastname("Gierke");
repository.save(person);
List<Person> lastNameResults = repository.findByLastname("Gierke");
List<Person> firstNameResults = repository.findByFirstnameLike("Oli*");
}
}
----
Using Node Client
[source,xml]
----
<?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">
<elasticsearch:node-client id="client" local="true"/>
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
----
Using Transport Client
[source,xml]
----
<?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">
<elasticsearch:repositories base-package="com.xyz.acme"/>
<elasticsearch:transport-client id="client" cluster-nodes="ip:9300,ip:9300" cluster-name="elasticsearch" />
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
----
=== Maven configuration
Add the Maven dependency:
[source,xml]
----
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${version}.RELEASE</version>
</dependency>
----
// NOTE: since Github does not support include directives, the content of
// the src/main/asciidoc/reference/preface.adoc file is duplicated here
// Always change both files!
**Compatibility Matrix**
The compatibility between Spring Data Elasticsearch, Elasticsearch client drivers and Spring Boot versions can be found in the https://docs.spring.io/spring-data/elasticsearch/docs/3.2.0.RC3/reference/html/#preface.versions[reference documentation].
To use the Release candidate versions of the upcoming major version, use our Maven milestone repository and declare the appropriate dependency version:
[source,xml]
----
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${version}.RCx</version> <!-- x being 1, 2, ... -->
</dependency>
<repository>
<id>spring-libs-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare the appropriate dependency version:
[source,xml]
----
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${version}.BUILD-SNAPSHOT</version>
</dependency>
<repository>
<id>spring-libs-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
----
== Getting Help
Having trouble with Spring Data? Wed love to help!
* Check the
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/[reference documentation], and https://docs.spring.io/spring-data/elasticsearch/docs/current/api/[Javadocs].
* Learn the Spring basics Spring Data builds on Spring Framework, check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
If you are just starting out with Spring, try one of the https://spring.io/guides[guides].
* If you are upgrading, check out the https://docs.spring.io/spring-data/elasticsearch/docs/current/changelog.txt[changelog] for "`new and noteworthy`" features.
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data[`spring-data-elasticsearch`].
You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter].
* Report bugs with Spring Data for Elasticsearch at https://jira.spring.io/browse/DATAES[jira.spring.io/browse/DATAES].
== Reporting Issues
Spring Data uses JIRA as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below:
* Before you log a bug, please search the
https://jira.spring.io/browse/DATAES[issue tracker] to see if someone has already reported the problem.
* If the issue doesnt already exist, https://jira.spring.io/browse/DATAES[create a new issue].
* Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using and JVM version.
* If you need to paste code, or include a stack trace use JIRA `{code}…{code}` escapes before and after your text.
* If possible try to create a test-case or project that replicates the issue. Attach a link to your code or a compressed file containing your code.
== Building from Source
You dont need to build from source to use Spring Data (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper].
You also need JDK 1.8.
[source,bash]
----
$ ./mvnw clean install
----
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._
=== Building reference documentation
Building the documentation builds also the project without running tests.
[source,bash]
----
$ ./mvnw clean install -Pdistribute
----
The generated documentation is available from `target/site/reference/html/index.html`.
== Examples
For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/master/elasticsearch/example[spring-data-examples] project.
== License
Spring Data for Elasticsearch Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
-324
View File
@@ -1,324 +0,0 @@
Spring Data Elasticsearch
=========================
Spring Data implementation for ElasticSearch
Spring Data makes it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services as well as provide improved support for relational database technologies.
The Spring Data Elasticsearch project provides integration with the [elasticsearch](http://www.elasticsearch.org/) search engine.
Guide
------------
* [Reference Documentation](http://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/)
* [API Documentation](http://docs.spring.io/spring-data/elasticsearch/docs/current/api/)
* [Spring Data Project](http://projects.spring.io/spring-data)
* [Sample Test Application](https://github.com/BioMedCentralLtd/spring-data-elasticsearch-sample-application)
* [Issues](https://jira.springsource.org/browse/DATAES)
* [Spring Data Elasticsearch Google Group](https://groups.google.com/d/forum/spring-data-elasticsearch-devs)
* [Questions](http://stackoverflow.com/questions/tagged/spring-data-elasticsearch)
Quick Start
-----------
Wiki page for [Getting Started](https://github.com/spring-projects/spring-data-elasticsearch/wiki/How-to-start-with-spring-data-elasticsearch)
### Maven configuration
Add the Maven dependency:
```xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>x.y.z.RELEASE</version>
</dependency>
```
If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare
the appropriate dependency version.
```xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>x.y.z.BUILD-SNAPSHOT</version>
</dependency>
<repository>
<id>spring-libs-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>http://repo.spring.io/libs-snapshot</url>
</repository>
```
| spring data elasticsearch | elasticsearch |
|:-------------------------:|:-------------:|
| 3.2.x | 6.5.0 |
| 3.1.x | 6.2.2 |
| 3.0.x | 5.5.0 |
| 2.1.x | 2.4.0 |
| 2.0.x | 2.2.0 |
| 1.3.x | 1.5.2 |
### ElasticsearchRepository
A default implementation of ElasticsearchRepository, aligning to the generic Repository Interfaces, is provided. Spring can do the Repository implementation for you depending on method names in the interface definition.
The ElasticsearchCrudRepository extends PagingAndSortingRepository
```java
public interface ElasticsearchCrudRepository<T, ID extends Serializable> extends ElasticsearchRepository<T, ID>, PagingAndSortingRepository<T, ID> {
}
```
Extending ElasticsearchRepository for custom methods
```java
public interface BookRepository extends Repository<Book, String> {
List<Book> findByNameAndPrice(String name, Integer price);
List<Book> findByNameOrPrice(String name, Integer price);
Page<Book> findByName(String name,Pageable page);
Page<Book> findByNameNot(String name,Pageable page);
Page<Book> findByPriceBetween(int price,Pageable page);
Page<Book> findByNameLike(String name,Pageable page);
@Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")
Page<Book> findByMessage(String message, Pageable pageable);
}
```
Indexing a single document with Repository
```java
@Autowired
private SampleElasticsearchRepository repository;
String documentId = "123456";
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setId(documentId);
sampleEntity.setMessage("some message");
repository.save(sampleEntity);
```
Indexing multiple Document(bulk index) using Repository
```java
@Autowired
private SampleElasticsearchRepository repository;
String documentId = "123456";
SampleEntity sampleEntity1 = new SampleEntity();
sampleEntity1.setId(documentId);
sampleEntity1.setMessage("some message");
String documentId2 = "123457"
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setId(documentId2);
sampleEntity2.setMessage("test message");
List<SampleEntity> sampleEntities = Arrays.asList(sampleEntity1, sampleEntity2);
//bulk index
repository.save(sampleEntities);
```
### ElasticsearchTemplate
ElasticsearchTemplate is the central support class for elasticsearch operations.
Indexing a single document using Elasticsearch Template
```java
String documentId = "123456";
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setId(documentId);
sampleEntity.setMessage("some message");
IndexQuery indexQuery = new IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity).build();
elasticsearchTemplate.index(indexQuery);
```
Indexing multiple Document(bulk index) using Elasticsearch Template
```java
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
List<IndexQuery> indexQueries = new ArrayList<IndexQuery>();
//first document
String documentId = "123456";
SampleEntity sampleEntity1 = new SampleEntity();
sampleEntity1.setId(documentId);
sampleEntity1.setMessage("some message");
IndexQuery indexQuery1 = new IndexQueryBuilder().withId(sampleEntity1.getId()).withObject(sampleEntity1).build();
indexQueries.add(indexQuery1);
//second document
String documentId2 = "123457";
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setId(documentId2);
sampleEntity2.setMessage("some message");
IndexQuery indexQuery2 = new IndexQueryBuilder().withId(sampleEntity2.getId()).withObject(sampleEntity2).build()
indexQueries.add(indexQuery2);
//bulk index
elasticsearchTemplate.bulkIndex(indexQueries);
```
Searching entities using Elasticsearch Template
```java
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryString(documentId).field("id"))
.build();
Page<SampleEntity> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
```
### Reactive Elasticsearch
The `ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
It uses the request/response objects provided by the Elasticsearch core project.
```java
@Configuration
public class Config {
@Bean
ReactiveElasticsearchClient client() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291")
.build();
return ReactiveRestClients.create(clientConfiguration);
}
}
// ...
Mono<IndexResponse> response = client.index(request ->
request.index("spring-data")
.type("elasticsearch")
.id(randomID())
.source(singletonMap("feature", "reactive-client"))
.setRefreshPolicy(IMMEDIATE)
);
```
The reactive client response, especially for search operations, is bound to the `from` (offset) & `size` (limit) options of the request.
`ReactiveElasticsearchOperations` is the gateway to executing high level commands against an Elasticsearch cluster using the `ReactiveElasticsearchClient`.
The easiest way of setting up the `ReactiveElasticsearchTemplate` is via `AbstractReactiveElasticsearchConfiguration`.
```java
@Configuration
public class Config extends AbstractReactiveElasticsearchConfiguration {
@Bean
@Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
}
```
If needed the `ReactiveElasticsearchTemplate` can be configured with default `RefreshPolicy` and `IndicesOptions` that get applied to the related requests by overriding the defaults of `refreshPolicy()` and `indicesOptions()`.
```java
template.save(new Person("Bruce Banner", 42))
.doOnNext(System.out::println)
.flatMap(person -> template.findById(person.id, Person.class))
.doOnNext(System.out::println)
.flatMap(person -> template.delete(person))
.doOnNext(System.out::println)
.flatMap(id -> template.count(Person.class))
.doOnNext(System.out::println)
.subscribe();
```
The above outputs the following sequence on the console.
```bash
> Person(id=QjWCWWcBXiLAnp77ksfR, name=Bruce Banner, age=42)
> Person(id=QjWCWWcBXiLAnp77ksfR, name=Bruce Banner, age=42)
> QjWCWWcBXiLAnp77ksfR
> 0
```
### XML Namespace
You can set up repository scanning via xml configuration, which will happily create your repositories.
Using Node Client
```xml
<?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 http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<elasticsearch:node-client id="client" local="true"/>
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
```
Using Transport Client
```xml
<?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 http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<elasticsearch:repositories base-package="com.xyz.acme"/>
<elasticsearch:transport-client id="client" cluster-nodes="ip:9300,ip:9300" cluster-name="elasticsearch" />
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
```
## Help Pages
* [Geo distance and location search](https://github.com/spring-projects/spring-data-elasticsearch/wiki/Geo-indexing-and-request)
* [Custom object mapper](https://github.com/spring-projects/spring-data-elasticsearch/wiki/Custom-ObjectMapper)
## Contributing to Spring Data
Here are some ways for you to get involved in the community:
* Get involved with the Spring community on Stack OverFlow. Please help out on the [forum](https://stackoverflow.com/questions/tagged/spring-data-elasticsearch) by responding to questions and joining the debate.
* Create [JIRA](https://jira.spring.io/browse/DATAES/) tickets for bugs and new features and comment and vote on the ones that you are interested in.
* Github is for social coding: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing.
* Watch for upcoming articles on Spring by [subscribing](http://www.springsource.org/node/feed) to springframework.org
Before we accept a non-trivial patch or pull request we will need you to [sign the Contributor License Agreement](https://cla.pivotal.io/sign/spring). Signing the contributors agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. If you forget to do so, you'll be reminded when you submit a pull request. Active contributors might be asked to join the core team, and given the ability to merge pull requests.
Code formatting for [Eclipse and Intellij](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide)
[More information about contributing to Spring Data](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc)
+9
View File
@@ -0,0 +1,9 @@
# Security Policy
## Supported Versions
Please see the https://spring.io/projects/spring-data-elasticsearch[Spring Data Elasticsearch] project page for supported versions.
## Reporting a Vulnerability
Please don't raise security vulnerabilities here. Head over to https://pivotal.io/security to learn how to disclose them responsibly.
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env sh
set -euo pipefail
[[ -d $PWD/maven && ! -d $HOME/.m2 ]] && ln -s $PWD/maven $HOME/.m2
spring_data_elasticsearch_artifactory=$(pwd)/spring-data-elasticsearch-artifactory
rm -rf $HOME/.m2/repository/org/springframework/data 2> /dev/null || :
cd spring-data-elasticsearch-github
./mvnw deploy \
-Dmaven.test.skip=true \
-DaltDeploymentRepository=distribution::default::file://${spring_data_elasticsearch_artifactory}
-20
View File
@@ -1,20 +0,0 @@
---
platform: linux
image_resource:
type: docker-image
source:
repository: openjdk
tag: 8-jdk-alpine
inputs:
- name: spring-data-elasticsearch-github
outputs:
- name: spring-data-elasticsearch-artifactory
caches:
- path: maven
run:
path: spring-data-elasticsearch-github/ci/build.sh
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env sh
set -euo pipefail
[[ -d $PWD/maven && ! -d $HOME/.m2 ]] && ln -s $PWD/maven $HOME/.m2
rm -rf $HOME/.m2/repository/org/springframework/data 2> /dev/null || :
cd spring-data-elasticsearch-github
./mvnw clean dependency:list test -P${PROFILE} -Dsort
-20
View File
@@ -1,20 +0,0 @@
---
platform: linux
image_resource:
type: docker-image
source:
repository: openjdk
tag: 8-jdk-alpine
inputs:
- name: spring-data-elasticsearch-github
outputs:
- name: spring-data-elasticsearch-artifactory
caches:
- path: maven
run:
path: spring-data-elasticsearch-github/ci/test.sh
Vendored
+1 -1
View File
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# 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
Vendored
+1 -1
View File
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
+79 -27
View File
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>3.2.0.M2</version>
<version>3.2.3.RELEASE</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.2.0.M2</version>
<version>2.2.3.RELEASE</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -19,9 +19,10 @@
<properties>
<commonslang>2.6</commonslang>
<elasticsearch>6.6.1</elasticsearch>
<elasticsearch>6.8.4</elasticsearch>
<log4j>2.9.1</log4j>
<springdata.commons>2.2.0.M2</springdata.commons>
<springdata.commons>2.2.3.RELEASE</springdata.commons>
<netty>4.1.39.Final</netty>
<java-module-name>spring.data.elasticsearch</java-module-name>
</properties>
@@ -36,7 +37,7 @@
<name>Christoph Strobl</name>
<email>cstrobl at pivotal.io</email>
<organization>Pivotal</organization>
<organizationUrl>http://www.pivotal.io</organizationUrl>
<organizationUrl>https://www.pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
@@ -47,7 +48,7 @@
<name>Mark Paluch</name>
<email>mpaluch at pivotal.io</email>
<organization>Pivotal</organization>
<organizationUrl>http://www.pivotal.io</organizationUrl>
<organizationUrl>https://www.pivotal.io</organizationUrl>
<roles>
<role>Developer</role>
</roles>
@@ -72,6 +73,19 @@
<url>https://jira.spring.io/browse/DATAES</url>
</issueManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>${netty}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring -->
@@ -227,6 +241,31 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.25.1</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>
@@ -237,8 +276,8 @@
<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>
@@ -246,18 +285,6 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-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
-->
@@ -275,28 +302,53 @@
</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>
<profiles>
<profile>
<id>release</id>
<id>ci</id>
<build>
<plugins>
<plugin>
<groupId>org.jfrog.buildinfo</groupId>
<artifactId>artifactory-maven-plugin</artifactId>
<inherited>false</inherited>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<checkstyleRules>
<module name="Checker">
<!-- Configure checker to use UTF-8 encoding -->
<property name="charset" value="UTF-8"/>
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/>
</module>
</checkstyleRules>
<includes>**/*</includes>
<excludes>.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy</excludes>
<sourceDirectories>./</sourceDirectories>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>spring-libs-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
<id>spring-libs-release</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
+3 -12
View File
@@ -1,15 +1,7 @@
= Spring Data Elasticsearch - Reference Documentation
BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Strobl;
BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Strobl; Peter-Josef Meisch
:revnumber: {version}
:revdate: {localdate}
:toc:
:toc-placement!:
:linkcss:
:doctype: book
:docinfo: shared
:source-highlighter: prettify
:icons: font
:imagesdir: images
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
@@ -30,9 +22,8 @@ include::{spring-data-commons-docs}/repositories.adoc[]
:leveloffset: +1
include::reference/elasticsearch-clients.adoc[]
include::reference/elasticsearch-object-mapping.adoc[]
include::reference/data-elasticsearch.adoc[]
include::reference/reactive-elasticsearch-operations.adoc[]
include::reference/reactive-elasticsearch-repositories.adoc[]
include::reference/elasticsearch-operations.adoc[]
include::reference/elasticsearch-repositories.adoc[]
include::reference/elasticsearch-misc.adoc[]
:leveloffset: -1
+30 -6
View File
@@ -1,20 +1,44 @@
[[preface]]
= Preface
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. We have povided a "template" as a high-level abstraction for storing,querying,sorting and faceting documents. You will notice similarities to the Spring data solr and mongodb support in the Spring Framework.
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. It provides:
[[project]]
[preface]
* _Templates_ as a high-level abstraction for storing, querying, sorting and faceting documents.
* _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]
[[preface.metadata]]
== Project Metadata
* Version Control - https://github.com/spring-projects/spring-data-elasticsearch
* API Documentation - https://docs.spring.io/spring-data/elasticsearch/docs/current/api/
* Bugtracker - https://jira.spring.io/browse/DATAES
* Release repository - https://repo.spring.io/libs-release
* Milestone repository - https://repo.spring.io/libs-milestone
* Snapshot repository - https://repo.spring.io/libs-snapshot
[[requirements]]
[preface]
[[preface.requirements]]
== Requirements
Requires http://www.elasticsearch.org/download/[Elasticsearch] 0.20.2 and above or optional dependency or not even that if you are using Embedded Node Client
Requires an installation of https://www.elastic.co/products/elasticsearch[Elasticsearch].
[[preface.versions]]
=== Versions
// NOTE: since Github does not support include directives, the content of
// this file is duplicated in the toplevel README
// Always change both files!
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
|Moore |3.2.x |6.8.4 |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>>.
@@ -1,288 +0,0 @@
[[elasticsearch.repositories]]
= Elasticsearch Repositories
This chapter includes details of the Elasticsearch repository implementation.
[[elasticsearch.introduction]]
== Introduction
[[elasticsearch.namespace]]
=== Spring Namespace
The Spring Data Elasticsearch module contains a custom namespace allowing definition of repository beans as well as elements for instantiating a `ElasticsearchServer` .
Using the `repositories` element looks up Spring Data repositories as described in <<repositories.create-instances>> .
.Setting up Elasticsearch repositories using Namespace
====
[source,xml]
----
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/data/elasticsearch
http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
<elasticsearch:repositories base-package="com.acme.repositories" />
</beans>
----
====
Using the `Transport Client` or `Node Client` element registers an instance of `Elasticsearch Server` in the context.
.Transport Client using Namespace
====
[source,xml]
----
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/data/elasticsearch
http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
<elasticsearch:transport-client id="client" cluster-nodes="localhost:9300,someip:9300" />
</beans>
----
====
.Node Client using Namespace
====
[source,xml]
----
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/data/elasticsearch
http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
<elasticsearch:node-client id="client" local="true"" />
</beans>
----
====
[[elasticsearch.annotation]]
=== Annotation based configuration
The Spring Data Elasticsearch repositories support cannot only be activated through an XML namespace but also using an annotation through JavaConfig.
.Spring Data Elasticsearch repositories using JavaConfig
====
[source,java]
----
@Configuration
@EnableElasticsearchRepositories(basePackages = "org/springframework/data/elasticsearch/repositories")
static class Config {
@Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchTemplate(nodeBuilder().local(true).node().client());
}
}
----
====
The configuration above sets up an `Embedded Elasticsearch Server` which is used by the `ElasticsearchTemplate` . Spring Data Elasticsearch Repositories are activated using the `@EnableElasticsearchRepositories` annotation, which essentially carries the same attributes as the XML namespace does. If no base package is configured, it will use the one the configuration class resides in.
[[elasticsearch.cdi]]
=== Elasticsearch Repositores using CDI
The Spring Data Elasticsearch repositories can also be set up using CDI functionality.
.Spring Data Elasticsearch repositories using JavaConfig
====
[source,java]
----
class ElasticsearchTemplateProducer {
@Produces
@ApplicationScoped
public ElasticsearchOperations createElasticsearchTemplate() {
return new ElasticsearchTemplate(nodeBuilder().local(true).node().client());
}
}
class ProductService {
private ProductRepository repository;
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
}
@Inject
public void setRepository(ProductRepository repository) {
this.repository = repository;
}
}
----
====
[[elasticsearch.query-methods]]
== Query methods
[[elasticsearch.query-methods.finders]]
=== Query lookup strategies
The Elasticsearch module supports all basic query building feature as String,Abstract,Criteria or have it being derived from the method name.
==== 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>> ).
[[elasticsearch.query-methods.criterions]]
=== Query creation
Generally the query creation mechanism for Elasticsearch works as described in <<repositories.query-methods>> . Here's a short example of what a Elasticsearch query method translates into:
.Query creation from method names
====
[source,java]
----
public interface BookRepository extends Repository<Book, String>
{
List<Book> findByNameAndPrice(String name, Integer price);
}
----
====
The method name above will be translated into the following Elasticsearch json query
[source]
----
{ "bool" :
{ "must" :
[
{ "field" : {"name" : "?"} },
{ "field" : {"price" : "?"} }
]
}
}
----
A list of supported keywords for Elasticsearch is shown below.
[cols="1,2,3", options="header"]
.Supported keywords inside method names
|===
| Keyword
| Sample
| Elasticsearch Query String| `And`
| `findByNameAndPrice`
| `{"bool" : {"must" : [ {"field" : {"name" : "?"}},
{"field" : {"price" : "?"}} ]}}`
| `Or`
| `findByNameOrPrice`
| `{"bool" : {"should" : [ {"field" : {"name" : "?"}},
{"field" : {"price" : "?"}} ]}}`
| `Is`
| `findByName`
| `{"bool" : {"must" : {"field" : {"name" : "?"}}}}`
| `Not`
| `findByNameNot`
| `{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}`
| `Between`
| `findByPriceBetween`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}`
| `LessThanEqual`
| `findByPriceLessThan`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
null,"to" : ?,"include_lower" : true,"include_upper" :
true}}}}}`
| `GreaterThanEqual`
| `findByPriceGreaterThan`
| `{"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}}}}}`
| `After`
| `findByPriceAfter`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
?,"to" : null,"include_lower" : true,"include_upper" :
true}}}}}`
| `Like`
| `findByNameLike`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"?*","analyze_wildcard" : true}}}}}`
| `StartingWith`
| `findByNameStartingWith`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"?*","analyze_wildcard" : true}}}}}`
| `EndingWith`
| `findByNameEndingWith`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"*?","analyze_wildcard" : true}}}}}`
| `Contains/Containing`
| `findByNameContaining`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"*?*","analyze_wildcard" : true}}}}}`
| `In`
| `findByNameIn(Collection<String>names)`
| `{"bool" : {"must" : {"bool" : {"should" : [ {"field" :
{"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}`
| `NotIn`
| `findByNameNotIn(Collection<String>names)`
| `{"bool" : {"must_not" : {"bool" : {"should" : {"field" :
{"name" : "?"}}}}}}`
| `Near`
| `findByStoreNear`
| `Not Supported Yet !`
| `True`
| `findByAvailableTrue`
| `{"bool" : {"must" : {"field" : {"available" : true}}}}`
| `False`
| `findByAvailableFalse`
| `{"bool" : {"must" : {"field" : {"available" : false}}}}`
| `OrderBy`
| `findByAvailableTrueOrderByNameDesc`
| `{"sort" : [{ "name" : {"order" : "desc"} }],"bool" :
{"must" : {"field" : {"available" : true}}}}`
|===
[[elasticsearch.query-methods.at-query]]
=== Using @Query Annotation
.Declare query at the method using the `@Query` annotation.
====
[source,java]
----
public interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
Page<Book> findByName(String name,Pageable pageable);
}
----
====
@@ -3,14 +3,50 @@
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.
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>>.
WARNING: The well known `TransportClient` is deprecated as of Elasticsearch 7.0.0 and is expected to be removed in Elasticsearch 8.0.
[[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>>.
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
.Transport Client
====
[source,java]
----
static class Config {
@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;
}
}
// ...
IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
.source(someObject)
.setRefreshPolicy(IMMEDIATE);
IndexResponse response = client.index(request);
----
<1> The `TransportClient` must be configured with the cluster name.
<2> The host and port to connect the client to.
====
[[elasticsearch.clients.rest]]
== High Level REST Client
The Java High Level REST Client provides a straight forward replacement for the `TransportClient` as it accepts and returns
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 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.
@@ -18,6 +54,7 @@ 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 {
@Bean
@@ -27,20 +64,28 @@ static class Config {
.connectedTo("localhost:9200", "localhost:9201")
.build();
return RestClients.create(clientConfiguration).rest(); <2>
return RestClients.create(clientConfiguration).rest(); <2>
}
}
// ...
@Autowired
RestHighLevelClient highLevelClient;
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
// ...
IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
.source(singletonMap("feature", "high-level-rest-client"))
.setRefreshPolicy(IMMEDIATE);
IndexResponse response = client.index(request);
IndexResponse response = highLevelClient.index(request);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enbale SSL.
<2> Next to the `rest()` client it is also possible to obtain the `lowLevelRest()` client.
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> Create the RestHighLevelClient.
<3> It is also possible to obtain the `lowLevelRest()` client.
====
[[elasticsearch.clients.reactive]]
@@ -78,7 +123,7 @@ Mono<IndexResponse> response = client.index(request ->
.setRefreshPolicy(IMMEDIATE);
);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enbale SSL.
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
====
NOTE: The ReactiveClient response, especially for search operations, is bound to the `from` (offset) & `size` (limit) options of the request.
@@ -92,19 +137,28 @@ Client behaviour can be changed via the `ClientConfiguration` that allows to set
====
[source,java]
----
// optional if Basic Auhtentication is needed
HttpHeaders defaultHeaders = new HttpHeaders();
defaultHeaders.setBasicAuth(USER_NAME, USER_PASS); <1>
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291") <1>
.withConnectTimeout(Duration.ofSeconds(5)) <2>
.withSocketTimeout(Duration.ofSeconds(3)) <3>
.useSsl()
.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>
. // ... other options
.build();
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enbale SSL.
<2> Set the connection timeout. Default is 10 sec.
<3> Set the socket timeout. Default is 5 sec.
<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.
====
[[elasticsearch.clients.logging]]
@@ -119,5 +173,4 @@ to be turned on as outlined in the snippet below.
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/>
----
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients`
respectively `ReactiveRestClients`.
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients` respectively `ReactiveRestClients`, is not available for the `TransportClient`.
@@ -14,12 +14,12 @@ Filter Builder improves query speed.
private ElasticsearchTemplate elasticsearchTemplate;
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFilter(boolFilter().must(termFilter("id", documentId)))
.build();
.withQuery(matchAllQuery())
.withFilter(boolFilter().must(termFilter("id", documentId)))
.build();
Page<SampleEntity> sampleEntities =
elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
----
====
@@ -33,21 +33,21 @@ Elasticsearch has a scroll API for getting big result set in chunks. `Elasticsea
[source,java]
----
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
Page<SampleEntity> scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class);
ScrolledPage<SampleEntity> scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class);
String scrollId = ((ScrolledPage) scroll).getScrollId();
String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
while (scroll.hasContent()) {
sampleEntities.addAll(scroll.getContent());
scrollId = ((ScrolledPage) scroll).getScrollId();
scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class);
sampleEntities.addAll(scroll.getContent());
scrollId = scroll.getScrollId();
scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class);
}
elasticsearchTemplate.clearScroll(scrollId);
----
@@ -60,18 +60,18 @@ elasticsearchTemplate.clearScroll(scrollId);
[source,java]
----
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
CloseableIterator<SampleEntity> stream = elasticsearchTemplate.stream(searchQuery, SampleEntity.class);
List<SampleEntity> sampleEntities = new ArrayList<>();
while (stream.hasNext()) {
sampleEntities.add(stream.next());
sampleEntities.add(stream.next());
}
----
====
====
@@ -0,0 +1,12 @@
[[new-features]]
= What's new
[[new-features.3-2-0]]
== New in Spring Data Elasticsearch 3.2
* Secured Elasticsearch cluster support with Basic Authentication and SSL transport.
* Upgrade to Elasticsearch 6.8.1.
* Reactive programming support with <<elasticsearch.reactive.operations>> and <<elasticsearch.reactive.repositories>>.
* Introduction of the <<elasticsearch.mapping.meta-model,ElasticsearchEntityMapper>> as an alternative to the Jackson `ObjectMapper`.
* Field name customization in `@Field`.
* Support for Delete by Query.
@@ -21,14 +21,16 @@ public class Config extends AbstractElasticsearchConfiguration { <1>
@Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest()
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`.
[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
@@ -50,11 +52,12 @@ public class Config extends AbstractElasticsearchConfiguration {
@Bean
@Override
public EntityMapper entityMapper() { <1>
public EntityMapper entityMapper() { <1>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService()); <2>
entityMapper.setConversions(elasticsearchCustomConversions()); <3>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService() <2>
);
entityMapper.setConversions(elasticsearchCustomConversions()); <3>
return entityMapper;
}
@@ -72,10 +75,26 @@ for `Converter` registration.
The `ElasticsearchEntityMapper` can use metadata to drive the mapping of objects to documents. 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. You can specify the index name and index type where the document will be stored.
* `@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.
** `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
* `@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 described the name of the field as it will be represented in the Elasticsearch document thus allowing the name to be different than the fieldname of the class.
* `@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:
** `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_.
** `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.
@@ -91,7 +110,7 @@ Those type hints are represented as `_class` attributes within the document and
====
[source,java]
----
public class Person { <1>
public class Person { <1>
@Id String id;
String firstname;
@@ -119,7 +138,7 @@ to already have entity information available when first reading data from the st
====
[source,java]
----
@TypeAlias("human") <1>
@TypeAlias("human") <1>
public class Person {
@Id String id;
@@ -129,7 +148,7 @@ public class Person {
[source,json]
----
{
"_class" : "human", <1>
"_class" : "human", <1>
"id" : ...
}
----
@@ -235,16 +254,16 @@ public class Config extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest()
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
}
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions()); <1>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions()); <1>
return entityMapper;
}
@@ -252,10 +271,11 @@ public class Config extends AbstractElasticsearchConfiguration {
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
return new ElasticsearchCustomConversions(Arrays.asList(new AddressToMap(), new MapToAddress())); <2>
return new ElasticsearchCustomConversions(
Arrays.asList(new AddressToMap(), new MapToAddress())); <2>
}
@WritingConverter <3>
@WritingConverter <3>
static class AddressToMap implements Converter<Address, Map<String, Object>> {
@Override
@@ -269,7 +289,7 @@ public class Config extends AbstractElasticsearchConfiguration {
}
}
@ReadingConverter <4>
@ReadingConverter <4>
static class MapToAddress implements Converter<Map<String, Object>, Address> {
@Override
@@ -0,0 +1,135 @@
[[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.
The default implementations of the interfaces offer:
* Read/Write mapping support for domain types.
* A rich query and criteria api.
* Resource management and Exception translation.
[[elasticsearch.operations.template]]
== ElasticsearchTemplate
The `ElasticsearchTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.transport>>.
.ElasticsearchTemplate configuration
====
[source,java]
----
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean
public Client elasticsearchClient() throws UnknownHostException { <1>
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 = {"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;
}
}
----
<1> Setting up the <<elasticsearch.clients.transport>>.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
<3> Using the <<elasticsearch.mapping.meta-model>> ElasticsearchMapper.
====
[[elasticsearch.operations.resttemplate]]
== ElasticsearchRestTemplate
The `ElasticsearchRestTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.rest>>.
.ElasticsearchRestTemplate configuration
====
[source,java]
----
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() { <1>
return RestClients.create(ClientConfiguration.localhost()).rest();
}
// 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.
.ElasticsearchOperations usage
====
[source,java]
----
@RestController
@RequestMapping("/")
public class TestController {
private ElasticsearchOperations elasticsearchOperations;
public TestController(ElasticsearchOperations elasticsearchOperations) { <1>
this.elasticsearchOperations = elasticsearchOperations;
}
@PostMapping("/person")
public String save(@RequestBody Person person) { <2>
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(person.getId().toString())
.withObject(person)
.build();
String documentId = elasticsearchOperations.index(indexQuery);
return documentId;
}
@GetMapping("/person/{id}")
public Person findById(@PathVariable("id") Long id) { <3>
Person person = elasticsearchOperations
.queryForObject(GetQuery.getById(id.toString()), Person.class);
return person;
}
}
----
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<2> Store some entity in the Elasticsearch cluster.
<3> Retrieve the entity with a query by id.
====
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.
include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
@@ -0,0 +1,149 @@
[[elasticsearch.repositories]]
= Elasticsearch Repositories
This chapter includes details of the Elasticsearch repository implementation.
include::elasticsearch-repository-queries.adoc[leveloffset=+1]
[[elasticsearch.annotation]]
== Annotation based configuration
The Spring Data Elasticsearch repositories support can be activated using an annotation through JavaConfig.
.Spring Data Elasticsearch repositories using JavaConfig
====
[source,java]
----
@Configuration
@EnableElasticsearchRepositories( <1>
basePackages = "org.springframework.data.elasticsearch.repositories"
)
static class Config {
@Bean
public ElasticsearchOperations elasticsearchTemplate() { <2>
// ...
}
}
class ProductService {
private ProductRepository repository; <3>
public ProductService(ProductRepository repository) {
this.repository = repository;
}
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
}
}
----
<1> The `EnableElasticsearchRepositories` annotation activates the Repository support. If no base package is configured, it will use the one of the configuration class it is put on.
<2> Provide a Bean named `elasticsearchTemplate` of type `ElasticsearchOperations` by using one of the configurations shown in the <<elasticsearch.operations>> chapter.
<3> Let Spring inject the Repository bean into your class.
====
[[elasticsearch.cdi]]
== Elasticsearch Repositories using CDI
The Spring Data Elasticsearch repositories can also be set up using CDI functionality.
.Spring Data Elasticsearch repositories using CDI
====
[source,java]
----
class ElasticsearchTemplateProducer {
@Produces
@ApplicationScoped
public ElasticsearchOperations createElasticsearchTemplate() {
// ... <1>
}
}
class ProductService {
private ProductRepository repository; <2>
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
}
@Inject
public void setRepository(ProductRepository repository) {
this.repository = repository;
}
}
----
<1> Create a component by using the same calls as are used in the <<elasticsearch.operations>> chapter.
<2> Let the CDI framework inject the Repository into your class.
====
[[elasticsearch.namespace]]
== Spring Namespace
The Spring Data Elasticsearch module contains a custom namespace allowing definition of repository beans as well as elements for instantiating a `ElasticsearchServer` .
Using the `repositories` element looks up Spring Data repositories as described in <<repositories.create-instances>> .
.Setting up Elasticsearch repositories using Namespace
====
[source,xml]
----
<?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/beans
https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/data/elasticsearch
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
<elasticsearch:repositories base-package="com.acme.repositories" />
</beans>
----
====
Using the `Transport Client` or `Rest Client` element registers an instance of `Elasticsearch Server` in the context.
.Transport Client using Namespace
====
[source,xml]
----
<?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/beans
https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/data/elasticsearch
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
<elasticsearch:transport-client id="client" cluster-nodes="localhost:9300,someip:9300" />
</beans>
----
====
.Rest Client using Namespace
====
[source,xml]
----
<?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">
<elasticsearch:rest-client id="restClient" hosts="http://localhost:9200">
</beans>
----
====
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
@@ -0,0 +1,156 @@
[[elasticsearch.query-methods]]
= Query methods
[[elasticsearch.query-methods.finders]]
== Query lookup strategies
The Elasticsearch module supports all basic query building feature as string queries, native search queries, criteria based queries or have it being derived from the method name.
=== 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>> ).
[[elasticsearch.query-methods.criterions]]
== Query creation
Generally the query creation mechanism for Elasticsearch works as described in <<repositories.query-methods>>. Here's a short example of what a Elasticsearch query method translates into:
.Query creation from method names
====
[source,java]
----
interface BookRepository extends Repository<Book, String> {
List<Book> findByNameAndPrice(String name, Integer price);
}
----
====
The method name above will be translated into the following Elasticsearch json query
[source]
----
{ "bool" :
{ "must" :
[
{ "field" : {"name" : "?"} },
{ "field" : {"price" : "?"} }
]
}
}
----
A list of supported keywords for Elasticsearch is shown below.
[cols="1,2,3", options="header"]
.Supported keywords inside method names
|===
| Keyword
| Sample
| Elasticsearch Query String| `And`
| `findByNameAndPrice`
| `{"bool" : {"must" : [ {"field" : {"name" : "?"}},
{"field" : {"price" : "?"}} ]}}`
| `Or`
| `findByNameOrPrice`
| `{"bool" : {"should" : [ {"field" : {"name" : "?"}},
{"field" : {"price" : "?"}} ]}}`
| `Is`
| `findByName`
| `{"bool" : {"must" : {"field" : {"name" : "?"}}}}`
| `Not`
| `findByNameNot`
| `{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}`
| `Between`
| `findByPriceBetween`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}`
| `LessThanEqual`
| `findByPriceLessThan`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
null,"to" : ?,"include_lower" : true,"include_upper" :
true}}}}}`
| `GreaterThanEqual`
| `findByPriceGreaterThan`
| `{"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}}}}}`
| `After`
| `findByPriceAfter`
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
?,"to" : null,"include_lower" : true,"include_upper" :
true}}}}}`
| `Like`
| `findByNameLike`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"?*","analyze_wildcard" : true}}}}}`
| `StartingWith`
| `findByNameStartingWith`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"?*","analyze_wildcard" : true}}}}}`
| `EndingWith`
| `findByNameEndingWith`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"*?","analyze_wildcard" : true}}}}}`
| `Contains/Containing`
| `findByNameContaining`
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
"*?*","analyze_wildcard" : true}}}}}`
| `In`
| `findByNameIn(Collection<String>names)`
| `{"bool" : {"must" : {"bool" : {"should" : [ {"field" :
{"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}`
| `NotIn`
| `findByNameNotIn(Collection<String>names)`
| `{"bool" : {"must_not" : {"bool" : {"should" : {"field" :
{"name" : "?"}}}}}}`
| `Near`
| `findByStoreNear`
| `Not Supported Yet !`
| `True`
| `findByAvailableTrue`
| `{"bool" : {"must" : {"field" : {"available" : true}}}}`
| `False`
| `findByAvailableFalse`
| `{"bool" : {"must" : {"field" : {"available" : false}}}}`
| `OrderBy`
| `findByAvailableTrueOrderByNameDesc`
| `{"sort" : [{ "name" : {"order" : "desc"} }],"bool" :
{"must" : {"field" : {"available" : true}}}}`
|===
[[elasticsearch.query-methods.at-query]]
== Using @Query Annotation
.Declare query at the method using the `@Query` annotation.
====
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}")
Page<Book> findByName(String name,Pageable pageable);
}
----
====
@@ -3,11 +3,7 @@
`ReactiveElasticsearchOperations` is the gateway to executing high level commands against an Elasticsearch cluster using the `ReactiveElasticsearchClient`.
The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations` and offers the following set of features.
* Read/Write mapping support for domain types.
* A rich query and criteria api.
* Resource management and Exception translation.
The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations`.
[[elasticsearch.reactive.template]]
== Reactive Elasticsearch Template
@@ -28,11 +24,11 @@ dedicated configuration method hooks for `base package`, the `initial entity set
@Configuration
public class Config extends AbstractReactiveElasticsearchConfiguration {
@Bean <1>
@Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
@Bean <1>
@Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
}
----
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
@@ -51,25 +47,22 @@ However one might want to be more in control over the actual components and use
@Configuration
public class Config {
@Bean <1>
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
@Bean <2>
public ElasticsearchConverter elasticsearchConverter() {
return new MappingElasticsearchConverter(elasticsearchMappingContext());
}
@Bean <3>
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
return new SimpleElasticsearchMappingContext();
}
@Bean <4>
public ReactiveElasticsearchOperations reactiveElasticsearchOperations() {
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(), elasticsearchConverter());
}
@Bean <1>
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
@Bean <2>
public ElasticsearchConverter elasticsearchConverter() {
return new MappingElasticsearchConverter(elasticsearchMappingContext());
}
@Bean <3>
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
return new SimpleElasticsearchMappingContext();
}
@Bean <4>
public ReactiveElasticsearchOperations reactiveElasticsearchOperations() {
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(), elasticsearchConverter());
}
}
----
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
@@ -92,25 +85,24 @@ Consider the following:
@Document(indexName = "marvel", type = "characters")
public class Person {
private @Id String id;
private String name;
private int age;
// Getter/Setter omitted...
private @Id String id;
private String name;
private int age;
// Getter/Setter omitted...
}
----
[source,java]
----
template.save(new Person("Bruce Banner", 42)) <1>
.doOnNext(System.out::println)
.flatMap(person -> template.findById(person.id, Person.class)) <2>
.doOnNext(System.out::println)
.flatMap(person -> template.delete(person)) <3>
.doOnNext(System.out::println)
.flatMap(id -> template.count(Person.class)) <4>
.doOnNext(System.out::println)
.subscribe(); <5>
template.save(new Person("Bruce Banner", 42)) <1>
.doOnNext(System.out::println)
.flatMap(person -> template.findById(person.id, Person.class)) <2>
.doOnNext(System.out::println)
.flatMap(person -> template.delete(person)) <3>
.doOnNext(System.out::println)
.flatMap(id -> template.count(Person.class)) <4>
.doOnNext(System.out::println)
.subscribe(); <5>
----
The above outputs the following sequence on the console.
@@ -4,7 +4,7 @@
Reactive Elasticsearch repository support builds on the core repository support explained in <<repositories>> utilizing
operations provided via <<elasticsearch.reactive.operations>> executed by a <<elasticsearch.clients.reactive>>.
Spring Data Elasticsearchs reactive repository support uses https://projectreactor.io/[Project Reactor] as its reactive
Spring Data Elasticsearch reactive repository support uses https://projectreactor.io/[Project Reactor] as its reactive
composition library of choice.
There are 3 main interfaces to be used:
@@ -42,7 +42,7 @@ NOTE: Please note that the `id` property needs to be of type `String`.
====
[source]
----
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
Flux<Person> findByFirstname(String firstname); <1>
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -0,0 +1,29 @@
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;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
*
* @author Robert Gruendler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface CompletionContext {
String name();
ContextMapping.Type type();
String precision() default "";
}
@@ -0,0 +1,12 @@
package org.springframework.data.elasticsearch.annotations;
/**
* Based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
*
* @author Robert Gruendler
*/
public enum CompletionContextType {
CATEGORY, GEO
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,12 +15,18 @@
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.*;
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;
/**
* Based on the reference doc - http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
* Based on the reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
*
* @author Mewes Kochheim
* @author Robert Gruendler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -37,4 +43,6 @@ public @interface CompletionField {
boolean preservePositionIncrements() default true;
int maxInputLength() default 50;
CompletionContext[] contexts() default {};
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.annotations;
/**
* @author Jakub Vavrik
* Values based on reference doc - http://www.elasticsearch.org/guide/reference/mapping/date-format/
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/
*/
public enum DateFormat {
none, custom, basic_date, basic_date_time, basic_date_time_no_millis, basic_ordinal_date, basic_ordinal_date_time,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,41 +15,81 @@
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.*;
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.elasticsearch.index.VersionType;
import org.springframework.data.annotation.Persistent;
/**
* Document
* Identifies a domain object to be persisted to Elasticsearch.
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Mason Chan
* @author Ivan Greene
* @author Mark Paluch
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Target({ ElementType.TYPE })
public @interface Document {
/**
* Name of the Elasticsearch index.
* <ul>
* <li>Lowercase only</li>
* <li><Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #/li>
* <li>Cannot start with -, _, +</li>
* <li>Cannot be . or ..</li>
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit
* faster)</li>
* </ul>
*/
String indexName();
/**
* Mapping type name.
*/
String type() default "";
/**
* Use server-side settings when creating the index.
*/
boolean useServerConfiguration() default false;
/**
* Number of shards for the index {@link #indexName()}. Used for index creation.
*/
short shards() default 5;
/**
* Number of replicas for the index {@link #indexName()}. Used for index creation.
*/
short replicas() default 1;
/**
* Refresh interval for the index {@link #indexName()}. Used for index creation.
*/
String refreshInterval() default "1s";
/**
* Index storage type for the index {@link #indexName()}. Used for index creation.
*/
String indexStoreType() default "fs";
/**
* Configuration whether to create an index on repository bootstrapping.
*/
boolean createIndex() default true;
/**
* Configuration of version management.
*/
VersionType versionType() default VersionType.EXTERNAL;
}
@@ -0,0 +1,27 @@
package org.springframework.data.elasticsearch.annotations;
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.data.annotation.Persistent;
/**
* Elasticsearch dynamic templates mapping.
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
* DynamicTemplates annotation is ommited if {@link Mapping} annotation is used.
*
* @author Petr Kukral
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DynamicTemplates {
String mappingPath() default "";
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,6 +15,8 @@
*/
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;
@@ -29,6 +31,7 @@ import java.lang.annotation.Target;
* @author Jonathan Yan
* @author Jakub Vavrik
* @author Kevin Leturc
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -36,6 +39,21 @@ import java.lang.annotation.Target;
@Inherited
public @interface Field {
/**
* Alias for {@link #name}.
* @since 3.2
*/
@AliasFor("name")
String value() default "";
/**
* 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.
* @since 3.2
*/
@AliasFor("value")
String name() default "";
FieldType type() default FieldType.Auto;
boolean index() default true;
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -19,12 +19,16 @@ package org.springframework.data.elasticsearch.annotations;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Zeng Zetang
*/
public enum FieldType {
Text,
Byte,
Short,
Integer,
Long,
Date,
Half_Float,
Float,
Double,
Boolean,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -21,6 +21,7 @@ import java.time.Duration;
import java.util.List;
import java.util.Optional;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.http.HttpHeaders;
@@ -29,6 +30,8 @@ import org.springframework.http.HttpHeaders;
* Configuration interface exposing common client configuration properties for Elasticsearch clients.
*
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Henrique Amaral
* @since 3.2
*/
public interface ClientConfiguration {
@@ -117,6 +120,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}.
*
@@ -134,6 +144,14 @@ public interface ClientConfiguration {
*/
Duration getSocketTimeout();
/**
* returns an optionally set proxy in the form host:port
*
* @return the optional proxy
* @since 4.0
*/
Optional<String> getProxy();
/**
* @author Christoph Strobl
*/
@@ -200,6 +218,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);
}
/**
@@ -257,6 +285,21 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder withSocketTimeout(Duration timeout);
/**
* Configure the username and password to be sent as a Basic Authentication header
*
* @param username the username. Must not be {@literal null}.
* @param password the password. Must not be {@literal null}.
* @return the {@link TerminalClientConfigurationBuilder}
*/
TerminalClientConfigurationBuilder withBasicAuth(String username, String password);
/**
* @param proxy a proxy formatted as String {@literal host:port}.
* @return the {@link MaybeSecureClientConfigurationBuilder}.
*/
MaybeSecureClientConfigurationBuilder withProxy(String proxy);
/**
* Build the {@link ClientConfiguration} object.
*
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
@@ -36,6 +37,8 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Henrique Amaral
* @since 3.2
*/
class ClientConfigurationBuilder
@@ -45,8 +48,12 @@ 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 String proxy;
/*
* (non-Javadoc)
@@ -75,6 +82,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()
@@ -100,6 +114,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)
@@ -109,7 +139,9 @@ class ClientConfigurationBuilder
Assert.notNull(defaultHeaders, "Default HTTP headers must not be null");
this.headers = defaultHeaders;
this.headers = new HttpHeaders();
this.headers.addAll(defaultHeaders);
return this;
}
@@ -139,17 +171,38 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder withBasicAuth(String username, String password) {
Assert.notNull(username, "username must not be null");
Assert.notNull(password, "password must not be null");
this.username = username;
this.password = password;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithOptionalDefaultHeaders#build()
*/
@Override
public ClientConfiguration build() {
return new DefaultClientConfiguration(this.hosts, this.headers, this.useSsl, this.sslContext, this.soTimeout,
this.connectTimeout);
if (username != null && password != null) {
if (HttpHeaders.EMPTY.equals(headers)) {
headers = new HttpHeaders();
}
headers.setBasicAuth(username, password);
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout,
hostnameVerifier, proxy);
}
private static InetSocketAddress parse(String hostAndPort) {
return InetSocketAddressParser.parse(hostAndPort, ElasticsearchHost.DEFAULT_PORT);
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -93,7 +93,7 @@ public abstract class ClientLogger {
public static void logRawResponse(String logId, HttpStatus statusCode) {
if (isEnabled()) {
WIRE_LOGGER.trace("[{}] Received raw response: ", logId, statusCode);
WIRE_LOGGER.trace("[{}] Received raw response: {}", logId, statusCode);
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.http.HttpHeaders;
@@ -42,9 +43,12 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final @Nullable SSLContext sslContext;
private final Duration soTimeout;
private final Duration connectTimeout;
private final @Nullable HostnameVerifier hostnameVerifier;
private final String proxy;
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout) {
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout,
@Nullable HostnameVerifier hostnameVerifier, String proxy) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
@@ -52,6 +56,8 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.sslContext = sslContext;
this.soTimeout = soTimeout;
this.connectTimeout = connectTimeout;
this.hostnameVerifier = hostnameVerifier;
this.proxy = proxy;
}
/*
@@ -90,6 +96,15 @@ class DefaultClientConfiguration implements ClientConfiguration {
return Optional.ofNullable(this.sslContext);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getHostNameVerifier()
*/
@Override
public Optional<HostnameVerifier> getHostNameVerifier() {
return Optional.ofNullable(this.hostnameVerifier);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getConnectTimeout()
@@ -108,4 +123,12 @@ class DefaultClientConfiguration implements ClientConfiguration {
return this.soTimeout;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration#getProxy()
*/
@Override
public Optional<String> getProxy() {
return Optional.ofNullable(proxy);
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -24,6 +24,7 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.apache.http.Header;
@@ -52,6 +53,7 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Henrique Amaral
* @since 3.2
*/
public final class RestClients {
@@ -87,7 +89,9 @@ public final class RestClients {
builder.setHttpClientConfigCallback(clientBuilder -> {
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
Optional<HostnameVerifier> hostNameVerifier = clientConfiguration.getHostNameVerifier();
sslContext.ifPresent(clientBuilder::setSSLContext);
hostNameVerifier.ifPresent(clientBuilder::setSSLHostnameVerifier);
if (ClientLogger.isEnabled()) {
@@ -114,6 +118,8 @@ public final class RestClients {
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
return clientBuilder;
});
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -72,7 +72,7 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
@Override
public boolean isSingleton() {
return false;
return true;
}
@Override
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -47,7 +47,18 @@ import javax.net.ssl.SSLContext;
import org.apache.http.util.EntityUtils;
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;
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
import org.elasticsearch.action.admin.indices.flush.FlushResponse;
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.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;
@@ -63,9 +74,12 @@ import org.elasticsearch.action.search.ClearScrollResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@@ -80,13 +94,13 @@ import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
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.util.Lazy;
import org.springframework.http.HttpHeaders;
@@ -111,11 +125,12 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Henrique Amaral
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
*/
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient {
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices {
private final HostProvider hostProvider;
@@ -279,6 +294,15 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return sendRequest(indexRequest, RequestCreator.index(), IndexResponse.class, headers).publishNext();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#indices()
*/
@Override
public Indices indices() {
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.update.UpdateRequest)
@@ -299,6 +323,17 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.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, CountRequest countRequest) {
return sendRequest(countRequest, RequestCreator.count(), CountResponse.class, headers) //
.map(CountResponse::getCount) //
.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
@@ -403,6 +438,107 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.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();
}
// --> INDICES
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#existsIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.get.GetIndexRequest)
*/
@Override
public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
return sendRequest(request, RequestCreator.indexExists(), RawActionResponse.class, headers) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#deleteIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest)
*/
@Override
public Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
return sendRequest(request, RequestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
.then();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#createIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.create.CreateIndexRequest)
*/
@Override
public Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
return sendRequest(createIndexRequest, RequestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
.then();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#openIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.open.OpenIndexRequest)
*/
@Override
public Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest request) {
return sendRequest(request, RequestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
.then();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#closeIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.close.CloseIndexRequest)
*/
@Override
public Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest) {
return sendRequest(closeIndexRequest, RequestCreator.indexClose(), AcknowledgedResponse.class, headers) //
.then();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#refreshIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.refresh.RefreshRequest)
*/
@Override
public Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest) {
return sendRequest(refreshRequest, RequestCreator.indexRefresh(), RefreshResponse.class, headers) //
.then();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#updateMapping(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest)
*/
@Override
public Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
return sendRequest(putMappingRequest, RequestCreator.putMapping(), AcknowledgedResponse.class, headers) //
.then();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#flushIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.flush.FlushRequest)
*/
@Override
public Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest) {
return sendRequest(flushRequest, RequestCreator.flushIndex(), FlushResponse.class, headers) //
.then();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.ReactiveElasticsearchClientCallback)
@@ -442,13 +578,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
// -->
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();
@@ -630,6 +765,57 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
}
};
}
static Function<BulkRequest, Request> bulk() {
return request -> {
try {
return RequestConverters.bulk(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;
}
static Function<CountRequest, Request> count() {
return RequestConverters::count;
}
}
/**
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,29 +15,22 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import org.elasticsearch.action.ActionResponse;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
/**
* Extension to {@link ActionResponse} that also implements {@link ClientResponse}.
* Extension to {@link ActionResponse} that also delegates to {@link ClientResponse}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Mark Paluch
* @since 3.2
*/
class RawActionResponse extends ActionResponse implements ClientResponse {
class RawActionResponse extends ActionResponse {
private final ClientResponse delegate;
@@ -49,129 +42,23 @@ class RawActionResponse extends ActionResponse implements ClientResponse {
return new RawActionResponse(response);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#statusCode()
*/
@Override
public HttpStatus statusCode() {
return delegate.statusCode();
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#rawStatusCode()
*/
@Override
public int rawStatusCode() {
return delegate.rawStatusCode();
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#headers()
*/
@Override
public Headers headers() {
public ClientResponse.Headers headers() {
return delegate.headers();
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#cookies()
*/
@Override
public MultiValueMap<String, ResponseCookie> cookies() {
return delegate.cookies();
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#strategies()
*/
@Override
public ExchangeStrategies strategies() {
return delegate.strategies();
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#body(org.springframework.web.reactive.function.BodyExtractor)
*/
@Override
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
return delegate.body(extractor);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#bodyToMono(java.lang.Class)
*/
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
return delegate.bodyToMono(elementClass);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#bodyToMono(org.springframework.core.ParameterizedTypeReference)
*/
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
return delegate.bodyToMono(typeReference);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#bodyToFlux(java.lang.Class)
*/
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
return delegate.bodyToFlux(elementClass);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#bodyToFlux(org.springframework.core.ParameterizedTypeReference)
*/
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
return delegate.bodyToFlux(typeReference);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#toEntity(java.lang.Class)
*/
@Override
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
return delegate.toEntity(bodyType);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#toEntity(org.springframework.core.ParameterizedTypeReference)
*/
@Override
public <T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> typeReference) {
return delegate.toEntity(typeReference);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#toEntityList(java.lang.Class)
*/
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType) {
return delegate.toEntityList(elementType);
}
/*
* (non-Javadoc)
* @see org.springframework.web.reactive.function.client.ClientResponse#toEntityList(org.springframework.core.ParameterizedTypeReference)
*/
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> typeReference) {
return delegate.toEntityList(typeReference);
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -22,6 +22,16 @@ import java.net.ConnectException;
import java.util.Collection;
import java.util.function.Consumer;
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.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
@@ -33,10 +43,12 @@ import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.core.CountRequest;
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.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.http.HttpHeaders;
@@ -49,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
@@ -237,6 +251,13 @@ public interface ReactiveElasticsearchClient {
*/
Mono<IndexResponse> index(HttpHeaders headers, IndexRequest indexRequest);
/**
* Gain access to index related commands.
*
* @return access to index related commands.
*/
Indices indices();
/**
* Execute an {@link UpdateRequest} against the {@literal update} API to alter a document.
*
@@ -313,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<CountRequest> consumer) {
CountRequest countRequest = new CountRequest();
consumer.accept(countRequest);
return count(countRequest);
}
/**
* Execute a {@link SearchRequest} against the {@literal count} API.
*
* @param countRequest 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(CountRequest countRequest) {
return count(HttpHeaders.EMPTY, countRequest);
}
/**
* Execute a {@link SearchRequest} against the {@literal count} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param countRequest 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, CountRequest countRequest);
/**
* Execute a {@link SearchRequest} against the {@literal search} API.
*
@@ -414,6 +476,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
@@ -473,4 +573,339 @@ public interface ReactiveElasticsearchClient {
return hosts().stream().anyMatch(ElasticsearchHost::isOnline);
}
}
/**
* Encapsulation of methods for accessing the Indices API.
*
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/_indices_apis.html">Indices
* API</a>.
* @author Christoph Strobl
*/
interface Indices {
/**
* Execute the given {@link GetIndexRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html"> Indices
* Exists API on elastic.co</a>
*/
default Mono<Boolean> existsIndex(Consumer<GetIndexRequest> consumer) {
GetIndexRequest request = new GetIndexRequest();
consumer.accept(request);
return existsIndex(request);
}
/**
* Execute the given {@link GetIndexRequest} against the {@literal indices} API.
*
* @param getIndexRequest must not be {@literal null}.
* @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html"> Indices
* Exists API on elastic.co</a>
*/
default Mono<Boolean> existsIndex(GetIndexRequest getIndexRequest) {
return existsIndex(HttpHeaders.EMPTY, getIndexRequest);
}
/**
* Execute the given {@link GetIndexRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param getIndexRequest must not be {@literal null}.
* @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html"> Indices
* Exists API on elastic.co</a>
*/
Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest getIndexRequest);
/**
* Execute the given {@link DeleteIndexRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
* Delete API on elastic.co</a>
*/
default Mono<Void> deleteIndex(Consumer<DeleteIndexRequest> consumer) {
DeleteIndexRequest request = new DeleteIndexRequest();
consumer.accept(request);
return deleteIndex(request);
}
/**
* Execute the given {@link DeleteIndexRequest} against the {@literal indices} API.
*
* @param deleteIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
* Delete API on elastic.co</a>
*/
default Mono<Void> deleteIndex(DeleteIndexRequest deleteIndexRequest) {
return deleteIndex(HttpHeaders.EMPTY, deleteIndexRequest);
}
/**
* Execute the given {@link DeleteIndexRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param deleteIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
* Delete API on elastic.co</a>
*/
Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest deleteIndexRequest);
/**
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* already exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
* Create API on elastic.co</a>
*/
default Mono<Void> createIndex(Consumer<CreateIndexRequest> consumer) {
CreateIndexRequest request = new CreateIndexRequest();
consumer.accept(request);
return createIndex(request);
}
/**
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
*
* @param createIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* already exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
* Create API on elastic.co</a>
*/
default Mono<Void> createIndex(CreateIndexRequest createIndexRequest) {
return createIndex(HttpHeaders.EMPTY, createIndexRequest);
}
/**
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param createIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* already exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
* Create API on elastic.co</a>
*/
Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest);
/**
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
* Open API on elastic.co</a>
*/
default Mono<Void> openIndex(Consumer<OpenIndexRequest> consumer) {
OpenIndexRequest request = new OpenIndexRequest();
consumer.accept(request);
return openIndex(request);
}
/**
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
*
* @param openIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
* Open API on elastic.co</a>
*/
default Mono<Void> openIndex(OpenIndexRequest openIndexRequest) {
return openIndex(HttpHeaders.EMPTY, openIndexRequest);
}
/**
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param openIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
* Open API on elastic.co</a>
*/
Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest openIndexRequest);
/**
* Execute the given {@link CloseIndexRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
* Close API on elastic.co</a>
*/
default Mono<Void> closeIndex(Consumer<CloseIndexRequest> consumer) {
CloseIndexRequest request = new CloseIndexRequest();
consumer.accept(request);
return closeIndex(request);
}
/**
* Execute the given {@link CloseIndexRequest} against the {@literal indices} API.
*
* @param closeIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
* Close API on elastic.co</a>
*/
default Mono<Void> closeIndex(CloseIndexRequest closeIndexRequest) {
return closeIndex(HttpHeaders.EMPTY, closeIndexRequest);
}
/**
* Execute the given {@link CloseIndexRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param closeIndexRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
* CLose API on elastic.co</a>
*/
Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest);
/**
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html"> Indices
* Refresh API on elastic.co</a>
*/
default Mono<Void> refreshIndex(Consumer<RefreshRequest> consumer) {
RefreshRequest request = new RefreshRequest();
consumer.accept(request);
return refreshIndex(request);
}
/**
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
*
* @param refreshRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html"> Indices
* Refresh API on elastic.co</a>
*/
default Mono<Void> refreshIndex(RefreshRequest refreshRequest) {
return refreshIndex(HttpHeaders.EMPTY, refreshRequest);
}
/**
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param refreshRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html"> Indices
* Refresh API on elastic.co</a>
*/
Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest);
/**
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
*/
default Mono<Void> updateMapping(Consumer<PutMappingRequest> consumer) {
PutMappingRequest request = new PutMappingRequest();
consumer.accept(request);
return updateMapping(request);
}
/**
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
*
* @param putMappingRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
*/
default Mono<Void> updateMapping(PutMappingRequest putMappingRequest) {
return updateMapping(HttpHeaders.EMPTY, putMappingRequest);
}
/**
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param putMappingRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
*/
Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest);
/**
* Execute the given {@link FlushRequest} against the {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html"> Indices Flush
* API on elastic.co</a>
*/
default Mono<Void> flushIndex(Consumer<FlushRequest> consumer) {
FlushRequest request = new FlushRequest();
consumer.accept(request);
return flushIndex(request);
}
/**
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
*
* @param flushRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html"> Indices Flush
* API on elastic.co</a>
*/
default Mono<Void> flushIndex(FlushRequest flushRequest) {
return flushIndex(HttpHeaders.EMPTY, flushRequest);
}
/**
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param flushRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html"> Indices Flush
* API on elastic.co</a>
*/
Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest);
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.util;
import java.io.ByteArrayOutputStream;
@@ -35,6 +34,14 @@ import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRe
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;
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.explain.ExplainRequest;
@@ -53,8 +60,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.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
@@ -75,18 +82,25 @@ import org.elasticsearch.index.reindex.AbstractBulkByScrollRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.ReindexRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.tasks.TaskId;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
/**
* <p>
* Original implementation source {@link org.elasticsearch.client.RequestConverters} by {@literal Elasticsearch}
* Original implementation source {@link org.elasticsearch.client.RequestConverters} and
* {@link org.elasticsearch.client.IndicesRequestConverters} 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 Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public class RequestConverters {
@@ -106,6 +120,8 @@ public class RequestConverters {
parameters.withTimeout(deleteRequest.timeout());
parameters.withVersion(deleteRequest.version());
parameters.withVersionType(deleteRequest.versionType());
parameters.withIfSeqNo(deleteRequest.ifSeqNo());
parameters.withIfPrimaryTerm(deleteRequest.ifPrimaryTerm());
parameters.withRefreshPolicy(deleteRequest.getRefreshPolicy());
parameters.withWaitForActiveShards(deleteRequest.waitForActiveShards());
return request;
@@ -305,6 +321,8 @@ public class RequestConverters {
parameters.withTimeout(indexRequest.timeout());
parameters.withVersion(indexRequest.version());
parameters.withVersionType(indexRequest.versionType());
parameters.withIfSeqNo(indexRequest.ifSeqNo());
parameters.withIfPrimaryTerm(indexRequest.ifPrimaryTerm());
parameters.withPipeline(indexRequest.getPipeline());
parameters.withRefreshPolicy(indexRequest.getRefreshPolicy());
parameters.withWaitForActiveShards(indexRequest.waitForActiveShards());
@@ -371,6 +389,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());
@@ -603,6 +647,125 @@ public class RequestConverters {
return request;
}
// --> INDICES
public static Request getIndex(GetIndexRequest getIndexRequest) {
String[] indices = getIndexRequest.indices() == null ? Strings.EMPTY_ARRAY : getIndexRequest.indices();
String endpoint = endpoint(indices);
Request request = new Request(HttpMethod.GET.name(), endpoint);
Params params = new Params(request);
params.withIndicesOptions(getIndexRequest.indicesOptions());
params.withLocal(getIndexRequest.local());
params.withIncludeDefaults(getIndexRequest.includeDefaults());
params.withHuman(getIndexRequest.humanReadable());
params.withMasterTimeout(getIndexRequest.masterNodeTimeout());
return request;
}
public static Request indexDelete(DeleteIndexRequest deleteIndexRequest) {
String endpoint = RequestConverters.endpoint(deleteIndexRequest.indices());
Request request = new Request(HttpMethod.DELETE.name(), endpoint);
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withTimeout(deleteIndexRequest.timeout());
parameters.withMasterTimeout(deleteIndexRequest.masterNodeTimeout());
parameters.withIndicesOptions(deleteIndexRequest.indicesOptions());
return request;
}
public static Request indexExists(GetIndexRequest getIndexRequest) {
// this can be called with no indices as argument by transport client, not via REST though
if (getIndexRequest.indices() == null || getIndexRequest.indices().length == 0) {
throw new IllegalArgumentException("indices are mandatory");
}
String endpoint = endpoint(getIndexRequest.indices(), "");
Request request = new Request(HttpMethod.HEAD.name(), endpoint);
Params params = new Params(request);
params.withLocal(getIndexRequest.local());
params.withHuman(getIndexRequest.humanReadable());
params.withIndicesOptions(getIndexRequest.indicesOptions());
params.withIncludeDefaults(getIndexRequest.includeDefaults());
return request;
}
public static Request indexOpen(OpenIndexRequest openIndexRequest) {
String endpoint = RequestConverters.endpoint(openIndexRequest.indices(), "_open");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params parameters = new Params(request);
parameters.withTimeout(openIndexRequest.timeout());
parameters.withMasterTimeout(openIndexRequest.masterNodeTimeout());
parameters.withWaitForActiveShards(openIndexRequest.waitForActiveShards(), ActiveShardCount.NONE);
parameters.withIndicesOptions(openIndexRequest.indicesOptions());
return request;
}
public static Request indexClose(CloseIndexRequest closeIndexRequest) {
String endpoint = RequestConverters.endpoint(closeIndexRequest.indices(), "_close");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params parameters = new Params(request);
parameters.withTimeout(closeIndexRequest.timeout());
parameters.withMasterTimeout(closeIndexRequest.masterNodeTimeout());
parameters.withIndicesOptions(closeIndexRequest.indicesOptions());
return request;
}
public static Request indexCreate(CreateIndexRequest createIndexRequest) {
String endpoint = RequestConverters.endpoint(createIndexRequest.indices());
Request request = new Request(HttpMethod.PUT.name(), endpoint);
Params parameters = new Params(request);
parameters.withTimeout(createIndexRequest.timeout());
parameters.withMasterTimeout(createIndexRequest.masterNodeTimeout());
parameters.withWaitForActiveShards(createIndexRequest.waitForActiveShards(), ActiveShardCount.DEFAULT);
request.setEntity(createEntity(createIndexRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
public static Request indexRefresh(RefreshRequest refreshRequest) {
String[] indices = refreshRequest.indices() == null ? Strings.EMPTY_ARRAY : refreshRequest.indices();
Request request = new Request(HttpMethod.POST.name(), RequestConverters.endpoint(indices, "_refresh"));
Params parameters = new Params(request);
parameters.withIndicesOptions(refreshRequest.indicesOptions());
return request;
}
public static Request putMapping(PutMappingRequest putMappingRequest) {
// The concreteIndex is an internal concept, not applicable to requests made over the REST API.
if (putMappingRequest.getConcreteIndex() != null) {
throw new IllegalArgumentException("concreteIndex cannot be set on PutMapping requests made over the REST API");
}
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());
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
public static Request flushIndex(FlushRequest flushRequest) {
String[] indices = flushRequest.indices() == null ? Strings.EMPTY_ARRAY : flushRequest.indices();
Request request = new Request(HttpMethod.POST.name(), RequestConverters.endpoint(indices, "_flush"));
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withIndicesOptions(flushRequest.indicesOptions());
parameters.putParam("wait_if_ongoing", Boolean.toString(flushRequest.waitIfOngoing()));
parameters.putParam("force", Boolean.toString(flushRequest.force()));
return request;
}
static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) {
try {
@@ -788,6 +951,20 @@ public class RequestConverters {
return this;
}
Params withIfSeqNo(long seqNo) {
if (seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
return putParam("if_seq_no", Long.toString(seqNo));
}
return this;
}
Params withIfPrimaryTerm(long primaryTerm) {
if (primaryTerm != SequenceNumbers.UNASSIGNED_PRIMARY_TERM) {
return putParam("if_primary_term", Long.toString(primaryTerm));
}
return this;
}
Params withWaitForActiveShards(ActiveShardCount activeShardCount) {
return withWaitForActiveShards(activeShardCount, ActiveShardCount.DEFAULT);
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -22,6 +22,7 @@ import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
* @see ElasticsearchConfigurationSupport
*/
@@ -41,7 +42,7 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
*
* @return never {@literal null}.
*/
@Bean
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
public ElasticsearchOperations elasticsearchOperations() {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter(), resultsMapper());
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -0,0 +1,52 @@
package org.springframework.data.elasticsearch.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* AbstractElasticsearchTemplate
*
* @author Sascha Woo
*/
public abstract class AbstractElasticsearchTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchTemplate.class);
protected ElasticsearchConverter elasticsearchConverter;
public AbstractElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
this.elasticsearchConverter = elasticsearchConverter;
}
protected String 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 mappings;
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
// 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);
}
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -20,7 +20,6 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.elasticsearch.action.get.GetResponse;
@@ -29,6 +28,7 @@ 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;
@@ -61,6 +61,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
* @author Ilkang Na
* @author Sascha Woo
* @author Christoph Strobl
* @author Dmitriy Yakovlev
*/
public class DefaultResultMapper extends AbstractResultMapper {
@@ -105,8 +106,9 @@ public class DefaultResultMapper extends AbstractResultMapper {
for (SearchHit hit : response.getHits()) {
if (hit != null) {
T result = null;
if (!StringUtils.isEmpty(hit.getSourceAsString())) {
result = mapEntity(hit.getSourceAsString(), clazz);
String hitSourceAsString = hit.getSourceAsString();
if (!StringUtils.isEmpty(hitSourceAsString)) {
result = mapEntity(hitSourceAsString, clazz);
} else {
result = mapEntity(hit.getFields().values(), clazz);
}
@@ -187,8 +189,8 @@ public class DefaultResultMapper extends AbstractResultMapper {
}
@Override
public <T> LinkedList<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
LinkedList<T> list = new LinkedList<>();
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);
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,6 +15,8 @@
*/
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;
@@ -56,6 +58,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* {@link ElasticsearchPersistentEntity metadata}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public class ElasticsearchEntityMapper implements
@@ -65,6 +68,8 @@ public class ElasticsearchEntityMapper implements
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();
@@ -78,6 +83,10 @@ public class ElasticsearchEntityMapper implements
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
@@ -543,12 +552,12 @@ public class ElasticsearchEntityMapper implements
Map<String, Object> sink = new LinkedHashMap<>();
write(source, sink);
return new ObjectMapper().writeValueAsString(sink);
return objectWriter.writeValueAsString(sink);
}
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return read(clazz, new ObjectMapper().readerFor(HashMap.class).readValue(source));
return read(clazz, objectReader.readValue(source));
}
// --> PRIVATE HELPERS
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -17,13 +17,17 @@
package org.springframework.data.elasticsearch.core;
import java.net.ConnectException;
import java.util.List;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
@@ -39,7 +43,8 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
if (!indexAvailable(elasticsearchException)) {
return new NoSuchIndexException(elasticsearchException.getMetadata("es.index").toString(), ex);
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
ex);
}
}
@@ -51,6 +56,13 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
}
private boolean indexAvailable(ElasticsearchException ex) {
return !CollectionUtils.contains(ex.getMetadata("es.index_uuid").iterator(), "_na_");
List<String> metadata = ex.getMetadata("es.index_uuid");
if (metadata == null) {
if (ex instanceof ElasticsearchStatusException) {
return StringUtils.hasText(ObjectUtils.nullSafeToString(((ElasticsearchStatusException) ex).getIndex()));
}
}
return !CollectionUtils.contains(metadata.iterator(), "_na_");
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,20 +15,28 @@
*/
package org.springframework.data.elasticsearch.core;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.Nullable;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.util.CloseableIterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
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.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.util.CloseableIterator;
import org.springframework.lang.Nullable;
/**
* ElasticsearchOperations
*
@@ -36,13 +44,26 @@ import java.util.stream.Collectors;
* @author Mohsin Husen
* @author Kevin Leturc
* @author Zetang Zeng
* @author Dmitriy Yakovlev
* @author Peter-Josef Meisch
*/
public interface ElasticsearchOperations {
/**
* @return Converter in use
* adding new alias
*
* @param query
* @return
*/
ElasticsearchConverter getElasticsearchConverter();
boolean addAlias(AliasQuery query);
/**
* removing previously created alias
*
* @param query
* @return
*/
boolean removeAlias(AliasQuery query);
/**
* Create an index for a class
@@ -83,6 +104,16 @@ public interface ElasticsearchOperations {
*/
<T> boolean putMapping(Class<T> clazz);
/**
* Create mapping for the given class and put the mapping to the given indexName and type.
*
* @param indexName
* @param type
* @param mappings
* @since 3.2
*/
<T> boolean putMapping(String indexName, String type, Class<T> clazz);
/**
* Create mapping for a given indexName and type
*
@@ -100,14 +131,13 @@ public interface ElasticsearchOperations {
*/
<T> boolean putMapping(Class<T> clazz, Object mappings);
/**
* Get mapping for a class
*
* @param clazz
* @param <T>
*/
<T> Map getMapping(Class<T> clazz);
<T> Map<String, Object> getMapping(Class<T> clazz);
/**
* Get mapping for a given indexName and type
@@ -115,22 +145,31 @@ public interface ElasticsearchOperations {
* @param indexName
* @param type
*/
Map getMapping(String indexName, String type);
Map<String, Object> getMapping(String indexName, String type);
/**
* Get settings for a given indexName
*
* @param indexName
*/
Map getSetting(String indexName);
Map<String, Object> getSetting(String indexName);
/**
* Get settings for a given class
*
* @param clazz
*/
<T> Map getSetting(Class<T> clazz);
<T> Map<String, Object> getSetting(Class<T> clazz);
/**
* get all the alias pointing to specified index
*
* @param indexName
* @return
*/
List<AliasMetaData> queryForAlias(String indexName);
<T> T query(SearchQuery query, ResultsExtractor<T> resultsExtractor);
/**
* Execute the query against elasticsearch and return the first returned object
@@ -197,7 +236,8 @@ public interface ElasticsearchOperations {
<T> List<Page<T>> queryForPage(List<SearchQuery> queries, Class<T> clazz);
/**
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom mapper
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom
* mapper
*
* @param queries
* @param clazz
@@ -215,7 +255,8 @@ public interface ElasticsearchOperations {
List<Page<?>> queryForPage(List<SearchQuery> queries, List<Class<?>> classes);
/**
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom mapper
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom
* mapper
*
* @param queries
* @param classes
@@ -253,7 +294,8 @@ public interface ElasticsearchOperations {
/**
* Executes the given {@link CriteriaQuery} against elasticsearch and return result as {@link CloseableIterator}.
* <p>
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of error.
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of
* error.
*
* @param <T> element return type
* @param query
@@ -266,7 +308,8 @@ public interface ElasticsearchOperations {
/**
* Executes the given {@link SearchQuery} against elasticsearch and return result as {@link CloseableIterator}.
* <p>
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of error.
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of
* error.
*
* @param <T> element return type
* @param query
@@ -277,9 +320,11 @@ public interface ElasticsearchOperations {
<T> CloseableIterator<T> stream(SearchQuery query, Class<T> clazz);
/**
* Executes the given {@link SearchQuery} against elasticsearch and return result as {@link CloseableIterator} using custom mapper.
* Executes the given {@link SearchQuery} against elasticsearch and return result as {@link CloseableIterator} using
* custom mapper.
* <p>
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of error.
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of
* error.
*
* @param <T> element return type
* @param query
@@ -392,7 +437,7 @@ public interface ElasticsearchOperations {
* @param clazz
* @return
*/
<T> LinkedList<T> multiGet(SearchQuery searchQuery, Class<T> clazz);
<T> List<T> multiGet(SearchQuery searchQuery, Class<T> clazz);
/**
* Execute a multiGet against elasticsearch for the given ids with MultiGetResultMapper
@@ -402,7 +447,7 @@ public interface ElasticsearchOperations {
* @param multiGetResultMapper
* @return
*/
<T> LinkedList<T> multiGet(SearchQuery searchQuery, Class<T> clazz, MultiGetResultMapper multiGetResultMapper);
<T> List<T> multiGet(SearchQuery searchQuery, Class<T> clazz, MultiGetResultMapper multiGetResultMapper);
/**
* Index an object. Will do save or update
@@ -421,18 +466,40 @@ public interface ElasticsearchOperations {
UpdateResponse update(UpdateQuery updateQuery);
/**
* Bulk index all objects. Will do save or update
* Bulk index all objects. Will do save or update.
*
* @param queries
* @param queries the queries to execute in bulk
*/
void bulkIndex(List<IndexQuery> queries);
default void bulkIndex(List<IndexQuery> queries) {
bulkIndex(queries, BulkOptions.defaultOptions());
}
/**
* 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
* @since 3.2
*/
void bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions);
/**
* Bulk update all objects. Will do update
*
* @param queries
* @param queries the queries to execute in bulk
*/
void bulkUpdate(List<UpdateQuery> queries);
default void bulkUpdate(List<UpdateQuery> queries) {
bulkUpdate(queries, BulkOptions.defaultOptions());
}
/**
* 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
* @since 3.2
*/
void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions);
/**
* Delete the one object with provided id
@@ -444,7 +511,6 @@ public interface ElasticsearchOperations {
*/
String delete(String indexName, String type, String id);
/**
* Delete all records matching the criteria
*
@@ -452,6 +518,7 @@ public interface ElasticsearchOperations {
* @param criteriaQuery
*/
<T> void delete(CriteriaQuery criteriaQuery, Class<T> clazz);
/**
* Delete the one object with provided id
*
@@ -523,7 +590,6 @@ public interface ElasticsearchOperations {
* refresh the index
*
* @param indexName
*
*/
void refresh(String indexName);
@@ -531,7 +597,6 @@ public interface ElasticsearchOperations {
* refresh the index
*
* @param clazz
*
*/
<T> void refresh(Class<T> clazz);
@@ -540,53 +605,56 @@ public interface ElasticsearchOperations {
*
* @param query The search query.
* @param scrollTimeInMillis The time in millisecond for scroll feature
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* @param clazz The class of entity to retrieve.
* @return The scan id for input query.
*/
<T> Page<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz);
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz);
/**
* Returns scrolled page for given query
*
* @param query The search query.
* @param scrollTimeInMillis The time in millisecond for scroll feature
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* @param mapper Custom impl to map result to entities
* @return The scan id for input query.
*/
<T> Page<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz, SearchResultMapper mapper);
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz,
SearchResultMapper mapper);
/**
* Returns scrolled page for given query
*
* @param criteriaQuery The search query.
* @param scrollTimeInMillis The time in millisecond for scroll feature
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* @param clazz The class of entity to retrieve.
* @return The scan id for input query.
*/
<T> Page<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz);
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz);
/**
* Returns scrolled page for given query
*
* @param criteriaQuery The search query.
* @param scrollTimeInMillis The time in millisecond for scroll feature
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
* @param mapper Custom impl to map result to entities
* @return The scan id for input query.
*/
<T> Page<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz, SearchResultMapper mapper);
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz,
SearchResultMapper mapper);
<T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz);
<T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz,
SearchResultMapper mapper);
<T> Page<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz);
<T> Page<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz, SearchResultMapper mapper);
/**
* Clears the search contexts associated with specified scroll ids.
*
* @param scrollId
*
*/
<T> void clearScroll(String scrollId);
@@ -600,33 +668,10 @@ public interface ElasticsearchOperations {
*/
<T> Page<T> moreLikeThis(MoreLikeThisQuery query, Class<T> clazz);
/**
* adding new alias
*
* @param query
* @return
*/
Boolean addAlias(AliasQuery query);
/**
* removing previously created alias
*
* @param query
* @return
*/
Boolean removeAlias(AliasQuery query);
/**
* get all the alias pointing to specified index
*
* @param indexName
* @return
*/
List<AliasMetaData> queryForAlias(String indexName);
<T> T query(SearchQuery query, ResultsExtractor<T> resultsExtractor);
ElasticsearchPersistentEntity getPersistentEntityFor(Class clazz);
/**
* @return Converter in use
*/
ElasticsearchConverter getElasticsearchConverter();
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -17,20 +17,14 @@ package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.client.Requests.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.core.MappingBuilder.*;
import static org.springframework.util.CollectionUtils.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import org.elasticsearch.action.ActionFuture;
@@ -53,12 +47,12 @@ import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
@@ -68,6 +62,8 @@ import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.index.reindex.DeleteByQueryRequestBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
@@ -82,17 +78,13 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
@@ -101,6 +93,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.util.CloseableIterator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -125,15 +118,20 @@ import org.springframework.util.StringUtils;
* @author Zetang Zeng
* @author Ivan Greene
* @author Christoph Strobl
* @author Dmitriy Yakovlev
* @author Peter-Josef Meisch
* @author Martin Choraine
* @author Farid Azaza
* @author Gyula Attila Csorogi
*/
public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<Client>, ApplicationContextAware {
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate
implements ElasticsearchOperations, EsClient<Client>, ApplicationContextAware {
private static final Logger QUERY_LOGGER = LoggerFactory.getLogger("org.springframework.data.elasticsearch.core.QUERY");
private static final Logger QUERY_LOGGER = LoggerFactory
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTemplate.class);
private static final String FIELD_SCORE = "_score";
private Client client;
private ElasticsearchConverter elasticsearchConverter;
private ResultsMapper resultsMapper;
private String searchTimeout;
@@ -162,12 +160,12 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter,
ResultsMapper resultsMapper) {
super(elasticsearchConverter);
Assert.notNull(client, "Client must not be null!");
Assert.notNull(elasticsearchConverter, "ElasticsearchConverter must not be null!");
Assert.notNull(resultsMapper, "ResultsMapper must not be null!");
this.client = client;
this.elasticsearchConverter = elasticsearchConverter;
this.resultsMapper = resultsMapper;
}
@@ -180,6 +178,35 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
this.searchTimeout = searchTimeout;
}
@Override
public boolean addAlias(AliasQuery query) {
Assert.notNull(query.getIndexName(), "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.add()
.alias(query.getAliasName()).index(query.getIndexName());
if (query.getFilterBuilder() != null) {
aliasAction.filter(query.getFilterBuilder());
} else if (query.getFilter() != null) {
aliasAction.filter(query.getFilter());
} else if (!StringUtils.isEmpty(query.getRouting())) {
aliasAction.routing(query.getRouting());
} else if (!StringUtils.isEmpty(query.getSearchRouting())) {
aliasAction.searchRouting(query.getSearchRouting());
} else if (!StringUtils.isEmpty(query.getIndexRouting())) {
aliasAction.indexRouting(query.getIndexRouting());
}
return client.admin().indices().prepareAliases().addAliasAction(aliasAction).execute().actionGet().isAcknowledged();
}
@Override
public boolean removeAlias(AliasQuery query) {
Assert.notNull(query.getIndexName(), "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
return client.admin().indices().prepareAliases().removeAlias(query.getIndexName(), query.getAliasName()).execute()
.actionGet().isAcknowledged();
}
@Override
public <T> boolean createIndex(Class<T> clazz) {
return createIndexIfNotCreated(clazz);
@@ -193,29 +220,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
@Override
public <T> boolean putMapping(Class<T> clazz) {
if (clazz.isAnnotationPresent(Mapping.class)) {
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
String mappings = readFileFromClasspath(mappingPath);
if (!StringUtils.isEmpty(mappings)) {
return putMapping(clazz, mappings);
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
XContentBuilder xContentBuilder = null;
try {
ElasticsearchPersistentProperty property = persistentEntity.getRequiredIdProperty();
xContentBuilder = buildMapping(clazz, persistentEntity.getIndexType(), property.getFieldName(),
persistentEntity.getParentType());
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
return putMapping(clazz, xContentBuilder);
return putMapping(clazz, buildMapping(clazz));
}
@Override
@@ -224,6 +229,11 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
mapping);
}
@Override
public <T> boolean putMapping(String indexName, String type, Class<T> clazz) {
return putMapping(indexName, type, buildMapping(clazz));
}
@Override
public boolean putMapping(String indexName, String type, Object mapping) {
Assert.notNull(indexName, "No index defined for putMapping()");
@@ -240,10 +250,10 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
@Override
public Map getMapping(String indexName, String type) {
public Map<String, Object> getMapping(String indexName, String type) {
Assert.notNull(indexName, "No index defined for putMapping()");
Assert.notNull(type, "No type defined for putMapping()");
Map mappings = null;
Map<String, Object> mappings = null;
try {
mappings = client.admin().indices().getMappings(new GetMappingsRequest().indices(indexName).types(type))
.actionGet().getMappings().get(indexName).get(type).getSourceAsMap();
@@ -255,7 +265,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
@Override
public <T> Map getMapping(Class<T> clazz) {
public <T> Map<String, Object> getMapping(Class<T> clazz) {
return getMapping(getPersistentEntityFor(clazz).getIndexName(), getPersistentEntityFor(clazz).getIndexType());
}
@@ -368,7 +378,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
@Override
public <T> T query(SearchQuery query, ResultsExtractor<T> resultsExtractor) {
SearchResponse response = doSearch(prepareSearch(query), query);
SearchResponse response = doSearch(prepareSearch(query, (ElasticsearchPersistentEntity) null), query);
return resultsExtractor.extract(response);
}
@@ -389,7 +399,8 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
@Override
public <T> List<String> queryForIds(SearchQuery query) {
SearchRequestBuilder request = prepareSearch(query).setQuery(query.getQuery());
SearchRequestBuilder request = prepareSearch(query, (ElasticsearchPersistentEntity) null)
.setQuery(query.getQuery());
if (query.getFilter() != null) {
request.setPostFilter(query.getFilter());
}
@@ -434,9 +445,8 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
@Override
public <T> CloseableIterator<T> stream(CriteriaQuery query, Class<T> clazz) {
final long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
return doStream(scrollTimeInMillis, (ScrolledPage<T>) startScroll(scrollTimeInMillis, query, clazz), clazz,
resultsMapper);
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz), clazz, resultsMapper);
}
@Override
@@ -445,69 +455,15 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
@Override
public <T> CloseableIterator<T> stream(SearchQuery query, final Class<T> clazz, final SearchResultMapper mapper) {
final long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
return doStream(scrollTimeInMillis, (ScrolledPage<T>) startScroll(scrollTimeInMillis, query, clazz, mapper), clazz,
mapper);
public <T> CloseableIterator<T> stream(SearchQuery query, Class<T> clazz, SearchResultMapper mapper) {
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz, mapper), clazz, mapper);
}
private <T> CloseableIterator<T> doStream(final long scrollTimeInMillis, final ScrolledPage<T> page,
final Class<T> clazz, final SearchResultMapper mapper) {
return new CloseableIterator<T>() {
/** As we couldn't retrieve single result with scroll, store current hits. */
private volatile Iterator<T> currentHits = page.iterator();
/** The scroll id. */
private volatile String scrollId = page.getScrollId();
/** If stream is finished (ie: cluster returns no results. */
private volatile boolean finished = !currentHits.hasNext();
@Override
public void close() {
try {
// Clear scroll on cluster only in case of error (cause elasticsearch auto clear scroll when it's done)
if (!finished && scrollId != null && currentHits != null && currentHits.hasNext()) {
clearScroll(scrollId);
}
} finally {
currentHits = null;
scrollId = null;
}
}
@Override
public boolean hasNext() {
// Test if stream is finished
if (finished) {
return false;
}
// Test if it remains hits
if (currentHits == null || !currentHits.hasNext()) {
// Do a new request
final ScrolledPage<T> scroll = (ScrolledPage<T>) continueScroll(scrollId, scrollTimeInMillis, clazz, mapper);
// Save hits and scroll id
currentHits = scroll.iterator();
finished = !currentHits.hasNext();
scrollId = scroll.getScrollId();
}
return currentHits.hasNext();
}
@Override
public T next() {
if (hasNext()) {
return currentHits.next();
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
};
private <T> CloseableIterator<T> doStream(long scrollTimeInMillis, ScrolledPage<T> page, Class<T> clazz,
SearchResultMapper mapper) {
return StreamQueries.streamResults(page, scrollId -> continueScroll(scrollId, scrollTimeInMillis, clazz, mapper),
this::clearScroll);
}
@Override
@@ -552,6 +508,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
if (elasticsearchQuery != null) {
countRequestBuilder.setQuery(elasticsearchQuery);
}
countRequestBuilder.setSize(0);
return countRequestBuilder.execute().actionGet().getHits().getTotalHits();
}
@@ -565,6 +522,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
if (elasticsearchFilter != null) {
searchRequestBuilder.setPostFilter(elasticsearchFilter);
}
searchRequestBuilder.setSize(0);
return searchRequestBuilder.execute().actionGet().getHits().getTotalHits();
}
@@ -587,7 +545,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
@Override
public <T> LinkedList<T> multiGet(SearchQuery searchQuery, Class<T> clazz) {
public <T> List<T> multiGet(SearchQuery searchQuery, Class<T> clazz) {
return resultsMapper.mapResults(getMultiResponse(searchQuery, clazz), clazz);
}
@@ -622,7 +580,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
@Override
public <T> LinkedList<T> multiGet(SearchQuery searchQuery, Class<T> clazz, MultiGetResultMapper getResultMapper) {
public <T> List<T> multiGet(SearchQuery searchQuery, Class<T> clazz, MultiGetResultMapper getResultMapper) {
return getResultMapper.mapResults(getMultiResponse(searchQuery, clazz), clazz);
}
@@ -642,35 +600,53 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
private UpdateRequestBuilder prepareUpdate(UpdateQuery query) {
String indexName = !StringUtils.isEmpty(query.getIndexName()) ? query.getIndexName()
: getPersistentEntityFor(query.getClazz()).getIndexName();
String type = !StringUtils.isEmpty(query.getType()) ? query.getType()
: getPersistentEntityFor(query.getClazz()).getIndexType();
Assert.notNull(indexName, "No index defined for Query");
Assert.notNull(type, "No type define for Query");
Assert.notNull(query.getId(), "No Id define for Query");
Assert.notNull(query.getUpdateRequest(), "No IndexRequest define for Query");
UpdateRequestBuilder updateRequestBuilder = client.prepareUpdate(indexName, type, query.getId());
updateRequestBuilder.setRouting(query.getUpdateRequest().routing());
Assert.notNull(query.getUpdateRequest(), "No UpdateRequest define for Query");
if (query.getUpdateRequest().script() == null) {
// doc
if (query.DoUpsert()) {
updateRequestBuilder.setDocAsUpsert(true).setDoc(query.getUpdateRequest().doc());
} else {
updateRequestBuilder.setDoc(query.getUpdateRequest().doc());
}
} else {
// or script
updateRequestBuilder.setScript(query.getUpdateRequest().script());
UpdateRequest queryUpdateRequest = query.getUpdateRequest();
UpdateRequestBuilder updateRequestBuilder = client.prepareUpdate(indexName, type, query.getId()) //
.setRouting(queryUpdateRequest.routing()) //
.setRetryOnConflict(queryUpdateRequest.retryOnConflict()) //
.setTimeout(queryUpdateRequest.timeout()) //
.setWaitForActiveShards(queryUpdateRequest.waitForActiveShards()) //
.setRefreshPolicy(queryUpdateRequest.getRefreshPolicy()) //
.setWaitForActiveShards(queryUpdateRequest.waitForActiveShards()) //
.setScriptedUpsert(queryUpdateRequest.scriptedUpsert()) //
.setDocAsUpsert(queryUpdateRequest.docAsUpsert());
if (query.DoUpsert()) {
updateRequestBuilder.setDocAsUpsert(true);
}
if (queryUpdateRequest.script() != null) {
updateRequestBuilder.setScript(queryUpdateRequest.script());
}
if (queryUpdateRequest.doc() != null) {
updateRequestBuilder.setDoc(queryUpdateRequest.doc());
}
if (queryUpdateRequest.upsertRequest() != null) {
updateRequestBuilder.setUpsert(queryUpdateRequest.upsertRequest());
}
return updateRequestBuilder;
}
@Override
public void bulkIndex(List<IndexQuery> queries) {
public void bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions) {
Assert.notNull(queries, "List of IndexQuery must not be null");
Assert.notNull(bulkOptions, "BulkOptions must not be null");
BulkRequestBuilder bulkRequest = client.prepareBulk();
setBulkOptions(bulkRequest, bulkOptions);
for (IndexQuery query : queries) {
bulkRequest.add(prepareIndex(query));
}
@@ -678,14 +654,42 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
@Override
public void bulkUpdate(List<UpdateQuery> queries) {
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions) {
Assert.notNull(queries, "List of UpdateQuery must not be null");
Assert.notNull(bulkOptions, "BulkOptions must not be null");
BulkRequestBuilder bulkRequest = client.prepareBulk();
setBulkOptions(bulkRequest, bulkOptions);
for (UpdateQuery query : queries) {
bulkRequest.add(prepareUpdate(query));
}
checkForBulkUpdateFailure(bulkRequest.execute().actionGet());
}
private static void setBulkOptions(BulkRequestBuilder bulkRequest, BulkOptions bulkOptions) {
if (bulkOptions.getTimeout() != null) {
bulkRequest.setTimeout(bulkOptions.getTimeout());
}
if (bulkOptions.getRefreshPolicy() != null) {
bulkRequest.setRefreshPolicy(bulkOptions.getRefreshPolicy());
}
if (bulkOptions.getWaitForActiveShards() != null) {
bulkRequest.setWaitForActiveShards(bulkOptions.getWaitForActiveShards());
}
if (bulkOptions.getPipeline() != null) {
bulkRequest.pipeline(bulkOptions.getPipeline());
}
if (bulkOptions.getRoutingId() != null) {
bulkRequest.routing(bulkOptions.getRoutingId());
}
}
private void checkForBulkUpdateFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
@@ -748,47 +752,20 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
: getPersistentEntityFor(clazz).getIndexName();
String typeName = !StringUtils.isEmpty(deleteQuery.getType()) ? deleteQuery.getType()
: getPersistentEntityFor(clazz).getIndexType();
Integer pageSize = deleteQuery.getPageSize() != null ? deleteQuery.getPageSize() : 1000;
Long scrollTimeInMillis = deleteQuery.getScrollTimeInMillis() != null ? deleteQuery.getScrollTimeInMillis()
: 10000l;
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(deleteQuery.getQuery()).withIndices(indexName)
.withTypes(typeName).withPageable(PageRequest.of(0, pageSize)).build();
DeleteByQueryRequestBuilder requestBuilder = new DeleteByQueryRequestBuilder(client, DeleteByQueryAction.INSTANCE) //
.source(indexName) //
.filter(deleteQuery.getQuery()) //
.abortOnVersionConflict(false) //
.refresh(true);
SearchResultMapper onlyIdResultMapper = new SearchResultMapperAdapter() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<String> result = new ArrayList<String>();
for (SearchHit searchHit : response.getHits().getHits()) {
String id = searchHit.getId();
result.add(id);
}
if (result.size() > 0) {
return new AggregatedPageImpl<T>((List<T>) result, response.getScrollId());
}
return new AggregatedPageImpl<T>(Collections.emptyList(), response.getScrollId());
}
};
SearchRequestBuilder source = requestBuilder.source() //
.setTypes(typeName);
Page<String> scrolledResult = startScroll(scrollTimeInMillis, searchQuery, String.class, onlyIdResultMapper);
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
List<String> ids = new ArrayList<String>();
if (deleteQuery.getScrollTimeInMillis() != null)
source.setScroll(TimeValue.timeValueMillis(deleteQuery.getScrollTimeInMillis()));
do {
ids.addAll(scrolledResult.getContent());
scrolledResult = continueScroll(((ScrolledPage<T>) scrolledResult).getScrollId(), scrollTimeInMillis,
String.class, onlyIdResultMapper);
} while (scrolledResult.getContent().size() != 0);
for (String id : ids) {
bulkRequestBuilder.add(client.prepareDelete(indexName, typeName, id));
}
if (bulkRequestBuilder.numberOfActions() > 0) {
bulkRequestBuilder.execute().actionGet();
}
clearScroll(((ScrolledPage<T>) scrolledResult).getScrollId());
requestBuilder.get();
}
@Override
@@ -809,10 +786,11 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
private <T> SearchRequestBuilder prepareScroll(Query query, long scrollTimeInMillis, Class<T> clazz) {
setPersistentEntityIndexAndType(query, clazz);
return prepareScroll(query, scrollTimeInMillis);
return prepareScroll(query, scrollTimeInMillis, getPersistentEntity(clazz));
}
private SearchRequestBuilder prepareScroll(Query query, long scrollTimeInMillis) {
private SearchRequestBuilder prepareScroll(Query query, long scrollTimeInMillis,
@Nullable ElasticsearchPersistentEntity<?> entity) {
SearchRequestBuilder requestBuilder = client.prepareSearch(toArray(query.getIndices()))
.setTypes(toArray(query.getTypes())).setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).setFrom(0)
.setVersion(true);
@@ -821,9 +799,40 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
requestBuilder.setSize(query.getPageable().getPageSize());
}
if (query.getSourceFilter() != null) {
SourceFilter sourceFilter = query.getSourceFilter();
requestBuilder.setFetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (!isEmpty(query.getFields())) {
requestBuilder.setFetchSource(toArray(query.getFields()), null);
}
if (query.getSort() != null) {
prepareSort(query, requestBuilder, entity);
}
if (query.getIndicesOptions() != null) {
requestBuilder.setIndicesOptions(query.getIndicesOptions());
}
if (query instanceof SearchQuery) {
SearchQuery searchQuery = (SearchQuery) query;
if (searchQuery.getHighlightFields() != null || searchQuery.getHighlightBuilder() != null) {
HighlightBuilder highlightBuilder = searchQuery.getHighlightBuilder();
if (highlightBuilder == null) {
highlightBuilder = new HighlightBuilder();
}
if (searchQuery.getHighlightFields() != null) {
for (HighlightBuilder.Field highlightField : searchQuery.getHighlightFields()) {
highlightBuilder.field(highlightField);
}
}
requestBuilder.highlighter(highlightBuilder);
}
}
return requestBuilder;
}
@@ -858,38 +867,44 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
requestBuilder.setPostFilter(searchQuery.getFilter());
}
if (!isEmpty(searchQuery.getElasticsearchSorts())) {
for (SortBuilder sort : searchQuery.getElasticsearchSorts()) {
requestBuilder.addSort(sort);
}
}
return getSearchResponse(requestBuilder.setQuery(searchQuery.getQuery()));
}
public <T> Page<T> startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class<T> clazz) {
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class<T> clazz) {
SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery);
return resultsMapper.mapResults(response, clazz, null);
}
public <T> Page<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz) {
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz) {
SearchResponse response = doScroll(prepareScroll(criteriaQuery, scrollTimeInMillis, clazz), criteriaQuery);
return resultsMapper.mapResults(response, clazz, null);
}
public <T> Page<T> startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class<T> clazz,
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class<T> clazz,
SearchResultMapper mapper) {
SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery);
return mapper.mapResults(response, clazz, null);
}
public <T> Page<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz,
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz,
SearchResultMapper mapper) {
SearchResponse response = doScroll(prepareScroll(criteriaQuery, scrollTimeInMillis, clazz), criteriaQuery);
return mapper.mapResults(response, clazz, null);
}
public <T> Page<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz) {
public <T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz) {
SearchResponse response = getSearchResponse(
client.prepareSearchScroll(scrollId).setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).execute());
return resultsMapper.mapResults(response, clazz, Pageable.unpaged());
}
public <T> Page<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz,
public <T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz,
SearchResultMapper mapper) {
SearchResponse response = getSearchResponse(
client.prepareSearchScroll(scrollId).setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).execute());
@@ -968,6 +983,10 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
}
if (searchQuery.getCollapseBuilder() != null) {
searchRequest.setCollapse(searchQuery.getCollapseBuilder());
}
if (searchQuery.getHighlightFields() != null || searchQuery.getHighlightBuilder() != null) {
HighlightBuilder highlightBuilder = searchQuery.getHighlightBuilder();
if (highlightBuilder == null) {
@@ -998,6 +1017,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
searchRequest.addAggregation(aggregatedFacet.getFacet());
}
}
return searchRequest.setQuery(searchQuery.getQuery());
}
@@ -1022,7 +1042,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
if (clazz.isAnnotationPresent(Setting.class)) {
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
if (!StringUtils.isEmpty(settingPath)) {
String settings = readFileFromClasspath(settingPath);
String settings = ResourceUtil.readFileFromClasspath(settingPath);
if (!StringUtils.isEmpty(settings)) {
return createIndex(getPersistentEntityFor(clazz).getIndexName(), settings);
}
@@ -1051,10 +1071,10 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
return createIndex(getPersistentEntityFor(clazz).getIndexName(), settings);
}
private <T> Map getDefaultSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
private <T> Map<String, String> getDefaultSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
if (persistentEntity.isUseServerConfiguration())
return new HashMap();
return new HashMap<>();
return new MapBuilder<String, String>().put("index.number_of_shards", String.valueOf(persistentEntity.getShards()))
.put("index.number_of_replicas", String.valueOf(persistentEntity.getReplicas()))
@@ -1063,12 +1083,12 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
@Override
public <T> Map getSetting(Class<T> clazz) {
public <T> Map<String, Object> getSetting(Class<T> clazz) {
return getSetting(getPersistentEntityFor(clazz).getIndexName());
}
@Override
public Map getSetting(String indexName) {
public Map<String, Object> getSetting(String indexName) {
Assert.notNull(indexName, "No index defined for getSettings");
Settings settings = client.admin().indices().getSettings(new GetSettingsRequest()).actionGet().getIndexToSettings()
.get(indexName);
@@ -1077,10 +1097,10 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
private <T> SearchRequestBuilder prepareSearch(Query query, Class<T> clazz) {
setPersistentEntityIndexAndType(query, clazz);
return prepareSearch(query);
return prepareSearch(query, getPersistentEntity(clazz));
}
private SearchRequestBuilder prepareSearch(Query query) {
private SearchRequestBuilder prepareSearch(Query query, @Nullable ElasticsearchPersistentEntity<?> entity) {
Assert.notNull(query.getIndices(), "No index defined for Query");
Assert.notNull(query.getTypes(), "No type defined for Query");
@@ -1109,37 +1129,51 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
}
if (query.getSort() != null) {
for (Sort.Order order : query.getSort()) {
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
if (FIELD_SCORE.equals(order.getProperty())) {
ScoreSortBuilder sort = SortBuilders //
.scoreSort() //
.order(sortOrder);
searchRequestBuilder.addSort(sort);
} else {
FieldSortBuilder sort = SortBuilders //
.fieldSort(order.getProperty()) //
.order(sortOrder);
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
sort.missing("_first");
} else if (order.getNullHandling() == Sort.NullHandling.NULLS_LAST) {
sort.missing("_last");
}
searchRequestBuilder.addSort(sort);
}
}
prepareSort(query, searchRequestBuilder, entity);
}
if (query.getMinScore() > 0) {
searchRequestBuilder.setMinScore(query.getMinScore());
}
if (query.getPreference() != null) {
searchRequestBuilder.setPreference(query.getPreference());
}
return searchRequestBuilder;
}
private void prepareSort(Query query, SearchRequestBuilder searchRequestBuilder,
@Nullable ElasticsearchPersistentEntity<?> entity) {
for (Sort.Order order : query.getSort()) {
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
if (ScoreSortBuilder.NAME.equals(order.getProperty())) {
ScoreSortBuilder sort = SortBuilders //
.scoreSort() //
.order(sortOrder);
searchRequestBuilder.addSort(sort);
} else {
ElasticsearchPersistentProperty property = entity != null //
? entity.getPersistentProperty(order.getProperty()) //
: null;
String fieldName = property != null ? property.getFieldName() : order.getProperty();
FieldSortBuilder sort = SortBuilders //
.fieldSort(fieldName) //
.order(sortOrder);
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
sort.missing("_first");
} else if (order.getNullHandling() == Sort.NullHandling.NULLS_LAST) {
sort.missing("_last");
}
searchRequestBuilder.addSort(sort);
}
}
}
private IndexRequestBuilder prepareIndex(IndexQuery query) {
try {
String indexName = StringUtils.isEmpty(query.getIndexName())
@@ -1195,41 +1229,17 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
refresh(getPersistentEntityFor(clazz).getIndexName());
}
@Override
public Boolean addAlias(AliasQuery query) {
Assert.notNull(query.getIndexName(), "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
final IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.add()
.alias(query.getAliasName()).index(query.getIndexName());
if (query.getFilterBuilder() != null) {
aliasAction.filter(query.getFilterBuilder());
} else if (query.getFilter() != null) {
aliasAction.filter(query.getFilter());
} else if (!StringUtils.isEmpty(query.getRouting())) {
aliasAction.routing(query.getRouting());
} else if (!StringUtils.isEmpty(query.getSearchRouting())) {
aliasAction.searchRouting(query.getSearchRouting());
} else if (!StringUtils.isEmpty(query.getIndexRouting())) {
aliasAction.indexRouting(query.getIndexRouting());
}
return client.admin().indices().prepareAliases().addAliasAction(aliasAction).execute().actionGet().isAcknowledged();
}
@Override
public Boolean removeAlias(AliasQuery query) {
Assert.notNull(query.getIndexName(), "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
return client.admin().indices().prepareAliases().removeAlias(query.getIndexName(), query.getAliasName()).execute()
.actionGet().isAcknowledged();
}
@Override
public List<AliasMetaData> queryForAlias(String indexName) {
return client.admin().indices().getAliases(new GetAliasesRequest().indices(indexName)).actionGet().getAliases()
.get(indexName);
}
@Nullable
private ElasticsearchPersistentEntity<?> getPersistentEntity(@Nullable Class<?> clazz) {
return clazz != null ? elasticsearchConverter.getMappingContext().getPersistentEntity(clazz) : null;
}
@Override
public ElasticsearchPersistentEntity getPersistentEntityFor(Class clazz) {
Assert.isTrue(clazz.isAnnotationPresent(Document.class), "Unable to identify index name. " + clazz.getSimpleName()
@@ -1321,34 +1331,9 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
return resultsMapper;
}
@Deprecated
public static String readFileFromClasspath(String url) {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
ClassPathResource classPathResource = new ClassPathResource(url);
InputStreamReader inputStreamReader = new InputStreamReader(classPathResource.getInputStream());
bufferedReader = new BufferedReader(inputStreamReader);
String line;
String lineSeparator = System.getProperty("line.separator");
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append(lineSeparator);
}
} catch (Exception e) {
LOGGER.debug(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
return null;
} finally {
if (bufferedReader != null)
try {
bufferedReader.close();
} catch (IOException e) {
LOGGER.debug(String.format("Unable to close buffered reader.. %s", e.getMessage()));
}
}
return stringBuilder.toString();
return ResourceUtil.readFileFromClasspath(url);
}
public SearchResponse suggest(SuggestBuilder suggestion, String... indices) {
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -15,17 +15,25 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.springframework.util.StringUtils.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Iterator;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.core.ResolvableType;
import org.elasticsearch.common.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.CompletionContext;
import org.springframework.data.elasticsearch.annotations.CompletionField;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
@@ -33,14 +41,17 @@ import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.core.completion.Completion;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.springframework.util.StringUtils.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Rizwan Idrees
@@ -53,175 +64,236 @@ import static org.springframework.util.StringUtils.*;
* @author Mark Paluch
* @author Sascha Woo
* @author Nordine Bittich
* @author Robert Gruendler
* @author Petr Kukral
* @author Peter-Josef Meisch
*/
class MappingBuilder {
public static final String FIELD_DATA = "fielddata";
public static final String FIELD_STORE = "store";
public static final String FIELD_TYPE = "type";
public static final String FIELD_INDEX = "index";
public static final String FIELD_FORMAT = "format";
public static final String FIELD_SEARCH_ANALYZER = "search_analyzer";
public static final String FIELD_INDEX_ANALYZER = "analyzer";
public static final String FIELD_NORMALIZER = "normalizer";
public static final String FIELD_PROPERTIES = "properties";
public static final String FIELD_PARENT = "_parent";
public static final String FIELD_COPY_TO = "copy_to";
private static final String FIELD_DATA = "fielddata";
private static final String FIELD_STORE = "store";
private static final String FIELD_TYPE = "type";
private static final String FIELD_INDEX = "index";
private static final String FIELD_FORMAT = "format";
private static final String FIELD_SEARCH_ANALYZER = "search_analyzer";
private static final String FIELD_INDEX_ANALYZER = "analyzer";
private static final String FIELD_NORMALIZER = "normalizer";
private static final String FIELD_PROPERTIES = "properties";
private static final String FIELD_PARENT = "_parent";
private static final String FIELD_COPY_TO = "copy_to";
private static final String FIELD_CONTEXT_NAME = "name";
private static final String FIELD_CONTEXT_TYPE = "type";
private static final String FIELD_CONTEXT_PRECISION = "precision";
private static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates";
public static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators";
public static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments";
public static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length";
private static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators";
private static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments";
private static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length";
private static final String COMPLETION_CONTEXTS = "contexts";
public static final String TYPE_VALUE_KEYWORD = "keyword";
public static final String TYPE_VALUE_GEO_POINT = "geo_point";
public static final String TYPE_VALUE_COMPLETION = "completion";
public static final String TYPE_VALUE_GEO_HASH_PREFIX = "geohash_prefix";
public static final String TYPE_VALUE_GEO_HASH_PRECISION = "geohash_precision";
private static final String TYPE_VALUE_KEYWORD = "keyword";
private static final String TYPE_VALUE_GEO_POINT = "geo_point";
private static final String TYPE_VALUE_COMPLETION = "completion";
private static SimpleTypeHolder SIMPLE_TYPE_HOLDER = SimpleTypeHolder.DEFAULT;
private static final Logger logger = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
static XContentBuilder buildMapping(Class clazz, String indexType, String idFieldName, String parentType) throws IOException {
private final ElasticsearchConverter elasticsearchConverter;
MappingBuilder(ElasticsearchConverter elasticsearchConverter) {
this.elasticsearchConverter = elasticsearchConverter;
}
/**
* builds the Elasticsearch mapping for the given clazz.
*
* @return JSON string
* @throws IOException
*/
String buildPropertyMapping(Class<?> clazz) throws IOException {
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
.getRequiredPersistentEntity(clazz);
XContentBuilder builder = jsonBuilder().startObject().startObject(entity.getIndexType());
// Dynamic templates
addDynamicTemplatesMapping(builder, entity);
XContentBuilder mapping = jsonBuilder().startObject().startObject(indexType);
// Parent
String parentType = entity.getParentType();
if (hasText(parentType)) {
mapping.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject();
builder.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject();
}
// Properties
XContentBuilder xContentBuilder = mapping.startObject(FIELD_PROPERTIES);
builder.startObject(FIELD_PROPERTIES);
mapEntity(xContentBuilder, clazz, true, idFieldName, "", false, FieldType.Auto, null);
mapEntity(builder, entity, true, "", false, FieldType.Auto, null);
return xContentBuilder.endObject().endObject().endObject();
builder.endObject() // FIELD_PROPERTIES
.endObject() // indexType
.endObject() // root object
.close();
return builder.getOutputStream().toString();
}
private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, boolean isRootObject, String idFieldName,
String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, Field fieldAnnotation) throws IOException {
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, boolean isRootObject,
String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
@Nullable Field parentFieldAnnotation) throws IOException {
java.lang.reflect.Field[] fields = retrieveFields(clazz);
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
if (writeNestedProperties) {
if (!isRootObject && (isAnyPropertyAnnotatedAsField(fields) || nestedOrObjectField)) {
String type = FieldType.Object.toString().toLowerCase();
if (nestedOrObjectField) {
type = fieldType.toString().toLowerCase();
String type = nestedOrObjectField ? fieldType.toString().toLowerCase()
: FieldType.Object.toString().toLowerCase();
builder.startObject(nestedObjectFieldName).field(FIELD_TYPE, type);
if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null
&& parentFieldAnnotation.includeInParent()) {
builder.field("include_in_parent", parentFieldAnnotation.includeInParent());
}
XContentBuilder t = xContentBuilder.startObject(nestedObjectFieldName).field(FIELD_TYPE, type);
if (nestedOrObjectField && FieldType.Nested == fieldType && fieldAnnotation.includeInParent()) {
t.field("include_in_parent", fieldAnnotation.includeInParent());
}
t.startObject(FIELD_PROPERTIES);
builder.startObject(FIELD_PROPERTIES);
}
if (entity != null) {
for (java.lang.reflect.Field field : fields) {
if (field.isAnnotationPresent(Transient.class) || isInIgnoreFields(field, fieldAnnotation)) {
continue;
}
if (field.isAnnotationPresent(Mapping.class)) {
String mappingPath = field.getAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
ClassPathResource mappings = new ClassPathResource(mappingPath);
if (mappings.exists()) {
xContentBuilder.rawField(field.getName(), mappings.getInputStream());
continue;
entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
try {
if (property.isAnnotationPresent(Transient.class) || isInIgnoreFields(property, parentFieldAnnotation)) {
return;
}
buildPropertyMapping(builder, isRootObject, property);
} catch (IOException e) {
logger.warn("error mapping property with name {}", property.getName(), e);
}
}
boolean isGeoPointField = isGeoPointField(field);
boolean isCompletionField = isCompletionField(field);
Field singleField = field.getAnnotation(Field.class);
if (!isGeoPointField && !isCompletionField && isEntity(field) && isAnnotated(field)) {
if (singleField == null) {
continue;
}
boolean nestedOrObject = isNestedOrObjectField(field);
mapEntity(xContentBuilder, getFieldType(field), false, "", field.getName(), nestedOrObject, singleField.type(), field.getAnnotation(Field.class));
if (nestedOrObject) {
continue;
}
}
MultiField multiField = field.getAnnotation(MultiField.class);
if (isGeoPointField) {
applyGeoPointFieldMapping(xContentBuilder, field);
}
if (isCompletionField) {
CompletionField completionField = field.getAnnotation(CompletionField.class);
applyCompletionFieldMapping(xContentBuilder, field, completionField);
}
if (isRootObject && singleField != null && isIdField(field, idFieldName)) {
applyDefaultIdFieldMapping(xContentBuilder, field);
} else if (multiField != null) {
addMultiFieldMapping(xContentBuilder, field, multiField, isNestedOrObjectField(field));
} else if (singleField != null) {
addSingleFieldMapping(xContentBuilder, field, singleField, isNestedOrObjectField(field));
}
});
}
if (!isRootObject && isAnyPropertyAnnotatedAsField(fields) || nestedOrObjectField) {
xContentBuilder.endObject().endObject();
if (writeNestedProperties) {
builder.endObject().endObject();
}
}
private static java.lang.reflect.Field[] retrieveFields(Class clazz) {
// Create list of fields.
List<java.lang.reflect.Field> fields = new ArrayList<>();
private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject,
ElasticsearchPersistentProperty property) throws IOException {
// Keep backing up the inheritance hierarchy.
Class targetClass = clazz;
do {
fields.addAll(Arrays.asList(targetClass.getDeclaredFields()));
targetClass = targetClass.getSuperclass();
if (property.isAnnotationPresent(Mapping.class)) {
String mappingPath = property.getRequiredAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
ClassPathResource mappings = new ClassPathResource(mappingPath);
if (mappings.exists()) {
builder.rawField(property.getFieldName(), mappings.getInputStream(), XContentType.JSON);
return;
}
}
}
while (targetClass != null && targetClass != Object.class);
return fields.toArray(new java.lang.reflect.Field[fields.size()]);
boolean isGeoPointProperty = isGeoPointProperty(property);
boolean isCompletionProperty = isCompletionProperty(property);
boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property);
Field fieldAnnotation = property.findAnnotation(Field.class);
if (!isGeoPointProperty && !isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) {
if (fieldAnnotation == null) {
return;
}
Iterator<? extends TypeInformation<?>> iterator = property.getPersistentEntityTypes().iterator();
ElasticsearchPersistentEntity<?> persistentEntity = iterator.hasNext()
? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next())
: null;
mapEntity(builder, persistentEntity, false, property.getFieldName(), isNestedOrObjectProperty,
fieldAnnotation.type(), fieldAnnotation);
if (isNestedOrObjectProperty) {
return;
}
}
MultiField multiField = property.findAnnotation(MultiField.class);
if (isGeoPointProperty) {
applyGeoPointFieldMapping(builder, property);
return;
}
if (isCompletionProperty) {
CompletionField completionField = property.findAnnotation(CompletionField.class);
applyCompletionFieldMapping(builder, property, completionField);
}
if (isRootObject && fieldAnnotation != null && property.isIdProperty()) {
applyDefaultIdFieldMapping(builder, property);
} else if (multiField != null) {
addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty);
} else if (fieldAnnotation != null) {
addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty);
}
}
private static boolean isAnnotated(java.lang.reflect.Field field) {
return field.getAnnotation(Field.class) != null ||
field.getAnnotation(MultiField.class) != null ||
field.getAnnotation(GeoPointField.class) != null ||
field.getAnnotation(CompletionField.class) != null;
private boolean hasRelevantAnnotation(ElasticsearchPersistentProperty property) {
return property.findAnnotation(Field.class) != null || property.findAnnotation(MultiField.class) != null
|| property.findAnnotation(GeoPointField.class) != null
|| property.findAnnotation(CompletionField.class) != null;
}
private static void applyGeoPointFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field) throws IOException {
xContentBuilder.startObject(field.getName());
xContentBuilder.field(FIELD_TYPE, TYPE_VALUE_GEO_POINT);
xContentBuilder.endObject();
private void applyGeoPointFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_GEO_POINT).endObject();
}
private static void applyCompletionFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field, CompletionField annotation) throws IOException {
xContentBuilder.startObject(field.getName());
xContentBuilder.field(FIELD_TYPE, TYPE_VALUE_COMPLETION);
private void applyCompletionFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
@Nullable CompletionField annotation) throws IOException {
builder.startObject(property.getFieldName());
builder.field(FIELD_TYPE, TYPE_VALUE_COMPLETION);
if (annotation != null) {
xContentBuilder.field(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength());
xContentBuilder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
xContentBuilder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
builder.field(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength());
builder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
builder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
if (!StringUtils.isEmpty(annotation.searchAnalyzer())) {
xContentBuilder.field(FIELD_SEARCH_ANALYZER, annotation.searchAnalyzer());
builder.field(FIELD_SEARCH_ANALYZER, annotation.searchAnalyzer());
}
if (!StringUtils.isEmpty(annotation.analyzer())) {
xContentBuilder.field(FIELD_INDEX_ANALYZER, annotation.analyzer());
builder.field(FIELD_INDEX_ANALYZER, annotation.analyzer());
}
if (annotation.contexts().length > 0) {
builder.startArray(COMPLETION_CONTEXTS);
for (CompletionContext context : annotation.contexts()) {
builder.startObject();
builder.field(FIELD_CONTEXT_NAME, context.name());
builder.field(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
if (context.precision().length() > 0) {
builder.field(FIELD_CONTEXT_PRECISION, context.precision());
}
builder.endObject();
}
builder.endArray();
}
}
xContentBuilder.endObject();
builder.endObject();
}
private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field)
private void applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
xContentBuilder.startObject(field.getName())
.field(FIELD_TYPE, TYPE_VALUE_KEYWORD)
.field(FIELD_INDEX, true);
xContentBuilder.endObject();
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_KEYWORD).field(FIELD_INDEX, true)
.endObject();
}
/**
@@ -229,8 +301,10 @@ class MappingBuilder {
*
* @throws IOException
*/
private static void addSingleFieldMapping(XContentBuilder builder, java.lang.reflect.Field field, Field annotation, boolean nestedOrObjectField) throws IOException {
builder.startObject(field.getName());
private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
Field annotation, boolean nestedOrObjectField) throws IOException {
builder.startObject(property.getFieldName());
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
builder.endObject();
}
@@ -240,14 +314,11 @@ class MappingBuilder {
*
* @throws IOException
*/
private static void addMultiFieldMapping(
XContentBuilder builder,
java.lang.reflect.Field field,
MultiField annotation,
boolean nestedOrObjectField) throws IOException {
private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
MultiField annotation, boolean nestedOrObjectField) throws IOException {
// main field
builder.startObject(field.getName());
builder.startObject(property.getFieldName());
addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField);
// inner fields
@@ -262,7 +333,9 @@ class MappingBuilder {
builder.endObject();
}
private static void addFieldMappingParameters(XContentBuilder builder, Object annotation, boolean nestedOrObjectField) throws IOException {
private void addFieldMappingParameters(XContentBuilder builder, Object annotation, boolean nestedOrObjectField)
throws IOException {
boolean index = true;
boolean store = false;
boolean fielddata = false;
@@ -333,63 +406,59 @@ class MappingBuilder {
}
}
protected static boolean isEntity(java.lang.reflect.Field field) {
TypeInformation typeInformation = ClassTypeInformation.from(field.getType());
Class<?> clazz = getFieldType(field);
boolean isComplexType = !SIMPLE_TYPE_HOLDER.isSimpleType(clazz);
return isComplexType && !Map.class.isAssignableFrom(typeInformation.getType());
}
/**
* Apply mapping for dynamic templates.
*
* @throws IOException
*/
private void addDynamicTemplatesMapping(XContentBuilder builder, ElasticsearchPersistentEntity<?> entity)
throws IOException {
protected static Class<?> getFieldType(java.lang.reflect.Field field) {
if (entity.isAnnotationPresent(DynamicTemplates.class)) {
String mappingPath = entity.getRequiredAnnotation(DynamicTemplates.class).mappingPath();
if (hasText(mappingPath)) {
ResolvableType resolvableType = ResolvableType.forField(field);
String jsonString = ResourceUtil.readFileFromClasspath(mappingPath);
if (hasText(jsonString)) {
if (resolvableType.isArray()) {
return resolvableType.getComponentType().getRawClass();
}
ResolvableType componentType = resolvableType.getGeneric(0);
if (Iterable.class.isAssignableFrom(field.getType())
&& componentType != ResolvableType.NONE) {
return componentType.getRawClass();
}
return resolvableType.getRawClass();
}
private static boolean isAnyPropertyAnnotatedAsField(java.lang.reflect.Field[] fields) {
if (fields != null) {
for (java.lang.reflect.Field field : fields) {
if (field.isAnnotationPresent(Field.class)) {
return true;
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(jsonString).get("dynamic_templates");
if (jsonNode != null && jsonNode.isArray()) {
String json = objectMapper.writeValueAsString(jsonNode);
builder.rawField(FIELD_DYNAMIC_TEMPLATES, new ByteArrayInputStream(json.getBytes()), XContentType.JSON);
}
}
}
}
return false;
}
private static boolean isIdField(java.lang.reflect.Field field, String idFieldName) {
return idFieldName.equals(field.getName());
private boolean isAnyPropertyAnnotatedWithField(@Nullable ElasticsearchPersistentEntity entity) {
return entity != null && entity.getPersistentProperty(Field.class) != null;
}
private static boolean isInIgnoreFields(java.lang.reflect.Field field, Field parentFieldAnnotation) {
private boolean isInIgnoreFields(ElasticsearchPersistentProperty property, @Nullable Field parentFieldAnnotation) {
if (null != parentFieldAnnotation) {
String[] ignoreFields = parentFieldAnnotation.ignoreFields();
return Arrays.asList(ignoreFields).contains(field.getName());
return Arrays.asList(ignoreFields).contains(property.getFieldName());
}
return false;
}
private static boolean isNestedOrObjectField(java.lang.reflect.Field field) {
Field fieldAnnotation = field.getAnnotation(Field.class);
return fieldAnnotation != null && (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type());
private boolean isNestedOrObjectProperty(ElasticsearchPersistentProperty property) {
Field fieldAnnotation = property.findAnnotation(Field.class);
return fieldAnnotation != null
&& (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type());
}
private static boolean isGeoPointField(java.lang.reflect.Field field) {
return field.getType() == GeoPoint.class || field.getAnnotation(GeoPointField.class) != null;
private boolean isGeoPointProperty(ElasticsearchPersistentProperty property) {
return property.getActualType() == GeoPoint.class || property.isAnnotationPresent(GeoPointField.class);
}
private static boolean isCompletionField(java.lang.reflect.Field field) {
return field.getType() == Completion.class;
private boolean isCompletionProperty(ElasticsearchPersistentProperty property) {
return property.getActualType() == Completion.class;
}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -16,7 +16,7 @@
package org.springframework.data.elasticsearch.core;
import java.util.LinkedList;
import java.util.List;
import org.elasticsearch.action.get.MultiGetResponse;
@@ -25,5 +25,5 @@ import org.elasticsearch.action.get.MultiGetResponse;
*/
public interface MultiGetResultMapper {
<T> LinkedList<T> mapResults(MultiGetResponse responses, Class<T> clazz);
<T> List<T> mapResults(MultiGetResponse responses, Class<T> clazz);
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -36,6 +36,7 @@ import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
@@ -50,7 +51,6 @@ import org.elasticsearch.search.sort.SortOrder;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.NoSuchIndexException;
@@ -76,6 +76,10 @@ import org.springframework.util.Assert;
/**
* @author Christoph Strobl
* @author Mark Paluch
* @author Farid Azaza
* @author Martin Choraine
* @author Peter-Josef Meisch
* @author Mathias Teier
* @since 3.2
*/
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations {
@@ -244,68 +248,133 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
@Nullable String type) {
return Flux.defer(() -> {
SearchRequest request = prepareSearchRequest(buildSearchRequest(query, entity, index, type));
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
SearchRequest request = new SearchRequest(indices(query, indexCoordinates::getIndexName));
request.types(indexTypes(query, indexCoordinates::getTypeName));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(mappedQuery(query, entity));
searchSourceBuilder.version(entity.hasVersionProperty());
searchSourceBuilder.trackScores(query.getTrackScores());
QueryBuilder postFilterQuery = mappedFilterQuery(query, entity);
if (postFilterQuery != null) {
searchSourceBuilder.postFilter(postFilterQuery);
}
if (query.getSourceFilter() != null) {
searchSourceBuilder.fetchSource(query.getSourceFilter().getIncludes(), query.getSourceFilter().getExcludes());
}
sort(query, entity).forEach(searchSourceBuilder::sort);
if (query.getMinScore() > 0) {
searchSourceBuilder.minScore(query.getMinScore());
}
if (query.getIndicesOptions() != null) {
request.indicesOptions(query.getIndicesOptions());
}
Pageable pageable = query.getPageable();
if (pageable.isPaged()) {
long offset = pageable.getOffset();
if (offset > Integer.MAX_VALUE) {
throw new IllegalArgumentException(String.format("Offset must not be more than %s", Integer.MAX_VALUE));
}
searchSourceBuilder.from((int) offset);
searchSourceBuilder.size(pageable.getPageSize());
request.source(searchSourceBuilder);
return doFind(prepareSearchRequest(request));
if (query.getPageable().isPaged()) {
return doFind(request);
} else {
request.source(searchSourceBuilder);
return doScan(prepareSearchRequest(request));
return doScroll(request);
}
});
}
@Override
public Mono<Long> count(Query query, Class<?> entityType, String index, String type) {
return doCount(query, getPersistentEntity(entityType), index, type);
}
private Mono<Long> doCount(Query query, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
@Nullable String type) {
return Mono.defer(() -> {
CountRequest countRequest = buildCountRequest(query, entity, index, type);
CountRequest request = prepareCountRequest(countRequest);
return doCount(request);
});
}
private CountRequest buildCountRequest(Query query, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
@Nullable String type) {
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
CountRequest request = new CountRequest(indices(query, indexCoordinates::getIndexName));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(mappedQuery(query, entity));
searchSourceBuilder.trackScores(query.getTrackScores());
QueryBuilder postFilterQuery = mappedFilterQuery(query, entity);
if (postFilterQuery != null) {
searchSourceBuilder.postFilter(postFilterQuery);
}
if (query.getSourceFilter() != null) {
searchSourceBuilder.fetchSource(query.getSourceFilter().getIncludes(), query.getSourceFilter().getExcludes());
}
if (query instanceof SearchQuery && ((SearchQuery) query).getCollapseBuilder() != null) {
searchSourceBuilder.collapse(((SearchQuery) query).getCollapseBuilder());
}
sort(query, entity).forEach(searchSourceBuilder::sort);
if (query.getMinScore() > 0) {
searchSourceBuilder.minScore(query.getMinScore());
}
if (query.getIndicesOptions() != null) {
request.indicesOptions(query.getIndicesOptions());
}
if (query.getPreference() != null) {
request.preference(query.getPreference());
}
request.source(searchSourceBuilder);
return request;
}
private SearchRequest buildSearchRequest(Query query, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
@Nullable String type) {
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
SearchRequest request = new SearchRequest(indices(query, indexCoordinates::getIndexName));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(mappedQuery(query, entity));
searchSourceBuilder.version(entity.hasVersionProperty());
searchSourceBuilder.trackScores(query.getTrackScores());
QueryBuilder postFilterQuery = mappedFilterQuery(query, entity);
if (postFilterQuery != null) {
searchSourceBuilder.postFilter(postFilterQuery);
}
if (query.getSourceFilter() != null) {
searchSourceBuilder.fetchSource(query.getSourceFilter().getIncludes(), query.getSourceFilter().getExcludes());
}
if (query instanceof SearchQuery && ((SearchQuery) query).getCollapseBuilder() != null) {
searchSourceBuilder.collapse(((SearchQuery) query).getCollapseBuilder());
}
sort(query, entity).forEach(searchSourceBuilder::sort);
if (query.getMinScore() > 0) {
searchSourceBuilder.minScore(query.getMinScore());
}
if (query.getIndicesOptions() != null) {
request.indicesOptions(query.getIndicesOptions());
}
if (query.getPreference() != null) {
request.preference(query.getPreference());
}
if (query.getSearchType() != null) {
request.searchType(query.getSearchType());
}
Pageable pageable = query.getPageable();
if (pageable.isPaged()) {
long offset = pageable.getOffset();
if (offset > Integer.MAX_VALUE) {
throw new IllegalArgumentException(String.format("Offset must not be more than %s", Integer.MAX_VALUE));
}
searchSourceBuilder.from((int) offset);
searchSourceBuilder.size(pageable.getPageSize());
request.source(searchSourceBuilder);
} else {
request.source(searchSourceBuilder);
}
return request;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#count(Query, Class, String, String)
*/
@Override
public Mono<Long> count(Query query, Class<?> entityType, String index, String type) {
// TODO: ES 7.0 has a dedicated CountRequest - use that one once available.
return find(query, entityType, index, type).count();
}
/*
* (non-Javadoc)
@@ -442,6 +511,22 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
return prepareWriteRequest(request);
}
/**
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
*
* @param request the generated {@link CountRequest}.
* @return never {@literal null}.
*/
protected CountRequest prepareCountRequest(CountRequest request) {
if (indicesOptions == null) {
return request;
}
return request.indicesOptions(indicesOptions);
}
/**
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
@@ -542,16 +627,32 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
}
/**
* Customization hook on the actual execution result {@link Publisher}. <br />
*
* @param request the already prepared {@link CountRequest} ready to be executed.
* @return a {@link Mono} emitting the result of the operation.
*/
protected Mono<Long> doCount(CountRequest request) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doCount: {}", request);
}
return Mono.from(execute(client -> client.count(request))) //
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
}
/**
* Customization hook on the actual execution result {@link Publisher}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @return a {@link Flux} emitting the result of the operation.
*/
protected Flux<SearchHit> doScan(SearchRequest request) {
protected Flux<SearchHit> doScroll(SearchRequest request) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doScan: {}", request);
QUERY_LOGGER.debug("Executing doScroll: {}", request);
}
return Flux.from(execute(client -> client.scroll(request))) //
@@ -650,9 +751,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
elasticsearchQuery = new WrapperQueryBuilder(((StringQuery) query).getSource());
} else if (query instanceof NativeSearchQuery) {
elasticsearchQuery = ((NativeSearchQuery) query).getQuery();
}
else {
} else {
throw new IllegalArgumentException(String.format("Unknown query type '%s'.", query.getClass()));
}
@@ -0,0 +1,56 @@
/*
* 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 java.io.InputStream;
import java.nio.charset.Charset;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;
/**
* Utility to read {@link org.springframework.core.io.Resource}s.
*
* @author Mark Paluch
* @since 3.2
*/
abstract class ResourceUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtil.class);
/**
* Read a {@link ClassPathResource} into a {@link String}.
*
* @param url
* @return
*/
public static String readFileFromClasspath(String url) {
ClassPathResource classPathResource = new ClassPathResource(url);
try (InputStream is = classPathResource.getInputStream()) {
return StreamUtils.copyToString(is, Charset.defaultCharset());
} catch (Exception e) {
LOGGER.debug(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
return null;
}
}
// Utility constructor
private ResourceUtil() {}
}
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,

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