1
0
mirror of synced 2026-05-24 21:23:18 +00:00

Compare commits

..

123 Commits

Author SHA1 Message Date
Peter-Josef Meisch 6c40f0bfe0 Use registered converters for parameters of @Query annotated methods.
Original Pull Request #1867
Closes #1866

(cherry picked from commit 27094724dc)
(cherry picked from commit 303438ae63)
(cherry picked from commit 626b274677)
2021-07-14 20:35:35 +02:00
Niklas Herder d6cfc20a69 Support collection parameters in @Query methods.
Original Pull Request #1856
Closes #1858

(cherry picked from commit 6f84a1c589)
(cherry picked from commit 254948d1c9)
(cherry picked from commit 979c164135)
2021-07-03 18:35:14 +02:00
Mark Paluch 5578ad19cd Updated changelog.
See #1775
2021-05-14 12:36:41 +02:00
Mark Paluch 194c47fae2 Updated changelog.
See #1774
2021-05-14 12:06:45 +02:00
Peter-Josef Meisch a6adcef67d SearchPage result in StringQuery methods.
Original Pull Request #1812
Closes #1811

(cherry picked from commit e96d09fa51)
(cherry picked from commit ad6022f64c)
(cherry picked from commit 26a3b324b7)
2021-05-14 06:59:22 +02:00
Peter-Josef Meisch 934086be81 Escape strings with quotes in custom query parameters.
Original Pull Request #1793
Closes #1790

(cherry picked from commit f8fbf7721a)
(cherry picked from commit 40972b21e0)
2021-04-29 06:57:33 +02:00
Peter-Josef Meisch 975a8dd9aa Search with MoreLikeThisQuery should use Pageable.
Original Pull Request #1789
Closes #1787

(cherry picked from commit a2ca312fb2)
(cherry picked from commit 85af54635d)
(cherry picked from commit 2cb9e30b61)
2021-04-26 23:19:36 +02:00
Peter-Josef Meisch ba84c1cdfe Fix documentation.
Original Pull Request #1786
Closes #1785

(cherry picked from commit 8b7f0f8327)
2021-04-23 17:53:14 +02:00
Greg L. Turnquist 8bdd73344e Authenticate with artifactory.
See #1750.
2021-04-20 10:43:55 -05:00
Peter-Josef Meisch dab5dc4ad2 DynamicMapping annotation should be applicable to any object field.
Original Pull Request #1779
Closes #1767
2021-04-17 20:20:48 +02:00
Mark Paluch 776252b4d6 Updated changelog.
See #1750
2021-04-14 14:40:04 +02:00
Mark Paluch f87abf879b Updated changelog.
See #1751
2021-04-14 11:43:36 +02:00
Mark Paluch 39307b73df After release cleanups.
See #1730
2021-04-14 11:16:06 +02:00
Mark Paluch 2b61b58229 Prepare next development iteration.
See #1730
2021-04-14 11:16:03 +02:00
Mark Paluch 3879313782 Release version 4.0.9 (Neumann SR9).
See #1730
2021-04-14 11:04:47 +02:00
Mark Paluch 65842e6905 Prepare 4.0.9 (Neumann SR9).
See #1730
2021-04-14 11:04:09 +02:00
Mark Paluch 61d0aa0852 Updated changelog.
See #1730
2021-04-14 11:04:05 +02:00
Peter-Josef Meisch b7ec0a9013 Fix reactive connection handling.
Original Pull Request #1766
Closes #1759

(cherry picked from commit 58bca88386)
2021-04-09 00:17:57 +02:00
Mark Paluch 3e20810452 Updated changelog.
See #1731
2021-03-31 18:30:49 +02:00
Mark Paluch 804e5bdc3b Updated changelog.
See #1699
2021-03-31 17:26:09 +02:00
Mark Paluch 121cd8aa50 Updated changelog.
See #1709
2021-03-17 11:31:32 +01:00
Mark Paluch 187b0befc6 Updated changelog.
See #1702
2021-03-17 11:03:44 +01:00
Mark Paluch 5bd0eb3115 After release cleanups.
See #1697
2021-03-17 10:33:58 +01:00
Mark Paluch cd4f0cd7e3 Prepare next development iteration.
See #1697
2021-03-17 10:33:56 +01:00
Mark Paluch 3a749bb963 Release version 4.0.8 (Neumann SR8).
See #1697
2021-03-17 10:21:34 +01:00
Mark Paluch 362d0543de Prepare 4.0.8 (Neumann SR8).
See #1697
2021-03-17 10:21:09 +01:00
Mark Paluch 4c0e9629d4 Updated changelog.
See #1697
2021-03-17 10:21:07 +01:00
Peter-Josef Meisch 3ca345b216 DefaultReactiveElasticsearchClient handle 5xx error with empty body
Original Pull Request #1713
Closes #1712

(cherry picked from commit 6634d0075a)
Test adapted
2021-03-03 06:53:21 +01:00
Christoph Strobl 82d9cb4cc6 Updated changelog.
See #1701
2021-02-18 11:37:50 +01:00
Christoph Strobl dd1f309810 Updated changelog.
See #1698
2021-02-18 11:18:33 +01:00
Christoph Strobl f91964719a Updated changelog.
See #1643
2021-02-17 14:20:37 +01:00
Christoph Strobl a02e190c49 Updated changelog.
See #1642
2021-02-17 13:49:24 +01:00
Christoph Strobl 64bf8139b2 After release cleanups.
See #1570
2021-02-17 11:32:50 +01:00
Christoph Strobl a597a4fee2 Prepare next development iteration.
See #1570
2021-02-17 11:32:49 +01:00
Christoph Strobl cfa3a1e762 Release version 4.0.7 (Neumann SR7).
See #1570
2021-02-17 11:07:42 +01:00
Christoph Strobl 401c689211 Prepare 4.0.7 (Neumann SR7).
See #1570
2021-02-17 11:07:10 +01:00
Christoph Strobl 24a4d150ef Updated changelog.
See #1570
2021-02-17 11:07:09 +01:00
Christoph Strobl 057455ec74 Updated changelog.
See #1569
2021-02-17 10:58:27 +01:00
Peter-Josef Meisch 40cff583f4 Allow CustomConversions for entities - adaption for 4.0.x. 2021-01-29 11:55:10 +01:00
Peter-Josef Meisch eefd5a2187 Allow CustomConversions for entities.
Original PullRequest #1672
Closes #1667

(cherry picked from commit 0ac1b4af00)
2021-01-29 11:43:47 +01:00
Peter-Josef Meisch fc1f65f87d ReactiveElasticsearchOperations indexName is encoded twice.
Original Pull Request #1666
Closes #1665

(cherry picked from commit 4829b07e53)
2021-01-25 22:24:27 +01:00
Peter-Josef Meisch e80e32eb97 Fix source filter setup in multiget requests.
Original Pull Request #1664
Closes #1659

(cherry picked from commit 1a02c1e05a)
2021-01-24 20:22:17 +01:00
Peter-Josef Meisch ef2600f091 Documentation fix.
Original Pull Request #1663
Closes #1662

(cherry picked from commit 1aabb42355)
2021-01-23 20:12:29 +01:00
Christoph Strobl ce67d8145d Updated changelog.
See #1571
2021-01-13 15:49:52 +01:00
Christoph Strobl e7417f8b73 Updated changelog.
See #1572
2021-01-13 15:16:30 +01:00
Greg L. Turnquist 2521f0760e DATAES-996 - Polishing. 2020-12-17 09:06:01 -06:00
Greg L. Turnquist c19ac47009 DATAES-996 - Use Docker hub credentials for all CI jobs. 2020-12-17 08:43:31 -06:00
Mark Paluch 4603526e88 DATAES-973 - Updated changelog. 2020-12-09 16:47:48 +01:00
Mark Paluch 39652ff48f DATAES-966 - Updated changelog. 2020-12-09 15:33:29 +01:00
Mark Paluch f92153eb4e DATAES-964 - After release cleanups. 2020-12-09 12:41:27 +01:00
Mark Paluch 84df3daccd DATAES-964 - Prepare next development iteration. 2020-12-09 12:41:22 +01:00
Mark Paluch 8c378033a4 DATAES-964 - Release version 4.0.6 (Neumann SR6). 2020-12-09 11:16:22 +01:00
Mark Paluch ed620a5574 DATAES-964 - Prepare 4.0.6 (Neumann SR6). 2020-12-09 11:15:56 +01:00
Mark Paluch f61ed32bab DATAES-964 - Updated changelog. 2020-12-09 11:15:53 +01:00
Mark Paluch 64fc98a8fa DATAES-963 - Updated changelog. 2020-12-09 09:59:16 +01:00
Peter-Josef Meisch 242cf7706b DATAES-991 - Wrong value for TermVector(with_positions_offets_payloads).
Original PR: #564

(cherry picked from commit 6a6ead5e1e)
2020-12-04 08:51:16 +01:00
Mark Paluch 1e6271edf2 DATAES-965 - Updated changelog. 2020-11-11 12:34:45 +01:00
Peter-Josef Meisch 7168c34ee6 DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree.
Original PR: #546

(cherry picked from commit d036693f05)
2020-11-07 18:47:20 +01:00
Mark Paluch fd118e67e5 DATAES-968 - Enable Maven caching for Jenkins jobs. 2020-10-30 08:37:40 +01:00
Mark Paluch 232f192a44 DATAES-950 - Updated changelog. 2020-10-28 16:28:06 +01:00
Mark Paluch 49a5bf642b DATAES-926 - After release cleanups. 2020-10-28 14:51:07 +01:00
Mark Paluch 71fa62262b DATAES-926 - Prepare next development iteration. 2020-10-28 14:51:04 +01:00
Mark Paluch d356b6c1b0 DATAES-926 - Release version 4.0.5 (Neumann SR5). 2020-10-28 14:34:43 +01:00
Mark Paluch a45f721122 DATAES-926 - Prepare 4.0.5 (Neumann SR5). 2020-10-28 14:34:16 +01:00
Mark Paluch 2dc4b57f2d DATAES-926 - Updated changelog. 2020-10-28 14:34:13 +01:00
Mark Paluch 01fd3d4121 DATAES-925 - Updated changelog. 2020-10-28 12:15:08 +01:00
Mark Paluch b48d4aed54 DATAES-958 - Updated changelog. 2020-10-28 11:32:35 +01:00
Peter-Josef Meisch 3cbf9dd0cc DATAES-953 - DateTimeException on converting Instant or Date to custom format.
Original PR: #538

(cherry picked from commit 9bc4bee86f)
2020-10-15 23:16:27 +02:00
Christoph Strobl 1d1268075f DATAES-927 - Updated changelog. 2020-10-14 14:51:53 +02:00
Peter-Josef Meisch e293da89f8 DATAES-937 - Repository queries with IN filters fail with empty input list.
Original PR: #525

(cherry picked from commit 7117e5d70d)
2020-09-24 22:37:14 +02:00
Peter-Josef Meisch a88612df71 DATAES-936 - Take id property from the source when deserializing an entity.
Original PR: #523

(cherry picked from commit 8d4c305732)
2020-09-23 20:08:54 +02:00
Mark Paluch 084dd1db56 DATAES-904 - Updated changelog. 2020-09-16 14:12:13 +02:00
Mark Paluch edd43b7e92 DATAES-905 - After release cleanups. 2020-09-16 12:15:46 +02:00
Mark Paluch e3b780050d DATAES-905 - Prepare next development iteration. 2020-09-16 12:15:43 +02:00
Mark Paluch 229fd977cd DATAES-905 - Release version 4.0.4 (Neumann SR4). 2020-09-16 11:43:15 +02:00
Mark Paluch c80c821c30 DATAES-905 - Prepare 4.0.4 (Neumann SR4). 2020-09-16 11:42:47 +02:00
Mark Paluch b12431dfec DATAES-905 - Updated changelog. 2020-09-16 11:42:44 +02:00
Mark Paluch 1120ce402b DATAES-888 - Updated changelog. 2020-09-16 11:20:14 +02:00
Mark Paluch e5593a07a1 DATAES-887 - Updated changelog. 2020-09-16 10:39:06 +02:00
Peter-Josef Meisch fa0fdd8c82 DATAES-924 - Conversion of properties of collections of Temporal values fails.
Original PR: #519

(cherry picked from commit 0e7791a687)
2020-09-15 23:25:00 +02:00
Peter-Josef Meisch 8686650261 DATAES-912 - Derived Query with "In" Keyword does not work on Text field.
Original PR: #510

(cherry picked from commit 79fdc449b8)
2020-08-24 07:32:33 +02:00
Mark Paluch 3a522cd432 DATAES-890 - After release cleanups. 2020-08-12 13:19:59 +02:00
Mark Paluch 0a1eec8f0b DATAES-890 - Prepare next development iteration. 2020-08-12 13:19:56 +02:00
Mark Paluch 63a3daf20a DATAES-890 - Release version 4.0.3 (Neumann SR3). 2020-08-12 13:07:48 +02:00
Mark Paluch 4d5638c6d7 DATAES-890 - Prepare 4.0.3 (Neumann SR3). 2020-08-12 13:07:22 +02:00
Mark Paluch 5180b2f8cd DATAES-890 - Updated changelog. 2020-08-12 13:07:18 +02:00
Mark Paluch 8eaf09cfc4 DATAES-872 - Updated changelog. 2020-08-12 12:01:28 +02:00
Peter-Josef Meisch 383fe3132e DATAES-896 - Use mainField property of @MultiField annotation.
Original PR: #500

(cherry picked from commit fd23c10c16)
2020-08-09 16:40:05 +02:00
Peter-Josef Meisch 96ce05794e DATAES-897 - Add documentation for Highlight annotation.
Original PR: #499

(cherry picked from commit fd77f62cc4)
2020-08-08 20:06:40 +02:00
Peter-Josef Meisch 4f29f0d60c DATAES-891 - Returning a Stream from a Query annotated repository method crashes.
Original PR: #497

(cherry picked from commit f989cf873b)
2020-07-29 13:07:41 +02:00
Mark Paluch 886503c41c DATAES-862 - After release cleanups. 2020-07-22 10:37:09 +02:00
Mark Paluch c429436f1c DATAES-862 - Prepare next development iteration. 2020-07-22 10:37:06 +02:00
Mark Paluch afa611ce09 DATAES-862 - Release version 4.0.2 (Neumann SR2). 2020-07-22 10:21:10 +02:00
Mark Paluch dc9db5dcdc DATAES-862 - Prepare 4.0.2 (Neumann SR2). 2020-07-22 10:20:45 +02:00
Mark Paluch 4ee592cd21 DATAES-862 - Updated changelog. 2020-07-22 10:20:41 +02:00
Mark Paluch cd7b6f8420 DATAES-861 - Updated changelog. 2020-07-22 10:08:51 +02:00
Mark Paluch 237c0ead2e DATAES-860 - Updated changelog. 2020-07-22 09:44:37 +02:00
Peter-Josef Meisch 6462305521 DATAES-883 - Fix log level on resource load error.
Original PR: #493

(cherry picked from commit 0f940b36d7)
2020-07-10 21:20:42 +02:00
Peter-Josef Meisch 0a2038505f DATAES-878 - Wrong value for TermVector.
Original PR: #492

(cherry picked from commit df4e6c449d)
2020-07-02 06:45:15 +02:00
Mark Paluch 8276023132 DATAES-824 - Updated changelog. 2020-06-25 12:00:26 +02:00
Peter-Josef Meisch ae94120d91 DATAES-865 - Polishing.
(cherry picked from commit 92f16846ab)
2020-06-16 18:59:16 +02:00
Been24 d2df9e7f4c DATAES-865 - Fix MappingElasticsearchConverter writing an Object property containing a Map.
Original PR: #482

(cherry picked from commit 1de1aeb2c7)
2020-06-16 18:59:03 +02:00
Peter-Josef Meisch 73fc8f65ee DATAES-863 - Improve server error response handling.
Original PR: #480

(cherry picked from commit 3c44a1c969)
2020-06-11 19:16:11 +02:00
Mark Paluch 4d2e4ac22c DATAES-823 - After release cleanups. 2020-06-10 14:29:30 +02:00
Mark Paluch 8d02946186 DATAES-823 - Prepare next development iteration. 2020-06-10 14:29:27 +02:00
Mark Paluch 3ac4e12e08 DATAES-823 - Release version 4.0.1 (Neumann SR1). 2020-06-10 14:02:28 +02:00
Mark Paluch bb69482b7b DATAES-823 - Prepare 4.0.1 (Neumann SR1). 2020-06-10 14:02:00 +02:00
Mark Paluch 20f3298f72 DATAES-823 - Updated changelog. 2020-06-10 14:01:56 +02:00
Mark Paluch 3178707172 DATAES-807 - Updated changelog. 2020-06-10 12:29:56 +02:00
Mark Paluch b60da78c5b DATAES-806 - Updated changelog. 2020-06-10 11:40:30 +02:00
Peter-Josef Meisch 8e765cf07c DATAES-857 - Registered simple types are not read from list.
Original PR: #478

(cherry picked from commit 407c8c6c17)
2020-06-09 16:31:14 +02:00
Peter-Josef Meisch ff999959a8 DATAES-850 - Add warning and docs for missing TemporalAccessor configuration.
Original PR: #472

(cherry picked from commit 859b22db8e)
2020-05-31 23:06:38 +02:00
Peter-Josef Meisch 333aba2c59 DATAES-845 - MappingElasticsearchConverter handles lists with null values.
Original PR: #470

(cherry picked from commit 852273eff5)
2020-05-29 19:12:24 +02:00
Mark Paluch e3e646eb72 DATAES-844 - Improve TOC formatting for migration guides. 2020-05-26 16:23:12 +02:00
Peter-Josef Meisch b918605efd DATAES-839 - ReactiveElasticsearchTemplate should use RequestFactory.
Original PR: #466

cherrypicked from dc6734db43
2020-05-21 12:32:30 +02:00
Peter-Josef Meisch c9667755f2 DATAES-835 - Fix code sample in documentation for scroll API.
Original PR: #462
2020-05-20 08:43:03 +02:00
Peter-Josef Meisch 421333dadc DATAES-832 - findAllById repository method returns iterable with null elements for not found ids. 2020-05-18 18:05:30 +02:00
Peter-Josef Meisch 34e3dc735c DATAES-832 - findAllById repository method returns iterable with null elements for not found ids. 2020-05-17 20:01:47 +02:00
Peter-Josef Meisch e7110c14ab DATAES-831 - SearchOperations.searchForStream does not use requested maxResults.
Original PR: #459

(cherry picked from commit 506f79a45a)
2020-05-17 10:53:29 +02:00
Peter-Josef Meisch 1cee4057d9 DATAES-828 - Fields of type date need to have a format defined.
Original PR: #457
2020-05-14 20:30:30 +02:00
Peter-Josef Meisch 68ce0c2184 DATAES-826 - Repositories should not try to create an index when it already exists.
original PR: #456

(cherry picked from commit c7339dc248)
2020-05-14 18:06:51 +02:00
Mark Paluch 9adfa0b389 DATAES-808 - After release cleanups. 2020-05-12 12:50:35 +02:00
Mark Paluch d28f643997 DATAES-808 - Prepare next development iteration. 2020-05-12 12:40:53 +02:00
532 changed files with 12077 additions and 34607 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ Make sure that:
-->
- [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc).
- [ ] There is a ticket in the bug tracker for the project in our [issue tracker](https://github.com/spring-projects/spring-data-elasticsearch/issues).
- [ ] There is a ticket in the bug tracker for the project in our [JIRA](https://jira.spring.io/browse/DATAES).
- [ ] You use the code formatters provided [here](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide) and have them applied to your changes. Dont submit any formatting related changes.
- [ ] You submit test cases (unit or integration tests) that back your changes.
- [ ] You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).
+4 -4
View File
@@ -1,8 +1,8 @@
= Continuous Integration
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=2020.0.0%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F4.0.x&subject=Neumann%20(4.0.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F3.2.x&subject=Moore%20(3.2.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
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
@@ -30,7 +30,7 @@ Since the container is binding to your source, you can make edits from your IDE
If you need to package things up, do this:
1. `docker run -it -v /var/run/docker.sock:/var/run/docker.sock --mount type=bind,source="$(pwd)",target=/spring-data-elasticsearch-github adoptopenjdk/openjdk8:latest /bin/bash`
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`.
+
+27
View File
@@ -0,0 +1,27 @@
= Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses,
without explicit permission
* Other unethical or unprofessional conduct
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at spring-code-of-conduct@pivotal.io.
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 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/].
-4
View File
@@ -1,7 +1,3 @@
= Spring Data contribution guidelines
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here].
== Running the test locally
In order to run the tests locally with `./mvnw test` you need to have docker running because Spring Data Elasticsearch uses https://www.testcontainers.org/[Testcontainers] to start a local running Elasticsearch instance.
Vendored
+8 -8
View File
@@ -3,7 +3,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/2.5.x", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.3.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -15,7 +15,7 @@ pipeline {
stage("test: baseline (jdk8)") {
when {
anyOf {
branch '4.2.x'
branch '4.0.x'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -44,8 +44,8 @@ pipeline {
stage("Test other configurations") {
when {
allOf {
branch '4.2.x'
anyOf {
branch '4.0.x'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -74,7 +74,7 @@ pipeline {
}
}
stage("test: baseline (jdk16)") {
stage("test: baseline (jdk12)") {
agent {
label 'data'
}
@@ -88,7 +88,7 @@ pipeline {
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
docker.image('adoptopenjdk/openjdk12:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=java11 ci/verify.sh'
sh "ci/clean.sh"
@@ -103,7 +103,7 @@ pipeline {
stage('Release to artifactory') {
when {
anyOf {
branch '4.2.x'
branch '4.0.x'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -135,7 +135,7 @@ pipeline {
}
stage('Publish documentation') {
when {
branch '4.2.x'
branch '4.0.x'
}
agent {
label 'data'
+10 -17
View File
@@ -19,7 +19,7 @@ This project is lead and maintained by the community.
== Code of Conduct
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[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.
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
@@ -114,7 +114,7 @@ Add the Maven dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${version}</version>
<version>${version}.RELEASE</version>
</dependency>
----
@@ -123,7 +123,7 @@ Add the Maven dependency:
// 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/current/reference/html/#preface.versions[reference documentation].
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:
@@ -149,7 +149,7 @@ If you'd rather like the latest snapshots of the upcoming major version, use our
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${version}-SNAPSHOT</version>
<version>${version}.BUILD-SNAPSHOT</version>
</dependency>
<repository>
@@ -170,17 +170,17 @@ If you are just starting out with Spring, try one of the https://spring.io/guide
* 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://github.com/spring-projects/spring-data-elasticsearch/issues[https://github.com/spring-projects/spring-data-elasticsearch/issues].
* Report bugs with Spring Data for Elasticsearch at https://jira.spring.io/browse/DATAES[jira.spring.io/browse/DATAES].
== Reporting Issues
Spring Data uses GitHub as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below:
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://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] to see if someone has already reported the problem.
* If the issue doesnt already exist, https://github.com/spring-projects/spring-data-elasticsearch/issues/new[create a new issue].
* Please provide as much information as possible with the issue report, we like to know the version of Spring Data Elasticsearch that you are using and JVM version.
* If you need to paste code, or include a stack trace use Markdown +++```+++ escapes before and after your text.
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
@@ -197,13 +197,6 @@ If you want to build with the regular `mvn` command, you will need https://maven
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributors Agreement] before submitting your first pull request._
IMPORTANT: When contributing, please make sure an issue exists in https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] and comment on this issue with how you want to address it. By this we not only know that someone is working on an issue, we can also align architectural questions and possible solutions before work is invested
. We
so
can prevent that much work is put into Pull Requests that have little
or no chances of being merged.
=== Building reference documentation
Building the documentation builds also the project without running tests.
-41
View File
@@ -1,41 +0,0 @@
= Testing
== Unit tests
Unit tests in the project are run with
----
./mvnw test
----
== Integration tests
Integration tests are executed when
----
./mvnw verify
----
is run. There must be _docker_ running, as the integration tests use docker to start an Elasticsearch server.
Integration tests are tests that have the Junit5 Tag `@Tag("integration-test")` on the test class. Normally this should not be set explicitly, but the annotation `@SpringIntegrationTest` should be used. This not only marks the test as integration test, but integrates an automatic setup of an Elasticsearch Testcontainer and integrate this with Spring, so
that the required Beans can be automatically injected. Check _src/test/java/org/springframework/data/elasticsearch/JUnit5SampleRestClientBasedTests.java_ as a reference setup
== Mutation testing
The pom includes a plugin dependency to run mutation tests using [pitest](https://pitest.org/). These tests must be explicitly configured and run, they are not included in the normal build steps. Before pitest can run, a normal `./mvnw test` must be executed. The configuration excludes integration tests, only unit tests are considered.
pitest can be run directly from the commandline
----
./mvnw org.pitest:pitest-maven:mutationCoverage
----
This will output an html report to _target/pit-reports/YYYYMMDDHHMI_.
To speed-up repeated analysis of the same codebase set the withHistory parameter to true.
----
./mvnw -DwithHistory org.pitest:pitest-maven:mutationCoverage
----
The classes to test are defined either in the pom.xml or can be set on the commandline:
----
./mvnw -DwithHistory org.pitest:pitest-maven:mutationCoverage -DtargetClasses="org.springframework.data.elasticsearch.support.*"
----
+18 -130
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.2.2</version>
<version>4.0.10.BUILD-SNAPSHOT</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.5.2</version>
<version>2.3.10.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -19,11 +19,10 @@
<properties>
<commonslang>2.6</commonslang>
<elasticsearch>7.12.1</elasticsearch>
<log4j>2.13.3</log4j>
<netty>4.1.52.Final</netty>
<springdata.commons>2.5.2</springdata.commons>
<testcontainers>1.15.1</testcontainers>
<elasticsearch>7.6.2</elasticsearch>
<log4j>2.9.1</log4j>
<springdata.commons>2.3.10.BUILD-SNAPSHOT</springdata.commons>
<netty>4.1.39.Final</netty>
<java-module-name>spring.data.elasticsearch</java-module-name>
</properties>
@@ -70,11 +69,12 @@
</ciManagement>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/spring-projects/spring-data-elasticsearch/issues</url>
<system>JIRA</system>
<url>https://jira.spring.io/browse/DATAES</url>
</issueManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
@@ -121,7 +121,7 @@
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty-http</artifactId>
<artifactId>reactor-netty</artifactId>
<optional>true</optional>
</dependency>
@@ -152,6 +152,12 @@
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -173,7 +179,6 @@
</exclusions>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
@@ -199,21 +204,6 @@
</dependency>
<!-- CDI -->
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_2.0_spec</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
@@ -222,20 +212,6 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax-annotation-api}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework</groupId>
@@ -249,30 +225,6 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j}</version>
<scope>test</scope>
</dependency>
<!--
we don't use lombok in Spring Data Elasticsearch anymore. But the dependency is set in the parent project, and so the
lombok compiler stuff is executed regardless of the fact that we don't need it.
On AdoptOpenJdk 16.0.0 this leads to an error, so the project does not build.
Therefore we replace lombok with a jar - that just contains an empty file - that lives in a local maven repository in
src/test/resources/local-maven-repo/
It was installed with
mvn deploy:deploy-file -DgroupId=org.projectlombok -DartifactId=lombok -Dversion=999999 -Durl=file:./src/test/resources/local-maven-repo/ -DrepositoryId=local-maven-repo -DupdateReleaseInfo=true -Dfile=path/to/empty.jar
-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>999999</version>
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
@@ -289,7 +241,6 @@
</exclusion>
</exclusions>
</dependency>
-->
<dependency>
<groupId>org.skyscreamer</groupId>
@@ -316,13 +267,6 @@
</exclusions>
</dependency>
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java-junit5</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>
<!-- Upgrade xbean to 4.5 to prevent incompatibilities due to ASM versions -->
<dependency>
<groupId>org.apache.xbean</groupId>
@@ -345,13 +289,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>elasticsearch</artifactId>
<version>${testcontainers}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -390,51 +327,7 @@
<es.set.netty.runtime.available.processors>false</es.set.netty.runtime.available.processors>
</systemPropertyVariables>
</configuration>
<executions>
<!-- the default-test execution runs only the unit tests -->
<execution>
<id>default-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<excludedGroups>integration-test</excludedGroups>
</configuration>
</execution>
<!-- execution to run the integration tests -->
<execution>
<id>integration-test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>integration-test</groups>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.5.2</version>
<dependencies>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>
<version>0.12</version>
</dependency>
</dependencies>
<configuration>
<excludedGroups>integration-test</excludedGroups>
<targetClasses>
<param>org.springframework.data.elasticsearch.core.geo.*</param>
</targetClasses>
<excludedMethods>toString</excludedMethods>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
@@ -480,13 +373,8 @@
<repositories>
<repository>
<id>spring-libs-release</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>local-maven-repo</id>
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
+1 -3
View File
@@ -5,7 +5,7 @@ BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Stro
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
(C) 2013-2021 The original author(s).
(C) 2013-2020 The original author(s).
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
@@ -33,8 +33,6 @@ include::reference/elasticsearch-auditing.adoc[]
include::{spring-data-commons-docs}/entity-callbacks.adoc[]
include::reference/elasticsearch-entity-callbacks.adoc[leveloffset=+1]
include::reference/elasticsearch-join-types.adoc[]
include::reference/elasticsearch-routing.adoc[]
include::reference/elasticsearch-misc.adoc[]
:leveloffset: -1
+13 -14
View File
@@ -1,8 +1,7 @@
[[preface]]
= Preface
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine.
It provides:
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. It provides:
* _Templates_ as a high-level abstraction for storing, searching, sorting documents and building aggregations.
* _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>).
@@ -16,7 +15,7 @@ include::reference/elasticsearch-new.adoc[leveloffset=+1]
* Version Control - https://github.com/spring-projects/spring-data-elasticsearch
* API Documentation - https://docs.spring.io/spring-data/elasticsearch/docs/current/api/
* Bugtracker - https://github.com/spring-projects/spring-data-elasticsearch/issues
* 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
@@ -28,19 +27,19 @@ Requires an installation of https://www.elastic.co/products/elasticsearch[Elasti
[[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 referring to that particular Spring Data release train:
[cols="^,^,^,^,^",options="header"]
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 Framework | Spring Boot
| 2021.0 (Pascal) | 4.2.1 | 7.12.1 | 5.3.7 | 2.5.x
| 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x
| Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x
| Moore | 3.2.x |6.8.12 | 5.2.12| 2.2.x
| Lovelacefootnote:oom[Out of maintenance] | 3.1.xfootnote:oom[] | 6.2.2 | 5.1.19 |2.1.x
| Kayfootnote:oom[] | 3.0.xfootnote:oom[] | 5.5.0 | 5.0.13 | 2.0.x
| Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 4.3.25 | 1.5.x
| Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
| Neumannfootnote:cdv[Currently in development] |4.0.xfootnote:cdv[]|7.6.2 |2.3.xfootnote:cdv[]
| Moore | 3.2.x |6.8.6 | 2.2.x
| Lovelace | 3.1.x | 6.2.2 |2.1.x
| Kayfootnote:oom[Out of maintenance] | 3.0.xfootnote:oom[] | 5.5.0 | 2.0.xfootnote:oom[]
| Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 1.5.xfootnote:oom[]
|===
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the <<elasticsearch.clients.rest,high-level REST client>>.
@@ -3,7 +3,7 @@
=== Preparing entities
In order for the auditing code to be able to decide whether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
In order for the auditing code to be able to decide wether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
[source,java]
----
@@ -41,22 +41,22 @@ public class Person implements Persistable<Long> {
@LastModifiedBy
private String lastModifiedBy;
public Long getId() { // <.>
public Long getId() { <1>
return id;
}
@Override
public boolean isNew() {
return id == null || (createdDate == null && createdBy == null); // <.>
return id == null || (createdDate == null && createdBy == null); <2>
}
}
----
<.> the getter is the required implementation from the interface
<.> an object is new if it either has no `id` or none of fields containing creation attributes are set.
<1> the getter also is the required implementation from the interface
<2> an object is new if it either has no `id` or none of fields containing creation attributes are set.
=== Activating auditing
After the entities have been set up and providing the `AuditorAware` - or `ReactiveAuditorAware` - the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
After the entities have been set up and providing the `AuditorAware` the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
[source,java]
----
@@ -68,16 +68,5 @@ class MyConfiguration {
}
----
When using the reactive stack this must be:
[source,java]
----
@Configuration
@EnableReactiveElasticsearchRepositories
@EnableReactiveElasticsearchAuditing
class MyConfiguration {
// configuration code
}
----
If your code contains more than one `AuditorAware` bean for different types, you must provide the name of the bean to use as an argument to the `auditorAwareRef` parameter of the
`@EnableElasticsearchAuditing` annotation.
@@ -8,7 +8,7 @@ Spring Data Elasticsearch operates upon an Elasticsearch client that is connecte
[[elasticsearch.clients.transport]]
== Transport Client
WARNING: The `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
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>> but has deprecated the classes using it since version 4.0.
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
@@ -22,33 +22,28 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build(); <.>
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build(); <1>
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); <.>
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); <2>
return client;
}
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
template.setRefreshPolicy(refreshPolicy()); <.>
return template;
return new ElasticsearchTemplate(elasticsearchClient());
}
}
// ...
IndexRequest request = new IndexRequest("spring-data")
.id(randomID())
.source(someObject);
IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
.source(someObject)
.setRefreshPolicy(IMMEDIATE);
IndexResponse response = client.index(request);
----
<.> The `TransportClient` must be configured with the cluster name.
<.> The host and port to connect the client to.
<.> the RefreshPolicy must be set in the `ElasticsearchTemplate` (override `refreshPolicy()` to not use the default)
<1> The `TransportClient` must be configured with the cluster name.
<2> The host and port to connect the client to.
====
[[elasticsearch.clients.rest]]
@@ -86,12 +81,11 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
// ...
IndexRequest request = new IndexRequest("spring-data")
.id(randomID())
IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
.source(singletonMap("feature", "high-level-rest-client"))
.setRefreshPolicy(IMMEDIATE);
IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
IndexResponse response = highLevelClient.index(request);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> Create the RestHighLevelClient.
@@ -109,29 +103,39 @@ Calls are directly operated on the reactive stack, **not** wrapping async (threa
====
[source,java]
----
@Configuration
public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration {
static class Config {
@Override
@Bean
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder() <.>
.connectedTo("localhost:9200") //
@Bean
ReactiveElasticsearchClient client() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
.connectedTo("localhost:9200", "localhost:9291")
.withWebClientConfigurer(webClient -> { <2>
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs()
.maxInMemorySize(-1))
.build();
return ReactiveRestClients.create(clientConfiguration);
return webClient.mutate().exchangeStrategies(exchangeStrategies).build();
})
.build();
}
return ReactiveRestClients.create(clientConfiguration);
}
}
// ...
Mono<IndexResponse> response = client.index(request ->
request.index("spring-data")
.type("elasticsearch")
.id(randomID())
.source(singletonMap("feature", "reactive-client"));
.source(singletonMap("feature", "reactive-client"))
.setRefreshPolicy(IMMEDIATE);
);
----
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> when configuring a reactive client, the `withWebClientConfigurer` hook can be used to customize the WebClient.
====
NOTE: The ReactiveClient response, especially for search operations, is bound to the `from` (offset) & `size` (limit) options of the request.
@@ -146,46 +150,36 @@ Client behaviour can be changed via the `ClientConfiguration` that allows to set
[source,java]
----
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("some-header", "on every request") <.>
httpHeaders.add("some-header", "on every request") <1>
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291") <.>
.usingSsl() <.>
.withProxy("localhost:8888") <.>
.withPathPrefix("ela") <.>
.withConnectTimeout(Duration.ofSeconds(5)) <.>
.withSocketTimeout(Duration.ofSeconds(3)) <.>
.withDefaultHeaders(defaultHeaders) <.>
.withBasicAuth(username, password) <.>
.withHeaders(() -> { <.>
.connectedTo("localhost:9200", "localhost:9291") <2>
.usingSsl() <3>
.withProxy("localhost:8888") <4>
.withPathPrefix("ela") <5>
.withConnectTimeout(Duration.ofSeconds(5)) <6>
.withSocketTimeout(Duration.ofSeconds(3)) <7>
.withDefaultHeaders(defaultHeaders) <8>
.withBasicAuth(username, password) <9>
.withHeaders(() -> { <10>
HttpHeaders headers = new HttpHeaders();
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
.withWebClientConfigurer(webClient -> { <.>
//...
return webClient;
})
.withHttpClientConfigurer(clientBuilder -> { <.>
//...
return clientBuilder;
})
. // ... other options
.build();
----
<.> Define default headers, if they need to be customized
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<.> Optionally enable SSL.
<.> Optionally set a proxy.
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<.> Set the connection timeout. Default is 10 sec.
<.> Set the socket timeout. Default is 5 sec.
<.> Optionally set headers.
<.> Add basic authentication.
<.> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
<.> for reactive setup a function configuring the `WebClient`
<.> for non-reactive setup a function configuring the REST client
<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> Optionally enable SSL.
<4> Optionally set a proxy.
<5> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<6> Set the connection timeout. Default is 10 sec.
<7> Set the socket timeout. Default is 5 sec.
<8> Optionally set headers.
<9> Add basic authentication.
<10> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens. If this is used in the reactive setup, the supplier function *must not* block!
@@ -1,229 +0,0 @@
[[elasticsearch.jointype]]
= Join-Type implementation
Spring Data Elasticsearch supports the https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html[Join data type] for creating the corresponding index mappings and for storing the relevant information.
== Setting up the data
For an entity to be used in a parent child join relationship, it must have a property of type `JoinField` which must be annotated.
Let's assume a `Statement` entity where a statement may be a _question_, an _answer_, a _comment_ or a _vote_ (a _Builder_ is also shown in this example, it's not necessary, but later used in the sample code):
====
[source,java]
----
@Document(indexName = "statements")
@Routing("routing") <.>
public class Statement {
@Id
private String id;
@Field(type = FieldType.Text)
private String text;
@Field(type = FieldType.Keyword)
private String routing;
@JoinTypeRelations(
relations =
{
@JoinTypeRelation(parent = "question", children = {"answer", "comment"}), <.>
@JoinTypeRelation(parent = "answer", children = "vote") <.>
}
)
private JoinField<String> relation; <.>
private Statement() {
}
public static StatementBuilder builder() {
return new StatementBuilder();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRouting() {
return routing;
}
public void setRouting(Routing routing) {
this.routing = routing;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public JoinField<String> getRelation() {
return relation;
}
public void setRelation(JoinField<String> relation) {
this.relation = relation;
}
public static final class StatementBuilder {
private String id;
private String text;
private String routing;
private JoinField<String> relation;
private StatementBuilder() {
}
public StatementBuilder withId(String id) {
this.id = id;
return this;
}
public StatementBuilder withRouting(String routing) {
this.routing = routing;
return this;
}
public StatementBuilder withText(String text) {
this.text = text;
return this;
}
public StatementBuilder withRelation(JoinField<String> relation) {
this.relation = relation;
return this;
}
public Statement build() {
Statement statement = new Statement();
statement.setId(id);
statement.setRouting(routing);
statement.setText(text);
statement.setRelation(relation);
return statement;
}
}
}
----
<.> for routing related info see <<elasticsearch.routing>>
<.> a question can have answers and comments
<.> an answer can have votes
<.> the `JoinField` property is used to combine the name (_question_, _answer_, _comment_ or _vote_) of the relation with the parent id.
The generic type must be the same as the `@Id` annotated property.
====
Spring Data Elasticsearch will build the following mapping for this class:
====
[source,json]
----
{
"statements": {
"mappings": {
"properties": {
"_class": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"routing": {
"type": "keyword"
},
"relation": {
"type": "join",
"eager_global_ordinals": true,
"relations": {
"question": [
"answer",
"comment"
],
"answer": "vote"
}
},
"text": {
"type": "text"
}
}
}
}
}
----
====
== Storing data
Given a repository for this class the following code inserts a question, two answers, a comment and a vote:
====
[source,java]
----
void init() {
repository.deleteAll();
Statement savedWeather = repository.save(
Statement.builder()
.withText("How is the weather?")
.withRelation(new JoinField<>("question")) <1>
.build());
Statement sunnyAnswer = repository.save(
Statement.builder()
.withText("sunny")
.withRelation(new JoinField<>("answer", savedWeather.getId())) <2>
.build());
repository.save(
Statement.builder()
.withText("rainy")
.withRelation(new JoinField<>("answer", savedWeather.getId())) <3>
.build());
repository.save(
Statement.builder()
.withText("I don't like the rain")
.withRelation(new JoinField<>("comment", savedWeather.getId())) <4>
.build());
repository.save(
Statement.builder()
.withText("+1 for the sun")
,withRouting(savedWeather.getId())
.withRelation(new JoinField<>("vote", sunnyAnswer.getId())) <5>
.build());
}
----
<1> create a question statement
<2> the first answer to the question
<3> the second answer
<4> a comment to the question
<5> a vote for the first answer, this needs to have the routing set to the weather document, see <<elasticsearch.routing>>.
====
== Retrieving data
Currently native search queries must be used to query the data, so there is no support from standard repository methods. <<repositories.custom-implementations>> can be used instead.
The following code shows as an example how to retrieve all entries that have a _vote_ (which must be _answers_, because only answers can have a vote) using an `ElasticsearchOperations` instance:
====
[source,java]
----
SearchHits<Statement> hasVotes() {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(hasChildQuery("vote", matchAllQuery(), ScoreMode.None))
.build();
return operations.search(query, Statement.class);
}
----
====
@@ -1,46 +0,0 @@
[[elasticsearch-migration-guide-4.0-4.1]]
= Upgrading from 4.0.x to 4.1.x
This section describes breaking changes from version 4.0.x to 4.1.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-4.0-4.1.deprecations]]
== Deprecations
.Definition of the id property
It is possible to define a property of en entity as the id property by naming it either `id` or `document`.
This behaviour is now deprecated and will produce a warning.
PLease us the `@Id` annotation to mark a property as being the id property.
.Index mappings
In the `ReactiveElasticsearchClient.Indices` interface the `updateMapping` methods are deprecated in favour of the `putMapping` methods.
They do the same, but `putMapping` is consistent with the naming in the Elasticsearch API:
.Alias handling
In the `IndexOperations` interface the methods `addAlias(AliasQuery)`, `removeAlias(AliasQuery)` and `queryForAlias()` have been deprecated.
The new methods `alias(AliasAction)`, `getAliases(String...)` and `getAliasesForIndex(String...)` offer more functionality and a cleaner API.
.Parent-ID
Usage of a parent-id has been removed from Elasticsearch since version 6. We now deprecate the corresponding fields and methods.
[[elasticsearch-migration-guide-4.0-4.1.removal]]
== Removals
.Type mappings
The _type mappings_ parameters of the `@Document` annotation and the `IndexCoordinates` object were removed.
They had been deprecated in Spring Data Elasticsearch 4.0 and their values weren't used anymore.
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes]]
== Breaking Changes
=== Return types of ReactiveElasticsearchClient.Indices methods
The methods in the `ReactiveElasticsearchClient.Indices` were not used up to now.
With the introduction of the `ReactiveIndexOperations` it became necessary to change some of the return types:
* the `createIndex` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful index creation.
* the `updateMapping` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful mappings storage.
=== Return types of DocumentOperartions.bulkIndex methods
These methods were returing a `List<String>` containing the ids of the new indexed records.
Now they return a `List<IndexedObjectInformation>`; these objects contain the id and information about optimistic locking (seq_no and primary_term)
@@ -1,66 +0,0 @@
[[elasticsearch-migration-guide-4.1-4.2]]
= Upgrading from 4.1.x to 4.2.x
This section describes breaking changes from version 4.1.x to 4.2.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-4.1-4.2.deprecations]]
== Deprecations
=== @Document parameters
The parameters of the `@Document` annotation that are relevant for the index settings (`useServerConfiguration`, `shards`. `replicas`, `refreshIntervall` and `indexStoretype`) have been moved to the `@Setting` annotation. Use in `@Document` is still possible but deprecated.
[[elasticsearch-migration-guide-4.1-4.2.removal]]
== Removals
The `@Score` annotation that was used to set the score return value in an entity was deprecated in version 4.0 and has been removed.
Scroe values are returned in the `SearchHit` instances that encapsulate the returned entities.
The `org.springframework.data.elasticsearch.ElasticsearchException` class has been removed.
The remaining usages have been replaced with `org.springframework.data.mapping.MappingException` and `org.springframework.dao.InvalidDataAccessApiUsageException`.
The deprecated `ScoredPage`, `ScrolledPage` `@AggregatedPage` and implementations has been removed.
The deprecated `GetQuery` and `DeleteQuery` have been removed.
The deprecated `find` methods from `ReactiveSearchOperations` and `ReactiveDocumentOperations` have been removed.
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes]]
== Breaking Changes
=== RefreshPolicy
==== Enum package changed
It was possible in 4.1 to configure the refresh policy for the `ReactiveElasticsearchTemplate` by overriding the method `AbstractReactiveElasticsearchConfiguration.refreshPolicy()` in a custom configuration class.
The return value of this method was an instance of the class `org.elasticsearch.action.support.WriteRequest.RefreshPolicy`.
Now the configuration must return `org.springframework.data.elasticsearch.core.RefreshPolicy`.
This enum has the same values and triggers the same behaviour as before, so only the `import` statement has to be adjusted.
==== Refresh behaviour
`ElasticsearchOperations` and `ReactiveElasticsearchOperations` now explicitly use the `RefreshPolicy` set on the template for write requests if not null.
If the refresh policy is null, then nothing special is done, so the cluster defaults are used. `ElasticsearchOperations` was always using the cluster default before this version.
The provided implementations for `ElasticsearchRepository` and `ReactiveElasticsearchRepository` will do an explicit refresh when the refresh policy is null.
This is the same behaviour as in previous versions.
If a refresh policy is set, then it will be used by the repositories as well.
==== Refresh configuration
When configuring Spring Data Elasticsearch like described in <<elasticsearch.clients>> by using `ElasticsearchConfigurationSupport`, `AbstractElasticsearchConfiguration` or `AbstractReactiveElasticsearchConfiguration` the refresh policy will be initialized to `null`.
Previously the reactive code initialized this to `IMMEDIATE`, now reactive and non-reactive code show the same behaviour.
=== Method return types
==== delete methods that take a Query
The reactive methods previously returned a `Mono<Long>` with the number of deleted documents, the non reactive versions were void. They now return a `Mono<ByQueryResponse>` which contains much more detailed information about the deleted documents and errors that might have occurred.
==== multiget methods
The implementations of _multiget_ previousl only returned the found entities in a `List<T>` for non-reactive implementations and in a `Flux<T>` for reactive implementations. If the request contained ids that were not found, the information that these are missing was not available. The user needed to compare the returned ids to the requested ones to find
which ones were missing.
Now the `multiget` methods return a `MultiGetItem` for every requested id. This contains information about failures (like non existing indices) and the information if the item existed (then it is contained in the `MultiGetItem) or not.
@@ -1,50 +1,7 @@
[[elasticsearch.misc]]
= Miscellaneous Elasticsearch Operation Support
This chapter covers additional support for Elasticsearch operations that cannot be directly accessed via the repository interface.
It is recommended to add those operations as custom implementation as described in <<repositories.custom-implementations>> .
[[elasticsearc.misc.index.settings]]
== Index settings
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation. The following arguments are available:
* `useServerConfiguration` does not send any settings parameters, so the Elasticsearch server configuration determines them.
* `settingPath` refers to a JSON file defining the settings that must be resolvable in the classpath
* `shards` the number of shards to use, defaults to _1_
* `replicas` the number of replicas, defaults to _1_
* `refreshIntervall`, defaults to _"1s"_
* `indexStoreType`, defaults to _"fs"_
It is as well possible to define https://www.elastic.co/guide/en/elasticsearch/reference/7.11/index-modules-index-sorting.html[index sorting] (check the linked Elasticsearch documentation for the possible field types and values):
====
[source,java]
----
@Document(indexName = "entities")
@Setting(
sortFields = { "secondField", "firstField" }, <.>
sortModes = { Setting.SortMode.max, Setting.SortMode.min }, <.>
sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc },
sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first })
class Entity {
@Nullable
@Id private String id;
@Nullable
@Field(name = "first_field", type = FieldType.Keyword)
private String firstField;
@Nullable @Field(name = "second_field", type = FieldType.Keyword)
private String secondField;
// getter and setter...
}
----
<.> when defining sort fields, use the name of the Java property (_firstField_), not the name that might be defined for Elasticsearch (_first_field_)
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
====
This chapter covers additional support for Elasticsearch operations that cannot be directly accessed via the repository interface. It is recommended to add those operations as custom implementation as described in <<repositories.custom-implementations>> .
[[elasticsearch.misc.filter]]
== Filter Builder
@@ -62,7 +19,7 @@ SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFilter(boolFilter().must(termFilter("id", documentId)))
.build();
Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, SampleEntity.class, index);
----
====
@@ -70,10 +27,8 @@ Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, Sample
[[elasticsearch.scroll]]
== Using Scroll For Big Result Set
Elasticsearch has a scroll API for getting big result set in chunks.
This is internally used by Spring Data Elasticsearch to provide the implementations of the `<T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index)` method.
Elasticsearch has a scroll API for getting big result set in chunks. This is internally used by Spring Data Elasticsearch to provide the implementations of the `<T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index)` method.
====
[source,java]
----
IndexCoordinates index = IndexCoordinates.of("sample-index");
@@ -93,11 +48,9 @@ while (stream.hasNext()) {
stream.close();
----
====
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `ElasticsearchRestTemplate` can be used:
====
[source,java]
----
@@ -122,12 +75,9 @@ while (scroll.hasSearchHits()) {
}
template.searchScrollClear(scrollId);
----
====
To use the Scroll API with repository methods, the return type must defined as `Stream` in the Elasticsearch Repository.
The implementation of the method will then use the scroll methods from the ElasticsearchTemplate.
To use the Scroll API with repository methods, the return type must defined as `Stream` in the Elasticsearch Repository. The implementation of the method will then use the scroll methods from the ElasticsearchTemplate.
====
[source,java]
----
interface SampleEntityRepository extends Repository<SampleEntity, String> {
@@ -136,7 +86,6 @@ interface SampleEntityRepository extends Repository<SampleEntity, String> {
}
----
====
[[elasticsearch.misc.sorts]]
== Sort options
@@ -145,9 +94,9 @@ In addition to the default sort options described <<repositories.paging-and-sort
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
====
[source,java]
----
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
----
====
@@ -1,22 +1,6 @@
[[new-features]]
= What's new
[[new-features.4-2-0]]
== New in Spring Data Elasticsearch 4.2
* Upgrade to Elasticsearch 7.10.0.
* Support for custom routing values
[[new-features.4-1-0]]
== New in Spring Data Elasticsearch 4.1
* Uses Spring 5.3.
* Upgrade to Elasticsearch 7.9.3.
* Improved API for alias management.
* Introduction of `ReactiveIndexOperations` for index management.
* Index templates support.
* Support for Geo-shape data with GeoJson.
[[new-features.4-0-0]]
== New in Spring Data Elasticsearch 4.0
@@ -27,7 +11,8 @@
* Removal of the Jackson `ObjectMapper`, now using the <<elasticsearch.mapping.meta-model,MappingElasticsearchConverter>>
* Cleanup of the API in the `*Operations` interfaces, grouping and renaming methods so that they match the Elasticsearch API, deprecating the old methods, aligning with other Spring Data modules.
* Introduction of `SearchHit<T>` class to represent a found document together with the relevant result metadata for this document (i.e. _sortValues_).
* Introduction of the `SearchHits<T>` class to represent a whole search result together with the metadata for the complete search result (i.e. _max_score_).
* Introduction of the `SearchHits<T>` class to represent a whole search result together with the
metadata for the complete search result (i.e. _max_score_).
* Introduction of `SearchPage<T>` class to represent a paged result containing a `SearchHits<T>` instance.
* Introduction of the `GeoDistanceOrder` class to be able to create sorting by geographical distance
* Implementation of Auditing Support
@@ -3,19 +3,17 @@
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>.
As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>. As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
The main reasons for the removal of the Jackson based mapper are:
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`.
This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
* Custom field types and formats also need to be stored into the Elasticsearch index mappings.
The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`. This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
* Custom field types and formats also need to be stored into the Elasticsearch index mappings. The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
Using the `MappingElasticsearchConverter` now covers all these cases.
[[elasticsearch.mapping.meta-model]]
== Meta Model Object Mapping
@@ -25,90 +23,36 @@ This allows to register `Converter` instances for specific domain type mapping.
[[elasticsearch.mapping.meta-model.annotations]]
=== Mapping Annotation Overview
The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents.
The metadata is taken from the entity's properties which can be annotated.
The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents. The metadata is taken from the entity's properties which can be annotated.
The following annotations are available:
* `@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.
This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"`
** `type`: [line-through]#the mapping type.
If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
** `createIndex`: flag whether to create an index on repository bootstrapping.
Default value is _true_.
See <<elasticsearch.repositories.autocreation>>
** `versionType`: Configuration of version management.
Default value is _EXTERNAL_.
* `@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`: [line-through]#the mapping type. If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
** `shards`: the number of shards for the index.
** `replicas`: the number of replicas for the index.
** `refreshIntervall`: Refresh interval for the index. Used for index creation. Default value is _"1s"_.
** `indexStoreType`: Index storage type for the index. Used for index creation. Default value is _"fs"_.
** `createIndex`: Configuration whether to create an index on repository bootstrapping. Default value is _true_.
** `versionType`: Configuration of version management. Default value is _EXTERNAL_.
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database.
Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used.
** `type`: The field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_.
See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
** `format`: One or more built-in date formats, see the next section <<elasticsearch.mapping.meta-model.date-formats>>.
** `pattern`: One or more custom date formats, see the next section <<elasticsearch.mapping.meta-model.date-formats>>.
** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_.
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer.
* `@GeoPoint`: Marks a field as _geo_point_ datatype.
Can be omitted if the field is an instance of the `GeoPoint` class.
** `type`: the field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
** `format` and `pattern` definitions for the _Date_ type. `format` must be defined for date types.
** `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.
* `@GeoPoint`: marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class.
NOTE: Properties that derive from `TemporalAccessor` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registerd for this type. +
If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
[[elasticsearch.mapping.meta-model.date-formats]]
==== Date format mapping
Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation
of type `FieldType.Date` or a custom converter must be registered for this type. This paragraph describes the use of
`FieldType.Date`.
There are two attributes of the `@Field` annotation that define which date format information is written to the
mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats])
The `format` attributes is used to define at least one of the predefined formats. If it is not defined, then a
default value of __date_optional_time_ and _epoch_millis_ is used.
The `pattern` attribute can be used to add additional custom format strings. If you want to use only custom date formats, you must set the `format` property to empty `{}`.
The following table shows the different attributes and the mapping created from their values:
[cols=2*,options=header]
|===
| annotation
| format string in Elasticsearch mapping
| @Field(type=FieldType.Date)
| "date_optional_time\|\|epoch_millis",
| @Field(type=FieldType.Date, format=DateFormat.basic_date)
| "basic_date"
| @Field(type=FieldType.Date, format={DateFormat.basic_date, DateFormat.basic_time})
| "basic_date\|\|basic_time"
| @Field(type=FieldType.Date, pattern="dd.MM.uuuu")
| "date_optional_time\|\|epoch_millis\|\|dd.MM.uuuu",
| @Field(type=FieldType.Date, format={}, pattern="dd.MM.uuuu")
| "dd.MM.uuuu"
|===
NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
==== Mapped field names
Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch. This can be changed for individual field by using the `@Field` annotation on that property.
It is also possible to define a `FieldNamingStrategy` in the configuration of the client (<<elasticsearch.clients>>). If for example a `SnakeCaseFieldNamingStrategy` is configured, the property _sampleProperty_ of the object would be mapped to _sample_property_ in Elasticsearch. A `FieldNamingStrategy` applies to all entities; it can be overwritten by
setting a specific name with `@Field` on a property.
[[elasticsearch.mapping.meta-model.rules]]
=== Mapping Rules
@@ -128,7 +72,6 @@ public class Person { <1>
String lastname;
}
----
[source,json]
----
{
@@ -141,10 +84,10 @@ public class Person { <1>
<1> By default the domain types class name is used for the type hint.
====
Type hints can be configured to hold custom information.
Use the `@TypeAlias` annotation to do so.
Type hints can be configured to hold custom information. Use the `@TypeAlias` annotation to do so.
NOTE: Make sure to add types with `@TypeAlias` to the initial entity set (`AbstractElasticsearchConfiguration#getInitialEntitySet`) to already have entity information available when first reading data from the store.
NOTE: Make sure to add types with `@TypeAlias` to the initial entity set (`AbstractElasticsearchConfiguration#getInitialEntitySet`)
to already have entity information available when first reading data from the store.
.Type Hints with Alias
====
@@ -157,7 +100,6 @@ public class Person {
// ...
}
----
[source,json]
----
{
@@ -184,7 +126,6 @@ public class Address {
Point location;
}
----
[source,json]
----
{
@@ -195,46 +136,6 @@ public class Address {
----
====
==== GeoJson Types
Spring Data Elasticsearch supports the GeoJson types by providing an interface `GeoJson` and implementations for the different geometries.
They are mapped to Elasticsearch documents according to the GeoJson specification.
The corresponding properties of the entity are specified in the index mappings as `geo_shape` when the index mappings is written. (check the https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html[Elasticsearch documentation] as well)
.GeoJson types
====
[source,java]
----
public class Address {
String city, street;
GeoJsonPoint location;
}
----
[source,json]
----
{
"city": "Los Angeles",
"street": "2800 East Observatory Road",
"location": {
"type": "Point",
"coordinates": [-118.3026284, 34.118347]
}
}
----
====
The following GeoJson types are implemented:
* `GeoJsonPoint`
* `GeoJsonMultiPoint`
* `GeoJsonLineString`
* `GeoJsonMultiLineString`
* `GeoJsonPolygon`
* `GeoJsonMultiPolygon`
* `GeoJsonGeometryCollection`
==== Collections
For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
@@ -251,7 +152,6 @@ public class Person {
}
----
[source,json]
----
{
@@ -279,7 +179,6 @@ public class Person {
}
----
[source,json]
----
{
@@ -346,7 +245,6 @@ public class Config extends AbstractElasticsearchConfiguration {
}
}
----
[source,json]
----
{
@@ -17,19 +17,6 @@ The default implementations of the interfaces offer:
* A rich query and criteria api.
* Resource management and Exception translation.
[NOTE]
====
.Index management and automatic creation of indices and mappings.
The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster. Details of the index that will be created
can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
**None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`. It is the user's responsibility to call the methods.
There is support for automatic creation of indices and writing the mappings when using Spring Data Elasticsearch repositories, see <<elasticsearch.repositories.autocreation>>
====
[[elasticsearch.operations.template]]
== ElasticsearchTemplate
@@ -58,8 +45,7 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
}
}
----
<1> Setting up the <<elasticsearch.clients.transport>>.
Deprecated as of version 4.0.
<1> Setting up the <<elasticsearch.clients.transport>>. Deprecated as of version 4.0.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
====
@@ -89,9 +75,7 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
[[elasticsearch.operations.usage]]
== Usage examples
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different.
The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller.
The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above.
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above.
.ElasticsearchOperations usage
====
@@ -139,12 +123,9 @@ include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
[[elasticsearch.operations.searchresulttypes]]
== Search Result Types
When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned.
When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned. When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information.
These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations.
The following classes and interfaces are now available:
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information. These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations. The following classes and interfaces are now available:
.SearchHit<T>
Contains the following information:
@@ -153,7 +134,6 @@ Contains the following information:
* Score
* Sort Values
* Highlight fields
* Inner hits (this is an embedded `SearchHits` object containing eventually returned inner hits)
* The retrieved entity of type <T>
.SearchHits<T>
@@ -174,112 +154,3 @@ Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, i
.SearchHitsIterator<T>
An Iterator returned by the streaming functions of the `SearchOperations` interface.
[[elasticsearch.operations.queries]]
== Queries
Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeSearchQuery`.
[[elasticsearch.operations.criteriaquery]]
=== CriteriaQuery
`CriteriaQuery` based queries allow the creation of queries to search for data without knowing the syntax or basics of Elasticsearch queries. They allow the user to build queries by simply chaining and combining `Criteria` objects that specifiy the criteria the searched documents must fulfill.
NOTE: when talking about AND or OR when combining criteria keep in mind, that in Elasticsearch AND are converted to a **must** condition and OR to a **should**
`Criteria` and their usage are best explained by example
(let's assume we have a `Book` entity with a `price` property):
.Get books with a given price
====
[source,java]
----
Criteria criteria = new Criteria("price").is(42.0);
Query query = new CriteriaQuery(criteria);
----
====
Conditions for the same field can be chained, they will be combined with a logical AND:
.Get books with a given price
====
[source,java]
----
Criteria criteria = new Criteria("price").greaterThan(42.0).lessThan(34.0L);
Query query = new CriteriaQuery(criteria);
----
====
When chaining `Criteria`, by default a AND logic is used:
.Get all persons with first name _James_ and last name _Miller_:
====
[source,java]
----
Criteria criteria = new Criteria("lastname").is("Miller") <1>
.and("firstname").is("James") <2>
Query query = new CriteriaQuery(criteria);
----
<1> the first `Criteria`
<2> the and() creates a new `Criteria` and chaines it to the first one.
====
If you want to create nested queries, you need to use subqueries for this. Let's assume we want to find all persons with a last name of _Miller_ and a first name of either _Jack_ or _John_:
.Nested subqueries
====
[source,java]
----
Criteria miller = new Criteria("lastName").is("Miller") <.>
.subCriteria( <.>
new Criteria().or("firstName").is("John") <.>
.or("firstName").is("Jack") <.>
);
Query query = new CriteriaQuery(criteria);
----
<.> create a first `Criteria` for the last name
<.> this is combined with AND to a subCriteria
<.> This sub Criteria is an OR combination for the first name _John_
<.> and the first name Jack
====
Please refer to the API documentation of the `Criteria` class for a complete overview of the different available operations.
[[elasticsearch.operations.stringquery]]
=== StringQuery
This class takes an Elasticsearch query as JSON String.
The following code shows a query that searches for persons having the first name "Jack":
====
[source,java]
----
Query query = new SearchQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====
Using `StringQuery` may be appropriate if you already have an Elasticsearch query to use.
[[elasticsearch.operations.nativesearchquery]]
=== NativeSearchQuery
`NativeSearchQuery` is the class to use when you have a complex query, or a query that cannot be expressed by using the `Criteria` API, for example when building queries and using aggregates.
It allows to use all the different `QueryBuilder` implementations from the Elasticsearch library therefore named "native".
The following code shows how to search for persons with a given firstname and for the found documents have a terms aggregation that counts the number of occurences of the lastnames for these persons:
====
[source,java]
----
Query query = new NativeSearchQueryBuilder()
.addAggregation(terms("lastnames").field("lastname").size(10)) //
.withQuery(QueryBuilders.matchQuery("firstname", firstName))
.build();
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====
@@ -26,13 +26,6 @@ class Book {
----
====
[[elasticsearch.repositories.autocreation]]
== Automatic creation of indices with the corresponding mapping
The `@Document` annotation has an argument `createIndex`. If this argument is set to true - which is the default value - Spring Data Elasticsearch will during bootstrapping the repository support on application startup check if the index defined by the `@Document` annotation exists.
If it does not exist, the index will be created and the mappings derived from the entity's annotations (see <<elasticsearch.mapping>>) will be written to the newly created index. Details of the index that will be created can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
include::elasticsearch-repository-queries.adoc[leveloffset=+1]
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
@@ -131,7 +124,7 @@ class ProductService {
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.
@@ -8,14 +8,12 @@ The Elasticsearch module supports all basic query building feature as string que
=== Declared queries
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names.
In this case one might make use of the `@Query` annotation (see <<elasticsearch.query-methods.at-query>> ).
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names. In this case one might make use of the `@Query` annotation (see <<elasticsearch.query-methods.at-query>> ).
[[elasticsearch.query-methods.criterions]]
== Query creation
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:
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
====
@@ -45,7 +43,7 @@ The method name above will be translated into the following Elasticsearch json q
A list of supported keywords for Elasticsearch is shown below.
[cols="1,2,3",options="header"]
[cols="1,2,3", options="header"]
.Supported keywords inside method names
|===
| Keyword
@@ -57,10 +55,10 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}}`
| `Or`
@@ -68,10 +66,10 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"should" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}}`
| `Is`
@@ -79,9 +77,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
]
}
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
]
}
}}`
| `Not`
@@ -89,9 +87,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must_not" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
]
}
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
]
}
}}`
| `Between`
@@ -99,9 +97,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
{"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `LessThan`
@@ -109,9 +107,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } }
]
}
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } }
]
}
}}`
| `LessThanEqual`
@@ -119,9 +117,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `GreaterThan`
@@ -129,9 +127,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } }
]
}
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } }
]
}
}}`
@@ -140,9 +138,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
]
}
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `Before`
@@ -150,9 +148,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `After`
@@ -160,9 +158,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
]
}
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
]
}
}}`
| `Like`
@@ -170,9 +168,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `StartingWith`
@@ -180,9 +178,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `EndingWith`
@@ -190,9 +188,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
{ "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `Contains/Containing`
@@ -200,9 +198,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "\*?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
{ "query_string" : { "query" : "\*?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
]
}
}}`
| `In` (when annotated as FieldType.Keyword)
@@ -210,13 +208,13 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"bool" : {"must" : [
{"terms" : {"name" : ["?","?"]}}
]
}
}
]
}
{"bool" : {"must" : [
{"terms" : {"name" : ["?","?"]}}
]
}
}
]
}
}}`
@@ -229,13 +227,13 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{"bool" : {"must_not" : [
{"terms" : {"name" : ["?","?"]}}
]
}
}
]
}
{"bool" : {"must_not" : [
{"terms" : {"name" : ["?","?"]}}
]
}
}
]
}
}}`
| `NotIn`
@@ -251,9 +249,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
]
}
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
]
}
}}`
| `False`
@@ -261,9 +259,9 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "false", "fields" : [ "available" ] } }
]
}
{ "query_string" : { "query" : "false", "fields" : [ "available" ] } }
]
}
}}`
| `OrderBy`
@@ -271,17 +269,14 @@ A list of supported keywords for Elasticsearch is shown below.
| `{ "query" : {
"bool" : {
"must" : [
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
]
}
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
]
}
}, "sort":[{"name":{"order":"desc"}}]
}`
|===
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
Use `ElasticsearchOperations` with `CriteriaQuery` in a custom repository implementation if you need to have such a function in a repository.
== Method return types
Repository methods can be defined to have the following return types for returning multiple Elements:
@@ -305,10 +300,7 @@ interface BookRepository extends ElasticsearchRepository<Book, String> {
Page<Book> findByName(String name,Pageable pageable);
}
----
The String that is set as the annotation argument must be a valid Elasticsearch JSON query.
It will be sent to Easticsearch as value of the query element; if for example the function is called with the parameter _John_, it would produce the following query body:
The String that is set as the annotation argument must be a valid Elasticsearch JSON query. It will be sent to Easticsearch as value of the query element; if for example the function is called with the parameter _John_, it would produce the following query body:
[source,json]
----
{
@@ -1,106 +0,0 @@
[[elasticsearch.routing]]
= Routing values
When Elasticsearch stores a document in an index that has multiple shards, it determines the shard to you use based on the _id_ of the document.
Sometimes it is necessary to predefine that multiple documents should be indexed on the same shard (join-types, faster search for related data).
For this Elasticsearch offers the possibility to define a routing, which is the value that should be used to calculate the shard from instead of the _id_.
Spring Data Elasticsearch supports routing definitions on storing and retrieving data in the following ways:
== Routing on join-types
When using join-types (see <<elasticsearch.jointype>>), Spring Data Elasticsearch will automatically use the `parent` property of the entity's `JoinField` property as the value for the routing.
This is correct for all the use-cases where the parent-child relationship has just one level.
If it is deeper, like a child-parent-grandparent relationship - like in the above example from _vote_ -> _answer_ -> _question_ - then the routing needs to explicitly specified by using the techniques described in the next section (the _vote_ needs the _question.id_ as routing value).
== Custom routing values
To define a custom routing for an entity, Spring Data Elasticsearch provides a `@Routing` annotation (reusing the `Statement` class from above):
====
[source,java]
----
@Document(indexName = "statements")
@Routing("routing") <.>
public class Statement {
@Id
private String id;
@Field(type = FieldType.Text)
private String text;
@JoinTypeRelations(
relations =
{
@JoinTypeRelation(parent = "question", children = {"answer", "comment"}),
@JoinTypeRelation(parent = "answer", children = "vote")
}
)
private JoinField<String> relation;
@Nullable
@Field(type = FieldType.Keyword)
private String routing; <.>
// getter/setter...
}
----
<.> This defines _"routing"_ as routing specification
<.> a property with the name _routing_
====
If the `routing` specification of the annotation is a plain string and not a SpEL expression, it is interpreted as the name of a property of the entity, in the example it's the _routing_ property.
The value of this property will then be used as the routing value for all requests that use the entity.
It is also possible to us a SpEL expression in the `@Document` annotation like this:
====
[source,java]
----
@Document(indexName = "statements")
@Routing("@myBean.getRouting(#entity)")
public class Statement{
// all the needed stuff
}
----
====
In this case the user needs to provide a bean with the name _myBean_ that has a method `String getRouting(Object)`. To reference the entity _"#entity"_ must be used in the SpEL expression, and the return value must be `null` or the routing value as a String.
If plain property's names and SpEL expressions are not enough to customize the routing definitions, it is possible to define provide an implementation of the `RoutingResolver` interface. This can then be set on the `ElasticOperations` instance:
====
[source,java]
----
RoutingResolver resolver = ...;
ElasticsearchOperations customOperations= operations.withRouting(resolver);
----
====
The `withRouting()` functions return a copy of the original `ElasticsearchOperations` instance with the customized routing set.
When a routing has been defined on an entity when it is stored in Elasticsearch, the same value must be provided when doing a _get_ or _delete_ operation. For methods that do not use an entity - like `get(ID)` or `delete(ID)` - the `ElasticsearchOperations.withRouting(RoutingResolver)` method can be used like this:
====
[source,java]
----
String id = "someId";
String routing = "theRoutingValue";
// get an entity
Statement s = operations
.withRouting(RoutingResolver.just(routing)) <.>
.get(id, Statement.class);
// delete an entity
operations.withRouting(RoutingResolver.just(routing)).delete(id);
----
<.> `RoutingResolver.just(s)` returns a resolver that will just return the given String.
====
@@ -1,11 +1,6 @@
[[elasticsearch.migration]]
= Appendix E: Migration Guides
// line breaks required otherwise the TOC breaks due to joining of first/last lines.
:leveloffset: +1
include::elasticsearch-migration-guide-3.2-4.0.adoc[]
include::elasticsearch-migration-guide-4.0-4.1.adoc[]
include::elasticsearch-migration-guide-4.1-4.2.adoc[]
:leveloffset: -1
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import java.util.Map;
/**
* @author Peter-Josef Meisch
* @since 4.1
* @since 4.0.1 (ported back from master (4.1) branch)
*/
public class BulkFailureException extends DataRetrievalFailureException {
private final Map<String, String> failedDocuments;
@@ -0,0 +1,57 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* ElasticsearchException
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @deprecated since 4.0, use {@link org.springframework.dao.UncategorizedDataAccessException}
*/
@Deprecated
public class ElasticsearchException extends RuntimeException {
@Nullable private Map<String, String> failedDocuments;
public ElasticsearchException(String message) {
super(message);
}
public ElasticsearchException(String message, Throwable cause) {
super(message, cause);
}
public ElasticsearchException(String message, Throwable cause, Map<String, String> failedDocuments) {
super(message, cause);
this.failedDocuments = failedDocuments;
}
public ElasticsearchException(String message, Map<String, String> failedDocuments) {
super(message);
this.failedDocuments = failedDocuments;
}
@Nullable
public Map<String, String> getFailedDocuments() {
return failedDocuments;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,10 +23,6 @@ import org.springframework.dao.UncategorizedDataAccessException;
*/
public class UncategorizedElasticsearchException extends UncategorizedDataAccessException {
public UncategorizedElasticsearchException(String msg) {
super(msg, null);
}
public UncategorizedElasticsearchException(String msg, Throwable cause) {
super(msg, cause);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,39 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Alias for a @Query annotation with the count parameter set to true.
*
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@Query(count = true)
public @interface CountQuery {
@AliasFor(annotation = Query.class)
String value() default "";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,85 +16,16 @@
package org.springframework.data.elasticsearch.annotations;
/**
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/. The patterns are taken
* from this documentation and slightly adapted so that a Java {@link java.time.format.DateTimeFormatter} produces the
* same values as the Elasticsearch formatter.
*
* @author Jakub Vavrik
* @author Tim te Beek
* @author Peter-Josef Meisch
* @author Sascha Woo
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/
*/
public enum DateFormat {
/**
* @deprecated since 4.2, will be removed in a future version. Use <code>format = {}</code> to disable built-in date
* formats in the @Field annotation.
*/
@Deprecated
none(""), //
/**
* @deprecated since 4.2, will be removed in a future version.It is no longer required for using a custom date format
* pattern. If you want to use only a custom date format pattern, you must set the <code>format</code>
* property to empty <code>{}</code>.
*/
@Deprecated
custom(""), //
basic_date("uuuuMMdd"), //
basic_date_time("uuuuMMdd'T'HHmmss.SSSXXX"), //
basic_date_time_no_millis("uuuuMMdd'T'HHmmssXXX"), //
basic_ordinal_date("uuuuDDD"), //
basic_ordinal_date_time("yyyyDDD'T'HHmmss.SSSXXX"), //
basic_ordinal_date_time_no_millis("yyyyDDD'T'HHmmssXXX"), //
basic_time("HHmmss.SSSXXX"), //
basic_time_no_millis("HHmmssXXX"), //
basic_t_time("'T'HHmmss.SSSXXX"), //
basic_t_time_no_millis("'T'HHmmssXXX"), //
basic_week_date("YYYY'W'wwe"), // week-based-year!
basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
date("uuuu-MM-dd"), //
date_hour("uuuu-MM-dd'T'HH"), //
date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in it's implementation
epoch_millis("epoch_millis"), //
epoch_second("epoch_second"), //
hour("HH"), //
hour_minute("HH:mm"), //
hour_minute_second("HH:mm:ss"), //
hour_minute_second_fraction("HH:mm:ss.SSS"), //
hour_minute_second_millis("HH:mm:ss.SSS"), //
ordinal_date("uuuu-DDD"), //
ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
time("HH:mm:ss.SSSXXX"), //
time_no_millis("HH:mm:ssXXX"), //
t_time("'T'HH:mm:ss.SSSXXX"), //
t_time_no_millis("'T'HH:mm:ssXXX"), //
week_date("YYYY-'W'ww-e"), //
week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
weekyear(""), // no TemporalAccessor available for these 3
weekyear_week(""), //
weekyear_week_day(""), //
year("uuuu"), //
year_month("uuuu-MM"), //
year_month_day("uuuu-MM-dd"); //
private final String pattern;
DateFormat(String pattern) {
this.pattern = pattern;
}
/**
* @since 4.2
*/
public String getPattern() {
return pattern;
}
none, custom, basic_date, basic_date_time, basic_date_time_no_millis, basic_ordinal_date, basic_ordinal_date_time,
basic_ordinal_date_time_no_millis, basic_time, basic_time_no_millis, basic_t_time, basic_t_time_no_millis,
basic_week_date, basic_week_date_time, basic_week_date_time_no_millis, date, date_hour, date_hour_minute,
date_hour_minute_second, date_hour_minute_second_fraction, date_hour_minute_second_millis, date_optional_time,
date_time, date_time_no_millis, hour, hour_minute, hour_minute_second, hour_minute_second_fraction,
hour_minute_second_millis, ordinal_date, ordinal_date_time, ordinal_date_time_no_millis, time, time_no_millis,
t_time, t_time_no_millis, week_date, week_date_time, weekDateTimeNoMillis, week_year, weekyearWeek,
weekyearWeekDay, year, year_month, year_month_day
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,46 +54,41 @@ public @interface Document {
String indexName();
/**
* Use server-side settings when creating the index.
* Mapping type name. <br/>
* deprecated as Elasticsearch does not support this anymore
* (@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/removal-of-types.html">Elastisearch removal of types documentation</a>) and will remove it in
* Elasticsearch 8.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
* @deprecated since 4.0
*/
@Deprecated
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. <br/>
* With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of
* Elasticsearch which changed to 1 as well in Elasticsearch 7.0.
* ComposableAnnotationsUnitTest.documentAnnotationShouldBeComposable:60
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
short shards() default 1;
/**
* Number of replicas for the index {@link #indexName()}. Used for index creation.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
short replicas() default 1;
/**
* Refresh interval for the index {@link #indexName()}. Used for index creation.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
String refreshInterval() default "1s";
/**
* Index storage type for the index {@link #indexName()}. Used for index creation.
*
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
*/
@Deprecated
String indexStoreType() default "fs";
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@ 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 omitted if {@link Mapping} annotation is used.
* DynamicTemplates annotation is ommited if {@link Mapping} annotation is used.
*
* @author Petr Kukral
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,12 +34,9 @@ import org.springframework.core.annotation.AliasFor;
* @author Peter-Josef Meisch
* @author Xiao Yu
* @author Aleksei Arsenev
* @author Brian Kimmig
* @author Morgan Lutz
* @author Sascha Woo
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Field {
@@ -66,9 +63,9 @@ public @interface Field {
boolean index() default true;
DateFormat[] format() default { DateFormat.date_optional_time, DateFormat.epoch_millis };
DateFormat format() default DateFormat.none;
String[] pattern() default {};
String pattern() default "";
boolean store() default false;
@@ -157,42 +154,4 @@ public @interface Field {
* @since 4.0
*/
int maxShingleSize() default -1;
/**
* if true, the field will be stored in Elasticsearch even if it has a null value
*
* @since 4.1
*/
boolean storeNullValue() default false;
/**
* to be used in combination with {@link FieldType#Rank_Feature}
*
* @since 4.1
*/
boolean positiveScoreImpact() default true;
/**
* to be used in combination with {@link FieldType#Object}
*
* @since 4.1
*/
boolean enabled() default true;
/**
* @since 4.1
*/
boolean eagerGlobalOrdinals() default false;
/**
* @since 4.1
*/
NullValueType nullValueType() default NullValueType.String;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 4.2
*/
int dims() default -1;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,44 +22,34 @@ package org.springframework.data.elasticsearch.annotations;
* @author Zeng Zetang
* @author Peter-Josef Meisch
* @author Aleksei Arsenev
* @author Brian Kimmig
* @author Morgan Lutz
*/
public enum FieldType {
Auto, //
Text, //
Keyword, //
Long, //
Integer, //
Short, //
Byte, //
Double, //
Float, //
Half_Float, //
Scaled_Float, //
Date, //
Date_Nanos, //
Boolean, //
Binary, //
Integer_Range, //
Float_Range, //
Long_Range, //
Double_Range, //
Date_Range, //
Ip_Range, //
Object, //
Nested, //
Ip, //
TokenCount, //
Percolator, //
Flattened, //
Search_As_You_Type, //
/** @since 4.1 */
Rank_Feature, //
/** @since 4.1 */
Rank_Features, //
/** since 4.2 */
Wildcard, //
/** @since 4.2 */
Dense_Vector //
Auto, //
Text, //
Keyword, //
Long, //
Integer, //
Short, //
Byte, //
Double, //
Float, //
Half_Float, //
Scaled_Float, //
Date, //
Date_Nanos, //
Boolean, //
Binary, //
Integer_Range, //
Float_Range, //
Long_Range, //
Double_Range, //
Date_Range, //
Ip_Range, //
Object, //
Nested, //
Ip, //
TokenCount, //
Percolator, //
Flattened, //
Search_As_You_Type //
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,44 +0,0 @@
/*
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Lukas Vorisek
* @author Peter-Josef Meisch
* @since 4.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface GeoShapeField {
Orientation orientation() default Orientation.ccw;
boolean ignoreMalformed() default false;
boolean ignoreZValue() default true;
boolean coerce() default false;
enum Orientation {
right, ccw, counterclockwise, left, cw, clockwise
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,6 @@ import java.lang.annotation.Target;
* @author Xiao Yu
* @author Peter-Josef Meisch
* @author Aleksei Arsenev
* @author Brian Kimmig
* @author Morgan Lutz
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@@ -40,9 +38,9 @@ public @interface InnerField {
boolean index() default true;
DateFormat[] format() default { DateFormat.date_optional_time, DateFormat.epoch_millis };
DateFormat format() default DateFormat.none;
String[] pattern() default {};
String pattern() default "";
boolean store() default false;
@@ -125,28 +123,4 @@ public @interface InnerField {
* @since 4.0
*/
int maxShingleSize() default -1;
/**
* to be used in combination with {@link FieldType#Rank_Feature}
*
* @since 4.1
*/
boolean positiveScoreImpact() default true;
/**
* @since 4.1
*/
boolean eagerGlobalOrdinals() default false;
/**
* @since 4.1
*/
NullValueType nullValueType() default NullValueType.String;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 4.2
*/
int dims() default -1;
}
@@ -1,36 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
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;
/**
* @author Subhobrata Dey
* @since 4.1
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
public @interface JoinTypeRelations {
JoinTypeRelation[] relations();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +15,7 @@
*/
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 java.lang.annotation.*;
import org.springframework.data.annotation.Persistent;
/**
@@ -31,15 +26,9 @@ import org.springframework.data.annotation.Persistent;
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface Mapping {
String mappingPath() default "";
/**
* whether mappings are enabled
*
* @since 4.2
*/
boolean enabled() default true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,10 +27,9 @@ import java.lang.annotation.Target;
* @author Artur Konczak
* @author Jonathan Yan
* @author Xiao Yu
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Target(ElementType.FIELD)
@Documented
public @interface MultiField {
@@ -1,24 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
/**
* @author Peter-Josef Meisch
* @since 4.1
*/
public enum NullValueType {
String, Integer, Long, Double
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,8 @@ import org.springframework.data.annotation.Persistent;
* Parent
*
* @author Philipp Jardas
* @deprecated since 4.1, not supported anymore by Elasticsearch
*/
@Deprecated
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,32 +22,24 @@ import java.lang.annotation.*;
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target(ElementType.METHOD)
@Documented
public @interface Query {
/**
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
* Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
*
* @return
*/
String value() default "";
/**
* Named Query Named looked up by repository.
*
* @deprecated since 4.2, not implemented and used anywhere
* @return
*/
String name() default "";
/**
* Returns whether the query defined should be executed as count projection.
*
* @return {@literal false} by default.
* @since 4.2
*/
boolean count() default false;
}
@@ -1,43 +0,0 @@
/*
* Copyright2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.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;
/**
* Annotation to enable custom routing values for an entity.
*
* @author Peter-Josef Meisch
* @since 4.2
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Routing {
/**
* defines how the routing is determined. Can be either the name of a property or a SpEL expression. See the reference
* documentation for examples how to use this annotation.
*/
String value();
}
@@ -0,0 +1,28 @@
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.elasticsearch.core.SearchHit;
/**
* Specifies that this field is used for storing the document score.
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @since 3.1
* @deprecated since 4.0, use {@link SearchHit#getScore()} instead
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
@ReadOnlyProperty
@Deprecated
public @interface Score {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,7 @@
*/
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 java.lang.annotation.*;
import org.springframework.data.annotation.Persistent;
@@ -27,84 +23,14 @@ import org.springframework.data.annotation.Persistent;
* Elasticsearch Setting
*
* @author Mohsin Husen
* @author Peter-Josef Meisch
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Target({ElementType.TYPE})
public @interface Setting {
/**
* Resource path for a settings configuration
*/
String settingPath() default "";
/**
* Use server-side settings when creating the index.
*/
boolean useServerConfiguration() default false;
/**
* Number of shards for the index. Used for index creation. <br/>
* With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of
* Elasticsearch which changed to 1 as well in Elasticsearch 7.0.
*/
short shards() default 1;
/**
* Number of replicas for the index. Used for index creation.
*/
short replicas() default 1;
/**
* Refresh interval for the index. Used for index creation.
*/
String refreshInterval() default "1s";
/**
* Index storage type for the index. Used for index creation.
*/
String indexStoreType() default "fs";
/**
* fields to define an index sorting
*
* @since 4.2
*/
String[] sortFields() default {};
/**
* defines the order for {@link #sortFields()}. If present, it must have the same number of elements
*
* @since 4.2
*/
SortOrder[] sortOrders() default {};
/**
* defines the mode for {@link #sortFields()}. If present, it must have the same number of elements
*
* @since 4.2
*/
SortMode[] sortModes() default {};
/**
* defines the missing value for {@link #sortFields()}. If present, it must have the same number of elements
*
* @since 4.2
*/
SortMissing[] sortMissingValues() default {};
enum SortOrder {
asc, desc
}
enum SortMode {
min, max
}
enum SortMissing {
_last, _first
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -172,12 +171,6 @@ public interface ClientConfiguration {
*/
Function<WebClient, WebClient> getWebClientConfigurer();
/**
* @return the client configuration callback.
* @since 4.2
*/
HttpClientConfigCallback getHttpClientConfigurer();
/**
* @return the supplier for custom headers.
*/
@@ -348,22 +341,13 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
/**
* Register a {HttpClientConfigCallback} to configure the non-reactive REST client.
*
* @param httpClientConfigurer configuration callback, must not be null.
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.2
*/
TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer);
/**
* set a supplier for custom headers. This is invoked for every HTTP request to Elasticsearch to retrieve headers
* that should be sent with the request. A common use case is passing in authentication headers that may change.
* <br/>
* Note: When used in a reactive environment, the calling of {@link Supplier#get()} function must not do any
* blocking operations. It may return {@literal null}.
*
*
* @param headers supplier function for headers, must not be {@literal null}
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.0
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@ import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
@@ -62,7 +61,6 @@ class ClientConfigurationBuilder
private @Nullable String proxy;
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
/*
* (non-Javadoc)
@@ -209,15 +207,6 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer) {
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
this.httpClientConfigurer = httpClientConfigurer;
return this;
}
@Override
public TerminalClientConfigurationBuilder withHeaders(Supplier<HttpHeaders> headers) {
@@ -242,7 +231,7 @@ class ClientConfigurationBuilder
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, headersSupplier);
hostnameVerifier, proxy, webClientConfigurer, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@ import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -53,14 +52,12 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final @Nullable HostnameVerifier hostnameVerifier;
private final @Nullable String proxy;
private final Function<WebClient, WebClient> webClientConfigurer;
private final HttpClientConfigCallback httpClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix,
@Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
Supplier<HttpHeaders> headersSupplier) {
Function<WebClient, WebClient> webClientConfigurer, Supplier<HttpHeaders> headersSupplier) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
@@ -72,7 +69,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.hostnameVerifier = hostnameVerifier;
this.proxy = proxy;
this.webClientConfigurer = webClientConfigurer;
this.httpClientConfigurer = httpClientConfigurer;
this.headersSupplier = headersSupplier;
}
@@ -127,11 +123,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
return webClientConfigurer;
}
@Override
public HttpClientConfigCallback getHttpClientConfigurer() {
return httpClientConfigurer;
}
@Override
public Supplier<HttpHeaders> getHeadersSupplier() {
return headersSupplier;
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2021 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,9 +44,7 @@ import org.springframework.util.StringUtils;
* @author Mohsin Husen
* @author Ilkang Na
* @author Peter-Josef Meisch
* @deprecated since 4.1, we're not supporting embedded Node clients anymore, use the REST client
*/
@Deprecated
public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(NodeClientFactoryBean.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -119,8 +119,6 @@ public final class RestClients {
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
clientBuilder = clientConfiguration.getHttpClientConfigurer().customizeHttpClient(clientBuilder);
return clientBuilder;
});
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,10 +22,13 @@ import io.netty.handler.ssl.IdentityCipherSuiteFilter;
import io.netty.handler.ssl.JdkSslContext;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import reactor.core.publisher.EmitterProcessor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.ProxyProvider;
import reactor.netty.tcp.ProxyProvider;
import reactor.netty.tcp.TcpClient;
import java.io.IOException;
import java.lang.reflect.Method;
@@ -45,27 +48,23 @@ import javax.net.ssl.SSLContext;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.ActionRequest;
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.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
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;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexRequest;
@@ -80,9 +79,7 @@ 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.GetAliasesResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.indices.*;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@@ -91,30 +88,24 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
import org.reactivestreams.Publisher;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
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.Cluster;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices;
import org.springframework.data.elasticsearch.client.util.NamedXContents;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.lang.Nullable;
@@ -137,15 +128,11 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
* @author Henrique Amaral
* @author Roman Puchkovskiy
* @author Russell Parry
* @author Thomas Geese
* @author Brian Clozel
* @author Farid Faoudi
* @author George Popides
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
*/
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices, Cluster {
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices {
private final HostProvider<?> hostProvider;
private final RequestCreator requestCreator;
@@ -177,6 +164,13 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
this.requestCreator = requestCreator;
}
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
Assert.notNull(headersSupplier, "headersSupplier must not be null");
this.headersSupplier = headersSupplier;
}
/**
* Create a new {@link DefaultReactiveElasticsearchClient} aware of the given nodes in the cluster. <br />
* <strong>NOTE</strong> If the cluster requires authentication be sure to provide the according {@link HttpHeaders}
@@ -226,7 +220,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
WebClientProvider provider = getWebClientProvider(clientConfiguration);
HostProvider<?> hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
DefaultReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator);
@@ -241,14 +235,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
Duration connectTimeout = clientConfiguration.getConnectTimeout();
Duration soTimeout = clientConfiguration.getSocketTimeout();
HttpClient httpClient = HttpClient.create().compress(true);
TcpClient tcpClient = TcpClient.create();
if (!connectTimeout.isNegative()) {
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
}
if (!soTimeout.isNegative()) {
httpClient = httpClient.doOnConnected(connection -> connection //
tcpClient = tcpClient.doOnConnected(connection -> connection //
.addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
}
@@ -260,20 +254,22 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
if (hostPort.length != 2) {
throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\"");
}
httpClient = httpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
tcpClient = tcpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
.port(Integer.parseInt(hostPort[1])));
}
String scheme = "http";
HttpClient httpClient = HttpClient.from(tcpClient);
if (clientConfiguration.useSsl()) {
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
if (sslContext.isPresent()) {
httpClient = httpClient
.secure(sslContextSpec -> sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null,
IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false)));
httpClient = httpClient.secure(sslContextSpec -> {
sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, IdentityCipherSuiteFilter.INSTANCE,
ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false));
});
} else {
httpClient = httpClient.secure();
}
@@ -281,41 +277,34 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
scheme = "https";
}
WebClientProvider provider = WebClientProvider.create(scheme, new ReactorClientHttpConnector(httpClient));
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
WebClientProvider provider = WebClientProvider.create(scheme, connector);
if (clientConfiguration.getPathPrefix() != null) {
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
}
provider = provider //
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()) //
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> {
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
httpHeaders.addAll(suppliedHeaders);
}
}));
provider = provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer());
return provider;
}
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
Assert.notNull(headersSupplier, "headersSupplier must not be null");
this.headersSupplier = headersSupplier;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders)
*/
@Override
public Mono<Boolean> ping(HttpHeaders headers) {
return sendRequest(new MainRequest(), requestCreator.ping(), RawActionResponse.class, headers) //
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.onErrorResume(NoReachableHostException.class, error -> Mono.just(false)).next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#info(org.springframework.http.HttpHeaders)
*/
@Override
public Mono<MainResponse> info(HttpHeaders headers) {
@@ -323,6 +312,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#get(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.GetRequest)
*/
@Override
public Mono<GetResult> get(HttpHeaders headers, GetRequest getRequest) {
@@ -332,49 +325,74 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#multiGet(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.MultiGetRequest)
*/
@Override
public Flux<MultiGetItemResponse> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) {
public Flux<GetResult> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) {
return sendRequest(multiGetRequest, requestCreator.multiGet(), MultiGetResponse.class, headers)
.map(MultiGetResponse::getResponses) //
.flatMap(Flux::fromArray); //
.flatMap(Flux::fromArray) //
.filter(it -> !it.isFailed() && it.getResponse().isExists()) //
.map(it -> DefaultReactiveElasticsearchClient.getResponseToGetResult(it.getResponse()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#exists(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.GetRequest)
*/
@Override
public Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest) {
return sendRequest(getRequest, requestCreator.exists(), RawActionResponse.class, headers) //
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.next();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.index.IndexRequest)
*/
@Override
public Mono<IndexResponse> index(HttpHeaders headers, IndexRequest indexRequest) {
return sendRequest(indexRequest, requestCreator.index(), IndexResponse.class, headers).next();
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;
}
@Override
public Cluster cluster() {
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.update.UpdateRequest)
*/
@Override
public Mono<UpdateResponse> update(HttpHeaders headers, UpdateRequest updateRequest) {
return sendRequest(updateRequest, requestCreator.update(), UpdateResponse.class, headers).next();
return sendRequest(updateRequest, requestCreator.update(), UpdateResponse.class, headers).publishNext();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.delete.DeleteRequest)
*/
@Override
public Mono<DeleteResponse> delete(HttpHeaders headers, DeleteRequest deleteRequest) {
return sendRequest(deleteRequest, requestCreator.delete(), DeleteResponse.class, headers) //
.next();
.publishNext();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#count(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Mono<Long> count(HttpHeaders headers, SearchRequest searchRequest) {
searchRequest.source().trackTotalHits(true);
@@ -386,12 +404,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.next();
}
@Override
public Flux<SearchHit> searchTemplate(HttpHeaders headers, SearchTemplateRequest searchTemplateRequest) {
return sendRequest(searchTemplateRequest, requestCreator.searchTemplate(), SearchTemplateResponse.class, headers)
.map(response -> response.getResponse().getHits()).flatMap(Flux::fromIterable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest) {
@@ -400,17 +416,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.flatMap(Flux::fromIterable);
}
@Override
public Mono<SearchResponse> searchForResponse(HttpHeaders headers, SearchRequest searchRequest) {
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers).next();
}
@Override
public Flux<Suggest> suggest(HttpHeaders headers, SearchRequest searchRequest) {
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
.map(SearchResponse::getSuggest);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#aggregate(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Flux<Aggregation> aggregate(HttpHeaders headers, SearchRequest searchRequest) {
@@ -425,6 +434,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.flatMap(Flux::fromIterable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#scroll(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Flux<SearchHit> scroll(HttpHeaders headers, SearchRequest searchRequest) {
@@ -435,26 +448,55 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
searchRequest.scroll(scrollTimeout);
}
EmitterProcessor<ActionRequest> outbound = EmitterProcessor.create(false);
FluxSink<ActionRequest> request = outbound.sink();
EmitterProcessor<SearchResponse> inbound = EmitterProcessor.create(false);
Flux<SearchResponse> exchange = outbound.startWith(searchRequest).flatMap(it -> {
if (it instanceof SearchRequest) {
return sendRequest((SearchRequest) it, requestCreator.search(), SearchResponse.class, headers);
} else if (it instanceof SearchScrollRequest) {
return sendRequest((SearchScrollRequest) it, requestCreator.scroll(), SearchResponse.class, headers);
} else if (it instanceof ClearScrollRequest) {
return sendRequest((ClearScrollRequest) it, requestCreator.clearScroll(), ClearScrollResponse.class, headers)
.flatMap(discard -> Flux.empty());
}
throw new IllegalArgumentException(
String.format("Cannot handle '%s'. Please make sure to use a 'SearchRequest' or 'SearchScrollRequest'.", it));
});
return Flux.usingWhen(Mono.fromSupplier(ScrollState::new),
state -> sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers)
.expand(searchResponse -> {
scrollState -> {
state.updateScrollId(searchResponse.getScrollId());
if (isEmpty(searchResponse.getHits())) {
return Mono.empty();
}
Flux<SearchHit> searchHits = inbound.<SearchResponse> handle((searchResponse, sink) -> {
return sendRequest(new SearchScrollRequest(searchResponse.getScrollId()).scroll(scrollTimeout),
requestCreator.scroll(), SearchResponse.class, headers);
scrollState.updateScrollId(searchResponse.getScrollId());
if (isEmpty(searchResponse.getHits())) {
}),
inbound.onComplete();
outbound.onComplete();
} else {
sink.next(searchResponse);
SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollState.getScrollId())
.scroll(scrollTimeout);
request.next(searchScrollRequest);
}
}).map(SearchResponse::getHits) //
.flatMap(Flux::fromIterable);
return searchHits.doOnSubscribe(ignore -> exchange.subscribe(inbound));
}, state -> cleanupScroll(headers, state), //
state -> cleanupScroll(headers, state), //
(state, ex) -> cleanupScroll(headers, state), //
state -> cleanupScroll(headers, state)) //
.filter(it -> !isEmpty(it.getHits())) //
.map(SearchResponse::getHits) //
.flatMapIterable(Function.identity()); //
state -> cleanupScroll(headers, state)); //
}
private static boolean isEmpty(@Nullable SearchHits hits) {
@@ -474,28 +516,124 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return sendRequest(clearScrollRequest, requestCreator.clearScroll(), ClearScrollResponse.class, headers);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.index.reindex.DeleteByQueryRequest)
*/
@Override
public Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest) {
return sendRequest(deleteRequest, requestCreator.deleteByQuery(), BulkByScrollResponse.class, headers) //
.next();
}
@Override
public Mono<ByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers) //
.next() //
.map(ByQueryResponse::of);
.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 <T> Mono<T> execute(ReactiveElasticsearchClientCallback<T> callback) {
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)
*/
@Override
public Mono<ClientResponse> execute(ReactiveElasticsearchClientCallback callback) {
return this.hostProvider.getActive(Verification.LAZY) //
.flatMap(callback::doWithClient) //
@@ -549,8 +687,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
// -->
private <REQ, RESP> 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);
}
@@ -558,14 +696,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
String logId = ClientLogger.newLogId();
return Flux
.from(execute(webClient -> sendRequest(webClient, logId, request, headers).exchangeToMono(clientResponse -> {
Publisher<? extends Resp> publisher = readResponseBody(logId, request, clientResponse, responseType);
return Mono.from(publisher);
})));
return execute(webClient -> sendRequest(webClient, logId, request, headers))
.flatMapMany(response -> readResponseBody(logId, request, response, responseType));
}
private RequestBodySpec sendRequest(WebClient webClient, String logId, Request request, HttpHeaders headers) {
private Mono<ClientResponse> sendRequest(WebClient webClient, String logId, Request request, HttpHeaders headers) {
RequestBodySpec requestBodySpec = webClient.method(HttpMethod.valueOf(request.getMethod().toUpperCase())) //
.uri(builder -> {
@@ -592,6 +727,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
request.getOptions().getHeaders().forEach(it -> theHeaders.add(it.getName(), it.getValue()));
}
}
// plus the ones from the supplier
HttpHeaders suppliedHeaders = headersSupplier.get();
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
theHeaders.addAll(suppliedHeaders);
}
});
if (request.getEntity() != null) {
@@ -601,180 +742,29 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters(),
body::get);
requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()))
.body(Mono.fromSupplier(body), String.class);
requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()));
requestBodySpec.body(Mono.fromSupplier(body), String.class);
} else {
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters());
}
return requestBodySpec;
return requestBodySpec //
.exchange() //
.onErrorReturn(ConnectException.class, ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE).build());
}
// region indices operations
@Override
public Mono<Boolean> createIndex(HttpHeaders headers,
org.elasticsearch.action.admin.indices.create.CreateIndexRequest createIndexRequest) {
private Lazy<String> bodyExtractor(Request request) {
return sendRequest(createIndexRequest, requestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
.map(AcknowledgedResponse::isAcknowledged) //
.next();
return Lazy.of(() -> {
try {
return EntityUtils.toString(request.getEntity());
} catch (IOException e) {
throw new RequestBodyEncodingException("Error encoding request", e);
}
});
}
@Override
public Mono<Boolean> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
return sendRequest(createIndexRequest, requestCreator.createIndexRequest(), AcknowledgedResponse.class, headers) //
.map(AcknowledgedResponse::isAcknowledged) //
.next();
}
@Override
public Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest) {
return sendRequest(closeIndexRequest, requestCreator.indexClose(), AcknowledgedResponse.class, headers) //
.then();
}
@Override
public Mono<Boolean> existsIndex(HttpHeaders headers,
org.elasticsearch.action.admin.indices.get.GetIndexRequest request) {
return sendRequest(request, requestCreator.indexExists(), RawActionResponse.class, headers) //
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
.next();
}
@Override
public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
return sendRequest(request, requestCreator.indexExistsRequest(), RawActionResponse.class, headers) //
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
.next();
}
@Override
public Mono<Boolean> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
return sendRequest(request, requestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
.map(AcknowledgedResponse::isAcknowledged) //
.next();
}
@Override
public Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest) {
return sendRequest(flushRequest, requestCreator.flushIndex(), FlushResponse.class, headers) //
.then();
}
@Override
@Deprecated
public Mono<org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse> getMapping(HttpHeaders headers,
org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest) {
return sendRequest(getMappingsRequest, requestCreator.getMapping(),
org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse.class, headers).next();
}
@Override
public Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest) {
return sendRequest(getMappingsRequest, requestCreator.getMappingRequest(), GetMappingsResponse.class, headers) //
.next();
}
@Override
public Mono<GetFieldMappingsResponse> getFieldMapping(HttpHeaders headers,
GetFieldMappingsRequest getFieldMappingsRequest) {
return sendRequest(getFieldMappingsRequest, requestCreator.getFieldMapping(), GetFieldMappingsResponse.class,
headers).next();
}
@Override
public Mono<GetSettingsResponse> getSettings(HttpHeaders headers, GetSettingsRequest getSettingsRequest) {
return sendRequest(getSettingsRequest, requestCreator.getSettings(), GetSettingsResponse.class, headers).next();
}
@Override
public Mono<Boolean> putMapping(HttpHeaders headers,
org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) {
return sendRequest(putMappingRequest, requestCreator.putMapping(), AcknowledgedResponse.class, headers) //
.map(AcknowledgedResponse::isAcknowledged) //
.next();
}
@Override
public Mono<Boolean> putMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
return sendRequest(putMappingRequest, requestCreator.putMappingRequest(), AcknowledgedResponse.class, headers) //
.map(AcknowledgedResponse::isAcknowledged) //
.next();
}
@Override
public Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest request) {
return sendRequest(request, requestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
.then();
}
@Override
public Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest) {
return sendRequest(refreshRequest, requestCreator.indexRefresh(), RefreshResponse.class, headers) //
.then();
}
@Override
public Mono<Boolean> updateAliases(HttpHeaders headers, IndicesAliasesRequest indicesAliasesRequest) {
return sendRequest(indicesAliasesRequest, requestCreator.updateAlias(), AcknowledgedResponse.class, headers)
.map(AcknowledgedResponse::isAcknowledged).next();
}
@Override
public Mono<GetAliasesResponse> getAliases(HttpHeaders headers, GetAliasesRequest getAliasesRequest) {
return sendRequest(getAliasesRequest, requestCreator.getAlias(), GetAliasesResponse.class, headers).next();
}
@Override
public Mono<Boolean> putTemplate(HttpHeaders headers, PutIndexTemplateRequest putIndexTemplateRequest) {
return sendRequest(putIndexTemplateRequest, requestCreator.putTemplate(), AcknowledgedResponse.class, headers)
.map(AcknowledgedResponse::isAcknowledged).next();
}
@Override
public Mono<GetIndexTemplatesResponse> getTemplate(HttpHeaders headers,
GetIndexTemplatesRequest getIndexTemplatesRequest) {
return (sendRequest(getIndexTemplatesRequest, requestCreator.getTemplates(), GetIndexTemplatesResponse.class,
headers)).next();
}
@Override
public Mono<Boolean> existsTemplate(HttpHeaders headers, IndexTemplatesExistRequest indexTemplatesExistRequest) {
return sendRequest(indexTemplatesExistRequest, requestCreator.templatesExist(), RawActionResponse.class, headers) //
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
.next();
}
@Override
public Mono<Boolean> deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest) {
return sendRequest(deleteIndexTemplateRequest, requestCreator.deleteTemplate(), AcknowledgedResponse.class, headers)
.map(AcknowledgedResponse::isAcknowledged).next();
}
@Override
public Mono<GetIndexResponse> getIndex(HttpHeaders headers, GetIndexRequest getIndexRequest) {
return sendRequest(getIndexRequest, requestCreator.getIndex(), GetIndexResponse.class, headers).next();
}
// endregion
// region cluster operations
@Override
public Mono<ClusterHealthResponse> health(HttpHeaders headers, ClusterHealthRequest clusterHealthRequest) {
return sendRequest(clusterHealthRequest, requestCreator.clusterHealth(), ClusterHealthResponse.class, headers)
.next();
}
// endregion
// region helper functions
private <T> Publisher<? extends T> readResponseBody(String logId, Request request, ClientResponse response,
Class<T> responseType) {
@@ -793,7 +783,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
if (response.statusCode().is4xxClientError()) {
ClientLogger.logRawResponse(logId, response.statusCode());
return handleClientError(logId, response, responseType);
return handleClientError(logId, request, response, responseType);
}
return response.body(BodyExtractors.toMono(byte[].class)) //
@@ -810,10 +800,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
Method fromXContent = ReflectionUtils.findMethod(responseType, "fromXContent", XContentParser.class);
if (fromXContent == null) {
return Mono.error(new UncategorizedElasticsearchException(
"No method named fromXContent found in " + responseType.getCanonicalName()));
}
return Mono.justOrEmpty(responseType
.cast(ReflectionUtils.invokeMethod(fromXContent, responseType, createParser(mediaType, content))));
@@ -836,20 +822,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
}
private Lazy<String> bodyExtractor(Request request) {
return Lazy.of(() -> {
try {
return EntityUtils.toString(request.getEntity());
} catch (IOException e) {
throw new RequestBodyEncodingException("Error encoding request", e);
}
});
}
// endregion
// region error and exception handling
private <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
int statusCode = response.statusCode().value();
@@ -867,7 +839,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
request.getMethod(), request.getEndpoint(), statusCode), status)));
}
private <T> Publisher<? extends T> handleClientError(String logId, ClientResponse response, Class<T> responseType) {
private <T> Publisher<? extends T> handleClientError(String logId, Request request, ClientResponse response,
Class<T> responseType) {
int statusCode = response.statusCode().value();
RestStatus status = RestStatus.fromCode(statusCode);
@@ -880,6 +853,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.flatMap(content -> doDecode(response, responseType, content));
}
// region ElasticsearchException helper
/**
* checks if the given content body contains an {@link ElasticsearchException}, if yes it is returned in a Mono.error.
* Otherwise the content is returned in the Mono
@@ -915,19 +889,18 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
try {
XContentParser parser = createParser(mediaType, content);
// we have a JSON object with an error and a status field
parser.nextToken(); // Skip START_OBJECT
XContentParser.Token token = parser.nextToken(); // Skip START_OBJECT
XContentParser.Token token;
do {
token = parser.nextToken();
if ("error".equals(parser.currentName())) {
if (parser.currentName().equals("error")) {
return ElasticsearchException.failureFromXContent(parser);
}
} while (token == XContentParser.Token.FIELD_NAME);
return null;
} catch (Exception e) {
} catch (IOException e) {
return new ElasticsearchStatusException(content, status);
}
}
@@ -948,7 +921,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
*
* @author Christoph Strobl
*/
static class ClientStatus implements Status {
class ClientStatus implements Status {
private final Collection<ElasticsearchHost> connectedHosts;
@@ -46,9 +46,8 @@ class DefaultWebClientProvider implements WebClientProvider {
private final @Nullable ClientHttpConnector connector;
private final Consumer<Throwable> errorListener;
private final HttpHeaders headers;
private final @Nullable String pathPrefix;
private final String pathPrefix;
private final Function<WebClient, WebClient> webClientConfigurer;
private final Consumer<WebClient.RequestHeadersSpec<?>> requestConfigurer;
/**
* Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}.
@@ -57,31 +56,28 @@ class DefaultWebClientProvider implements WebClientProvider {
* @param connector can be {@literal null}.
*/
DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector) {
this(scheme, connector, e -> {}, HttpHeaders.EMPTY, null, Function.identity(), requestHeadersSpec -> {});
this(scheme, connector, e -> {}, HttpHeaders.EMPTY, null, Function.identity());
}
/**
* Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}.
*
* @param pathPrefix can be {@literal null}
* @param scheme must not be {@literal null}.
* @param connector can be {@literal null}.
* @param errorListener must not be {@literal null}.
* @param headers must not be {@literal null}.
* @param pathPrefix can be {@literal null}.
* @param webClientConfigurer must not be {@literal null}.
* @param requestConfigurer must not be {@literal null}.
*/
private DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector,
Consumer<Throwable> errorListener, HttpHeaders headers, @Nullable String pathPrefix,
Function<WebClient, WebClient> webClientConfigurer, Consumer<WebClient.RequestHeadersSpec<?>> requestConfigurer) {
Function<WebClient, WebClient> webClientConfigurer) {
Assert.notNull(scheme, "Scheme must not be null! A common scheme would be 'http'.");
Assert.notNull(errorListener, "errorListener must not be null! You may want use a no-op one 'e -> {}' instead.");
Assert.notNull(headers, "headers must not be null! Think about using 'HttpHeaders.EMPTY' as an alternative.");
Assert.notNull(webClientConfigurer,
"webClientConfigurer must not be null! You may want use a no-op one 'Function.identity()' instead.");
Assert.notNull(requestConfigurer,
"requestConfigurer must not be null! You may want use a no-op one 'r -> {}' instead.\"");
this.cachedClients = new ConcurrentHashMap<>();
this.scheme = scheme;
@@ -90,7 +86,6 @@ class DefaultWebClientProvider implements WebClientProvider {
this.headers = headers;
this.pathPrefix = pathPrefix;
this.webClientConfigurer = webClientConfigurer;
this.requestConfigurer = requestConfigurer;
}
@Override
@@ -111,7 +106,6 @@ class DefaultWebClientProvider implements WebClientProvider {
return this.errorListener;
}
@Nullable
@Override
public String getPathPrefix() {
return pathPrefix;
@@ -126,17 +120,7 @@ class DefaultWebClientProvider implements WebClientProvider {
merged.addAll(this.headers);
merged.addAll(headers);
return new DefaultWebClientProvider(scheme, connector, errorListener, merged, pathPrefix, webClientConfigurer,
requestConfigurer);
}
@Override
public WebClientProvider withRequestConfigurer(Consumer<WebClient.RequestHeadersSpec<?>> requestConfigurer) {
Assert.notNull(requestConfigurer, "requestConfigurer must not be null.");
return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer,
requestConfigurer);
return new DefaultWebClientProvider(scheme, connector, errorListener, merged, pathPrefix, webClientConfigurer);
}
@Override
@@ -145,8 +129,7 @@ class DefaultWebClientProvider implements WebClientProvider {
Assert.notNull(errorListener, "Error listener must not be null.");
Consumer<Throwable> listener = this.errorListener.andThen(errorListener);
return new DefaultWebClientProvider(scheme, this.connector, listener, headers, pathPrefix, webClientConfigurer,
requestConfigurer);
return new DefaultWebClientProvider(scheme, this.connector, listener, headers, pathPrefix, webClientConfigurer);
}
@Override
@@ -154,21 +137,19 @@ class DefaultWebClientProvider implements WebClientProvider {
Assert.notNull(pathPrefix, "pathPrefix must not be null.");
return new DefaultWebClientProvider(this.scheme, this.connector, this.errorListener, this.headers, pathPrefix,
webClientConfigurer, requestConfigurer);
webClientConfigurer);
}
@Override
public WebClientProvider withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer) {
return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer,
requestConfigurer);
return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer);
}
protected WebClient createWebClientForSocketAddress(InetSocketAddress socketAddress) {
Builder builder = WebClient.builder() //
.defaultHeaders(it -> it.addAll(getDefaultHeaders())) //
.defaultRequest(requestConfigurer);
Builder builder = WebClient.builder().defaultHeaders(it -> it.addAll(getDefaultHeaders()));
if (connector != null) {
builder = builder.clientConnector(connector);
@@ -43,7 +43,7 @@ public interface HostProvider<T extends HostProvider<T>> {
* Create a new {@link HostProvider} best suited for the given {@link WebClientProvider} and number of hosts.
*
* @param clientProvider must not be {@literal null} .
* @param headersSupplier to supply custom headers, must not be {@literal null}
* @param headersSupplier to supply custom headers, must not be {@literal null}
* @param endpoints must not be {@literal null} nor empty.
* @return new instance of {@link HostProvider}.
*/
@@ -54,9 +54,9 @@ public interface HostProvider<T extends HostProvider<T>> {
Assert.notEmpty(endpoints, "Please provide at least one endpoint to connect to.");
if (endpoints.length == 1) {
return new SingleNodeHostProvider(clientProvider, endpoints[0]);
return new SingleNodeHostProvider(clientProvider, headersSupplier, endpoints[0]);
} else {
return new MultiNodeHostProvider(clientProvider, endpoints);
return new MultiNodeHostProvider(clientProvider,headersSupplier, endpoints);
}
}
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -28,6 +29,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,11 +53,14 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
private final static Logger LOG = LoggerFactory.getLogger(MultiNodeHostProvider.class);
private final WebClientProvider clientProvider;
private final Supplier<HttpHeaders> headersSupplier;
private final Map<InetSocketAddress, ElasticsearchHost> hosts;
MultiNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress... endpoints) {
MultiNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier,
InetSocketAddress... endpoints) {
this.clientProvider = clientProvider;
this.headersSupplier = headersSupplier;
this.hosts = new ConcurrentHashMap<>();
for (InetSocketAddress endpoint : endpoints) {
this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN));
@@ -140,43 +145,34 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
.next();
}
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, State> tuple2) {
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, ClientResponse> tuple2) {
State state = tuple2.getT2();
State state = tuple2.getT2().statusCode().isError() ? State.OFFLINE : State.ONLINE;
ElasticsearchHost elasticsearchHost = new ElasticsearchHost(tuple2.getT1(), state);
hosts.put(tuple2.getT1(), elasticsearchHost);
return elasticsearchHost;
}
private Flux<Tuple2<InetSocketAddress, State>> checkNodes(@Nullable State state) {
LOG.trace("checkNodes() with state " + state);
private Flux<Tuple2<InetSocketAddress, ClientResponse>> checkNodes(@Nullable State state) {
return Flux.fromIterable(hosts()) //
.filter(entry -> state == null || entry.getState().equals(state)) //
.map(ElasticsearchHost::getEndpoint) //
.concatMap(host -> {
LOG.trace("checking host " + host);
Mono<ClientResponse> clientResponseMono = createWebClient(host) //
Mono<ClientResponse> exchange = createWebClient(host) //
.head().uri("/") //
.exchangeToMono(Mono::just) //
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
.exchange() //
.timeout(Duration.ofSeconds(1)) //
.doOnError(throwable -> {
LOG.trace("error checking host " + host + ", " + throwable.getMessage());
hosts.put(host, new ElasticsearchHost(host, State.OFFLINE));
clientProvider.getErrorListener().accept(throwable);
});
return Mono.just(host) //
.zipWith(clientResponseMono.flatMap(it -> it.releaseBody() //
.thenReturn(it.statusCode().isError() ? State.OFFLINE : State.ONLINE)));
return Mono.just(host).zipWith(exchange);
}) //
.map(tuple -> {
LOG.trace("check result " + tuple);
return tuple;
}).onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
.onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
}
private List<ElasticsearchHost> hosts() {
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +15,11 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import reactor.core.publisher.Mono;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ClientHttpResponse;
@@ -74,13 +73,4 @@ class RawActionResponse extends ActionResponse {
@Override
public void writeTo(StreamOutput out) throws IOException {
}
/**
* Ensure the response body is released to properly release the underlying connection.
*
* @return
*/
public Mono<Void> releaseBody() {
return delegate.releaseBody();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -3,16 +3,14 @@ package org.springframework.data.elasticsearch.client.reactive;
import java.io.IOException;
import java.util.function.Function;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
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.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
@@ -25,24 +23,12 @@ import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetFieldMappingsRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.client.util.RequestConverters;
/**
* @author Roman Puchkovskiy
* @author Farid Faoudi
* @author George Popides
* @since 4.0
*/
public interface RequestCreator {
@@ -51,10 +37,6 @@ public interface RequestCreator {
return RequestConverters::search;
}
default Function<SearchTemplateRequest, Request> searchTemplate() {
return RequestConverters::searchTemplate;
}
default Function<SearchScrollRequest, Request> scroll() {
return RequestConverters::searchScroll;
}
@@ -99,13 +81,6 @@ public interface RequestCreator {
return RequestConverters::deleteByQuery;
}
/**
* @since 4.2
*/
default Function<UpdateByQueryRequest, Request> updateByQuery() {
return RequestConverters::updateByQuery;
}
default Function<BulkRequest, Request> bulk() {
return request -> {
@@ -113,25 +88,14 @@ public interface RequestCreator {
try {
return RequestConverters.bulk(request);
} catch (IOException e) {
throw new UncategorizedElasticsearchException("Could not parse request", e);
throw new ElasticsearchException("Could not parse request", e);
}
};
}
// --> INDICES
/**
* @deprecated since 4.2
*/
@Deprecated
default Function<org.elasticsearch.action.admin.indices.get.GetIndexRequest, Request> indexExists() {
return RequestConverters::indexExists;
}
/**
* @since 4.2
*/
default Function<GetIndexRequest, Request> indexExistsRequest() {
default Function<GetIndexRequest, Request> indexExists() {
return RequestConverters::indexExists;
}
@@ -139,18 +103,7 @@ public interface RequestCreator {
return RequestConverters::indexDelete;
}
/**
* @deprecated since 4.2
*/
@Deprecated
default Function<org.elasticsearch.action.admin.indices.create.CreateIndexRequest, Request> indexCreate() {
return RequestConverters::indexCreate;
}
/**
* @since 4.2
*/
default Function<CreateIndexRequest, Request> createIndexRequest() {
default Function<CreateIndexRequest, Request> indexCreate() {
return RequestConverters::indexCreate;
}
@@ -166,18 +119,7 @@ public interface RequestCreator {
return RequestConverters::indexRefresh;
}
/**
* @deprecated since 4.2
*/
@Deprecated
default Function<org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest, Request> putMapping() {
return RequestConverters::putMapping;
}
/**
* @since 4.2
*/
default Function<PutMappingRequest, Request> putMappingRequest() {
default Function<PutMappingRequest, Request> putMapping() {
return RequestConverters::putMapping;
}
@@ -189,89 +131,4 @@ public interface RequestCreator {
return RequestConverters::count;
}
/**
* @since 4.1
*/
default Function<GetSettingsRequest, Request> getSettings() {
return RequestConverters::getSettings;
}
/**
* @since 4.1
* @deprecated since 4.2
*/
@Deprecated
default Function<org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest, Request> getMapping() {
return RequestConverters::getMapping;
}
/**
* @since 4.2
*/
default Function<GetMappingsRequest, Request> getMappingRequest() {
return RequestConverters::getMapping;
}
/**
* @since 4.1
*/
default Function<IndicesAliasesRequest, Request> updateAlias() {
return RequestConverters::updateAliases;
}
/**
* @since 4.1
*/
default Function<GetAliasesRequest, Request> getAlias() {
return RequestConverters::getAlias;
}
/**
* @since 4.1
*/
default Function<PutIndexTemplateRequest, Request> putTemplate() {
return RequestConverters::putTemplate;
}
/**
* @since 4.1
*/
default Function<GetIndexTemplatesRequest, Request> getTemplates() {
return RequestConverters::getTemplates;
}
/**
* @since 4.1
*/
default Function<IndexTemplatesExistRequest, Request> templatesExist() {
return RequestConverters::templatesExist;
}
/**
* @since 4.1
*/
default Function<DeleteIndexTemplateRequest, Request> deleteTemplate() {
return RequestConverters::deleteTemplate;
}
/**
* @since 4.2
*/
default Function<GetFieldMappingsRequest, Request> getFieldMapping() {
return RequestConverters::getFieldMapping;
}
/**
* @since 4.2
*/
default Function<GetIndexRequest, Request> getIndex() {
return RequestConverters::getIndex;
}
/**
* @since 4.2
*/
default Function<ClusterHealthRequest, Request> clusterHealth() {
return RequestConverters::clusterHealth;
}
}
@@ -15,10 +15,12 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
@@ -36,12 +38,14 @@ import org.springframework.web.reactive.function.client.WebClient;
class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
private final WebClientProvider clientProvider;
private final Supplier<HttpHeaders> headersSupplier;
private final InetSocketAddress endpoint;
private volatile ElasticsearchHost state;
SingleNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress endpoint) {
SingleNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, InetSocketAddress endpoint) {
this.clientProvider = clientProvider;
this.headersSupplier = headersSupplier;
this.endpoint = endpoint;
this.state = new ElasticsearchHost(this.endpoint, State.UNKNOWN);
}
@@ -54,8 +58,10 @@ class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
public Mono<ClusterInformation> clusterInfo() {
return createWebClient(endpoint) //
.head().uri("/") //
.exchangeToMono(it -> {
.head().uri("/")
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
.exchange() //
.flatMap(it -> {
if (it.statusCode().isError()) {
state = ElasticsearchHost.offline(endpoint);
} else {
@@ -63,10 +69,12 @@ class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
}
return Mono.just(state);
}).onErrorResume(throwable -> {
state = ElasticsearchHost.offline(endpoint);
clientProvider.getErrorListener().accept(throwable);
return Mono.just(state);
}).map(elasticsearchHost -> new ClusterInformation(Collections.singleton(elasticsearchHost)));
}) //
.flatMap(it -> Mono.just(new ClusterInformation(Collections.singleton(it))));
}
/*
@@ -89,16 +97,14 @@ class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
return Mono.just(endpoint);
}
return clusterInfo().handle((information, sink) -> {
return clusterInfo().flatMap(it -> {
ElasticsearchHost host = information.getNodes().iterator().next();
ElasticsearchHost host = it.getNodes().iterator().next();
if (host.isOnline()) {
sink.next(host.getEndpoint());
return;
return Mono.just(host.getEndpoint());
}
sink.error(new NoReachableHostException(Collections.singleton(host)));
return Mono.error(() -> new NoReachableHostException(Collections.singleton(host)));
});
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -101,7 +101,7 @@ public interface WebClientProvider {
/**
* Obtain the {@link String pathPrefix} to be used.
*
*
* @return the pathPrefix if set.
* @since 4.0
*/
@@ -126,7 +126,7 @@ public interface WebClientProvider {
/**
* Create a new instance of {@link WebClientProvider} where HTTP requests are called with the given path prefix.
*
*
* @param pathPrefix Path prefix to add to requests
* @return new instance of {@link WebClientProvider}
* @since 4.0
@@ -136,20 +136,10 @@ public interface WebClientProvider {
/**
* Create a new instance of {@link WebClientProvider} calling the given {@link Function} to configure the
* {@link WebClient}.
*
*
* @param webClientConfigurer configuration function
* @return new instance of {@link WebClientProvider}
* @since 4.0
*/
WebClientProvider withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
/**
* Create a new instance of {@link WebClientProvider} calling the given {@link Consumer} to configure the requests of
* this {@link WebClient}.
*
* @param requestConfigurer request configuration callback
* @return new instance of {@link WebClientProvider}
* @since 4.3
*/
WebClientProvider withRequestConfigurer(Consumer<WebClient.RequestHeadersSpec<?>> requestConfigurer);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,12 +20,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.client.analytics.InferencePipelineAggregationBuilder;
import org.elasticsearch.client.analytics.ParsedInference;
import org.elasticsearch.client.analytics.ParsedStringStats;
import org.elasticsearch.client.analytics.ParsedTopMetrics;
import org.elasticsearch.client.analytics.StringStatsAggregationBuilder;
import org.elasticsearch.client.analytics.TopMetricsAggregationBuilder;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ContextParser;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@@ -50,8 +44,6 @@ import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregati
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedAutoDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedVariableWidthHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.VariableWidthHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.missing.MissingAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.missing.ParsedMissing;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
@@ -68,7 +60,16 @@ import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.sampler.InternalSampler;
import org.elasticsearch.search.aggregations.bucket.sampler.ParsedSampler;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantLongTerms;
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantStringTerms;
import org.elasticsearch.search.aggregations.bucket.significant.SignificantLongTerms;
import org.elasticsearch.search.aggregations.bucket.significant.SignificantStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.metrics.*;
import org.elasticsearch.search.aggregations.pipeline.*;
import org.elasticsearch.search.suggest.Suggest;
@@ -84,7 +85,7 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsea
* <p>
* Original implementation source {@link org.elasticsearch.client.RestHighLevelClient#getDefaultNamedXContents()} by
* {@literal Elasticsearch} (<a href="https://www.elastic.co">https://www.elastic.co</a>) licensed under the Apache
* License, Version 2.0. The latest version used from Elasticsearch is 7.10.2.
* License, Version 2.0.
* </p>
* Modified for usage with {@link ReactiveElasticsearchClient}.
* <p>
@@ -129,13 +130,9 @@ public class NamedXContents {
map.put(HistogramAggregationBuilder.NAME, (p, c) -> ParsedHistogram.fromXContent(p, (String) c));
map.put(DateHistogramAggregationBuilder.NAME, (p, c) -> ParsedDateHistogram.fromXContent(p, (String) c));
map.put(AutoDateHistogramAggregationBuilder.NAME, (p, c) -> ParsedAutoDateHistogram.fromXContent(p, (String) c));
map.put(VariableWidthHistogramAggregationBuilder.NAME,
(p, c) -> ParsedVariableWidthHistogram.fromXContent(p, (String) c));
map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
map.put(LongTerms.NAME, (p, c) -> ParsedLongTerms.fromXContent(p, (String) c));
map.put(DoubleTerms.NAME, (p, c) -> ParsedDoubleTerms.fromXContent(p, (String) c));
map.put(LongRareTerms.NAME, (p, c) -> ParsedLongRareTerms.fromXContent(p, (String) c));
map.put(StringRareTerms.NAME, (p, c) -> ParsedStringRareTerms.fromXContent(p, (String) c));
map.put(MissingAggregationBuilder.NAME, (p, c) -> ParsedMissing.fromXContent(p, (String) c));
map.put(NestedAggregationBuilder.NAME, (p, c) -> ParsedNested.fromXContent(p, (String) c));
map.put(ReverseNestedAggregationBuilder.NAME, (p, c) -> ParsedReverseNested.fromXContent(p, (String) c));
@@ -155,9 +152,6 @@ public class NamedXContents {
map.put(IpRangeAggregationBuilder.NAME, (p, c) -> ParsedBinaryRange.fromXContent(p, (String) c));
map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
map.put(CompositeAggregationBuilder.NAME, (p, c) -> ParsedComposite.fromXContent(p, (String) c));
map.put(StringStatsAggregationBuilder.NAME, (p, c) -> ParsedStringStats.PARSER.parse(p, (String) c));
map.put(TopMetricsAggregationBuilder.NAME, (p, c) -> ParsedTopMetrics.PARSER.parse(p, (String) c));
map.put(InferencePipelineAggregationBuilder.NAME, (p, c) -> ParsedInference.fromXContent(p, (String) (c)));
List<NamedXContentRegistry.Entry> entries = map.entrySet().stream().map(
entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
.collect(Collectors.toList());
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,11 +25,6 @@ import java.util.Locale;
import java.util.StringJoiner;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.lucene.util.BytesRef;
@@ -38,19 +33,14 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
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.get.GetMappingsRequest;
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.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.explain.ExplainRequest;
@@ -71,10 +61,6 @@ import org.elasticsearch.client.Requests;
import org.elasticsearch.client.RethrottleRequest;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.client.indices.GetFieldMappingsRequest;
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
@@ -98,7 +84,6 @@ 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.script.mustache.SearchTemplateRequest;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.tasks.TaskId;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
@@ -107,9 +92,8 @@ import org.springframework.lang.Nullable;
/**
* <p>
* Original implementation source {@link org.elasticsearch.client.RequestConverters},
* {@link org.elasticsearch.client.IndicesRequestConverters} and
* {@link org.elasticsearch.client.ClusterRequestConverters} 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}.
@@ -118,7 +102,6 @@ import org.springframework.lang.Nullable;
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Farid Faoudi
* @since 3.2
*/
@SuppressWarnings("JavadocReference")
@@ -413,24 +396,9 @@ public class RequestConverters {
return request;
}
public static Request searchTemplate(SearchTemplateRequest templateRequest) {
SearchRequest searchRequest = templateRequest.getRequest();
String endpoint = new EndpointBuilder().addCommaSeparatedPathParts(templateRequest.getRequest().indices())
.addPathPart("_search").addPathPart("template").build();
Request request = new Request(HttpMethod.GET.name(), endpoint);
Params params = new Params(request);
addSearchRequestParams(params, searchRequest);
request.setEntity(createEntity(templateRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
/**
* Creates a count request.
*
*
* @param countRequest the search defining the data to be counted
* @return Elasticsearch count request
* @since 4.0
@@ -555,32 +523,26 @@ public class RequestConverters {
}
public static Request updateByQuery(UpdateByQueryRequest updateByQueryRequest) {
String endpoint = endpoint(updateByQueryRequest.indices(), "_update_by_query");
String endpoint = endpoint(updateByQueryRequest.indices(), updateByQueryRequest.getDocTypes(), "_update_by_query");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withRouting(updateByQueryRequest.getRouting()) //
.withPipeline(updateByQueryRequest.getPipeline()) //
.withRefresh(updateByQueryRequest.isRefresh()) //
.withTimeout(updateByQueryRequest.getTimeout()) //
.withWaitForActiveShards(updateByQueryRequest.getWaitForActiveShards()) //
.withRequestsPerSecond(updateByQueryRequest.getRequestsPerSecond()) //
.withIndicesOptions(updateByQueryRequest.indicesOptions()); //
Params params = new Params(request).withRouting(updateByQueryRequest.getRouting())
.withPipeline(updateByQueryRequest.getPipeline()).withRefresh(updateByQueryRequest.isRefresh())
.withTimeout(updateByQueryRequest.getTimeout())
.withWaitForActiveShards(updateByQueryRequest.getWaitForActiveShards())
.withRequestsPerSecond(updateByQueryRequest.getRequestsPerSecond())
.withIndicesOptions(updateByQueryRequest.indicesOptions());
if (!updateByQueryRequest.isAbortOnVersionConflict()) {
params.putParam("conflicts", "proceed");
}
if (updateByQueryRequest.getBatchSize() != AbstractBulkByScrollRequest.DEFAULT_SCROLL_SIZE) {
params.putParam("scroll_size", Integer.toString(updateByQueryRequest.getBatchSize()));
}
if (updateByQueryRequest.getScrollTime() != AbstractBulkByScrollRequest.DEFAULT_SCROLL_TIMEOUT) {
params.putParam("scroll", updateByQueryRequest.getScrollTime());
}
if (updateByQueryRequest.getMaxDocs() > 0) {
params.putParam("max_docs", Integer.toString(updateByQueryRequest.getMaxDocs()));
if (updateByQueryRequest.getSize() > 0) {
params.putParam("size", Integer.toString(updateByQueryRequest.getSize()));
}
request.setEntity(createEntity(updateByQueryRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -709,22 +671,6 @@ public class RequestConverters {
return request;
}
public static Request getIndex(org.elasticsearch.client.indices.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);
@@ -752,22 +698,6 @@ public class RequestConverters {
return request;
}
public static Request indexExists(org.elasticsearch.client.indices.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);
@@ -804,32 +734,16 @@ public class RequestConverters {
return request;
}
public static Request indexCreate(org.elasticsearch.client.indices.CreateIndexRequest createIndexRequest) {
String endpoint = RequestConverters.endpoint(new String[] { createIndexRequest.index() });
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();
// using a GET here as reactor-netty set the transfer-encoding to chunked on POST requests which blocks on
// Elasticsearch when no body is sent.
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_refresh"));
Request request = new Request(HttpMethod.POST.name(), RequestConverters.endpoint(indices, "_refresh"));
Params parameters = new Params(request);
parameters.withIndicesOptions(refreshRequest.indicesOptions());
return request;
}
@Deprecated
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) {
@@ -837,24 +751,12 @@ public class RequestConverters {
}
Request request = new Request(HttpMethod.PUT.name(),
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping"));
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping", putMappingRequest.type()));
RequestConverters.Params parameters = new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(false);
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
public static Request putMapping(org.elasticsearch.client.indices.PutMappingRequest putMappingRequest) {
Request request = new Request(HttpMethod.PUT.name(),
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping"));
new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(false);
.withIncludeTypeName(true);
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -870,160 +772,6 @@ public class RequestConverters {
return request;
}
public static Request getMapping(GetMappingsRequest getMappingsRequest) {
String[] indices = getMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.indices();
String[] types = getMappingsRequest.types() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.types();
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_mapping", types));
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
parameters.withLocal(getMappingsRequest.local());
parameters.withIncludeTypeName(false);
return request;
}
public static Request getMapping(org.elasticsearch.client.indices.GetMappingsRequest getMappingsRequest) {
String[] indices = getMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.indices();
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_mapping"));
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
parameters.withLocal(getMappingsRequest.local());
parameters.withIncludeTypeName(false);
return request;
}
public static Request getSettings(GetSettingsRequest getSettingsRequest) {
String[] indices = getSettingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getSettingsRequest.indices();
String[] names = getSettingsRequest.names() == null ? Strings.EMPTY_ARRAY : getSettingsRequest.names();
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_settings", names));
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withIndicesOptions(getSettingsRequest.indicesOptions());
parameters.withLocal(getSettingsRequest.local());
parameters.withIncludeDefaults(getSettingsRequest.includeDefaults());
parameters.withMasterTimeout(getSettingsRequest.masterNodeTimeout());
return request;
}
public static Request updateAliases(IndicesAliasesRequest indicesAliasesRequest) {
Request request = new Request(HttpPost.METHOD_NAME, "/_aliases");
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withTimeout(indicesAliasesRequest.timeout());
parameters.withMasterTimeout(indicesAliasesRequest.masterNodeTimeout());
request
.setEntity(RequestConverters.createEntity(indicesAliasesRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
public static Request getAlias(GetAliasesRequest getAliasesRequest) {
String[] indices = getAliasesRequest.indices() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.indices();
String[] aliases = getAliasesRequest.aliases() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.aliases();
String endpoint = RequestConverters.endpoint(indices, "_alias", aliases);
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
RequestConverters.Params params = new RequestConverters.Params(request);
params.withIndicesOptions(getAliasesRequest.indicesOptions());
params.withLocal(getAliasesRequest.local());
return request;
}
public static Request putTemplate(PutIndexTemplateRequest putIndexTemplateRequest) {
String endpoint = (new RequestConverters.EndpointBuilder()) //
.addPathPartAsIs("_template") //
.addPathPart(putIndexTemplateRequest.name()) //
.build(); //
Request request = new Request(HttpPut.METHOD_NAME, endpoint);
RequestConverters.Params params = new RequestConverters.Params(request);
params.withMasterTimeout(putIndexTemplateRequest.masterNodeTimeout());
if (putIndexTemplateRequest.create()) {
params.putParam("create", Boolean.TRUE.toString());
}
if (Strings.hasText(putIndexTemplateRequest.cause())) {
params.putParam("cause", putIndexTemplateRequest.cause());
}
request.setEntity(
RequestConverters.createEntity(putIndexTemplateRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
public static Request getTemplates(GetIndexTemplatesRequest getIndexTemplatesRequest) {
final String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template")
.addCommaSeparatedPathParts(getIndexTemplatesRequest.names()).build();
final Request request = new Request(HttpGet.METHOD_NAME, endpoint);
RequestConverters.Params params = new RequestConverters.Params(request);
params.withLocal(getIndexTemplatesRequest.isLocal());
params.withMasterTimeout(getIndexTemplatesRequest.getMasterNodeTimeout());
return request;
}
public static Request templatesExist(IndexTemplatesExistRequest indexTemplatesExistRequest) {
final String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template")
.addCommaSeparatedPathParts(indexTemplatesExistRequest.names()).build();
final Request request = new Request(HttpHead.METHOD_NAME, endpoint);
final RequestConverters.Params params = new RequestConverters.Params(request);
params.withLocal(indexTemplatesExistRequest.isLocal());
params.withMasterTimeout(indexTemplatesExistRequest.getMasterNodeTimeout());
return request;
}
public static Request deleteTemplate(DeleteIndexTemplateRequest deleteIndexTemplateRequest) {
String name = deleteIndexTemplateRequest.name();
String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template").addPathPart(name).build();
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
RequestConverters.Params params = new RequestConverters.Params(request);
params.withMasterTimeout(deleteIndexTemplateRequest.masterNodeTimeout());
return request;
}
public static Request getFieldMapping(GetFieldMappingsRequest getFieldMappingsRequest) {
String[] indices = getFieldMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY
: getFieldMappingsRequest.indices();
String[] fields = getFieldMappingsRequest.fields() == null ? Strings.EMPTY_ARRAY : getFieldMappingsRequest.fields();
final String endpoint = new EndpointBuilder().addCommaSeparatedPathParts(indices).addPathPartAsIs("_mapping")
.addPathPartAsIs("field").addCommaSeparatedPathParts(fields).build();
Request request = new Request(HttpMethod.GET.name(), endpoint);
RequestConverters.Params parameters = new Params(request);
parameters.withIndicesOptions(getFieldMappingsRequest.indicesOptions());
parameters.withIncludeDefaults(getFieldMappingsRequest.includeDefaults());
parameters.withIncludeTypeName(false);
return request;
}
public static Request clusterHealth(ClusterHealthRequest healthRequest) {
String[] indices = healthRequest.indices() == null ? Strings.EMPTY_ARRAY : healthRequest.indices();
String endpoint = new EndpointBuilder().addPathPartAsIs(new String[] { "_cluster/health" })
.addCommaSeparatedPathParts(indices).build();
Request request = new Request("GET", endpoint);
RequestConverters.Params parameters = new Params(request);
parameters.withWaitForStatus(healthRequest.waitForStatus());
parameters.withWaitForNoRelocatingShards(healthRequest.waitForNoRelocatingShards());
parameters.withWaitForNoInitializingShards(healthRequest.waitForNoInitializingShards());
parameters.withWaitForActiveShards(healthRequest.waitForActiveShards(), ActiveShardCount.NONE);
parameters.withWaitForNodes(healthRequest.waitForNodes());
parameters.withWaitForEvents(healthRequest.waitForEvents());
parameters.withTimeout(healthRequest.timeout());
parameters.withMasterTimeout(healthRequest.masterNodeTimeout());
parameters.withLocal(healthRequest.local()).withLevel(healthRequest.level());
return request;
}
static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) {
try {
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ public class ScrollState {
}
}
public void updateScrollId(@Nullable String scrollId) {
public void updateScrollId(String scrollId) {
if (StringUtils.hasText(scrollId)) {
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,10 +31,11 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
/**
* Return the {@link RestHighLevelClient} instance used to connect to the cluster. <br />
* Annotate with {@link Bean} in case you want to expose a {@link RestHighLevelClient} instance to the
* {@link org.springframework.context.ApplicationContext}.
*
* @return never {@literal null}.
*/
@Bean
public abstract RestHighLevelClient elasticsearchClient();
/**
@@ -43,12 +44,7 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
* @return never {@literal null}.
*/
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
RestHighLevelClient elasticsearchClient) {
ElasticsearchRestTemplate template = new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
template.setRefreshPolicy(refreshPolicy());
return template;
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,12 @@
package org.springframework.data.elasticsearch.config;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.lang.Nullable;
@@ -30,14 +31,16 @@ import org.springframework.lang.Nullable;
* @since 3.2
* @see ElasticsearchConfigurationSupport
*/
@Configuration
public abstract class AbstractReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
/**
* Return the {@link ReactiveElasticsearchClient} instance used to connect to the cluster. <br />
* Annotate with {@link Bean} in case you want to expose a {@link ReactiveElasticsearchClient} instance to the
* {@link org.springframework.context.ApplicationContext}.
*
* @return never {@literal null}.
*/
@Bean
public abstract ReactiveElasticsearchClient reactiveElasticsearchClient();
/**
@@ -46,10 +49,9 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic
* @return never {@literal null}.
*/
@Bean
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter,
ReactiveElasticsearchClient reactiveElasticsearchClient) {
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient,
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(),
elasticsearchConverter);
template.setIndicesOptions(indicesOptions());
template.setRefreshPolicy(refreshPolicy());
@@ -58,13 +60,13 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic
}
/**
* Set up the write {@link RefreshPolicy}. Default is set to null to use the cluster defaults..
* Set up the write {@link RefreshPolicy}. Default is set to {@link RefreshPolicy#IMMEDIATE}.
*
* @return {@literal null} to use the server defaults.
*/
@Nullable
protected RefreshPolicy refreshPolicy() {
return null;
return RefreshPolicy.IMMEDIATE;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,16 +17,24 @@ package org.springframework.data.elasticsearch.config;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.util.Assert;
/**
@@ -37,16 +45,41 @@ import org.springframework.util.Assert;
*/
class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableElasticsearchAuditing.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
*/
@Override
protected String getAuditingHandlerBeanName() {
return "elasticsearchAuditingHandler";
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
super.registerBeanDefinitions(annotationMetadata, registry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
*/
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
@@ -54,13 +87,18 @@ class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupp
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(ElasticsearchMappingContextLookup.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
@@ -68,9 +106,76 @@ class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupp
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(AuditingEntityCallback.class);
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(AuditingEntityCallback.class);
listenerBeanDefinitionBuilder
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(builder.getBeanDefinition(), AuditingEntityCallback.class.getName(), registry);
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
AuditingEntityCallback.class.getName(), registry);
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
registerReactiveAuditingEntityCallback(registry, auditingHandlerDefinition.getSource());
}
}
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry, Object source) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
builder.getRawBeanDefinition().setSource(source);
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
registry);
}
/**
* Simple helper to be able to wire the {@link MappingContext} from a {@link MappingElasticsearchConverter} bean
* available in the application context.
*
* @author Oliver Gierke
*/
static class ElasticsearchMappingContextLookup implements
FactoryBean<MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>> {
private final MappingElasticsearchConverter converter;
/**
* Creates a new {@link ElasticsearchMappingContextLookup} for the given {@link MappingElasticsearchConverter}.
*
* @param converter must not be {@literal null}.
*/
public ElasticsearchMappingContextLookup(MappingElasticsearchConverter converter) {
this.converter = converter;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getObject()
throws Exception {
return converter.getMappingContext();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@Override
public Class<?> getObjectType() {
return MappingContext.class;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
@Override
public boolean isSingleton() {
return true;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,14 +28,10 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -44,16 +40,15 @@ import org.springframework.util.StringUtils;
* @author Peter-Josef Meisch
* @since 3.2
*/
@Configuration(proxyBeanMethods = false)
@Configuration
public class ElasticsearchConfigurationSupport {
@Bean
public ElasticsearchConverter elasticsearchEntityMapper(SimpleElasticsearchMappingContext elasticsearchMappingContext,
ElasticsearchCustomConversions elasticsearchCustomConversions) {
public ElasticsearchConverter elasticsearchEntityMapper(
SimpleElasticsearchMappingContext elasticsearchMappingContext) {
MappingElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
elasticsearchMappingContext);
elasticsearchConverter.setConversions(elasticsearchCustomConversions);
elasticsearchConverter.setConversions(elasticsearchCustomConversions());
return elasticsearchConverter;
}
@@ -65,13 +60,11 @@ public class ElasticsearchConfigurationSupport {
* @return never {@literal null}.
*/
@Bean
public SimpleElasticsearchMappingContext elasticsearchMappingContext(
ElasticsearchCustomConversions elasticsearchCustomConversions) {
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
mappingContext.setInitialEntitySet(getInitialEntitySet());
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder());
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions().getSimpleTypeHolder());
return mappingContext;
}
@@ -120,7 +113,8 @@ public class ElasticsearchConfigurationSupport {
}
/**
* Scans the given base package for entities, i.e. Elasticsearch specific types annotated with {@link Document}.
* Scans the given base package for entities, i.e. Elasticsearch specific types annotated with {@link Document} and
* {@link Persistent}.
*
* @param basePackage must not be {@literal null}.
* @return never {@literal null}.
@@ -136,6 +130,7 @@ public class ElasticsearchConfigurationSupport {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
@@ -151,24 +146,4 @@ public class ElasticsearchConfigurationSupport {
return initialEntitySet;
}
/**
* Set up the write {@link RefreshPolicy}. Default is set to null to use the cluster defaults..
*
* @return {@literal null} to use the server defaults.
*/
@Nullable
protected RefreshPolicy refreshPolicy() {
return null;
}
/**
* Configures a {@link FieldNamingStrategy} on the {@link SimpleElasticsearchMappingContext} instance created.
*
* @return the {@link FieldNamingStrategy} to use
* @since 4.2
*/
protected FieldNamingStrategy fieldNamingStrategy() {
return PropertyNameFieldNamingStrategy.INSTANCE;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,8 +65,6 @@ public @interface EnableElasticsearchAuditing {
* used for setting creation and modification dates.
*
* @return
* @deprecated since 4.1
*/
@Deprecated
String dateTimeProviderRef() default "";
}
@@ -1,72 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
/**
* Annotation to enable auditing in Elasticsearch using reactive infrastructure via annotation configuration.
*
* @author Peter-Josef Meisch
* @since 4.1
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ReactiveElasticsearchAuditingRegistrar.class)
public @interface EnableReactiveElasticsearchAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
*
* @return
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
* used for setting creation and modification dates.
*
* @return
* @deprecated since 4.1
*/
@Deprecated
String dateTimeProviderRef() default "";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2021 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,54 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.mapping.context.PersistentEntities;
/**
* Simple helper to be able to wire the {@link PersistentEntities} from a {@link MappingElasticsearchConverter} bean
* available in the application context.
*
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 4.1
*/
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
private final MappingElasticsearchConverter converter;
/**
* Creates a new {@link PersistentEntitiesFactoryBean} for the given {@link MappingElasticsearchConverter}.
*
* @param converter must not be {@literal null}.
*/
public PersistentEntitiesFactoryBean(MappingElasticsearchConverter converter) {
this.converter = converter;
}
@Override
public PersistentEntities getObject() {
return PersistentEntities.of(converter.getMappingContext());
}
@Override
public Class<?> getObjectType() {
return PersistentEntities.class;
}
}
@@ -1,77 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.util.Assert;
/**
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableReactiveElasticsearchAuditing} annotation.
*
* @author Peter-Josef Meisch
* @since 4.1
*/
class ReactiveElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableReactiveElasticsearchAuditing.class;
}
@Override
protected String getAuditingHandlerBeanName() {
return "reactiveElasticsearchAuditingHandler";
}
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
registry);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,32 +17,32 @@ package org.springframework.data.elasticsearch.core;
import static org.springframework.util.StringUtils.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base implementation of {@link IndexOperations} common to Transport and Rest based Implementations of IndexOperations.
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
@@ -55,24 +55,20 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
protected final RequestFactory requestFactory;
@Nullable protected final Class<?> boundClass;
@Nullable private final IndexCoordinates boundIndex;
private final IndexCoordinates boundIndex;
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
Assert.notNull(boundClass, "boundClass may not be null");
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
this.boundClass = boundClass;
this.boundIndex = null;
this.boundIndex = getIndexCoordinatesFor(boundClass);
}
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
Assert.notNull(boundIndex, "boundIndex may not be null");
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
this.boundClass = null;
this.boundIndex = boundIndex;
}
@@ -89,59 +85,48 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
@Override
public boolean create() {
Settings settings = boundClass != null ? createSettings(boundClass) : new Settings();
return doCreate(getIndexCoordinates(), settings, null);
if (boundClass != null) {
Class<?> clazz = boundClass;
String indexName = getIndexCoordinates().getIndexName();
if (clazz.isAnnotationPresent(Setting.class)) {
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
if (hasText(settingPath)) {
String settings = ResourceUtil.readFileFromClasspath(settingPath);
if (hasText(settings)) {
return doCreate(indexName, Document.parse(settings));
}
} else {
LOGGER.info("settingPath in @Setting has to be defined. Using default instead.");
}
}
return doCreate(indexName, getDefaultSettings(getRequiredPersistentEntity(clazz)));
}
return doCreate(getIndexCoordinates().getIndexName(), null);
}
@Override
public Settings createSettings(Class<?> clazz) {
Assert.notNull(clazz, "clazz must not be null");
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(clazz);
String settingPath = persistentEntity.settingPath();
return hasText(settingPath) //
? Settings.parse(ResourceUtil.readFileFromClasspath(settingPath)) //
: persistentEntity.getDefaultSettings();
public boolean create(Document settings) {
return doCreate(getIndexCoordinates().getIndexName(), settings);
}
@Override
public boolean createWithMapping() {
return doCreate(getIndexCoordinates(), createSettings(), createMapping());
}
@Override
public boolean create(Map<String, Object> settings) {
Assert.notNull(settings, "settings must not be null");
return doCreate(getIndexCoordinates(), settings, null);
}
@Override
public boolean create(Map<String, Object> settings, Document mapping) {
Assert.notNull(settings, "settings must not be null");
Assert.notNull(mapping, "mapping must not be null");
return doCreate(getIndexCoordinates(), settings, mapping);
}
protected abstract boolean doCreate(IndexCoordinates index, Map<String, Object> settings, @Nullable Document mapping);
protected abstract boolean doCreate(String indexName, @Nullable Document settings);
@Override
public boolean delete() {
return doDelete(getIndexCoordinates());
return doDelete(getIndexCoordinates().getIndexName());
}
protected abstract boolean doDelete(IndexCoordinates index);
protected abstract boolean doDelete(String indexName);
@Override
public boolean exists() {
return doExists(getIndexCoordinates());
return doExists(getIndexCoordinates().getIndexName());
}
protected abstract boolean doExists(IndexCoordinates index);
protected abstract boolean doExists(String indexName);
@Override
public boolean putMapping(Document mapping) {
@@ -158,16 +143,16 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
abstract protected Map<String, Object> doGetMapping(IndexCoordinates index);
@Override
public Settings getSettings() {
public Map<String, Object> getSettings() {
return getSettings(false);
}
@Override
public Settings getSettings(boolean includeDefaults) {
return doGetSettings(getIndexCoordinates(), includeDefaults);
public Map<String, Object> getSettings(boolean includeDefaults) {
return doGetSettings(getIndexCoordinates().getIndexName(), includeDefaults);
}
protected abstract Settings doGetSettings(IndexCoordinates index, boolean includeDefaults);
protected abstract Map<String, Object> doGetSettings(String indexName, boolean includeDefaults);
@Override
public void refresh() {
@@ -177,49 +162,26 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
protected abstract void doRefresh(IndexCoordinates indexCoordinates);
@Override
@Deprecated
public boolean addAlias(AliasQuery query) {
return doAddAlias(query, getIndexCoordinates());
}
@Deprecated
protected abstract boolean doAddAlias(AliasQuery query, IndexCoordinates index);
@Override
public List<AliasMetadata> queryForAlias() {
return doQueryForAlias(getIndexCoordinates());
public List<AliasMetaData> queryForAlias() {
return doQueryForAlias(getIndexCoordinates().getIndexName());
}
protected abstract List<AliasMetadata> doQueryForAlias(IndexCoordinates index);
protected abstract List<AliasMetaData> doQueryForAlias(String indexName);
@Override
@Deprecated
public boolean removeAlias(AliasQuery query) {
return doRemoveAlias(query, getIndexCoordinates());
}
@Deprecated
protected abstract boolean doRemoveAlias(AliasQuery query, IndexCoordinates index);
@Override
public Map<String, Set<AliasData>> getAliases(String... aliasNames) {
Assert.notEmpty(aliasNames, "aliasNames must not be empty");
return doGetAliases(aliasNames, null);
}
@Override
public Map<String, Set<AliasData>> getAliasesForIndex(String... indexNames) {
Assert.notEmpty(indexNames, "indexNames must not be empty");
return doGetAliases(null, indexNames);
}
protected abstract Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames,
@Nullable String[] indexNames);
@Override
public Document createMapping() {
return createMapping(checkForBoundClass());
@@ -233,14 +195,13 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
protected Document buildMapping(Class<?> clazz) {
// load mapping specified in Mapping annotation if present
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
if (mappingAnnotation != null) {
String mappingPath = mappingAnnotation.mappingPath();
if (clazz.isAnnotationPresent(Mapping.class)) {
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
if (hasText(mappingPath)) {
if (!StringUtils.isEmpty(mappingPath)) {
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
if (hasText(mappings)) {
if (!StringUtils.isEmpty(mappings)) {
return Document.parse(mappings);
}
} else {
@@ -253,29 +214,64 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz);
return Document.parse(mapping);
} catch (Exception e) {
throw new UncategorizedElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
}
@Override
public Settings createSettings() {
return createSettings(checkForBoundClass());
}
// endregion
// region Helper functions
private <T> Document getDefaultSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
if (persistentEntity.isUseServerConfiguration()) {
return Document.create();
}
Map<String, String> map = new MapBuilder<String, String>()
.put("index.number_of_shards", String.valueOf(persistentEntity.getShards()))
.put("index.number_of_replicas", String.valueOf(persistentEntity.getReplicas()))
.put("index.refresh_interval", persistentEntity.getRefreshInterval())
.put("index.store.type", persistentEntity.getIndexStoreType()).map();
return Document.from(map);
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
}
@Override
public IndexCoordinates getIndexCoordinates() {
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : Objects.requireNonNull(boundIndex);
/**
* get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed
* index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned.
*
* @return IndexCoordinates
*/
protected IndexCoordinates getIndexCoordinates() {
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex;
}
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
protected Map<String, Object> convertSettingsResponseToMap(GetSettingsResponse response, String indexName) {
Map<String, Object> settings = new HashMap<>();
if (!response.getIndexToDefaultSettings().isEmpty()) {
Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName);
for (String key : defaultSettings.keySet()) {
settings.put(key, defaultSettings.get(key));
}
}
if (!response.getIndexToSettings().isEmpty()) {
Settings customSettings = response.getIndexToSettings().get(indexName);
for (String key : customSettings.keySet()) {
settings.put(key, customSettings.get(key));
}
}
return settings;
}
// endregion
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,17 +25,13 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequestBuilder;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -52,21 +48,16 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.GetQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
@@ -78,16 +69,13 @@ import org.springframework.util.Assert;
* @author Sascha Woo
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
* @author Subhobrata Dey
*/
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
@Nullable protected ElasticsearchConverter elasticsearchConverter;
@Nullable protected RequestFactory requestFactory;
@Nullable private EntityOperations entityOperations;
@Nullable private EntityCallbacks entityCallbacks;
@Nullable private RefreshPolicy refreshPolicy;
@Nullable protected RoutingResolver routingResolver;
protected @Nullable ElasticsearchConverter elasticsearchConverter;
protected @Nullable RequestFactory requestFactory;
private @Nullable EntityCallbacks entityCallbacks;
// region Initialization
protected void initialize(ElasticsearchConverter elasticsearchConverter) {
@@ -95,33 +83,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
this.elasticsearchConverter = elasticsearchConverter;
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext = this.elasticsearchConverter
.getMappingContext();
this.entityOperations = new EntityOperations(mappingContext);
this.routingResolver = new DefaultRoutingResolver((SimpleElasticsearchMappingContext) mappingContext);
requestFactory = new RequestFactory(elasticsearchConverter);
VersionInfo.logVersions(getClusterVersion());
}
/**
* @return copy of this instance.
*/
private AbstractElasticsearchTemplate copy() {
AbstractElasticsearchTemplate copy = doCopy();
if (entityCallbacks != null) {
copy.setEntityCallbacks(entityCallbacks);
}
copy.setRoutingResolver(routingResolver);
return copy;
}
protected abstract AbstractElasticsearchTemplate doCopy();
protected ElasticsearchConverter createElasticsearchConverter() {
MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext());
@@ -157,15 +123,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.entityCallbacks = entityCallbacks;
}
public void setRefreshPolicy(@Nullable RefreshPolicy refreshPolicy) {
this.refreshPolicy = refreshPolicy;
}
@Nullable
public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}
// endregion
// region DocumentOperations
@@ -183,14 +140,13 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Assert.notNull(entity, "entity must not be null");
Assert.notNull(index, "index must not be null");
T entityAfterBeforeConvert = maybeCallbackBeforeConvert(entity, index);
IndexQuery query = getIndexQuery(entity);
index(query, index);
IndexQuery query = getIndexQuery(entityAfterBeforeConvert);
doIndex(query, index);
T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index);
return entityAfterAfterSave;
// suppressing because it's either entity itself or something of a correct type returned by an entity callback
@SuppressWarnings("unchecked")
T castResult = (T) query.getObject();
return castResult;
}
@Override
@@ -216,9 +172,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
.collect(Collectors.toList());
if (!indexQueries.isEmpty()) {
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
entities.forEach(entity -> updateIndexedObject(entity, iterator.next()));
List<String> ids = bulkIndex(indexQueries, index);
Iterator<String> idIterator = ids.iterator();
entities.forEach(entity -> {
setPersistentEntityId(entity, idIterator.next());
});
}
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
@@ -229,20 +187,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return save(Arrays.asList(entities));
}
@Override
public String index(IndexQuery query, IndexCoordinates index) {
maybeCallbackBeforeConvertWithQuery(query, index);
String documentId = doIndex(query, index);
maybeCallbackAfterSaveWithQuery(query, index);
return documentId;
}
public abstract String doIndex(IndexQuery query, IndexCoordinates indexCoordinates);
@Override
@Nullable
public <T> T get(String id, Class<T> clazz) {
@@ -250,8 +194,15 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
@Override
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz) {
return multiGet(query, clazz, getIndexCoordinatesFor(clazz));
@Nullable
public <T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index) {
return get(query.getId(), clazz, index);
}
@Override
@Nullable
public <T> T queryForObject(GetQuery query, Class<T> clazz) {
return get(query.getId(), clazz, getIndexCoordinatesFor(clazz));
}
@Override
@@ -275,11 +226,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return this.delete(id, getIndexCoordinatesFor(entityType));
}
@Override
public ByQueryResponse delete(Query query, Class<?> clazz) {
return delete(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public String delete(Object entity) {
return delete(entity, getIndexCoordinatesFor(entity.getClass()));
@@ -289,97 +235,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
public String delete(Object entity, IndexCoordinates index) {
return this.delete(getEntityId(entity), index);
}
@Override
public String delete(String id, IndexCoordinates index) {
return doDelete(id, routingResolver.getRouting(), index);
}
@Override
@Deprecated
final public String delete(String id, @Nullable String routing, IndexCoordinates index) {
return doDelete(id, routing, index);
}
protected abstract String doDelete(String id, @Nullable String routing, IndexCoordinates index);
@Override
public List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
return bulkIndex(queries, getIndexCoordinatesFor(clazz));
}
@Override
public List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz) {
return bulkIndex(queries, bulkOptions, getIndexCoordinatesFor(clazz));
}
@Override
public final List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions,
IndexCoordinates index) {
Assert.notNull(queries, "List of IndexQuery must not be null");
Assert.notNull(bulkOptions, "BulkOptions must not be null");
return bulkOperation(queries, bulkOptions, index);
}
@Override
public void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz) {
bulkUpdate(queries, getIndexCoordinatesFor(clazz));
}
public List<IndexedObjectInformation> bulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index) {
Assert.notNull(queries, "List of IndexQuery must not be null");
Assert.notNull(bulkOptions, "BulkOptions must not be null");
maybeCallbackBeforeConvertWithQueries(queries, index);
List<IndexedObjectInformation> indexedObjectInformations = doBulkOperation(queries, bulkOptions, index);
maybeCallbackAfterSaveWithQueries(queries, index);
return indexedObjectInformations;
}
public abstract List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index);
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequest<R>> R prepareWriteRequest(R request) {
if (refreshPolicy == null) {
return request;
}
return request.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param requestBuilder must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequestBuilder<R>> R prepareWriteRequestBuilder(R requestBuilder) {
if (refreshPolicy == null) {
return requestBuilder;
}
return requestBuilder.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
// endregion
// region SearchOperations
@@ -388,6 +243,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return count(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> CloseableIterator<T> stream(Query query, Class<T> clazz, IndexCoordinates index) {
return (CloseableIterator<T>) SearchHitSupport.unwrapSearchHits(searchForStream(query, clazz, index));
}
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz) {
return searchForStream(query, clazz, getIndexCoordinatesFor(clazz));
@@ -422,11 +282,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index);
}
@Override
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz) {
return multiSearch(queries, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
MultiSearchRequest request = new MultiSearchRequest();
@@ -445,46 +300,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes) {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(classes, "classes must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
MultiSearchRequest request = new MultiSearchRequest();
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
Class<?> clazz = it.next();
request.add(requestFactory.searchRequest(query, clazz, getIndexCoordinatesFor(clazz)));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
getIndexCoordinatesFor(entityClass));
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response)));
}
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes,
IndexCoordinates index) {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(classes, "classes must not be null");
Assert.notNull(index, "index must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
MultiSearchRequest request = new MultiSearchRequest();
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
@@ -538,12 +356,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
abstract protected void searchScrollClear(List<String> scrollIds);
abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request);
@Override
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
return suggest(suggestion, getIndexCoordinatesFor(clazz));
}
// endregion
// region Helper methods
@@ -584,7 +396,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
* @param bulkResponse
* @return the list of the item id's
*/
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
protected List<String> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
@@ -599,43 +411,17 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
failedDocuments);
}
return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> {
DocWriteResponse response = bulkItemResponse.getResponse();
if (response != null) {
return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(),
response.getVersion());
} else {
return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null);
}
}).collect(Collectors.toList());
return Stream.of(bulkResponse.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
}
protected void updateIndexedObject(Object entity, IndexedObjectInformation indexedObjectInformation) {
protected void setPersistentEntityId(Object entity, String id) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
.getPersistentEntity(entity.getClass());
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
if (persistentEntity != null) {
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
// Only deal with text because ES generated Ids are strings!
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
persistentEntity.getPropertyAccessor(entity).setProperty(idProperty, id);
}
}
@@ -645,31 +431,27 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Nullable
private String getEntityId(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
Object id = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver)
.getId();
if (id != null) {
return stringIdRepresentation(id);
if (idProperty != null) {
return stringIdRepresentation(persistentEntity.getPropertyAccessor(entity).getProperty(idProperty));
}
return null;
}
@Nullable
public String getEntityRouting(Object entity) {
return entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver)
.getRouting();
}
@Nullable
private Long getEntityVersion(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
Number version = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver)
.getVersion();
if (versionProperty != null) {
Object version = persistentEntity.getPropertyAccessor(entity).getProperty(versionProperty);
if (version != null && Long.class.isAssignableFrom(version.getClass())) {
return ((Long) version);
if (version != null && Long.class.isAssignableFrom(version.getClass())) {
return ((Long) version);
}
}
return null;
@@ -677,14 +459,21 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Nullable
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty property = persistentEntity.getSeqNoPrimaryTermProperty();
EntityOperations.AdaptibleEntity<Object> adaptibleEntity = entityOperations.forEntity(entity,
elasticsearchConverter.getConversionService(), routingResolver);
return adaptibleEntity.hasSeqNoPrimaryTerm() ? adaptibleEntity.getSeqNoPrimaryTerm() : null;
if (property != null) {
Object seqNoPrimaryTerm = persistentEntity.getPropertyAccessor(entity).getProperty(property);
if (seqNoPrimaryTerm != null && SeqNoPrimaryTerm.class.isAssignableFrom(seqNoPrimaryTerm.getClass())) {
return (SeqNoPrimaryTerm) seqNoPrimaryTerm;
}
}
return null;
}
private <T> IndexQuery getIndexQuery(T entity) {
String id = getEntityId(entity);
if (id != null) {
@@ -694,22 +483,13 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
IndexQueryBuilder builder = new IndexQueryBuilder() //
.withId(id) //
.withObject(entity);
SeqNoPrimaryTerm seqNoPrimaryTerm = getEntitySeqNoPrimaryTerm(entity);
if (seqNoPrimaryTerm != null) {
builder.withSeqNoPrimaryTerm(seqNoPrimaryTerm);
} else {
// version cannot be used together with seq_no and primary_term
builder.withVersion(getEntityVersion(entity));
}
String routing = getEntityRouting(entity);
if (routing != null) {
builder.withRouting(routing);
}
return builder.build();
}
@@ -742,20 +522,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
if (queryObject != null) {
queryObject = maybeCallbackBeforeConvert(queryObject, index);
indexQuery.setObject(queryObject);
// the callback might have set som values relevant for the IndexQuery
IndexQuery newQuery = getIndexQuery(queryObject);
if (indexQuery.getRouting() == null && newQuery.getRouting() != null) {
indexQuery.setRouting(newQuery.getRouting());
}
if (indexQuery.getSeqNo() == null && newQuery.getSeqNo() != null) {
indexQuery.setSeqNo(newQuery.getSeqNo());
}
if (indexQuery.getPrimaryTerm() == null && newQuery.getPrimaryTerm() != null) {
indexQuery.setPrimaryTerm(newQuery.getPrimaryTerm());
}
}
}
}
@@ -805,20 +571,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
// endregion
protected void updateIndexedObjectsWithQueries(List<?> queries,
List<IndexedObjectInformation> indexedObjectInformations) {
for (int i = 0; i < queries.size(); i++) {
Object query = queries.get(i);
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
updateIndexedObject(queryObject, indexedObjectInformations.get(i));
}
}
}
}
// region Document callbacks
protected interface DocumentCallback<T> {
@Nullable
@@ -872,7 +624,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter).mapHits(response, entities);
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapHits(response, entities);
}
}
@@ -892,28 +644,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter).mapScrollHits(response, entities);
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapScrollHits(response,
entities);
}
}
// endregion
// region routing
private void setRoutingResolver(RoutingResolver routingResolver) {
Assert.notNull(routingResolver, "routingResolver must not be null");
this.routingResolver = routingResolver;
}
@Override
public ElasticsearchOperations withRouting(RoutingResolver routingResolver) {
Assert.notNull(routingResolver, "routingResolver must not be null");
AbstractElasticsearchTemplate copy = copy();
copy.setRoutingResolver(routingResolver);
return copy;
}
// endregion
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +17,9 @@ package org.springframework.data.elasticsearch.core;
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.index.query.BoolQueryBuilder;
@@ -30,7 +28,6 @@ import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.elasticsearch.core.geo.GeoBox;
import org.springframework.data.elasticsearch.core.geo.GeoJson;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.geo.Box;
@@ -50,158 +47,139 @@ import org.springframework.util.Assert;
*/
class CriteriaFilterProcessor {
@Nullable
QueryBuilder createFilter(Criteria criteria) {
List<QueryBuilder> filterBuilders = new ArrayList<>();
QueryBuilder createFilterFromCriteria(Criteria criteria) {
List<QueryBuilder> fbList = new LinkedList<>();
QueryBuilder filter = null;
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
QueryBuilder fb = null;
if (chainedCriteria.isOr()) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
queriesForEntries(chainedCriteria).forEach(boolQuery::should);
filterBuilders.add(boolQuery);
fb = QueryBuilders.boolQuery();
for (QueryBuilder f : createFilterFragmentForCriteria(chainedCriteria)) {
((BoolQueryBuilder) fb).should(f);
}
fbList.add(fb);
} else if (chainedCriteria.isNegating()) {
List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries().iterator());
filterBuilders.addAll(negationFilters);
if (!negationFilters.isEmpty()) {
fbList.addAll(negationFilters);
}
} else {
filterBuilders.addAll(queriesForEntries(chainedCriteria));
fbList.addAll(createFilterFragmentForCriteria(chainedCriteria));
}
}
QueryBuilder filter = null;
if (!filterBuilders.isEmpty()) {
if (filterBuilders.size() == 1) {
filter = filterBuilders.get(0);
if (!fbList.isEmpty()) {
if (fbList.size() == 1) {
filter = fbList.get(0);
} else {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
filterBuilders.forEach(boolQuery::must);
filter = boolQuery;
filter = QueryBuilders.boolQuery();
for (QueryBuilder f : fbList) {
((BoolQueryBuilder) filter).must(f);
}
}
}
return filter;
}
private List<QueryBuilder> queriesForEntries(Criteria criteria) {
private List<QueryBuilder> createFilterFragmentForCriteria(Criteria chainedCriteria) {
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getFilterCriteriaEntries().iterator();
List<QueryBuilder> filterList = new LinkedList<>();
Assert.notNull(criteria.getField(), "criteria must have a field");
String fieldName = criteria.getField().getName();
String fieldName = chainedCriteria.getField().getName();
Assert.notNull(fieldName, "Unknown field");
QueryBuilder filter = null;
return criteria.getFilterCriteriaEntries().stream()
.map(entry -> queryFor(entry.getKey(), entry.getValue(), fieldName)).collect(Collectors.toList());
while (it.hasNext()) {
Criteria.CriteriaEntry entry = it.next();
filter = processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName);
filterList.add(filter);
}
return filterList;
}
@Nullable
private QueryBuilder queryFor(OperationKey key, Object value, String fieldName) {
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
if (value == null) {
return null;
}
QueryBuilder filter = null;
switch (key) {
case WITHIN:
case WITHIN: {
GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders.geoDistanceQuery(fieldName);
Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values.");
filter = withinQuery(fieldName, (Object[]) value);
Object[] valArray = (Object[]) value;
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point,
"First element of a geo distance filter must be a GeoPoint, a Point or a text");
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance,
"Second element of a geo distance filter must be a text or a Distance");
StringBuilder dist = new StringBuilder();
if (valArray[1] instanceof Distance) {
extractDistanceString((Distance) valArray[1], dist);
} else {
dist.append((String) valArray[1]);
}
if (valArray[0] instanceof GeoPoint) {
GeoPoint loc = (GeoPoint) valArray[0];
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
} else if (valArray[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
} else {
String loc = (String) valArray[0];
if (loc.contains(",")) {
String[] c = loc.split(",");
geoDistanceQueryBuilder.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
} else {
geoDistanceQueryBuilder.geohash(loc).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
}
}
filter = geoDistanceQueryBuilder;
break;
case BBOX:
}
case BBOX: {
filter = QueryBuilders.geoBoundingBoxQuery(fieldName);
Assert.isTrue(value instanceof Object[],
"Value of a boundedBy filter should be an array of one or two values.");
filter = boundingBoxQuery(fieldName, (Object[]) value);
Object[] valArray = (Object[]) value;
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
if (valArray.length == 1) {
// GeoEnvelop
oneParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray[0]);
} else if (valArray.length == 2) {
// 2x GeoPoint
// 2x text
twoParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray);
} else {
// error
Assert.isTrue(false,
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
}
break;
case GEO_INTERSECTS:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_INTERSECTS filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "intersects");
break;
case GEO_IS_DISJOINT:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_IS_DISJOINT filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "disjoint");
break;
case GEO_WITHIN:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_WITHIN filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "within");
break;
case GEO_CONTAINS:
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_CONTAINS filter must be a GeoJson object");
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "contains");
break;
}
return filter;
}
private QueryBuilder withinQuery(String fieldName, Object[] valArray) {
GeoDistanceQueryBuilder filter = QueryBuilders.geoDistanceQuery(fieldName);
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point,
"First element of a geo distance filter must be a GeoPoint, a Point or a text");
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance,
"Second element of a geo distance filter must be a text or a Distance");
StringBuilder dist = new StringBuilder();
if (valArray[1] instanceof Distance) {
extractDistanceString((Distance) valArray[1], dist);
} else {
dist.append((String) valArray[1]);
}
if (valArray[0] instanceof GeoPoint) {
GeoPoint loc = (GeoPoint) valArray[0];
filter.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
} else if (valArray[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
filter.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
} else {
String loc = (String) valArray[0];
if (loc.contains(",")) {
String[] c = loc.split(",");
filter.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
} else {
filter.geohash(loc).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
}
}
return filter;
}
private QueryBuilder boundingBoxQuery(String fieldName, Object[] valArray) {
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
GeoBoundingBoxQueryBuilder filter = QueryBuilders.geoBoundingBoxQuery(fieldName);
if (valArray.length == 1) {
// GeoEnvelop
oneParameterBBox(filter, valArray[0]);
} else if (valArray.length == 2) {
// 2x GeoPoint
// 2x text
twoParameterBBox(filter, valArray);
} else {
throw new IllegalArgumentException(
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
}
return filter;
}
private QueryBuilder geoJsonQuery(String fieldName, GeoJson<?> geoJson, String relation) {
return QueryBuilders.wrapperQuery(buildJsonQuery(fieldName, geoJson, relation));
}
private String buildJsonQuery(String fieldName, GeoJson<?> geoJson, String relation) {
return "{\"geo_shape\": {\"" + fieldName + "\": {\"shape\": " + geoJson.toJson() + ", \"relation\": \"" + relation
+ "\"}}}";
}
/**
* extract the distance string from a {@link org.springframework.data.geo.Distance} object.
*
@@ -230,7 +208,8 @@ class CriteriaFilterProcessor {
GeoBox geoBBox;
if (value instanceof Box) {
geoBBox = GeoBox.fromBox((Box) value);
Box sdbox = (Box) value;
geoBBox = GeoBox.fromBox(sdbox);
} else {
geoBBox = (GeoBox) value;
}
@@ -239,7 +218,7 @@ class CriteriaFilterProcessor {
geoBBox.getBottomRight().getLon());
}
private static boolean isType(Object[] array, Class<?> clazz) {
private static boolean isType(Object[] array, Class clazz) {
for (Object o : array) {
if (!clazz.isInstance(o)) {
return false;
@@ -268,7 +247,7 @@ class CriteriaFilterProcessor {
while (it.hasNext()) {
Criteria.CriteriaEntry criteriaEntry = it.next();
QueryBuilder notFilter = QueryBuilders.boolQuery()
.mustNot(queryFor(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
.mustNot(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
notFilterList.add(notFilter);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,15 @@ package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.index.query.Operator.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import static org.springframework.util.StringUtils.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.elasticsearch.annotations.FieldType;
@@ -47,81 +48,63 @@ import org.springframework.util.Assert;
*/
class CriteriaQueryProcessor {
@Nullable
QueryBuilder createQuery(Criteria criteria) {
QueryBuilder createQueryFromCriteria(Criteria criteria) {
Assert.notNull(criteria, "criteria must not be null");
List<QueryBuilder> shouldQueryBuilders = new ArrayList<>();
List<QueryBuilder> mustNotQueryBuilders = new ArrayList<>();
List<QueryBuilder> mustQueryBuilders = new ArrayList<>();
List<QueryBuilder> shouldQueryBuilderList = new LinkedList<>();
List<QueryBuilder> mustNotQueryBuilderList = new LinkedList<>();
List<QueryBuilder> mustQueryBuilderList = new LinkedList<>();
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
QueryBuilder firstQuery = null;
boolean negateFirstQuery = false;
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
QueryBuilder queryFragment = queryForEntries(chainedCriteria);
if (queryFragment != null) {
while (chainIterator.hasNext()) {
Criteria chainedCriteria = chainIterator.next();
QueryBuilder queryFragmentForCriteria = createQueryFragmentForCriteria(chainedCriteria);
if (queryFragmentForCriteria != null) {
if (firstQuery == null) {
firstQuery = queryFragment;
firstQuery = queryFragmentForCriteria;
negateFirstQuery = chainedCriteria.isNegating();
continue;
}
if (chainedCriteria.isOr()) {
shouldQueryBuilders.add(queryFragment);
shouldQueryBuilderList.add(queryFragmentForCriteria);
} else if (chainedCriteria.isNegating()) {
mustNotQueryBuilders.add(queryFragment);
mustNotQueryBuilderList.add(queryFragmentForCriteria);
} else {
mustQueryBuilders.add(queryFragment);
}
}
}
for (Criteria subCriteria : criteria.getSubCriteria()) {
QueryBuilder subQuery = createQuery(subCriteria);
if (subQuery != null) {
if (criteria.isOr()) {
shouldQueryBuilders.add(subQuery);
} else if (criteria.isNegating()) {
mustNotQueryBuilders.add(subQuery);
} else {
mustQueryBuilders.add(subQuery);
mustQueryBuilderList.add(queryFragmentForCriteria);
}
}
}
if (firstQuery != null) {
if (!shouldQueryBuilders.isEmpty() && mustNotQueryBuilders.isEmpty() && mustQueryBuilders.isEmpty()) {
shouldQueryBuilders.add(0, firstQuery);
if (!shouldQueryBuilderList.isEmpty() && mustNotQueryBuilderList.isEmpty() && mustQueryBuilderList.isEmpty()) {
shouldQueryBuilderList.add(0, firstQuery);
} else {
if (negateFirstQuery) {
mustNotQueryBuilders.add(0, firstQuery);
mustNotQueryBuilderList.add(0, firstQuery);
} else {
mustQueryBuilders.add(0, firstQuery);
mustQueryBuilderList.add(0, firstQuery);
}
}
}
BoolQueryBuilder query = null;
if (!shouldQueryBuilders.isEmpty() || !mustNotQueryBuilders.isEmpty() || !mustQueryBuilders.isEmpty()) {
if (!shouldQueryBuilderList.isEmpty() || !mustNotQueryBuilderList.isEmpty() || !mustQueryBuilderList.isEmpty()) {
query = boolQuery();
for (QueryBuilder qb : shouldQueryBuilders) {
for (QueryBuilder qb : shouldQueryBuilderList) {
query.should(qb);
}
for (QueryBuilder qb : mustNotQueryBuilders) {
for (QueryBuilder qb : mustNotQueryBuilderList) {
query.mustNot(qb);
}
for (QueryBuilder qb : mustQueryBuilders) {
for (QueryBuilder qb : mustQueryBuilderList) {
query.must(qb);
}
}
@@ -130,51 +113,51 @@ class CriteriaQueryProcessor {
}
@Nullable
private QueryBuilder queryForEntries(Criteria criteria) {
Field field = criteria.getField();
if (field == null || criteria.getQueryCriteriaEntries().isEmpty())
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
if (chainedCriteria.getQueryCriteriaEntries().isEmpty())
return null;
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getQueryCriteriaEntries().iterator();
boolean singeEntryCriteria = (chainedCriteria.getQueryCriteriaEntries().size() == 1);
Field field = chainedCriteria.getField();
String fieldName = field.getName();
Assert.notNull(fieldName, "Unknown field " + fieldName);
Assert.notNull(fieldName, "Unknown field");
QueryBuilder query = null;
Iterator<Criteria.CriteriaEntry> it = criteria.getQueryCriteriaEntries().iterator();
QueryBuilder query;
if (criteria.getQueryCriteriaEntries().size() == 1) {
query = queryFor(it.next(), field);
if (singeEntryCriteria) {
Criteria.CriteriaEntry entry = it.next();
query = processCriteriaEntry(entry, field);
} else {
query = boolQuery();
while (it.hasNext()) {
Criteria.CriteriaEntry entry = it.next();
((BoolQueryBuilder) query).must(queryFor(entry, field));
((BoolQueryBuilder) query).must(processCriteriaEntry(entry, field));
}
}
addBoost(query, criteria.getBoost());
if (hasText(field.getPath())) {
query = nestedQuery(field.getPath(), query, ScoreMode.Avg);
}
addBoost(query, chainedCriteria.getBoost());
return query;
}
@Nullable
private QueryBuilder queryFor(Criteria.CriteriaEntry entry, Field field) {
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry, Field field) {
String fieldName = field.getName();
boolean isKeywordField = FieldType.Keyword == field.getFieldType();
OperationKey key = entry.getKey();
Object value = entry.getValue();
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
if (value == null) {
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
} else {
return null;
}
}
Object value = entry.getValue();
String searchText = QueryParserUtil.escape(value.toString());
QueryBuilder query = null;
@@ -214,12 +197,6 @@ class CriteriaQueryProcessor {
case FUZZY:
query = fuzzyQuery(fieldName, searchText);
break;
case MATCHES:
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.OR);
break;
case MATCHES_ALL:
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.AND);
break;
case IN:
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
@@ -247,7 +224,7 @@ class CriteriaQueryProcessor {
private static List<String> toStringList(Iterable<?> iterable) {
List<String> list = new ArrayList<>();
for (Object item : iterable) {
list.add(item != null ? item.toString() : null);
list.add(StringUtils.toString(item));
}
return list;
}
@@ -271,12 +248,10 @@ class CriteriaQueryProcessor {
return sb.toString();
}
private void addBoost(@Nullable QueryBuilder query, float boost) {
if (query == null || Float.isNaN(boost)) {
private void addBoost(QueryBuilder query, float boost) {
if (Float.isNaN(boost)) {
return;
}
query.boost(boost);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,61 +15,50 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.client.Requests.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.GetIndexTemplatesResponse;
import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.client.support.AliasData;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link IndexOperations} implementation using the RestClient.
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @author George Popides
* @since 4.0
*/
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIndexOperations.class);
private final ElasticsearchRestTemplate restTemplate;
private ElasticsearchRestTemplate restTemplate;
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
super(restTemplate.getElasticsearchConverter(), boundClass);
@@ -82,29 +71,27 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
}
@Override
protected boolean doCreate(IndexCoordinates index, Map<String, Object> settings, @Nullable Document mapping) {
CreateIndexRequest request = requestFactory.createIndexRequest(index, settings, mapping);
protected boolean doCreate(String indexName, @Nullable Document settings) {
CreateIndexRequest request = requestFactory.createIndexRequest(indexName, settings);
return restTemplate.execute(client -> client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected boolean doDelete(IndexCoordinates index) {
protected boolean doDelete(String indexName) {
Assert.notNull(index, "index must not be null");
Assert.notNull(indexName, "No index defined for delete operation");
if (doExists(index)) {
DeleteIndexRequest deleteIndexRequest = requestFactory.deleteIndexRequest(index);
return restTemplate
.execute(client -> client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT).isAcknowledged());
if (doExists(indexName)) {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
return restTemplate.execute(client -> client.indices().delete(request, RequestOptions.DEFAULT).isAcknowledged());
}
return false;
}
@Override
protected boolean doExists(IndexCoordinates index) {
GetIndexRequest getIndexRequest = requestFactory.getIndexRequest(index);
return restTemplate.execute(client -> client.indices().exists(getIndexRequest, RequestOptions.DEFAULT));
protected boolean doExists(String indexName) {
GetIndexRequest request = new GetIndexRequest(indexName);
return restTemplate.execute(client -> client.indices().exists(request, RequestOptions.DEFAULT));
}
@Override
@@ -122,36 +109,22 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
Assert.notNull(index, "No index defined for getMapping()");
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(index);
return restTemplate.execute(client -> {
Map<String, MappingMetadata> mappings = client.indices() //
.getMapping(mappingsRequest, RequestOptions.DEFAULT) //
.mappings(); //
if (mappings == null || mappings.size() == 0) {
return Collections.emptyMap();
}
if (mappings.size() > 1) {
LOGGER.warn("more than one mapping returned for " + index.getIndexName());
}
// we have at least one, take the first from the iterator
return mappings.entrySet().iterator().next().getValue().getSourceAsMap();
RestClient restClient = client.getLowLevelClient();
Request request = new Request("GET", '/' + index.getIndexName() + "/_mapping");
Response response = restClient.performRequest(request);
return convertMappingResponse(EntityUtils.toString(response.getEntity()));
});
}
@Override
@Deprecated
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index);
return restTemplate
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
@Deprecated
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
@@ -163,45 +136,34 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
}
@Override
protected List<AliasMetadata> doQueryForAlias(IndexCoordinates index) {
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
protected List<AliasMetaData> doQueryForAlias(String indexName) {
List<AliasMetaData> aliases = null;
return restTemplate.execute(client -> {
GetAliasesResponse alias = client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT);
// we only return data for the first index name that was requested (always have done so)
String index1 = getAliasesRequest.indices()[0];
return new ArrayList<>(alias.getAliases().get(index1));
RestClient restClient = client.getLowLevelClient();
Response response;
String aliasResponse;
response = restClient.performRequest(new Request("GET", '/' + indexName + "/_alias/*"));
aliasResponse = EntityUtils.toString(response.getEntity());
return convertAliasResponse(aliasResponse);
});
}
@Override
protected Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
protected Map<String, Object> doGetSettings(String indexName, boolean includeDefaults) {
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames);
Assert.notNull(indexName, "No index defined for getSettings");
return restTemplate.execute(client -> ResponseConverter
.aliasDatas(client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT).getAliases()));
}
GetSettingsRequest request = new GetSettingsRequest() //
.indices(indexName) //
.includeDefaults(includeDefaults);
@Override
public boolean alias(AliasActions aliasActions) {
IndicesAliasesRequest request = requestFactory.indicesAliasesRequest(aliasActions);
return restTemplate
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected Settings doGetSettings(IndexCoordinates index, boolean includeDefaults) {
Assert.notNull(index, "index must not be null");
GetSettingsRequest getSettingsRequest = requestFactory.getSettingsRequest(index, includeDefaults);
//
GetSettingsResponse response = restTemplate.execute(client -> client.indices() //
.getSettings(getSettingsRequest, RequestOptions.DEFAULT));
.getSettings(request, RequestOptions.DEFAULT));
return ResponseConverter.fromSettingsResponse(response, getSettingsRequest.indices()[0]);
return convertSettingsResponseToMap(response, indexName);
}
@Override
@@ -209,68 +171,61 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
Assert.notNull(index, "No index defined for refresh()");
RefreshRequest refreshRequest = requestFactory.refreshRequest(index);
restTemplate.execute(client -> client.indices().refresh(refreshRequest, RequestOptions.DEFAULT));
restTemplate
.execute(client -> client.indices().refresh(refreshRequest(index.getIndexNames()), RequestOptions.DEFAULT));
}
@Override
public boolean putTemplate(PutTemplateRequest putTemplateRequest) {
// region Helper methods
private Map<String, Object> convertMappingResponse(String mappingResponse) {
ObjectMapper mapper = new ObjectMapper();
Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null");
try {
Map<String, Object> result = null;
JsonNode node = mapper.readTree(mappingResponse);
PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(putTemplateRequest);
return restTemplate.execute(
client -> client.indices().putTemplate(putIndexTemplateRequest, RequestOptions.DEFAULT).isAcknowledged());
}
node = node.findValue("mappings");
result = mapper.readValue(mapper.writeValueAsString(node), HashMap.class);
@Override
public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) {
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
// getIndexTemplate throws an error on non-existing template names
if (!existsTemplate(new ExistsTemplateRequest(getTemplateRequest.getTemplateName()))) {
return null;
return result;
} catch (IOException e) {
throw new UncategorizedElasticsearchException("Could not map alias response : " + mappingResponse, e);
}
GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(getTemplateRequest);
GetIndexTemplatesResponse getIndexTemplatesResponse = restTemplate
.execute(client -> client.indices().getIndexTemplate(getIndexTemplatesRequest, RequestOptions.DEFAULT));
return ResponseConverter.getTemplateData(getIndexTemplatesResponse, getTemplateRequest.getTemplateName());
}
@Override
public boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest) {
/**
* It takes two steps to create a List<AliasMetadata> from the elasticsearch http response because the aliases field
* is actually a Map by alias name, but the alias name is on the AliasMetadata.
*
* @param aliasResponse
* @return
*/
private List<AliasMetaData> convertAliasResponse(String aliasResponse) {
ObjectMapper mapper = new ObjectMapper();
Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null");
try {
JsonNode node = mapper.readTree(aliasResponse);
node = node.findValue("aliases");
IndexTemplatesExistRequest putIndexTemplateRequest = requestFactory
.indexTemplatesExistsRequest(existsTemplateRequest);
return restTemplate
.execute(client -> client.indices().existsTemplate(putIndexTemplateRequest, RequestOptions.DEFAULT));
}
if (node == null) {
return Collections.emptyList();
}
@Override
public boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) {
Map<String, AliasData> aliasData = mapper.readValue(mapper.writeValueAsString(node),
new TypeReference<Map<String, AliasData>>() {});
Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null");
Iterable<Map.Entry<String, AliasData>> aliasIter = aliasData.entrySet();
List<AliasMetaData> aliasMetaDataList = new ArrayList<>();
DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory
.deleteIndexTemplateRequest(deleteTemplateRequest);
return restTemplate.execute(
client -> client.indices().deleteTemplate(deleteIndexTemplateRequest, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
public List<IndexInformation> getInformation(IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
GetIndexRequest request = requestFactory.getIndexRequest(index);
return restTemplate.execute(client -> {
GetIndexResponse getIndexResponse = client.indices().get(request, RequestOptions.DEFAULT);
return ResponseConverter.getIndexInformations(getIndexResponse);
});
for (Map.Entry<String, AliasData> aliasentry : aliasIter) {
AliasData data = aliasentry.getValue();
aliasMetaDataList.add(AliasMetaData.newAliasMetaDataBuilder(aliasentry.getKey()).filter(data.getFilter())
.routing(data.getRouting()).searchRouting(data.getSearch_routing()).indexRouting(data.getIndex_routing())
.build());
}
return aliasMetaDataList;
} catch (IOException e) {
throw new UncategorizedElasticsearchException("Could not map alias response : " + aliasResponse, e);
}
}
// endregion
}
@@ -1,377 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.client.Requests.*;
import static org.springframework.util.StringUtils.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @author George Popides
* @since 4.1
*/
class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReactiveIndexOperations.class);
@Nullable private final Class<?> boundClass;
private final IndexCoordinates boundIndex;
private final RequestFactory requestFactory;
private final ReactiveElasticsearchOperations operations;
private final ElasticsearchConverter converter;
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, IndexCoordinates index) {
Assert.notNull(operations, "operations must not be null");
Assert.notNull(index, "index must not be null");
this.operations = operations;
this.converter = operations.getElasticsearchConverter();
this.requestFactory = new RequestFactory(operations.getElasticsearchConverter());
this.boundClass = null;
this.boundIndex = index;
}
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, Class<?> clazz) {
Assert.notNull(operations, "operations must not be null");
Assert.notNull(clazz, "clazz must not be null");
this.operations = operations;
this.converter = operations.getElasticsearchConverter();
this.requestFactory = new RequestFactory(operations.getElasticsearchConverter());
this.boundClass = clazz;
this.boundIndex = getIndexCoordinatesFor(clazz);
}
// region index management
@Override
public Mono<Boolean> create() {
IndexCoordinates index = getIndexCoordinates();
if (boundClass != null) {
return createSettings(boundClass).flatMap(settings -> doCreate(index, settings, null));
} else {
return doCreate(index, new Settings(), null);
}
}
@Override
public Mono<Boolean> createWithMapping() {
return createSettings() //
.flatMap(settings -> //
createMapping().flatMap(mapping -> //
doCreate(getIndexCoordinates(), settings, mapping))); //
}
@Override
public Mono<Boolean> create(Map<String, Object> settings) {
Assert.notNull(settings, "settings must not be null");
return doCreate(getIndexCoordinates(), settings, null);
}
@Override
public Mono<Boolean> create(Map<String, Object> settings, Document mapping) {
Assert.notNull(settings, "settings must not be null");
Assert.notNull(mapping, "mapping must not be null");
return doCreate(getIndexCoordinates(), settings, mapping);
}
private Mono<Boolean> doCreate(IndexCoordinates index, Map<String, Object> settings, @Nullable Document mapping) {
CreateIndexRequest request = requestFactory.createIndexRequest(index, settings, mapping);
return Mono.from(operations.executeWithIndicesClient(client -> client.createIndex(request)));
}
@Override
public Mono<Boolean> delete() {
return exists() //
.flatMap(exists -> {
if (exists) {
DeleteIndexRequest request = requestFactory.deleteIndexRequest(getIndexCoordinates());
return Mono.from(operations.executeWithIndicesClient(client -> client.deleteIndex(request)))
.onErrorResume(NoSuchIndexException.class, e -> Mono.just(false));
} else {
return Mono.just(false);
}
});
}
@Override
public Mono<Boolean> exists() {
GetIndexRequest request = requestFactory.getIndexRequest(getIndexCoordinates());
return Mono.from(operations.executeWithIndicesClient(client -> client.existsIndex(request)));
}
@Override
public Mono<Void> refresh() {
return Mono.from(operations.executeWithIndicesClient(
client -> client.refreshIndex(refreshRequest(getIndexCoordinates().getIndexNames()))));
}
// endregion
// region mappings
@Override
public Mono<Document> createMapping() {
return createMapping(checkForBoundClass());
}
@Override
public Mono<Document> createMapping(Class<?> clazz) {
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
if (mappingAnnotation != null) {
return loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
}
String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz);
return Mono.just(Document.parse(mapping));
}
@Override
public Mono<Boolean> putMapping(Mono<Document> mapping) {
return mapping.map(document -> requestFactory.putMappingRequest(getIndexCoordinates(), document)) //
.flatMap(request -> Mono.from(operations.executeWithIndicesClient(client -> client.putMapping(request))));
}
@Override
public Mono<Document> getMapping() {
IndexCoordinates indexCoordinates = getIndexCoordinates();
GetMappingsRequest request = requestFactory.getMappingsRequest(indexCoordinates);
return Mono.from(operations.executeWithIndicesClient(client -> client.getMapping(request)))
.flatMap(getMappingsResponse -> {
Map<String, Object> source = getMappingsResponse.mappings().get(indexCoordinates.getIndexName())
.getSourceAsMap();
Document document = Document.from(source);
return Mono.just(document);
});
}
// endregion
// region settings
@Override
public Mono<Settings> createSettings() {
return createSettings(checkForBoundClass());
}
@Override
public Mono<Settings> createSettings(Class<?> clazz) {
Assert.notNull(clazz, "clazz must not be null");
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(clazz);
String settingPath = persistentEntity.settingPath();
return hasText(settingPath) //
? loadDocument(settingPath, "@Setting") //
.map(Settings::new) //
: Mono.just(persistentEntity.getDefaultSettings());
}
@Override
public Mono<Settings> getSettings(boolean includeDefaults) {
String indexName = getIndexCoordinates().getIndexName();
GetSettingsRequest request = requestFactory.getSettingsRequest(indexName, includeDefaults);
return Mono.from(operations.executeWithIndicesClient(client -> client.getSettings(request)))
.map(getSettingsResponse -> ResponseConverter.fromSettingsResponse(getSettingsResponse, indexName));
}
// endregion
// region aliases
@Override
public Mono<Boolean> alias(AliasActions aliasActions) {
IndicesAliasesRequest request = requestFactory.indicesAliasesRequest(aliasActions);
return Mono.from(operations.executeWithIndicesClient(client -> client.updateAliases(request)));
}
@Override
public Mono<Map<String, Set<AliasData>>> getAliases(String... aliasNames) {
return getAliases(aliasNames, null);
}
@Override
public Mono<Map<String, Set<AliasData>>> getAliasesForIndex(String... indexNames) {
return getAliases(null, indexNames);
}
private Mono<Map<String, Set<AliasData>>> getAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames);
return Mono.from(operations.executeWithIndicesClient(client -> client.getAliases(getAliasesRequest)))
.map(GetAliasesResponse::getAliases).map(ResponseConverter::aliasDatas);
}
// endregion
// region templates
@Override
public Mono<Boolean> putTemplate(PutTemplateRequest putTemplateRequest) {
Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null");
PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(putTemplateRequest);
return Mono.from(operations.executeWithIndicesClient(client -> client.putTemplate(putIndexTemplateRequest)));
}
@Override
public Mono<TemplateData> getTemplate(GetTemplateRequest getTemplateRequest) {
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(getTemplateRequest);
return Mono.from(operations.executeWithIndicesClient(client -> client.getTemplate(getIndexTemplatesRequest)))
.flatMap(response -> {
if (response != null) {
TemplateData templateData = ResponseConverter.getTemplateData(response,
getTemplateRequest.getTemplateName());
if (templateData != null) {
return Mono.just(templateData);
}
}
return Mono.empty();
});
}
@Override
public Mono<Boolean> existsTemplate(ExistsTemplateRequest existsTemplateRequest) {
Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null");
IndexTemplatesExistRequest indexTemplatesExistRequest = requestFactory
.indexTemplatesExistsRequest(existsTemplateRequest);
return Mono.from(operations.executeWithIndicesClient(client -> client.existsTemplate(indexTemplatesExistRequest)));
}
@Override
public Mono<Boolean> deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) {
Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null");
DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory
.deleteIndexTemplateRequest(deleteTemplateRequest);
return Mono.from(operations.executeWithIndicesClient(client -> client.deleteTemplate(deleteIndexTemplateRequest)));
}
// endregion
// region helper functions
@Override
public IndexCoordinates getIndexCoordinates() {
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex;
}
@Override
public Flux<IndexInformation> getInformation(IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
org.elasticsearch.client.indices.GetIndexRequest getIndexRequest = requestFactory.getIndexRequest(index);
return Mono
.from(operations.executeWithIndicesClient(
client -> client.getIndex(getIndexRequest).map(ResponseConverter::getIndexInformations)))
.flatMapMany(Flux::fromIterable);
}
private IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
return operations.getElasticsearchConverter().getMappingContext().getRequiredPersistentEntity(clazz)
.getIndexCoordinates();
}
private ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
return converter.getMappingContext().getRequiredPersistentEntity(clazz);
}
private Mono<Document> loadDocument(String path, String annotation) {
if (hasText(path)) {
return ReactiveResourceUtil.readFileFromClasspath(path).flatMap(s -> {
if (hasText(s)) {
return Mono.just(Document.parse(s));
} else {
return Mono.just(Document.create());
}
});
} else {
LOGGER.info("path in {} has to be defined. Using default instead.", annotation);
}
return Mono.just(Document.create());
}
private Class<?> checkForBoundClass() {
if (boundClass == null) {
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
}
return boundClass;
}
// endregion
}

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