1
0
mirror of synced 2026-05-23 12:43:17 +00:00

Compare commits

..

124 Commits

Author SHA1 Message Date
Christoph Strobl 95762b4fde Release version 4.3.1 (2021.1.1).
See #1987
2022-01-14 10:28:59 +01:00
Christoph Strobl 0771e90031 Prepare 4.3.1 (2021.1.1).
See #1987
2022-01-14 10:28:27 +01:00
Peter-Josef Meisch 6729330500 Update to log4j 2.17.0 2021-12-18 20:34:28 +01:00
Peter-Josef Meisch 8796292611 update log4j dependency version 2021-12-14 13:53:59 +01:00
Peter-Josef Meisch f3f9ca4002 Fix FieldType mapping.
Original PullRequest #2026
Closes #2024

(cherry picked from commit f7a6a97c4e)
2021-12-13 21:52:28 +01:00
Peter-Josef Meisch 083a38ed57 Fix IndexOutOfBoundsException when try to map inner hits with no results returned.
Original Pull Request #1998
Closes #1997
Co-authored-by: Peter-Josef Meisch <pj.meisch@sothawo.com>

(cherry picked from commit 49324a369a)
2021-12-05 09:02:06 +01:00
Sascha Woo 4f3aa52958 Fix IndexOutOfBoundsException when try to map inner hits with no results returned.
Original Pull Request #1998
Closes #1997
Co-authored-by: Peter-Josef Meisch <pj.meisch@sothawo.com>

(cherry picked from commit 49324a369a)
2021-11-23 20:26:14 +01:00
Peter-Josef Meisch 3256a2bfe0 Fix RestStatusException cause.
Original Pull Request #1996
Closes #1995

(cherry picked from commit 45b4c99e95)
2021-11-16 07:38:01 +01:00
Peter-Josef Meisch 95401a5bd7 Exclude commons-logging dependency from Elasticsearch dependencies.
Original Pull Request #1993
Closes #1989
2021-11-13 14:56:57 +01:00
Mark Paluch 12cd64cfc8 Update build trigger to use branch build.
See #1965
2021-11-12 14:31:54 +01:00
Jens Schauder 7e557317d1 After release cleanups.
See #1965
2021-11-12 11:00:07 +01:00
Jens Schauder c9846ab8ad Prepare next development iteration.
See #1965
2021-11-12 11:00:05 +01:00
Jens Schauder 08f7ad85ca Release version 4.3 GA (2021.1.0).
See #1965
2021-11-12 10:49:37 +01:00
Jens Schauder 4db496ca4e Prepare 4.3 GA (2021.1.0).
See #1965
2021-11-12 10:49:16 +01:00
Peter-Josef Meisch 9353de13ee Upgrade to Elasticsearch 7.15.2.
Original Pull Request #1985
Closes #1984
2021-11-11 17:08:59 +01:00
Peter-Josef Meisch 5717d6ee57 Documentation for bulk errors.
Original Pull Request #1982 
Closes #1981
2021-11-01 08:57:32 +01:00
Peter-Josef Meisch bbee64c8a4 Upgrade to Elasticsearch 7.15.1.
Original Pull Request #1980 
Closes #1979
2021-10-30 21:27:44 +02:00
Peter-Josef Meisch 5cf38d064f Remove reflective access to java.time classes.
Original Pull Request #1970 
Closes #1969
2021-10-30 17:34:49 +02:00
Anton Naydenov 8894dd3d21 Added RefreshPolicy setter to the AbstractElasticsearchTemplate copy method.
Original Pull Request #1976 
Closes #1978
2021-10-26 19:12:53 +02:00
jongchan lee f1b4a54bc2 Add scrolltime condition when using SearchRequest.
Original Pull Request #1975
Closes #1974
2021-10-26 18:23:29 +02:00
Peter-Josef Meisch c37bbd5a7e Add possibility to define runtime fields in a search request.
Original Pull Request #1972
Closes #1971
2021-10-24 20:01:43 +02:00
Peter-Josef Meisch 2b5ab64724 Revert Testcontainers to 1.15.3.
the update causes issues when running on macOS, the started containers are not reliably reachable
2021-10-24 17:40:35 +02:00
Peter-Josef Meisch 0d943069e3 Upgrade Testcontainers to 1.16.1.
Original Pull Request #1968
Closes #1967
2021-10-19 17:39:28 +02:00
Mark Paluch ceadec0a3c After release cleanups.
See #1937
2021-10-18 13:55:43 +02:00
Mark Paluch 9b2ff56dad Prepare next development iteration.
See #1937
2021-10-18 13:55:39 +02:00
Mark Paluch d221fa8c49 Release version 4.3 RC1 (2021.1.0).
See #1937
2021-10-18 13:48:15 +02:00
Mark Paluch c4fd0acbc6 Prepare 4.3 RC1 (2021.1.0).
See #1937
2021-10-18 13:47:48 +02:00
Peter-Josef Meisch 288705ca72 Support field exclusion from source.
Original Pull Request #1962 
Closes #769
2021-10-16 13:01:14 +02:00
Mark Paluch 59fdbbeb19 Upgrade to Maven Wrapper 3.8.3.
See #1960
2021-10-11 14:30:32 +02:00
Peter-Josef Meisch 4a8e012e04 Don't expose ElasticsearchStatusException.
Original Pull Request #1959 
Closes #1957
2021-10-11 12:38:55 +02:00
Peter-Josef Meisch 2450d579e9 Support different routing for each id in multiget.
Original Pull Request #1956 
Closes #1954
2021-10-09 19:43:16 +02:00
Peter-Josef Meisch 5e4ce56414 Update documentation 2021-10-08 13:44:58 +02:00
Peter-Josef Meisch b8f8a60fd7 Custom Order class with specific parameters for Elasticsearch.
Original Pull Request #1955 
Closes #1911
2021-10-08 13:38:22 +02:00
Peter-Josef Meisch 7ae55b9e75 Add custom property converters.
Original Pull Request #1953 
Closes #1945
2021-10-05 21:31:41 +02:00
Steven 464fc31d87 AbstractElasticsearchTemplate.searchForStream use Query scrolltime.
Original Pull Request #1951 
Closes #1950
2021-10-03 19:08:49 +02:00
Peter-Josef Meisch 175e7b51ae Add repository search for nullable or empty properties.
Original Pull Request #1946 
Closes #1909
2021-09-25 14:51:40 +02:00
Peter-Josef Meisch b8ae9b4a83 switch other config from jdk 16 to 17.
Original Pull Request #1944 
Closes #1942
2021-09-23 23:19:56 +02:00
Peter-Josef Meisch 53581a9a79 Upgrade to Elasticsearch 7.15.0.
Original Pull Request #1943 
Closes #1941
2021-09-23 23:11:11 +02:00
Peter-Josef Meisch d9b23ede70 Remove Elasticsearch classes from suggest response data.
Original Pull Request #1940 
Closes #1302
2021-09-21 21:15:09 +02:00
Steven d1528ed67f Add @QueryAnnotation meta annotation to @Query.
Original Pull Request #1939 
Closes #1938
2021-09-21 19:49:18 +02:00
Mark Paluch c436c4cd63 After release cleanups.
See #1899
2021-09-17 09:52:22 +02:00
Mark Paluch 8907be835f Prepare next development iteration.
See #1899
2021-09-17 09:52:19 +02:00
Mark Paluch 1d291aeeff Release version 4.3 M3 (2021.1.0).
See #1899
2021-09-17 09:44:58 +02:00
Mark Paluch 56b1343ee7 Prepare 4.3 M3 (2021.1.0).
See #1899
2021-09-17 09:44:35 +02:00
Christoph Strobl bf165a663a Change visibility of PersistentEntitiesFactoryBean.
Closes: #1934
2021-09-16 08:08:55 +02:00
Peter-Josef Meisch 35b6efebf3 Update documentation 2021-09-14 21:45:00 +02:00
Peter-Josef Meisch 3128fab595 upgrade to Elasticsearch 7.14.1.
Original Pull Request #1933 
Closes #1892
2021-09-14 21:38:45 +02:00
Peter-Josef Meisch fa66702980 MappingElasticsearchConverter cleanup.
Original Pull Request #1931
Closes #1923
2021-09-11 11:51:05 +02:00
Peter-Josef Meisch 8ab84fcc7a Improve client configuration callbacks.
Original Pull Request #1930 
Closes #1929
2021-09-10 17:20:15 +02:00
Peter-Josef Meisch 7c35e5327e Fix MappingElasticsearchConverter memory leak.
Original Pull Request #1928
Closes #1924
2021-09-10 16:04:40 +02:00
Peter-Josef Meisch 6941e31ba4 Polishing 2021-09-10 08:29:52 +02:00
Nic Hines 3b8f0c9d56 Change mapping of connectionRequestTimeout to ConnPool leaseTimeout.
Original Pull Request: #1925 
Closes: #1926
2021-09-10 08:10:45 +02:00
Peter-Josef Meisch 64624aec70 Encapsulate client specific aggregation return types.
Original Pull Request #1921
Closes #1920
2021-09-04 13:20:01 +02:00
Peter-Josef Meisch e71758686c Fix @Query method implementation for unpaged queries.
Original Pull Request #1919 
Closes #1917
2021-09-03 21:36:57 +02:00
Jade Peng 35c7ef4fed Correct indexName comment.
Original Pull Request #1916
2021-09-01 20:36:08 +02:00
Peter-Josef Meisch 305d930870 Remove org.elasticsearch dependencies from API classes.
Original Pull Request #1913
Closes #1884
Closes #1885
2021-08-30 21:37:04 +02:00
Daniel Franco e688fc70e0 Update maven wrapper version to 3.8.2
Original Pull Request #1905
2021-08-26 20:13:19 +02:00
Peter-Josef Meisch 50f2d83442 Upgrade to Elasticsearch 7.13.4.
Original Pull Request #1900
Closes #1896
2021-08-12 20:38:27 +02:00
Jens Schauder fd3410bb75 After release cleanups.
See #1876
2021-08-12 15:16:23 +02:00
Jens Schauder 325d3f7a09 Prepare next development iteration.
See #1876
2021-08-12 15:16:22 +02:00
Jens Schauder 655db1e362 Release version 4.3 M2 (2021.1.0).
See #1876
2021-08-12 15:03:18 +02:00
Jens Schauder 3eac1bb173 Prepare 4.3 M2 (2021.1.0).
See #1876
2021-08-12 15:02:57 +02:00
Peter-Josef Meisch 36b449c385 Fix NPE on IndexQuery with source and version.
Original Pull Request #1894
Closes #1893
2021-08-06 20:02:13 +02:00
Peter-Josef Meisch 1c8e0e03d3 Make integration tests configurable to use different containers.
Original Pull Request: #1888 
Closes #1882
2021-08-01 19:36:57 +02:00
Peter-Josef Meisch d80d920a57 Remove changelog shipped with the binaries.
Original Pull Request #1881
Closes #1879
2021-07-24 07:08:16 +02:00
Peter-Josef Meisch e6869bcdfd Fix http URL in license header 2021-07-21 08:06:03 +02:00
Peter-Josef Meisch d2e3ea26b8 Upgrade maven wrapper to use maven 3.8.1.
Original Pull Request #1878
Closes #1877
2021-07-21 07:33:58 +02:00
Sascha Woo f74dd879df Move dynamic mapping parameter configuration to @Document and @Field.
Original Pull Request #1872
Closes #1871
2021-07-20 07:51:39 +02:00
Peter-Josef Meisch 3b921b7454 Revert build jdk11 and jdk16 sequentially 2021-07-18 14:03:42 +02:00
Peter-Josef Meisch fa6f636906 Build jdk11 and jdk16 sequentially 2021-07-18 14:02:07 +02:00
Peter-Josef Meisch d3e8c9fce5 Polishing. 2021-07-17 19:15:30 +02:00
Frnandu Martinski d88fb037da Fix uri encode bug when url path start with '/'.
Original Pull Request #1873 
Closes #1870
2021-07-17 19:09:35 +02:00
Jens Schauder 7c35756923 After release cleanups.
See #1777
2021-07-16 14:19:58 +02:00
Jens Schauder 4f7e7526e3 Prepare next development iteration.
See #1777
2021-07-16 14:19:56 +02:00
Jens Schauder c470a3d4eb Release version 4.3 M1 (2021.1.0).
See #1777
2021-07-16 14:09:26 +02:00
Jens Schauder c79b6d158a Prepare 4.3 M1 (2021.1.0).
See #1777
2021-07-16 14:08:59 +02:00
Jens Schauder bf248d78de Updated changelog.
See #1777
2021-07-16 14:08:52 +02:00
Jens Schauder 8e3d8669ea Updated changelog.
See #1849
2021-07-16 10:48:20 +02:00
Peter-Josef Meisch 039e59d3c2 rename a couple of package private classes to a consistent naming scheme.
Original Pull Request #1869 
Closes #1868
2021-07-14 22:19:39 +02:00
Peter-Josef Meisch 27094724dc Use registered converters for parameters of @Query annotated methods.
Original Pull Request #1867 
Closes #1866
2021-07-14 19:31:30 +02:00
Peter-Josef Meisch 567bdf21fa Upgrade to Elasticsearch 7.13.3.
Original Pull Request #1865
Closes #1864
2021-07-12 22:12:11 +02:00
Peter-Josef Meisch 7c340bc9a2 Polishing and documentation. 2021-07-12 21:22:01 +02:00
Sascha Woo 271e1eee0d Add native support for range field types by using a range object
Original Pull Request #1863
Closes #1862
2021-07-12 20:15:35 +02:00
Peter-Josef Meisch 66d13444aa Add TestContainers configuration.
Original Pull Request #1861 
Closes #1860
2021-07-05 20:20:11 +02:00
Niklas Herder 6f84a1c589 Support collection parameters in @Query methods.
Original Pull Request #1856 
Closes #1858
2021-07-03 13:42:56 +02:00
Peter-Josef Meisch 112ca59c95 Adapt pull request template. 2021-07-02 18:07:23 +02:00
Sascha Woo 4ce1137b4e Improve NativeSearchQueryBuilder by adding convenience methods and modifying existing ones.
Original Pull Request #1855 
Closes #1854
2021-07-02 15:47:49 +02:00
Sascha Woo a16a87f3fa Add missing hashCode and equals methods to JoinField.
Original Pull Request #1847 
Closes #1846
2021-06-22 20:51:47 +02:00
Mark Paluch 3871d2d073 Updated changelog.
See #1814
2021-06-22 16:07:28 +02:00
Mark Paluch e80737f76d Updated changelog.
See #1813
2021-06-22 15:29:54 +02:00
Peter-Josef Meisch bc4c913a97 Make CompletionField annotation composable.
Original Pull Request #1841
Closes #1836
2021-06-06 21:14:20 +02:00
Peter-Josef Meisch b515f18b33 Upgrade to Elasticsearch 7.13.1.
Original Pull Request #1840
Closes #1839
2021-06-06 16:55:35 +02:00
Peter-Josef Meisch 38dc7fb0fb Adapt XNamedContents used by ReactiveElasticsearchClient for missing entries (terms and aggregations).
Original Pull Request #1837 
Closes #1834
2021-06-02 22:08:22 +02:00
Peter-Josef Meisch 6b0c4281a4 Upgrade to Elasticsearch 7.13.0.
Original Pull Request #1832
Closes #1831
2021-05-26 22:34:25 +02:00
Peter-Josef Meisch 44e7ab63b0 Dependency cleanup and minor updates.
Original Pull Request #1829
Closes #1828
2021-05-24 14:47:47 +02:00
Peter-Josef Meisch 67d084beea Improve integration test time.
Original Pull Request #1827 
Closes #1826
2021-05-24 12:29:53 +02:00
Peter-Josef Meisch 7582617a26 Fix reactive blocking calls.
Original Pull Request #1825 
Closes #1824
2021-05-22 17:16:16 +02:00
Peter-Josef Meisch e8f73b75ba Add Blockhound to test setup.
Original Pull Request #1823
Closes #1822
2021-05-21 15:29:08 +02:00
Peter-Josef Meisch 5ed655e0aa Fix recative mapping creation.
Original Pull Request #1821
2021-05-19 23:40:11 +02:00
Peter-Josef Meisch 0836411d45 Add runtime fields to index mapping.
Original Pull Request: #1820 
Closes: #1816
2021-05-19 21:38:48 +02:00
Peter-Josef Meisch 25b323c00d source_filter and fields fixes.
Original Pull Request #1819 
Closes #1817
2021-05-18 21:47:47 +02:00
Peter-Josef Meisch 33bc36d111 Add inner_hits support to the collapse field in NativeSearchQuery.
Original Pull Request #1815
Closes #1498
2021-05-15 22:31:45 +02:00
Mark Paluch a830e76807 Updated changelog.
See #1775
2021-05-14 12:36:39 +02:00
Mark Paluch 06f2103c2e Updated changelog.
See #1774
2021-05-14 12:06:43 +02:00
Peter-Josef Meisch e96d09fa51 SearchPage result in StringQuery methods.
Original Pull Request #1812
Closes #1811
2021-05-13 16:48:57 +02:00
Peter-Josef Meisch 38b1763b34 datatype detection support in mapping.
Original Pull Request #1810
Closes #638
2021-05-13 10:26:24 +02:00
Peter-Josef Meisch df0d65eda2 Add pipeline aggregations to NativeSearchQuery.
Original Pull Request #1809 
Closes #1255
2021-05-11 23:21:26 +02:00
Peter-Josef Meisch 3a900599f2 Add documentation about FieldType.Auto.
Original Pull Request #1807 
Closes #1803
2021-05-10 18:46:35 +02:00
Peter-Josef Meisch be93ebd6a6 Can use @ScriptedFields annotated property as ctor parameter in records and Kotlin data classes.
Original Pull Request #1802 
Closes #1488
2021-05-07 08:14:13 +02:00
Peter-Josef Meisch 159687e241 Improve handling of immutable classes.
Original Pull Request #1801
Closes #1800
2021-05-03 21:45:22 +02:00
Peter-Josef Meisch 502ce0b6aa Add requestCache parameter to Query implementations.
Original Pull Request #1799
Closes #1564
2021-05-01 21:20:07 +02:00
Peter-Josef Meisch 5b6789539c Upgrade to Elasticsearch 7.12.1.
Original Pull Request #1796
Closes #1792
2021-04-30 21:18:25 +02:00
Peter-Josef Meisch 775bf66401 Refactor DefaultReactiveElasticsearchClient to do request customization with the WebClient. (#1795)
Original Pull Request #1795 
Closes #1794
2021-04-30 06:48:07 +02:00
Peter-Josef Meisch f8fbf7721a Escape strings with quotes in custom query parameters. (#1793)
Original Pull Request #1793 
Closes #1790
2021-04-28 21:46:10 +02:00
Peter-Josef Meisch a2ca312fb2 Search with MoreLikeThisQuery should use Pageable.
Original Pull Request #1789
Closes #1787
2021-04-26 22:26:39 +02:00
Peter-Josef Meisch 91742b1114 Allow disabling TypeHints.
Original Pull Request #1788
Closes #1788
2021-04-25 21:57:13 +02:00
Peter-Josef Meisch 8b7f0f8327 Fix documentation.
Original Pull Request #1786 
Closes #1785
2021-04-23 09:38:05 +02:00
Greg L. Turnquist 84b441eadc Authenticate with artifactory.
See #1750.
2021-04-20 10:40:58 -05:00
Peter-Josef Meisch e193e1672b Remove deprecated code.
Original Pull Request #1782
Closes #1781
2021-04-19 18:02:02 +02:00
Peter-Josef Meisch 79087c4ada Custom property names must be used in SourceFilter and source fields.
Original Pull Request #1780 
Closes #1778
2021-04-18 14:08:45 +02:00
Peter-Josef Meisch 7ace63485d DynamicMapping annotation should be applicable to any object field.
Original Pull Request #1779
Closes #1767
2021-04-17 14:53:12 +02:00
Greg L. Turnquist 19ec481856 Migrate to main branch.
See #1750.
2021-04-15 13:04:54 -05:00
Mark Paluch 728ba0af5b After release cleanups.
See #1750
2021-04-14 14:30:15 +02:00
Mark Paluch 054235e590 Prepare next development iteration.
See #1750
2021-04-14 14:30:12 +02:00
237 changed files with 12369 additions and 6968 deletions
+7 -1
View File
@@ -1,12 +1,18 @@
<!--
Thank you for proposing a pull request. This template will guide you through the essential steps necessary for a pull request.
When contributing, please make sure an issue exists in 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.
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 [issue tracker](https://github.
com/spring-projects/spring-data-elasticsearch/issues)**. Add the issue number to the _Closes #issue-number_ line below
- [ ] 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).
Closes #issue-number
+117
View File
@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
Vendored Executable → Regular
BIN
View File
Binary file not shown.
Vendored Executable → Regular
+3 -1
View File
@@ -1 +1,3 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
#Mon Oct 11 14:30:32 CEST 2021
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
+1 -1
View File
@@ -1,6 +1,6 @@
= 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%2Fmain&subject=2020.0.0%20(main)[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/]
+1 -1
View File
@@ -1,6 +1,6 @@
= 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].
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here].
== Running the test locally
Vendored
+7 -7
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.6.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -15,7 +15,7 @@ pipeline {
stage("test: baseline (jdk8)") {
when {
anyOf {
branch '4.2.x'
branch 'main'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -45,7 +45,7 @@ pipeline {
stage("Test other configurations") {
when {
allOf {
branch '4.2.x'
branch 'main'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -74,7 +74,7 @@ pipeline {
}
}
stage("test: baseline (jdk16)") {
stage("test: baseline (jdk17)") {
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('openjdk:17-bullseye').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 'main'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -135,7 +135,7 @@ pipeline {
}
stage('Publish documentation') {
when {
branch '4.2.x'
branch 'main'
}
agent {
label 'data'
+2 -2
View File
@@ -1,6 +1,6 @@
image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] image:https://spring.io/badges/spring-data-elasticsearch/snapshot.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start]
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
@@ -217,7 +217,7 @@ The generated documentation is available from `target/site/reference/html/index.
== Examples
For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/master/elasticsearch/example[spring-data-examples] project.
For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/main/elasticsearch/example[spring-data-examples] project.
== License
Vendored
+30 -6
View File
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
@@ -114,7 +114,6 @@ if $mingw ; then
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
@@ -212,7 +211,11 @@ else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
@@ -221,22 +224,38 @@ else
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
wget "$jarUrl" -O "$wrapperJarPath"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
curl -o "$wrapperJarPath" "$jarUrl"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
@@ -277,6 +296,11 @@ if $cygwin; then
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
Vendored Executable → Regular
+33 -12
View File
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,7 +18,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@@ -26,7 +26,7 @@
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -37,7 +37,7 @@
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@@ -120,23 +120,44 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
echo Finished downloading %WRAPPER_JAR%
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
+79 -54
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.2.2</version>
<version>4.3.1</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.5.2</version>
<version>2.6.1</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,13 +18,22 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<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.15.2</elasticsearch>
<log4j>2.17.0</log4j>
<netty>4.1.65.Final</netty>
<springdata.commons>2.6.1</springdata.commons>
<testcontainers>1.15.3</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<java-module-name>spring.data.elasticsearch</java-module-name>
<!--
properties defining the maven phase for the tests and integration tests
set to "none" to disable the corresponding test execution (-Dmvn.unit-test.goal=none)
default execution for unit-test: "test", for the integration tests: "integration-test"
-->
<mvn.unit-test.goal>test</mvn.unit-test.goal>
<mvn.integration-test-elasticsearch.goal>integration-test</mvn.integration-test-elasticsearch.goal>
<mvn.integration-test-opensearch.goal>none</mvn.integration-test-opensearch.goal>
</properties>
<developers>
@@ -131,27 +140,17 @@
<scope>test</scope>
</dependency>
<!-- APACHE -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commonslang}</version>
<scope>test</scope>
</dependency>
<!-- JODA Time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime}</version>
<optional>true</optional>
</dependency>
<!-- Elasticsearch -->
<dependency>
<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>
@@ -159,6 +158,12 @@
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -256,6 +261,14 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>${blockhound-junit}</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.
@@ -272,25 +285,6 @@
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
<version>1.2.8</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_1.0_spec</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
-->
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
@@ -373,9 +367,6 @@
</resources>
<plugins>
<!--
please do not remove this configuration for surefire - we need that to avoid issue with jar hell
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
@@ -394,7 +385,7 @@
<!-- the default-test execution runs only the unit tests -->
<execution>
<id>default-test</id>
<phase>test</phase>
<phase>${mvn.unit-test.goal}</phase>
<goals>
<goal>test</goal>
</goals>
@@ -402,15 +393,32 @@
<excludedGroups>integration-test</excludedGroups>
</configuration>
</execution>
<!-- execution to run the integration tests -->
<!-- execution to run the integration tests against Elasticsearch -->
<execution>
<id>integration-test</id>
<phase>integration-test</phase>
<id>integration-test-elasticsearch</id>
<phase>${mvn.integration-test-elasticsearch.goal}</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>integration-test</groups>
<systemPropertyVariables>
<sde.integration-test.environment>elasticsearch</sde.integration-test.environment>
</systemPropertyVariables>
</configuration>
</execution>
<!-- execution to run the integration tests against Opensearch -->
<execution>
<id>integration-test-opensearch</id>
<phase>${mvn.integration-test-opensearch.goal}</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>integration-test</groups>
<systemPropertyVariables>
<sde.integration-test.environment>opensearch</sde.integration-test.environment>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
@@ -448,9 +456,7 @@
<profiles>
<profile>
<id>ci</id>
<build>
<plugins>
<plugin>
@@ -467,15 +473,34 @@
</module>
</checkstyleRules>
<includes>**/*</includes>
<excludes>.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy</excludes>
<excludes>
.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy
</excludes>
<sourceDirectories>./</sourceDirectories>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jdk13+</id>
<!-- on jDK13+, Blockhound needs this JVM flag set -->
<activation>
<jdk>[13,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-XX:+AllowRedefinitionToAddDeleteMethods</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
+2 -1
View File
@@ -34,7 +34,8 @@ The following table shows the Elasticsearch versions that are used by Spring Dat
[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
| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.15.2 | 5.3.xfootnote:cdv[] | 2.5 .xfootnote:cdv[]
| 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 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
@@ -3,13 +3,14 @@
This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster.
Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.transport]]
== Transport Client
WARNING: The `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.
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 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`.
@@ -46,6 +47,7 @@ IndexRequest request = new IndexRequest("spring-data")
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)
@@ -54,8 +56,7 @@ IndexResponse response = client.index(request);
[[elasticsearch.clients.rest]]
== High Level REST Client
The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns
the very same request/response objects and therefore depends on the Elasticsearch core project.
The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns the very same request/response objects and therefore depends on the Elasticsearch core project.
Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done.
.High Level REST Client
@@ -93,6 +94,7 @@ IndexRequest request = new IndexRequest("spring-data")
IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> Create the RestHighLevelClient.
<3> It is also possible to obtain the `lowLevelRest()` client.
@@ -131,6 +133,7 @@ Mono<IndexResponse> response = client.index(request ->
.source(singletonMap("feature", "reactive-client"));
);
----
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
====
@@ -162,25 +165,30 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
.withWebClientConfigurer(webClient -> { <.>
//...
return webClient;
})
.withHttpClientConfigurer(clientBuilder -> { <.>
//...
.withClientConfigurer( <.>
ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
// ...
return webClient;
}))
.withClientConfigurer( <.>
RestClients.RestClientConfigurationCallback.from(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.
<.> 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.
@@ -188,13 +196,13 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
<.> for non-reactive setup a function configuring the REST client
====
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!
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
If this is used in the reactive setup, the supplier function *must not* block!
[[elasticsearch.clients.logging]]
== Client Logging
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs
to be turned on as outlined in the snippet below.
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs to be turned on as outlined in the snippet below.
.Enable transport layer logging
[source,xml]
@@ -0,0 +1,79 @@
[[elasticsearch-migration-guide-4.2-4.3]]
= Upgrading from 4.2.x to 4.3.x
This section describes breaking changes from version 4.2.x to 4.3.x and how removed features can be replaced by new introduced features.
[NOTE]
====
Elasticsearch is working on a new Client that will replace the `RestHighLevelClient` because the `RestHighLevelClient` uses code from Elasticsearch core libraries which are not Apache 2 licensed anymore.
Spring Data Elasticsearch is preparing for this change as well.
This means that internally the implementations for the `*Operations` interfaces need to change - which should be no problem if users program against the interfaces like `ElasticsearchOperations` or `ReactiveElasticsearchOperations`.
If you are using the implementation classes like `ElasticsearchRestTemplate` directly, you will need to adapt to these changes.
Spring Data Elasticsearch also removes or replaces the use of classes from the `org.elasticsearch` packages in it's API classes and methods, only using them in the implementation where the access to Elasticsearch is implemented.
For the user that means, that some enum classes that were used are replaced by enums that live in `org.springframework.data.elasticsearch` with the same values, these are internally mapped onto the Elasticsearch ones.
Places where classes are used that cannot easily be replaced, this usage is marked as deprecated, we are working on replacements.
Check the sections on <<elasticsearch-migration-guide-4.2-4.3.deprecations>> and <<elasticsearch-migration-guide-4.2-4.3.breaking-changes>> for further details.
====
[[elasticsearch-migration-guide-4.2-4.3.deprecations]]
== Deprecations
=== suggest methods
In `SearchOperations`, and so in `ElasticsearchOperations` as well, the `suggest` methods taking a `org.elasticsearch.search.suggest.SuggestBuilder` as argument and returning a `org.elasticsearch.action.search.SearchResponse` have been deprecated.
Use `SearchHits<T> search(Query query, Class<T> clazz)` instead, passing in a `NativeSearchQuery` which can contain a `SuggestBuilder` and read the suggest results from the returned `SearchHit<T>`.
In `ReactiveSearchOperations` the new `suggest` methods return a `Mono<org.springframework.data.elasticsearch.core.suggest.response.Suggest>` now.
Here as well the old methods are deprecated.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes]]
== Breaking Changes
=== Removal of `org.elasticsearch` classes from the API.
* In the `org.springframework.data.elasticsearch.annotations.CompletionContext` annotation the property `type()` has changed from `org.elasticsearch.search.suggest.completion.context.ContextMapping.Type` to `org.springframework.data.elasticsearch.annotations.CompletionContext.ContextMappingType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.annotations.Document` annotation the `versionType()` property has changed to `org.springframework.data.elasticsearch.annotations.Document.VersionType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.core.query.Query` interface the `searchType()` property has changed to `org.springframework.data.elasticsearch.core.query.Query.SearchType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.core.query.Query` interface the return value of `timeout()` was changed to `java.time.Duration`.
* The `SearchHits<T>`class does not contain the `org.elasticsearch.search.aggregations.Aggregations` anymore.
Instead it now contains an instance of the `org.springframework.data.elasticsearch.core.AggregationsContainer<T>` class where `T` is the concrete aggregations type from the underlying client that is used.
Currently this will be a `org
.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregations` object; later different implementations will be available.
The same change has been done to the `ReactiveSearchOperations.aggregate()` functions, the now return a `Flux<AggregationContainer<?>>`.
Programs using the aggregations need to be changed to cast the returned value to the appropriate class to further proces it.
* methods that might have thrown a `org.elasticsearch.ElasticsearchStatusException` now will throw `org.springframework.data.elasticsearch.RestStatusException` instead.
=== Handling of field and sourceFilter properties of Query
Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`.
This was not correct, as these are different things for Elasticsearch.
This has been corrected.
As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`.
=== search_type default value
The default value for the `search_type` in Elasticsearch is `query_then_fetch`.
This now is also set as default value in the `Query` implementations, it was previously set to `dfs_query_then_fetch`.
=== BulkOptions changes
Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOptions` class have changed their type:
* the type of the `timeout` property has been changed to `java.time.Duration`.
* the type of the`refreshPolicy` property has been changed to `org.springframework.data.elasticsearch.core.RefreshPolicy`.
=== IndicesOptions change
Spring Data Elasticsearch now uses `org.springframework.data.elasticsearch.core.query.IndicesOptions` instead of `org.elasticsearch.action.support.IndicesOptions`.
=== Completion classes
The classes from the package `org.springframework.data.elasticsearch.core.completion` have been moved to `org.springframework.data.elasticsearch.core.suggest`.
=== Other renamings
The `org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter` interface has been renamed to `org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter`.
Likewise the implementations classes named _XXPersistentPropertyConverter_ have been renamed to _XXPropertyValueConverter_.
@@ -7,7 +7,8 @@ It is recommended to add those operations as custom implementation as described
[[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:
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
@@ -42,10 +43,38 @@ class Entity {
// 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
====
[[elasticsearch.misc.mappings]]
== Index Mapping
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation.
In addition to that it is possible to add the `@Mapping` annotation to a class.
This annotation has the following properties:
* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done.
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
====
[source,json]
----
{
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}
----
====
[[elasticsearch.misc.filter]]
== Filter Builder
@@ -141,7 +170,10 @@ interface SampleEntityRepository extends Repository<SampleEntity, String> {
[[elasticsearch.misc.sorts]]
== Sort options
In addition to the default sort options described <<repositories.paging-and-sorting>> Spring Data Elasticsearch has a `GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
In addition to the default sort options described in <<repositories.paging-and-sorting>>, Spring Data Elasticsearch provides the class `org.springframework.data.elasticsearch.core.query.Order` which derives from `org.springframework.data.domain.Sort.Order`.
It offers additional parameters that can be sent to Elasticsearch when specifying the sorting of the result (see https://www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html).
There also is the `org.springframework.data.elasticsearch.core.query.GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
@@ -151,3 +183,81 @@ If the class to be retrieved has a `GeoPoint` property named _location_, the fol
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
----
====
[[elasticsearch.misc.runtime-fields]]
== Runtime Fields
From version 7.12 on Elasticsearch has added the feature of runtime fields (https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime.html).
Spring Data Elasticsearch supports this in two ways:
=== Runtime field definitions in the index mappings
The first way to define runtime fields is by adding the definitions to the index mappings (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html).
To use this approach in Spring Data Elasticsearch the user must provide a JSON file that contains the corresponding definition, for example:
.runtime-fields.json
====
[source,json]
----
{
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}
----
====
The path to this JSON file, which must be present on the classpath, must then be set in the `@Mapping` annotation of the entity:
====
[source,java]
----
@Document(indexName = "runtime-fields")
@Mapping(runtimeFieldsPath = "/runtime-fields.json")
public class RuntimeFieldEntity {
// properties, getter, setter,...
}
----
====
=== Runtime fields definitions set on a Query
The second way to define runtime fields is by adding the definitions to a search query (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-search-request.html).
The following code example shows how to do this with Spring Data Elasticsearch :
The entity used is a simple object that has a `price` property:
====
[source,java]
----
@Document(indexName = "some_index_name")
public class SomethingToBuy {
private @Id @Nullable String id;
@Nullable @Field(type = FieldType.Text) private String description;
@Nullable @Field(type = FieldType.Double) private Double price;
// getter and setter
}
----
====
The following query uses a runtime field that calculates a `priceWithTax` value by adding 19% to the price and uses this value in the search query to find all entities where `priceWithTax` is higher or equal than a given value:
====
[source,java]
----
RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
query.addRuntimeField(runtimeField);
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
----
====
This works with every implementation of the `Query` interface.
@@ -1,6 +1,16 @@
[[new-features]]
= What's new
[[new-features.4-3-0]]
== New in Spring Data Elasticsearch 4.3
* Upgrade to Elasticsearch 7.15.2.
* Allow runtime_fields to be defined in the index mapping.
* Add native support for range field types by using a range object.
* Add repository search for nullable or empty properties.
* Enable custom converters for single fields.
* Supply a custom `Sort.Order` providing Elasticsearch specific parameters.
[[new-features.4-2-0]]
== New in Spring Data Elasticsearch 4.2
@@ -34,8 +34,6 @@ The following annotations are available:
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>>
@@ -49,34 +47,37 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu
* `@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]
See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types].
If the field type is not specified, it defaults to `FieldType.Auto`.
This means, that no mapping entry is written for the property and that Elasticsearch will add a mapping entry dynamically when the first data for this property is stored (check the Elasticsearch documentation for dynamic mapping rules).
** `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.
* `@ValueConverter` defines a class to be used to convert the given property.
In difference to a registered Spring `Converter` this only converts the annotated property and not every property of the given type.
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
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])
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 `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 `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
@@ -102,12 +103,59 @@ The following table shows the different attributes and the mapping created from
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].
==== Range types
When a field is annotated with a type of one of _Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range,_ or _Ip_Range_ the field must be an instance of a class that will be mapped to an Elasticsearch range, for example:
====
[source,java]
----
class SomePersonData {
@Field(type = FieldType.Integer_Range)
private ValidAge validAge;
// getter and setter
}
class ValidAge {
@Field(name="gte")
private Integer from;
@Field(name="lte")
private Integer to;
// getter and setter
}
----
====
As an alternative Spring Data Elasticsearch provides a `Range<T>` class so that the previous example can be written as:
====
[source,java]
----
class SomePersonData {
@Field(type = FieldType.Integer_Range)
private Range<Integer> validAge;
// getter and setter
}
----
====
Supported classes for the type `<T>` are `Integer`, `Long`, `Float`, `Double`, `Date` and classes that implement the
`TemporalAccessor` interface.
==== 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.
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.
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
@@ -138,6 +186,7 @@ public class Person { <1>
"lastname" : "Connor"
}
----
<1> By default the domain types class name is used for the type hint.
====
@@ -165,11 +214,32 @@ public class Person {
"id" : ...
}
----
<1> The configured alias is used when writing the entity.
====
NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the actual value type does not match the properties declaration.
===== Disabling Type Hints
It may be necessary to disable writing of type hints when the index that should be used already exists without having the type hints defined in its mapping and with the mapping mode set to strict.
In this case, writing the type hint will produce an error, as the field cannot be added automatically.
Type hints can be disabled for the whole application by overriding the method `writeTypeHints()` in a configuration class derived from `AbstractElasticsearchConfiguration` (see <<elasticsearch.clients>>).
As an alternativ they can be disabled for a single index with the `@Document` annotation:
====
[source,java]
----
@Document(indexName = "index", writeTypeHint = WriteTypeHint.FALSE)
----
====
WARNING: We strongly advise against disabling Type Hints.
Only do this if you are forced to.
Disabling type hints can lead to documents not being retrieved correctly from Elasticsearch in case of polymorphic data or document retrieval may fail completely.
==== Geospatial Types
Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
@@ -355,6 +425,7 @@ public class Config extends AbstractElasticsearchConfiguration {
"localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
}
----
<1> Add `Converter` implementations.
<2> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<3> Set up the `Converter` used for reading `DomainType` from search result.
@@ -20,11 +20,11 @@ The default implementations of the interfaces offer:
[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.
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.
**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>>
@@ -58,6 +58,7 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
}
}
----
<1> Setting up the <<elasticsearch.clients.transport>>.
Deprecated as of version 4.0.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
@@ -82,6 +83,7 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
// no special bean creation needed <2>
}
----
<1> Setting up the <<elasticsearch.clients.rest>>.
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
====
@@ -127,6 +129,7 @@ public class TestController {
}
----
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<2> Store some entity in the Elasticsearch cluster.
<3> Retrieve the entity with a query by id.
@@ -164,6 +167,7 @@ Contains the following information:
* Maximum score
* A list of `SearchHit<T>` objects
* Returned aggregations
* Returned suggest results
.SearchPage<T>
Defines a Spring Data `Page` that contains a `SearchHits<T>` element and can be used for paging access using repository methods.
@@ -182,12 +186,12 @@ Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchO
[[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.
`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):
`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
====
@@ -211,7 +215,7 @@ 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_:
.Get all persons with first name _James_ and last name _Miller_:
====
[source,java]
----
@@ -219,11 +223,13 @@ 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_:
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
====
@@ -236,6 +242,7 @@ Criteria miller = new Criteria("lastName").is("Miller") <.>
);
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_
@@ -281,5 +288,3 @@ Query query = new NativeSearchQueryBuilder()
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====
@@ -242,10 +242,6 @@ A list of supported keywords for Elasticsearch is shown below.
| `findByNameNotIn(Collection<String>names)`
| `{"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}`
| `Near`
| `findByStoreNear`
| `Not Supported Yet !`
| `True`
| `findByAvailableTrue`
| `{ "query" : {
@@ -277,6 +273,26 @@ A list of supported keywords for Elasticsearch is shown below.
}, "sort":[{"name":{"order":"desc"}}]
}`
| `Exists`
| `findByNameExists`
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
| `IsNull`
| `findByNameIsNull`
| `{"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}}`
| `IsNotNull`
| `findByNameIsNotNull`
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
| `IsEmpty`
| `findByNameIsEmpty`
| `{"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}}`
| `IsNotEmpty`
| `findByNameIsNotEmpty`
| `{"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}}`
|===
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
@@ -8,4 +8,6 @@ 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[]
include::elasticsearch-migration-guide-4.2-4.3.adoc[]
:leveloffset: -1
@@ -0,0 +1,49 @@
/*
* 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;
import org.springframework.dao.DataAccessException;
/**
* Exception class for REST status exceptions independent from the used client/backend.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class RestStatusException extends DataAccessException {
// we do not use a dedicated status class from Elasticsearch, OpenSearch, Spring web or webflux here
private final int status;
public RestStatusException(int status, String msg) {
super(msg);
this.status = status;
}
public RestStatusException(int status, String msg, Throwable cause) {
super(msg, cause);
this.status = status;
}
public int getStatus() {
return status;
}
@Override
public String toString() {
return "RestStatusException{" + "status=" + status + "} " + super.toString();
}
}
@@ -1,3 +1,18 @@
/*
* Copyright 2019-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
@@ -7,12 +22,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
/**
* Based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
*
* @author Robert Gruendler
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -22,9 +36,26 @@ public @interface CompletionContext {
String name();
ContextMapping.Type type();
ContextMappingType type();
String precision() default "";
String path() default "";
/**
* @since 4.3
*/
enum ContextMappingType {
CATEGORY("category"), GEO("geo");
private final String mappedName;
ContextMappingType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
}
@@ -23,13 +23,15 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Based on the reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
* Based on the reference doc -
* https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
*
* @author Mewes Kochheim
* @author Robert Gruendler
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Inherited
public @interface CompletionField {
@@ -21,7 +21,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.index.VersionType;
import org.springframework.data.annotation.Persistent;
/**
@@ -33,6 +32,7 @@ import org.springframework.data.annotation.Persistent;
* @author Ivan Greene
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Sascha Woo
*/
@Persistent
@Inherited
@@ -44,7 +44,7 @@ public @interface Document {
* Name of the Elasticsearch index.
* <ul>
* <li>Lowercase only</li>
* <li><Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #/li>
* <li>Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #</li>
* <li>Cannot start with -, _, +</li>
* <li>Cannot be . or ..</li>
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit
@@ -105,4 +105,25 @@ public @interface Document {
* Configuration of version management.
*/
VersionType versionType() default VersionType.EXTERNAL;
/**
* Defines if type hints should be written. {@see WriteTypeHint}.
*
* @since 4.3
*/
WriteTypeHint writeTypeHint() default WriteTypeHint.DEFAULT;
/**
* Controls how Elasticsearch dynamically adds fields to the document.
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
/**
* @since 4.3
*/
enum VersionType {
INTERNAL, EXTERNAL, EXTERNAL_GTE
}
}
@@ -0,0 +1,60 @@
/*
* 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;
/**
* Values for the {@code dynamic} mapping parameter.
*
* @author Sascha Woo
* @since 4.3
*/
public enum Dynamic {
/**
* New fields are added to the mapping.
*/
TRUE("true"),
/**
* New fields are added to the mapping as
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
* fields are not indexed, and are loaded from {@code _source} at query time.
*/
RUNTIME("runtime"),
/**
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
* explicitly.
*/
FALSE("false"),
/**
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
* added to the mapping.
*/
STRICT("strict"),
/**
* Inherit the dynamic setting from their parent object or from the mapping type.
*/
INHERIT("nherit");
private final String mappedName;
Dynamic(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -26,11 +26,14 @@ import java.lang.annotation.Target;
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
@Documented
@Deprecated
public @interface DynamicMapping {
DynamicMappingValue value() default DynamicMappingValue.True;
@@ -17,10 +17,23 @@ package org.springframework.data.elasticsearch.annotations;
/**
* values for the {@link DynamicMapping annotation}
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Deprecated
public enum DynamicMappingValue {
True, False, Strict
True("true"), False("false"), Strict("strict");
private final String mappedName;
DynamicMappingValue(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -9,19 +9,17 @@ import java.lang.annotation.Target;
import org.springframework.data.annotation.Persistent;
/**
* Elasticsearch dynamic templates mapping.
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
* DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
* 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.
*
* @author Petr Kukral
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Target({ ElementType.TYPE })
public @interface DynamicTemplates {
String mappingPath() default "";
}
@@ -195,4 +195,20 @@ public @interface Field {
* @since 4.2
*/
int dims() default -1;
/**
* Controls how Elasticsearch dynamically adds fields to the inner object within the document.<br>
* To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested}
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
/**
* marks this field to be excluded from the _source in Elasticsearch
* (https://www.elastic.co/guide/en/elasticsearch/reference/7.15/mapping-source-field.html#include-exclude)
*
* @since 4.3
*/
boolean excludeFromSource() default false;
}
@@ -26,40 +26,51 @@ package org.springframework.data.elasticsearch.annotations;
* @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, //
Auto("auto"), //
Text("text"), //
Keyword("keyword"), //
Long("long"), //
Integer("integer"), //
Short("short"), //
Byte("byte"), //
Double("double"), //
Float("float"), //
Half_Float("half_float"), //
Scaled_Float("scaled_float"), //
Date("date"), //
Date_Nanos("date_nanos"), //
Boolean("boolean"), //
Binary("binary"), //
Integer_Range("integer_range"), //
Float_Range("float_range"), //
Long_Range("long_range"), //
Double_Range("double_range"), //
Date_Range("date_range"), //
Ip_Range("ip_range"), //
Object("object"), //
Nested("nested"), //
Ip("ip"), //
TokenCount("token_count"), //
Percolator("percolator"), //
Flattened("flattened"), //
Search_As_You_Type("search_as_you_type"), //
/** @since 4.1 */
Rank_Feature, //
Rank_Feature("rank_feature"), //
/** @since 4.1 */
Rank_Features, //
Rank_Features("rank_features"), //
/** since 4.2 */
Wildcard, //
Wildcard("wildcard"), //
/** @since 4.2 */
Dense_Vector //
Dense_Vector("dense_vector") //
;
private final String mappedName;
FieldType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -27,6 +27,7 @@ import org.springframework.data.annotation.Persistent;
* Elasticsearch Mapping
*
* @author Mohsin Husen
* @author Peter-Josef Meisch
*/
@Persistent
@Inherited
@@ -38,8 +39,42 @@ public @interface Mapping {
/**
* whether mappings are enabled
*
*
* @since 4.2
*/
boolean enabled() default true;
/**
* whether date_detection is enabled
*
* @since 4.3
*/
Detection dateDetection() default Detection.DEFAULT;
/**
* whether numeric_detection is enabled
*
* @since 4.3
*/
Detection numericDetection() default Detection.DEFAULT;
/**
* custom dynamic date formats
*
* @since 4.3
*/
String[] dynamicDateFormats() default {};
/**
* classpath to a JSON file containing the values for a runtime mapping definition. The file must contain the JSON
* object that is written as the value of the runtime property. {@see <a href=
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html">elasticsearch doc</a>}
*
* @since 4.3
*/
String runtimeFieldsPath() default "";
enum Detection {
DEFAULT, TRUE, FALSE;
}
}
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.data.annotation.QueryAnnotation;
import java.lang.annotation.*;
/**
@@ -23,11 +24,13 @@ import java.lang.annotation.*;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @author Steven Pearce
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Documented
@QueryAnnotation
public @interface Query {
/**
@@ -0,0 +1,48 @@
/*
* 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.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
/**
* Annotation to put on a property of an entity to define a value converter which can convert the property to a type
* that Elasticsearch understands and back.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Inherited
public @interface ValueConverter {
/**
* Defines the class implementing the {@link PropertyValueConverter} interface. If this is a normal class, it must
* provide a default constructor with no arguments. If this is an enum and thus implementing a singleton by enum it
* must only have one enum value.
*
* @return the class to use for conversion
*/
Class<? extends PropertyValueConverter> value();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* 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.
@@ -15,22 +15,26 @@
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.*;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.mapping.context.MappingContext;
/**
* Parent
* Defines if type hints should be written. Used by {@link Document} annotation.
*
* @author Philipp Jardas
* @deprecated since 4.1, not supported anymore by Elasticsearch
* @author Peter-Josef Meisch
* @since 4.3
*/
@Deprecated
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Parent {
public enum WriteTypeHint {
String type();
/**
* Use the global settings from the {@link MappingContext}.
*/
DEFAULT,
/**
* Always write type hints for the entity.
*/
TRUE,
/**
* Never write type hints for the entity.
*/
FALSE
}
@@ -27,6 +27,7 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -120,16 +121,16 @@ public interface ClientConfiguration {
boolean useSsl();
/**
* Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
* Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured.
*
* @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
* @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured.
*/
Optional<SSLContext> getSslContext();
/**
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
*
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
*/
Optional<HostnameVerifier> getHostNameVerifier();
@@ -152,7 +153,7 @@ public interface ClientConfiguration {
/**
* Returns the path prefix that should be prepended to HTTP(s) requests for Elasticsearch behind a proxy.
*
*
* @return the path prefix.
* @since 4.0
*/
@@ -161,7 +162,7 @@ public interface ClientConfiguration {
/**
* returns an optionally set proxy in the form host:port
*
*
* @return the optional proxy
* @since 4.0
*/
@@ -173,11 +174,19 @@ public interface ClientConfiguration {
Function<WebClient, WebClient> getWebClientConfigurer();
/**
* @return the client configuration callback.
* @return the Rest Client configuration callback.
* @since 4.2
* @deprecated since 4.3 use {@link #getClientConfigurers()}
*/
@Deprecated
HttpClientConfigCallback getHttpClientConfigurer();
/**
* @return the client configuration callbacks
* @since 4.3
*/
<T> List<ClientConfigurationCallback<?>> getClientConfigurers();
/**
* @return the supplier for custom headers.
*/
@@ -274,7 +283,7 @@ public interface ClientConfiguration {
TerminalClientConfigurationBuilder withDefaultHeaders(HttpHeaders defaultHeaders);
/**
* Configure the {@literal milliseconds} for the connect timeout.
* Configure the {@literal milliseconds} for the connect-timeout.
*
* @param millis the timeout to use.
* @return the {@link TerminalClientConfigurationBuilder}
@@ -327,7 +336,7 @@ public interface ClientConfiguration {
/**
* Configure the path prefix that will be prepended to any HTTP(s) requests
*
*
* @param pathPrefix the pathPrefix.
* @return the {@link TerminalClientConfigurationBuilder}
* @since 4.0
@@ -342,21 +351,36 @@ public interface ClientConfiguration {
/**
* set customization hook in case of a reactive configuration
*
*
* @param webClientConfigurer function to configure the WebClient
* @return the {@link TerminalClientConfigurationBuilder}.
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link ReactiveRestClients.WebClientConfigurationCallback}
*/
@Deprecated
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
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link RestClients.RestClientConfigurationCallback}
*/
@Deprecated
TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer);
/**
* Register a {@link ClientConfigurationCallback} to configure the client.
*
* @param clientConfigurer configuration callback, must not be {@literal null}.
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.3
*/
TerminalClientConfigurationBuilder withClientConfigurer(ClientConfigurationCallback<?> clientConfigurer);
/**
* 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.
@@ -377,4 +401,15 @@ public interface ClientConfiguration {
*/
ClientConfiguration build();
}
/**
* Callback to be executed to configure a client.
*
* @param <T> the type of the client configuration class.
* @since 4.3
*/
@FunctionalInterface
interface ClientConfigurationCallback<T> {
T configure(T clientConfigurer);
}
}
@@ -31,6 +31,7 @@ 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;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -49,7 +50,7 @@ import org.springframework.web.reactive.function.client.WebClient;
class ClientConfigurationBuilder
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
private List<InetSocketAddress> hosts = new ArrayList<>();
private final List<InetSocketAddress> hosts = new ArrayList<>();
private HttpHeaders headers = HttpHeaders.EMPTY;
private boolean useSsl;
private @Nullable SSLContext sslContext;
@@ -62,7 +63,8 @@ class ClientConfigurationBuilder
private @Nullable String proxy;
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
@Deprecated private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
private List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
/*
* (non-Javadoc)
@@ -206,6 +208,7 @@ class ClientConfigurationBuilder
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
this.webClientConfigurer = webClientConfigurer;
this.clientConfigurers.add(ReactiveRestClients.WebClientConfigurationCallback.from(webClientConfigurer));
return this;
}
@@ -215,6 +218,18 @@ class ClientConfigurationBuilder
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers
.add(RestClients.RestClientConfigurationCallback.from(httpClientConfigurer::customizeHttpClient));
return this;
}
@Override
public TerminalClientConfigurationBuilder withClientConfigurer(
ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer) {
Assert.notNull(clientConfigurer, "clientConfigurer must not be null");
this.clientConfigurers.add(clientConfigurer);
return this;
}
@@ -242,7 +257,7 @@ class ClientConfigurationBuilder
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, headersSupplier);
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, clientConfigurers, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {
@@ -32,7 +32,10 @@ import org.springframework.util.StringUtils;
*
* @author Oliver Gierke
* @since 3.1
* @deprecated only used in {@link TransportClientFactoryBean}.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
class ClusterNodes implements Streamable<TransportAddress> {
public static ClusterNodes DEFAULT = ClusterNodes.of("127.0.0.1:9300");
@@ -44,7 +47,7 @@ class ClusterNodes implements Streamable<TransportAddress> {
/**
* Creates a new {@link ClusterNodes} by parsing the given source.
*
*
* @param source must not be {@literal null} or empty.
*/
private ClusterNodes(String source) {
@@ -74,15 +77,14 @@ class ClusterNodes implements Streamable<TransportAddress> {
/**
* Creates a new {@link ClusterNodes} by parsing the given source. The expected format is a comma separated list of
* host-port-combinations separated by a colon: {@code host:port,host:port,…}.
*
*
* @param source must not be {@literal null} or empty.
* @return
*/
public static ClusterNodes of(String source) {
return new ClusterNodes(source);
}
/*
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@@ -55,12 +55,13 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final Function<WebClient, WebClient> webClientConfigurer;
private final HttpClientConfigCallback httpClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
private final List<ClientConfigurationCallback<?>> clientConfigurers;
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) {
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
@@ -73,6 +74,7 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.proxy = proxy;
this.webClientConfigurer = webClientConfigurer;
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers = clientConfigurers;
this.headersSupplier = headersSupplier;
}
@@ -132,6 +134,12 @@ class DefaultClientConfiguration implements ClientConfiguration {
return httpClientConfigurer;
}
@SuppressWarnings("unchecked")
@Override
public <T> List<ClientConfigurationCallback<?>> getClientConfigurers() {
return clientConfigurers;
}
@Override
public Supplier<HttpHeaders> getHeadersSupplier() {
return headersSupplier;
@@ -1,173 +0,0 @@
/*
* Copyright 2015-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.client;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.logging.LogConfigurator;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.InternalSettingsPreparer;
import org.elasticsearch.node.Node;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.transport.Netty4Plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* NodeClientFactoryBean
*
* @author Rizwan Idrees
* @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);
private boolean local;
private boolean enableHttp;
private @Nullable String clusterName;
private @Nullable Node node;
private @Nullable NodeClient nodeClient;
private @Nullable String pathData;
private @Nullable String pathHome;
private @Nullable String pathConfiguration;
public static class TestNode extends Node {
private static final String DEFAULT_NODE_NAME = "spring-data-elasticsearch-nodeclientfactorybean-test";
public TestNode(Settings preparedSettings, Collection<Class<? extends Plugin>> classpathPlugins) {
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null,
() -> DEFAULT_NODE_NAME), classpathPlugins, false);
}
protected void registerDerivedNodeNameWithLogger(String nodeName) {
try {
LogConfigurator.setNodeName(nodeName);
} catch (Exception e) {
// nagh - just forget about it
}
}
}
NodeClientFactoryBean() {}
public NodeClientFactoryBean(boolean local) {
this.local = local;
}
@Override
public NodeClient getObject() {
if (nodeClient == null) {
throw new FactoryBeanNotInitializedException();
}
return nodeClient;
}
@Override
public Class<? extends Client> getObjectType() {
return NodeClient.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
Settings settings = Settings.builder() //
.put(loadConfig()) //
.put("transport.type", "netty4") //
.put("http.type", "netty4") //
.put("path.home", this.pathHome) //
.put("path.data", this.pathData) //
.put("cluster.name", this.clusterName) //
.put("node.max_local_storage_nodes", 100) //
.build();
node = new TestNode(settings, Collections.singletonList(Netty4Plugin.class));
nodeClient = (NodeClient) node.start().client();
}
private Settings loadConfig() throws IOException {
if (!StringUtils.isEmpty(pathConfiguration)) {
InputStream stream = getClass().getClassLoader().getResourceAsStream(pathConfiguration);
if (stream != null) {
return Settings.builder().loadFromStream(pathConfiguration,
getClass().getClassLoader().getResourceAsStream(pathConfiguration), false).build();
}
logger.error(String.format("Unable to read node configuration from file [%s]", pathConfiguration));
}
return Settings.builder().build();
}
public void setLocal(boolean local) {
this.local = local;
}
public void setEnableHttp(boolean enableHttp) {
this.enableHttp = enableHttp;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public void setPathData(String pathData) {
this.pathData = pathData;
}
public void setPathHome(String pathHome) {
this.pathHome = pathHome;
}
public void setPathConfiguration(String configuration) {
this.pathConfiguration = configuration;
}
@Override
public void destroy() {
try {
// NodeClient.close() is a noop, no need to call it here
nodeClient = null;
logger.info("Closing elasticSearch node");
if (node != null) {
node.close();
node = null;
}
} catch (final Exception e) {
logger.error("Error closing ElasticSearch client: ", e);
}
}
}
@@ -22,6 +22,7 @@ import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -36,6 +37,7 @@ import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HttpContext;
import org.elasticsearch.client.RestClient;
@@ -54,6 +56,7 @@ import org.springframework.util.Assert;
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @author Peter-Josef Meisch
* @author Nic Hines
* @since 3.2
*/
public final class RestClients {
@@ -104,22 +107,27 @@ public final class RestClients {
Duration connectTimeout = clientConfiguration.getConnectTimeout();
if (!connectTimeout.isNegative()) {
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
}
Duration timeout = clientConfiguration.getSocketTimeout();
Duration socketTimeout = clientConfiguration.getSocketTimeout();
if (!timeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
if (!socketTimeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
}
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
clientBuilder = clientConfiguration.getHttpClientConfigurer().customizeHttpClient(clientBuilder);
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof RestClientConfigurationCallback) {
RestClientConfigurationCallback restClientConfigurationCallback = (RestClientConfigurationCallback) clientConfigurer;
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
}
}
return clientBuilder;
});
@@ -198,7 +206,7 @@ public final class RestClients {
}
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
() -> new String(buffer.toByteArray()));
buffer::toString);
} else {
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "");
}
@@ -213,7 +221,7 @@ public final class RestClients {
/**
* Interceptor to inject custom supplied headers.
*
*
* @since 4.0
*/
private static class CustomHeaderInjector implements HttpRequestInterceptor {
@@ -233,4 +241,23 @@ public final class RestClients {
}
}
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the RestClient with a {@link HttpAsyncClientBuilder}
*
* @since 4.3
*/
public interface RestClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
static RestClientConfigurationCallback from(
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> clientBuilderCallback) {
Assert.notNull(clientBuilderCallback, "clientBuilderCallback must not be null");
// noinspection NullableProblems
return clientBuilderCallback::apply;
}
}
}
@@ -83,11 +83,11 @@ 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;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
@@ -101,6 +101,7 @@ 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.RestStatusException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
@@ -111,6 +112,7 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsea
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.ResponseConverter;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpHeaders;
@@ -287,9 +289,21 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
}
Function<WebClient, WebClient> webClientConfigurer = webClient -> {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) {
ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer;
webClient = webClientConfigurationCallback.configure(webClient);
}
}
return webClient;
};
provider = provider //
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()) //
.withWebClientConfigurer(webClientConfigurer) //
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> {
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
@@ -485,7 +499,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
public Mono<ByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers) //
.next() //
.map(ByQueryResponse::of);
.map(ResponseConverter::byQueryResponseOf);
}
@Override
@@ -823,8 +837,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return Mono.error(BytesRestResponse.errorFromXContent(createParser(mediaType, content)));
} catch (Exception e) {
return Mono
.error(new ElasticsearchStatusException(content, RestStatus.fromCode(response.statusCode().value())));
return Mono.error(new RestStatusException(response.statusCode().value(), content));
}
}
}
@@ -857,14 +870,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
return response.body(BodyExtractors.toMono(byte[].class)) //
.switchIfEmpty(Mono.error(
new ElasticsearchStatusException(String.format("%s request to %s returned error code %s and no body.",
request.getMethod(), request.getEndpoint(), statusCode), status)))
.switchIfEmpty(Mono.error(new RestStatusException(status.getStatus(),
String.format("%s request to %s returned error code %s and no body.", request.getMethod(),
request.getEndpoint(), statusCode))))
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
.flatMap(content -> contentOrError(content, mediaType, status))
.flatMap(unused -> Mono
.error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), statusCode), status)));
.error(new RestStatusException(status.getStatus(), String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), statusCode))));
}
private <T> Publisher<? extends T> handleClientError(String logId, ClientResponse response, Class<T> responseType) {
@@ -896,7 +909,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
if (exception != null) {
StringBuilder sb = new StringBuilder();
buildExceptionMessages(sb, exception);
return Mono.error(new ElasticsearchStatusException(sb.toString(), status, exception));
return Mono.error(new RestStatusException(status.getStatus(), sb.toString(), exception));
}
return Mono.just(content);
@@ -1102,58 +1102,6 @@ public interface ReactiveElasticsearchClient {
*/
Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest);
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
* @deprecated since 4.1, use {@link #putMapping(Consumer)}
*/
@Deprecated
default Mono<Boolean> updateMapping(
Consumer<org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest> consumer) {
return putMapping(consumer);
}
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
*
* @param putMappingRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
* @deprecated since 4.1, use {@link #putMapping(PutMappingRequest)}
*/
@Deprecated
default Mono<Boolean> updateMapping(
org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) {
return putMapping(putMappingRequest);
}
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param putMappingRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
* @deprecated since 4.1, use {@link #putMapping(HttpHeaders, PutMappingRequest)}
*/
@Deprecated
default Mono<Boolean> updateMapping(HttpHeaders headers,
org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) {
return putMapping(headers, putMappingRequest);
}
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
@@ -15,8 +15,11 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import java.util.function.Function;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Utility class for common access to reactive Elasticsearch clients. {@link ReactiveRestClients} consolidates set up
@@ -61,4 +64,21 @@ public final class ReactiveRestClients {
return DefaultReactiveElasticsearchClient.create(clientConfiguration, requestCreator);
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the ReactiveElasticsearchClient with a {@link WebClient}
*
* @since 4.3
*/
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
Assert.notNull(webClientCallback, "webClientCallback must not be null");
// noinspection NullableProblems
return webClientCallback::apply;
}
}
}
@@ -26,9 +26,9 @@ 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;
import org.elasticsearch.common.xcontent.ParseField;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.bucket.adjacency.AdjacencyMatrixAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.adjacency.ParsedAdjacencyMatrix;
@@ -78,10 +78,8 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
@@ -90,6 +88,8 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.rankeval.RankEvalRequest;
@@ -841,8 +841,7 @@ public class RequestConverters {
RequestConverters.Params parameters = new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(false);
.withMasterTimeout(putMappingRequest.masterNodeTimeout());
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -853,8 +852,7 @@ public class RequestConverters {
new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(false);
.withMasterTimeout(putMappingRequest.masterNodeTimeout());
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -880,7 +878,6 @@ public class RequestConverters {
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
parameters.withLocal(getMappingsRequest.local());
parameters.withIncludeTypeName(false);
return request;
}
@@ -893,7 +890,6 @@ public class RequestConverters {
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
parameters.withLocal(getMappingsRequest.local());
parameters.withIncludeTypeName(false);
return request;
}
@@ -1000,7 +996,6 @@ public class RequestConverters {
RequestConverters.Params parameters = new Params(request);
parameters.withIndicesOptions(getFieldMappingsRequest.indicesOptions());
parameters.withIncludeDefaults(getFieldMappingsRequest.includeDefaults());
parameters.withIncludeTypeName(false);
return request;
}
@@ -1372,18 +1367,6 @@ public class RequestConverters {
}
return this;
}
/**
* sets the include_type_name parameter. Needed for Elasticsearch 7 to be used with the mapping types still
* available. Will be removed again when Elasticsearch drops the support for this parameter in Elasticsearch 8.
*
* @deprecated since 4.0
* @since 4.0
*/
@Deprecated
Params withIncludeTypeName(boolean includeTypeName) {
return putParam("include_type_name", String.valueOf(includeTypeName));
}
}
/**
@@ -1452,7 +1435,8 @@ public class RequestConverters {
// encode each part (e.g. index, type and id) separately before merging them into the path
// we prepend "/" to the path part to make this path absolute, otherwise there can be issues with
// paths that start with `-` or contain `:`
URI uri = new URI(null, null, null, -1, '/' + pathPart, null, null);
// the authority must be an empty string and not null, else paths that being with slashes could have them
URI uri = new URI((String) null, "", "/" + pathPart, (String) null, (String) null);
// manually encode any slash that each part may contain
return uri.getRawPath().substring(1).replaceAll("/", "%2F");
} catch (URISyntaxException e) {
@@ -21,13 +21,11 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.search.Scroll;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)}
* Mutable state object holding scrollId to be used for scroll requests.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
@@ -26,7 +26,6 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.context.annotation.Configuration;
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;
@@ -72,6 +71,7 @@ public class ElasticsearchConfigurationSupport {
mappingContext.setInitialEntitySet(getInitialEntitySet());
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder());
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
mappingContext.setWriteTypeHints(writeTypeHints());
return mappingContext;
}
@@ -171,4 +171,17 @@ public class ElasticsearchConfigurationSupport {
protected FieldNamingStrategy fieldNamingStrategy() {
return PropertyNameFieldNamingStrategy.INSTANCE;
}
/**
* Flag specifiying if type hints (_class fields) should be written in the index. It is strongly advised to keep the
* default value of {@literal true}. If you need to write to an existing index that does not have a mapping defined
* for these fields and that has a strict mapping set, then it might be necessary to disable type hints. But notice
* that in this case reading polymorphic types may fail.
*
* @return flag if type hints should be written
* @since 4.3
*/
protected boolean writeTypeHints() {
return true;
}
}
@@ -35,7 +35,6 @@ public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension);
registerBeanDefinitionParser("repositories", parser);
registerBeanDefinitionParser("node-client", new NodeClientBeanDefinitionParser());
registerBeanDefinitionParser("transport-client", new TransportClientBeanDefinitionParser());
registerBeanDefinitionParser("rest-client", new RestClientBeanDefinitionParser());
}
@@ -41,32 +41,22 @@ public @interface EnableElasticsearchAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
*
* @return
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
* used for setting creation and modification dates.
*
* @return
* @deprecated since 4.1
*/
@Deprecated
String dateTimeProviderRef() default "";
}
@@ -41,32 +41,22 @@ 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,57 +0,0 @@
/*
* Copyright 2015-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.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.elasticsearch.client.NodeClientFactoryBean;
import org.w3c.dom.Element;
/**
* NodeClientBeanDefinitionParser
*
* @author Rizwan Idrees
* @author Mohsin Husen
*/
public class NodeClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(NodeClientFactoryBean.class);
setLocalSettings(element, builder);
return getSourcedBeanDefinition(builder, element, parserContext);
}
private void setLocalSettings(Element element, BeanDefinitionBuilder builder) {
builder.addPropertyValue("local", Boolean.valueOf(element.getAttribute("local")));
builder.addPropertyValue("clusterName", element.getAttribute("cluster-name"));
builder.addPropertyValue("enableHttp", Boolean.valueOf(element.getAttribute("http-enabled")));
builder.addPropertyValue("pathData", element.getAttribute("path-data"));
builder.addPropertyValue("pathHome", element.getAttribute("path-home"));
builder.addPropertyValue("pathConfiguration", element.getAttribute("path-configuration"));
}
private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source,
ParserContext context) {
AbstractBeanDefinition definition = builder.getBeanDefinition();
definition.setSource(context.extractSource(source));
return definition;
}
}
@@ -29,7 +29,7 @@ import org.springframework.data.mapping.context.PersistentEntities;
* @author Peter-Josef Meisch
* @since 4.1
*/
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
public class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
private final MappingElasticsearchConverter converter;
@@ -0,0 +1,219 @@
/*
* 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.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.elasticsearch.Version;
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.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.springframework.data.elasticsearch.BulkFailureException;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.util.Assert;
/**
* This class contains methods that are common the implementations derived from {@link AbstractElasticsearchTemplate}
* using either the {@link org.elasticsearch.client.transport.TransportClient} or the
* {@link org.elasticsearch.client.RestHighLevelClient} and that use Elasticsearch specific libraries.
* <p>
* <strong>Note:</strong> Although this class is public, it is not considered to be part of the official Spring Data
* Elasticsearch API and so might change at any time.
*
* @author Peter-Josef Meisch
*/
public abstract class AbstractElasticsearchRestTransportTemplate extends AbstractElasticsearchTemplate {
// region DocumentOperations
/**
* @param bulkResponse
* @return the list of the item id's
*/
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new BulkFailureException(
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + ']',
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());
}
// endregion
// region SearchOperations
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
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, IndexCoordinates index) {
MultiSearchRequest request = new MultiSearchRequest();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, clazz, index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), documentCallback::doWith)));
}
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();
IndexCoordinates index = getIndexCoordinatesFor(entityClass);
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, entityClass, index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
}
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) {
request.add(requestFactory.searchRequest(query, it.next(), index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, entityClass, index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
}
return res;
}
abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request);
// endregion
// region helper
@Override
public Query matchAllQuery() {
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build();
}
@Override
public Query idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.idsQuery().addIds(ids.toArray(new String[] {})))
.build();
}
@Override
protected String getVendor() {
return "Elasticsearch";
}
@Override
protected String getRuntimeLibraryVersion() {
return Version.CURRENT.toString();
}
@Override
@Deprecated
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
return suggest(suggestion, getIndexCoordinatesFor(clazz));
}
// endregion
}
@@ -15,32 +15,17 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.elasticsearch.action.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;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.elasticsearch.BulkFailureException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
@@ -57,7 +42,6 @@ import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
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;
@@ -73,20 +57,28 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* AbstractElasticsearchTemplate
* This class contains methods that are common to different implementations of the {@link ElasticsearchOperations}
* interface that use different clients, like TransportClient, RestHighLevelClient and the next Java client from
* Elasticsearch or some future implementation that might use an Opensearch client. This class must not contain imports
* or use classes that are specific to one of these implementations.
* <p>
* <strong>Note:</strong> Although this class is public, it is not considered to be part of the official Spring Data
* Elasticsearch API and so might change at any time.
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
* @author Subhobrata Dey
* @author Steven Pearce
* @author Anton Naydenov
*/
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 EntityOperations entityOperations;
@Nullable protected EntityCallbacks entityCallbacks;
@Nullable protected RefreshPolicy refreshPolicy;
@Nullable protected RoutingResolver routingResolver;
// region Initialization
@@ -101,7 +93,10 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.routingResolver = new DefaultRoutingResolver((SimpleElasticsearchMappingContext) mappingContext);
requestFactory = new RequestFactory(elasticsearchConverter);
VersionInfo.logVersions(getClusterVersion());
// initialize the VersionInfo class in the initialization phase
// noinspection ResultOfMethodCallIgnored
VersionInfo.versionProperties();
}
/**
@@ -116,6 +111,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
copy.setRoutingResolver(routingResolver);
copy.setRefreshPolicy(refreshPolicy);
return copy;
}
@@ -166,6 +162,16 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}
/**
* logs the versions of the different Elasticsearch components.
*
* @since 4.3
*/
public void logVersions() {
VersionInfo.logVersions(getVendor(), getRuntimeLibraryVersion(), getClusterVersion());
}
// endregion
// region DocumentOperations
@@ -188,7 +194,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
IndexQuery query = getIndexQuery(entityAfterBeforeConvert);
doIndex(query, index);
T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index);
T entityAfterAfterSave = (T) maybeCallbackAfterSave(query.getObject(), index);
return entityAfterAfterSave;
}
@@ -215,13 +221,18 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
List<IndexQuery> indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery)
.collect(Collectors.toList());
if (!indexQueries.isEmpty()) {
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
entities.forEach(entity -> updateIndexedObject(entity, iterator.next()));
if (indexQueries.isEmpty()) {
return Collections.emptyList();
}
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
// noinspection unchecked
return indexQueries.stream() //
.map(IndexQuery::getObject) //
.map(entity -> (T) updateIndexedObject(entity, iterator.next())) //
.collect(Collectors.toList()); //
}
@Override
@@ -346,40 +357,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
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
@@ -396,8 +373,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index) {
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
Duration scrollTime = query.getScrollTime() != null ? query.getScrollTime() : Duration.ofMinutes(1);
long scrollTimeInMillis = scrollTime.toMillis();
// noinspection ConstantConditions
int maxCount = query.isLimiting() ? query.getMaxResults() : 0;
@@ -418,96 +395,16 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index);
return doSearch(query, clazz, index);
}
protected abstract <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates 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();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, clazz, index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse())));
}
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes) {
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) {
request.add(requestFactory.searchRequest(query, it.next(), index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response)));
}
return res;
}
@Override
public <T> SearchHits<T> search(Query query, Class<T> clazz) {
return search(query, clazz, getIndexCoordinatesFor(clazz));
@@ -537,13 +434,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
@@ -580,38 +470,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
/**
* @param bulkResponse
* @return the list of the item id's
*/
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new BulkFailureException(
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + ']',
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());
}
protected void updateIndexedObject(Object entity, IndexedObjectInformation indexedObjectInformation) {
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
.getPersistentEntity(entity.getClass());
@@ -621,22 +480,30 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
if (indexedObjectInformation.getId() != null && idProperty != null
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
// noinspection unchecked
T updatedEntity = (T) propertyAccessor.getBean();
return updatedEntity;
}
return entity;
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
@@ -721,6 +588,18 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Nullable
abstract protected String getClusterVersion();
/**
* @return the vendor name of the used cluster and client library
* @since 4.3
*/
abstract protected String getVendor();
/**
* @return the version of the used client runtime library.
* @since 4.3
*/
abstract protected String getRuntimeLibraryVersion();
// endregion
// region Entity callbacks
@@ -807,13 +686,16 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
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));
indexQuery.setObject(updateIndexedObject(queryObject, indexedObjectInformations.get(i)));
}
}
}
@@ -848,6 +730,10 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
T entity = reader.read(type, document);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
document.getVersion());
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallbackAfterConvert(entity, document, index);
}
}
@@ -869,6 +755,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -889,6 +776,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -17,14 +17,10 @@ package org.springframework.data.elasticsearch.core;
import static org.springframework.util.StringUtils.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.cluster.metadata.AliasMetadata;
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;
@@ -36,7 +32,6 @@ 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;
@@ -47,9 +42,7 @@ import org.springframework.util.Assert;
* @author Sascha Woo
* @since 4.0
*/
abstract class AbstractDefaultIndexOperations implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDefaultIndexOperations.class);
abstract class AbstractIndexTemplate implements IndexOperations {
protected final ElasticsearchConverter elasticsearchConverter;
protected final RequestFactory requestFactory;
@@ -57,7 +50,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
@Nullable protected final Class<?> boundClass;
@Nullable private final IndexCoordinates boundIndex;
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
public AbstractIndexTemplate(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
Assert.notNull(boundClass, "boundClass may not be null");
@@ -67,7 +60,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
this.boundIndex = null;
}
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
public AbstractIndexTemplate(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
Assert.notNull(boundIndex, "boundIndex may not be null");
@@ -176,31 +169,6 @@ 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());
}
protected abstract List<AliasMetadata> doQueryForAlias(IndexCoordinates index);
@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) {
@@ -234,6 +202,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
// load mapping specified in Mapping annotation if present
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
if (mappingAnnotation != null) {
String mappingPath = mappingAnnotation.mappingPath();
@@ -243,8 +212,6 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
if (hasText(mappings)) {
return Document.parse(mappings);
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
@@ -0,0 +1,41 @@
/*
* 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.core;
/**
* Class corresponding to the Elasticsearch class, but in the org.springframework.data.elasticsearch package
*
* @author Peter-Josef Meisch
*/
public class ActiveShardCount {
private static final int ACTIVE_SHARD_COUNT_DEFAULT = -2;
private static final int ALL_ACTIVE_SHARDS = -1;
public static final ActiveShardCount DEFAULT = new ActiveShardCount(ACTIVE_SHARD_COUNT_DEFAULT);
public static final ActiveShardCount ALL = new ActiveShardCount(ALL_ACTIVE_SHARDS);
public static final ActiveShardCount NONE = new ActiveShardCount(0);
public static final ActiveShardCount ONE = new ActiveShardCount(1);
private final int value;
public ActiveShardCount(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
@@ -0,0 +1,31 @@
/*
* 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.core;
/**
* Aggregation container used in the Spring Data Elasticsearch API for a single aggregation. The concrete
* implementations must be provided by the code handling the direct communication with Elasticsearch.
*
* @author Peter-Josef Meisch
* @param <T> the aggregation class from the used client implementation.
* @since 4.3
*/
public interface AggregationContainer<T> {
/**
* @return the concrete aggregations implementation
*/
T aggregation();
}
@@ -0,0 +1,31 @@
/*
* 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.core;
/**
* Aggregations container used in the Spring Data Elasticsearch API. The concrete implementations must be provided by
* the code handling the direct communication with Elasticsearch.
*
* @author Peter-Josef Meisch
* @param <T> the aggregations class from the used client implementation.
* @since 4.3
*/
public interface AggregationsContainer<T> {
/**
* @return the concrete aggregations implementation
*/
T aggregations();
}
@@ -165,20 +165,35 @@ class CriteriaQueryProcessor {
@Nullable
private QueryBuilder queryFor(Criteria.CriteriaEntry entry, Field field) {
QueryBuilder query = null;
String fieldName = field.getName();
boolean isKeywordField = FieldType.Keyword == field.getFieldType();
OperationKey key = entry.getKey();
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
// operations without a value
switch (key) {
case EXISTS:
query = existsQuery(fieldName);
break;
case EMPTY:
query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
break;
case NOT_EMPTY:
query = wildcardQuery(fieldName, "*");
break;
default:
break;
}
if (query != null) {
return query;
}
// now operation keys with a value
Object value = entry.getValue();
String searchText = QueryParserUtil.escape(value.toString());
QueryBuilder query = null;
switch (key) {
case EQUALS:
query = queryStringQuery(searchText).field(fieldName).defaultOperator(AND);
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.Collection;
import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -121,6 +122,8 @@ public interface DocumentOperations {
* @param query the query defining the ids of the objects to get
* @param clazz the type of the object to be returned
* @return list of {@link MultiGetItem}s
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
* @since 4.1
*/
<T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz);
@@ -132,6 +135,8 @@ public interface DocumentOperations {
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return list of {@link MultiGetItem}s
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
*/
<T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
@@ -159,6 +164,7 @@ public interface DocumentOperations {
* @param queries the queries to execute in bulk
* @param clazz the entity class
* @return the information about the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
@@ -170,6 +176,7 @@ public interface DocumentOperations {
*
* @param queries the queries to execute in bulk
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
return bulkIndex(queries, BulkOptions.defaultOptions(), index);
@@ -182,6 +189,7 @@ public interface DocumentOperations {
* @param bulkOptions options to be added to the bulk request
* @param clazz the entity class
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz);
@@ -192,6 +200,7 @@ public interface DocumentOperations {
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -199,6 +208,7 @@ public interface DocumentOperations {
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
default void bulkUpdate(List<UpdateQuery> queries, IndexCoordinates index) {
bulkUpdate(queries, BulkOptions.defaultOptions(), index);
@@ -209,6 +219,7 @@ public interface DocumentOperations {
*
* @param clazz the entity class
* @param queries the queries to execute in bulk
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz);
@@ -218,6 +229,7 @@ public interface DocumentOperations {
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -283,7 +295,7 @@ public interface DocumentOperations {
/**
* Delete all records matching the query.
*
*
* @param query query defining the objects
* @param clazz The entity class, must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
@@ -22,13 +22,13 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.rest.RestStatus;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -38,7 +38,7 @@ import org.springframework.util.StringUtils;
* Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an
* appropriate exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is
* appropriate: any other exception may have resulted from user code, and should not be translated.
*
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
@@ -63,9 +63,28 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
ex);
}
if (elasticsearchException instanceof ElasticsearchStatusException) {
ElasticsearchStatusException elasticsearchStatusException = (ElasticsearchStatusException) elasticsearchException;
return new RestStatusException(elasticsearchStatusException.status().getStatus(),
elasticsearchStatusException.getMessage(), elasticsearchStatusException);
}
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
}
if (ex instanceof RestStatusException) {
RestStatusException restStatusException = (RestStatusException) ex;
Throwable cause = restStatusException.getCause();
if (cause instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) cause;
if (!indexAvailable(elasticsearchException)) {
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
ex);
}
}
}
if (ex instanceof ValidationException) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
@@ -80,13 +99,26 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
private boolean isSeqNoConflict(Exception exception) {
Integer status = null;
String message = null;
if (exception instanceof ElasticsearchStatusException) {
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
status = statusException.status().getStatus();
message = statusException.getMessage();
}
return statusException.status() == RestStatus.CONFLICT && statusException.getMessage() != null
&& statusException.getMessage().contains("type=version_conflict_engine_exception")
&& statusException.getMessage().contains("version conflict, required seqNo");
if (exception instanceof RestStatusException) {
RestStatusException statusException = (RestStatusException) exception;
status = statusException.getStatus();
message = statusException.getMessage();
}
if (status != null && message != null) {
return status == 409 && message.contains("type=version_conflict_engine_exception")
&& message.contains("version conflict, required seqNo");
}
if (exception instanceof VersionConflictEngineException) {
@@ -33,10 +33,11 @@ import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
@@ -92,7 +93,7 @@ import org.springframework.util.Assert;
* @author Massimiliano Poggi
* @author Farid Faoudi
*/
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTransportTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
@@ -131,7 +132,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(clazz, "clazz must not be null");
return new DefaultIndexOperations(this, clazz);
return new RestIndexTemplate(this, clazz);
}
@Override
@@ -139,7 +140,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(index, "index must not be null");
return new DefaultIndexOperations(this, index);
return new RestIndexTemplate(this, index);
}
// endregion
@@ -157,9 +158,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
IndexResponse indexResponse = execute(client -> client.index(request, RequestOptions.DEFAULT));
Object queryObject = query.getObject();
if (queryObject != null) {
updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), indexResponse.getSeqNo(),
indexResponse.getPrimaryTerm(), indexResponse.getVersion()));
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(),
indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion())));
}
return indexResponse.getId();
@@ -168,6 +170,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
@Override
@Nullable
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(), index);
GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT));
@@ -179,7 +182,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
Assert.notEmpty(query.getIds(), "No Id defined for Query");
MultiGetRequest request = requestFactory.multiGetRequest(query, clazz, index);
MultiGetResponse result = execute(client -> client.mget(request, RequestOptions.DEFAULT));
@@ -221,7 +223,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
DeleteByQueryRequest deleteByQueryRequest = requestFactory.deleteByQueryRequest(query, clazz, index);
return ByQueryResponse.of(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT)));
return ResponseConverter
.byQueryResponseOf(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT)));
}
@Override
@@ -259,7 +262,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
final BulkByScrollResponse bulkByScrollResponse = execute(
client -> client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT));
return ByQueryResponse.of(bulkByScrollResponse);
return ResponseConverter.byQueryResponseOf(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
@@ -270,6 +273,24 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
updateIndexedObjectsWithQueries(queries, indexedObjectInformationList);
return indexedObjectInformationList;
}
/**
* 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));
}
// endregion
// region SearchOperations
@@ -295,8 +316,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index);
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -310,9 +333,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response));
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -324,9 +348,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
SearchResponse response = execute(client -> client.scroll(request, RequestOptions.DEFAULT));
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = //
new ReadSearchScrollDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -15,7 +15,10 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.elasticsearch.action.ActionFuture;
@@ -33,9 +36,11 @@ import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequestBuilder;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder;
@@ -87,7 +92,7 @@ import org.springframework.util.Assert;
* @deprecated as of 4.0
*/
@Deprecated
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTemplate {
private static final Logger QUERY_LOGGER = LoggerFactory
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTemplate.class);
@@ -131,7 +136,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(clazz, "clazz must not be null");
return new DefaultTransportIndexOperations(client, elasticsearchConverter, clazz);
return new TransportIndexTemplate(client, elasticsearchConverter, clazz);
}
@Override
@@ -139,7 +144,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(index, "index must not be null");
return new DefaultTransportIndexOperations(client, elasticsearchConverter, index);
return new TransportIndexTemplate(client, elasticsearchConverter, index);
}
// endregion
@@ -177,8 +182,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Object queryObject = query.getObject();
if (queryObject != null) {
updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion()));
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion())));
}
return documentId;
@@ -200,7 +205,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
Assert.notEmpty(query.getIds(), "No Ids defined for Query");
MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, clazz, index);
@@ -243,7 +247,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
return ByQueryResponse.of(requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get());
return ResponseConverter
.byQueryResponseOf(requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get());
}
@Override
@@ -285,18 +290,45 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
// UpdateByQueryRequestBuilder has not parameters to set a routing value
final BulkByScrollResponse bulkByScrollResponse = updateByQueryRequestBuilder.execute().actionGet();
return ByQueryResponse.of(bulkByScrollResponse);
return ResponseConverter.byQueryResponseOf(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index) {
BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index);
bulkRequestBuilder = prepareWriteRequestBuilder(bulkRequestBuilder);
final List<IndexedObjectInformation> indexedObjectInformations = checkForBulkOperationFailure(
bulkRequestBuilder.execute().actionGet());
updateIndexedObjectsWithQueries(queries, indexedObjectInformations);
return indexedObjectInformations;
// do it in batches; test code on some machines kills the transport node when the size gets too much
Collection<? extends List<?>> queryLists = partitionBasedOnSize(queries, 2500);
List<IndexedObjectInformation> allIndexedObjectInformations = new ArrayList<>(queries.size());
queryLists.forEach(queryList -> {
BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queryList, bulkOptions, index);
bulkRequestBuilder = prepareWriteRequestBuilder(bulkRequestBuilder);
final List<IndexedObjectInformation> indexedObjectInformations = checkForBulkOperationFailure(
bulkRequestBuilder.execute().actionGet());
updateIndexedObjectsWithQueries(queryList, indexedObjectInformations);
allIndexedObjectInformations.addAll(indexedObjectInformations);
});
return allIndexedObjectInformations;
}
/**
* 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
@@ -320,8 +352,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
SearchResponse response = getSearchResponse(searchRequestBuilder);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -336,9 +369,10 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
SearchResponse response = getSearchResponseWithTimeout(action);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response));
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -352,9 +386,10 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
SearchResponse response = getSearchResponseWithTimeout(action);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response));
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -411,6 +446,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
public Client getClient() {
return client;
}
<T> Collection<List<T>> partitionBasedOnSize(List<T> inputList, int size) {
final AtomicInteger counter = new AtomicInteger(0);
return inputList.stream().collect(Collectors.groupingBy(s -> counter.getAndIncrement() / size)).values();
}
// endregion
/**
@@ -93,22 +93,6 @@ class EntityOperations {
return AdaptibleMappedEntity.of(entity, context, conversionService, routingResolver);
}
/**
* Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using
* preferred values for index and type if provided, otherwise fall back to index and type defined on entity level.
*
* @param entity the entity to determine the index name. Can be {@literal null} if {@code index} and {@literal type}
* are provided.
* @param index index name override can be {@literal null}.
* @param type index type override can be {@literal null}.
* @return the {@link IndexCoordinates} containing index name and index type.
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(Entity, String)}
*/
@Deprecated
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index, @Nullable String type) {
return determineIndex(entity.getPersistentEntity(), index, type);
}
/**
* Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using
* preferred values for index and type if provided, otherwise fall back to index and type defined on entity level.
@@ -122,24 +106,6 @@ class EntityOperations {
return determineIndex(entity.getPersistentEntity(), index);
}
/**
* Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type}
* overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type
* defined on entity level.
*
* @param persistentEntity the entity to determine the index name. Can be {@literal null} if {@code index} and
* {@literal type} are provided.
* @param index index name override can be {@literal null}.
* @param type index type override can be {@literal null}.
* @return the {@link IndexCoordinates} containing index name and index type.
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(ElasticsearchPersistentEntity, String)}
*/
@Deprecated
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index,
@Nullable String type) {
return determineIndex(persistentEntity, index);
}
/**
* Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type}
* overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type
@@ -234,25 +200,6 @@ class EntityOperations {
*/
interface AdaptibleEntity<T> extends Entity<T> {
/**
* Returns whether the entity has a parent.
*
* @return {@literal true} if the entity has a parent that has an {@literal id}.
* @deprecated since 4.1, not supported anymore by Elasticsearch
*/
@Deprecated
boolean hasParent();
/**
* Returns the parent Id. Can be {@literal null}.
*
* @return can be {@literal null}.
* @deprecated since 4.1, not supported anymore by Elasticsearch
*/
@Deprecated
@Nullable
Object getParentId();
/**
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
* currently present.
@@ -339,24 +286,6 @@ class EntityOperations {
return map.get(ID_FIELD);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#hasParent()
*/
@Override
public boolean hasParent() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getParentId()
*/
@Override
public Entity<?> getParentId() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
@@ -548,7 +477,6 @@ class EntityOperations {
*/
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
private final T bean;
private final ElasticsearchPersistentEntity<?> entity;
private final ConvertingPropertyAccessor<T> propertyAccessor;
private final IdentifierAccessor identifierAccessor;
@@ -561,7 +489,6 @@ class EntityOperations {
super(entity, identifierAccessor, propertyAccessor);
this.bean = bean;
this.entity = entity;
this.propertyAccessor = propertyAccessor;
this.identifierAccessor = identifierAccessor;
@@ -582,16 +509,8 @@ class EntityOperations {
}
@Override
public boolean hasParent() {
return getRequiredPersistentEntity().getParentIdProperty() != null;
}
@Deprecated
@Override
public Object getParentId() {
ElasticsearchPersistentProperty parentProperty = getRequiredPersistentEntity().getParentIdProperty();
return propertyAccessor.getProperty(parentProperty);
public T getBean() {
return propertyAccessor.getBean();
}
@Nullable
@@ -668,7 +587,7 @@ class EntityOperations {
@Override
public String getRouting() {
String routing = routingResolver.getRouting(bean);
String routing = routingResolver.getRouting(propertyAccessor.getBean());
if (routing != null) {
return routing;
@@ -19,7 +19,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.data.elasticsearch.core.index.AliasData;
@@ -30,7 +29,6 @@ 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;
/**
@@ -190,35 +188,6 @@ public interface IndexOperations {
// endregion
// region aliases
/**
* Add an alias.
*
* @param query query defining the alias
* @return true if the alias was created
* @deprecated since 4.1 use {@link #alias(AliasActions)}
*/
@Deprecated
boolean addAlias(AliasQuery query);
/**
* Get the alias information for a specified index.
*
* @return alias information
* @deprecated since 4.1, use {@link #getAliases(String...)} or {@link #getAliasesForIndex(String...)}.
*/
@Deprecated
List<AliasMetadata> queryForAlias();
/**
* Remove an alias.
*
* @param query query defining the alias
* @return true if the alias was removed
* @deprecated since 4.1 use {@link #alias(AliasActions)}
*/
@Deprecated
boolean removeAlias(AliasQuery query);
/**
* Executes the given {@link AliasActions}.
*
@@ -25,12 +25,12 @@ import org.springframework.lang.Nullable;
* @since 4.1
*/
public class IndexedObjectInformation {
private final String id;
@Nullable private final String id;
@Nullable private final Long seqNo;
@Nullable private final Long primaryTerm;
@Nullable private final Long version;
private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
private IndexedObjectInformation(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
@Nullable Long version) {
this.id = id;
this.seqNo = seqNo;
@@ -38,11 +38,12 @@ public class IndexedObjectInformation {
this.version = version;
}
public static IndexedObjectInformation of(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
public static IndexedObjectInformation of(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
@Nullable Long version) {
return new IndexedObjectInformation(id, seqNo, primaryTerm, version);
}
@Nullable
public String getId() {
return id;
}
@@ -0,0 +1,442 @@
/*
* 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.core;
import java.util.Optional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Simple value object to work with ranges and boundaries.
*
* @author Sascha Woo
* @since 4.3
*/
public class Range<T> {
private final static Range<?> UNBOUNDED = Range.of(Bound.unbounded(), Bound.UNBOUNDED);
/**
* The lower bound of the range.
*/
private final Bound<T> lowerBound;
/**
* The upper bound of the range.
*/
private final Bound<T> upperBound;
/**
* Creates a new {@link Range} with inclusive bounds for both values.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> closed(T from, T to) {
return new Range<>(Bound.inclusive(from), Bound.inclusive(to));
}
/**
* Creates a new Range with the given value as sole member.
*
* @param <T>
* @param value must not be {@literal null}.
* @return
* @see Range#closed(T, T)
*/
public static <T> Range<T> just(T value) {
return Range.closed(value, value);
}
/**
* Creates a new left-open {@link Range}, i.e. left exclusive, right inclusive.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> leftOpen(T from, T to) {
return new Range<>(Bound.exclusive(from), Bound.inclusive(to));
}
/**
* Creates a left-unbounded {@link Range} (the left bound set to {@link Bound#unbounded()}) with the given right
* bound.
*
* @param <T>
* @param to the right {@link Bound}, must not be {@literal null}.
* @return
*/
public static <T> Range<T> leftUnbounded(Bound<T> to) {
return new Range<>(Bound.unbounded(), to);
}
/**
* Creates a new {@link Range} with the given lower and upper bound.
*
* @param lowerBound must not be {@literal null}.
* @param upperBound must not be {@literal null}.
*/
public static <T> Range<T> of(Bound<T> lowerBound, Bound<T> upperBound) {
return new Range<>(lowerBound, upperBound);
}
/**
* Creates a new {@link Range} with exclusive bounds for both values.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> open(T from, T to) {
return new Range<>(Bound.exclusive(from), Bound.exclusive(to));
}
/**
* Creates a new right-open {@link Range}, i.e. left inclusive, right exclusive.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> rightOpen(T from, T to) {
return new Range<>(Bound.inclusive(from), Bound.exclusive(to));
}
/**
* Creates a right-unbounded {@link Range} (the right bound set to {@link Bound#unbounded()}) with the given left
* bound.
*
* @param <T>
* @param from the left {@link Bound}, must not be {@literal null}.
* @return
*/
public static <T> Range<T> rightUnbounded(Bound<T> from) {
return new Range<>(from, Bound.unbounded());
}
/**
* Returns an unbounded {@link Range}.
*
* @return
*/
@SuppressWarnings("unchecked")
public static <T> Range<T> unbounded() {
return (Range<T>) UNBOUNDED;
}
private Range(Bound<T> lowerBound, Bound<T> upperBound) {
Assert.notNull(lowerBound, "Lower bound must not be null!");
Assert.notNull(upperBound, "Upper bound must not be null!");
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
/**
* Returns whether the {@link Range} contains the given value.
*
* @param value must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public boolean contains(T value) {
Assert.notNull(value, "Reference value must not be null!");
Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!");
boolean greaterThanLowerBound = lowerBound.getValue() //
.map(it -> lowerBound.isInclusive() ? ((Comparable<? super T>) it).compareTo(value) <= 0
: ((Comparable<? super T>) it).compareTo(value) < 0) //
.orElse(true);
boolean lessThanUpperBound = upperBound.getValue() //
.map(it -> upperBound.isInclusive() ? ((Comparable<? super T>) it).compareTo(value) >= 0
: ((Comparable<? super T>) it).compareTo(value) > 0) //
.orElse(true);
return greaterThanLowerBound && lessThanUpperBound;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Range)) {
return false;
}
Range<?> range = (Range<?>) o;
if (!ObjectUtils.nullSafeEquals(lowerBound, range.lowerBound)) {
return false;
}
return ObjectUtils.nullSafeEquals(upperBound, range.upperBound);
}
public Range.Bound<T> getLowerBound() {
return this.lowerBound;
}
public Range.Bound<T> getUpperBound() {
return this.upperBound;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(lowerBound);
result = 31 * result + ObjectUtils.nullSafeHashCode(upperBound);
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s-%s", lowerBound.toPrefixString(), upperBound.toSuffixString());
}
/**
* Value object representing a boundary. A boundary can either be {@link #unbounded() unbounded}, {@link #inclusive(T)
* including its value} or {@link #exclusive(T) its value}.
*/
public static final class Bound<T> {
@SuppressWarnings({ "rawtypes", "unchecked" }) //
private static final Bound<?> UNBOUNDED = new Bound(Optional.empty(), true);
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<T> value;
private final boolean inclusive;
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Double> exclusive(double value) {
return exclusive((Double) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Float> exclusive(float value) {
return exclusive((Float) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Integer> exclusive(int value) {
return exclusive((Integer) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Long> exclusive(long value) {
return exclusive((Long) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static <T> Bound<T> exclusive(T value) {
Assert.notNull(value, "Value must not be null!");
Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!");
return new Bound<>(Optional.of(value), false);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Double> inclusive(double value) {
return inclusive((Double) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Float> inclusive(float value) {
return inclusive((Float) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Integer> inclusive(int value) {
return inclusive((Integer) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Long> inclusive(long value) {
return inclusive((Long) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static <T> Bound<T> inclusive(T value) {
Assert.notNull(value, "Value must not be null!");
Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!");
return new Bound<>(Optional.of(value), true);
}
/**
* Creates an unbounded {@link Bound}.
*/
@SuppressWarnings("unchecked")
public static <T> Bound<T> unbounded() {
return (Bound<T>) UNBOUNDED;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Bound(Optional<T> value, boolean inclusive) {
this.value = value;
this.inclusive = inclusive;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Bound)) {
return false;
}
Bound<?> bound = (Bound<?>) o;
if (inclusive != bound.inclusive)
return false;
return ObjectUtils.nullSafeEquals(value, bound.value);
}
public Optional<T> getValue() {
return this.value;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(value);
result = 31 * result + (inclusive ? 1 : 0);
return result;
}
/**
* Returns whether this boundary is bounded.
*
* @return
*/
public boolean isBounded() {
return value.isPresent();
}
public boolean isInclusive() {
return this.inclusive;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return value.map(Object::toString).orElse("unbounded");
}
String toPrefixString() {
return getValue() //
.map(Object::toString) //
.map(it -> isInclusive() ? "[".concat(it) : "(".concat(it)) //
.orElse("unbounded");
}
String toSuffixString() {
return getValue() //
.map(Object::toString) //
.map(it -> isInclusive() ? it.concat("]") : it.concat(")")) //
.orElse("unbounded");
}
}
}
@@ -106,7 +106,8 @@ public interface ReactiveDocumentOperations {
/**
* Index entities in the given {@literal index}. If the {@literal index} is {@literal null} or empty the index name
* provided via entity metadata is used.
* provided via entity metadata is used. On errors returns with
* {@link org.springframework.data.elasticsearch.BulkFailureException} with information about the failed operation
*
* @param entities must not be {@literal null}.
* @param index the target index, must not be {@literal null}
@@ -148,6 +149,8 @@ public interface ReactiveDocumentOperations {
* @param query the query defining the ids of the objects to get
* @param clazz the type of the object to be returned, used to determine the index
* @return flux with list of {@link MultiGetItem}s that contain the entities
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
* @since 4.1
*/
<T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz);
@@ -159,12 +162,15 @@ public interface ReactiveDocumentOperations {
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return flux with list of {@link MultiGetItem}s that contain the entities
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
* @since 4.0
*/
<T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
/**
* Bulk update all objects. Will do update.
* Bulk update all objects. Will do update. On errors returns with
* {@link org.springframework.data.elasticsearch.BulkFailureException} with information about the failed operation
*
* @param queries the queries to execute in bulk
* @since 4.0
@@ -178,6 +184,7 @@ public interface ReactiveDocumentOperations {
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.0
*/
Mono<Void> bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -23,8 +23,10 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.elasticsearch.Version;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
@@ -39,11 +41,10 @@ import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
@@ -57,6 +58,7 @@ import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregation;
import org.springframework.data.elasticsearch.core.cluster.DefaultReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
@@ -75,12 +77,14 @@ import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMa
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
@@ -142,7 +146,9 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
this.operations = new EntityOperations(this.mappingContext);
this.requestFactory = new RequestFactory(converter);
logVersions();
// initialize the VersionInfo class in the initialization phase
// noinspection ResultOfMethodCallIgnored
VersionInfo.versionProperties();
}
private ReactiveElasticsearchTemplate copy() {
@@ -155,11 +161,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
return copy;
}
private void logVersions() {
getClusterVersion() //
.doOnSuccess(VersionInfo::logVersions) //
.doOnError(e -> VersionInfo.logVersions(null)) //
.subscribe();
/**
* logs the versions of the different Elasticsearch components.
*
* @return a Mono signalling finished execution
* @since 4.3
*/
public Mono<Void> logVersions() {
return getVendor() //
.doOnNext(vendor -> getRuntimeLibraryVersion() //
.doOnNext(runtimeLibraryVersion -> getClusterVersion() //
.doOnNext(clusterVersion -> VersionInfo.logVersions(vendor, runtimeLibraryVersion, clusterVersion)))) //
.then(); //
}
@Override
@@ -275,27 +288,42 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
private <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
routingResolver);
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntityFor(entity.getClass());
ElasticsearchPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(entity.getClass());
if (persistentEntity != null) {
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
}
// noinspection unchecked
T updatedEntity = (T) propertyAccessor.getBean();
return updatedEntity;
} else {
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
routingResolver);
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
}
return entity;
}
@@ -310,7 +338,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
Assert.notNull(index, "Index must not be null");
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(query, "Query must not be null");
Assert.notEmpty(query.getIds(), "No Id define for Query");
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
@@ -348,8 +375,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
protected Flux<BulkItemResponse> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
return client.bulk(bulkRequest) //
.onErrorMap(
e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e)) //
.onErrorMap(e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest, e)) //
.flatMap(this::checkForBulkOperationFailure) //
.flatMapMany(response -> Flux.fromArray(response.getItems()));
}
@@ -457,7 +483,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
return doGet(id, index).flatMap(it -> callback.toEntity(DocumentAdapters.from(it)));
return doGet(id, index).flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
}
private Mono<GetResult> doGet(String id, IndexCoordinates index) {
@@ -537,7 +563,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
Assert.notNull(query, "Query must not be null!");
return doDeleteBy(query, entityType, index).map(ByQueryResponse::of);
return doDeleteBy(query, entityType, index).map(ResponseConverter::byQueryResponseOf);
}
@Override
@@ -631,7 +657,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link DeleteRequest} prior to its execution. Eg. by setting the
* Customization hook to modify a generated {@link DeleteRequest} prior to its execution. E.g. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request the generated {@link DeleteRequest}.
@@ -642,7 +668,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link DeleteByQueryRequest} prior to its execution. Eg. by setting the
* Customization hook to modify a generated {@link DeleteByQueryRequest} prior to its execution. E.g. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request the generated {@link DeleteByQueryRequest}.
@@ -667,7 +693,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link IndexRequest} prior to its execution. Eg. by setting the
* Customization hook to modify a generated {@link IndexRequest} prior to its execution. E.g. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param source the source object the {@link IndexRequest} was derived from.
@@ -679,7 +705,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* Preprocess the write request before it is sent to the server, e.g. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request must not be {@literal null}.
@@ -732,13 +758,15 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
private Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
request = prepareSearchRequest(request);
if (query.getPageable().isPaged() || query.isLimiting()) {
return doFind(request);
} else {
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
boolean useScroll = !(query.getPageable().isPaged() || query.isLimiting());
request = prepareSearchRequest(request, useScroll);
if (useScroll) {
return doScroll(request);
} else {
return doFind(request);
}
});
}
@@ -747,46 +775,88 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
return Mono.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
request = prepareSearchRequest(request);
return doFindForResponse(request);
request = prepareSearchRequest(request, false);
SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index);
return doFindForResponse(request, searchDocument -> documentCallback.toEntity(searchDocument).block());
});
}
@Override
public Flux<Aggregation> aggregate(Query query, Class<?> entityType) {
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType) {
return aggregate(query, entityType, getIndexCoordinatesFor(entityType));
}
@Override
public Flux<Aggregation> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
return doAggregate(query, entityType, index);
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
Assert.notNull(entityType, "entityType must not be null");
Assert.notNull(index, "index must not be null");
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
request = prepareSearchRequest(request, false);
return doAggregate(request);
});
}
/**
* Customization hook on the actual execution result {@link Publisher}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @return a {@link Flux} emitting the result of the operation.
*/
protected Flux<AggregationContainer<?>> doAggregate(SearchRequest request) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doCount: {}", request);
}
return Flux.from(execute(client -> client.aggregate(request))) //
.onErrorResume(NoSuchIndexException.class, it -> Flux.empty()).map(ElasticsearchAggregation::new);
}
@Override
public Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
public Mono<Suggest> suggest(Query query, Class<?> entityType) {
return suggest(query, entityType, getIndexCoordinatesFor(entityType));
}
@Override
public Mono<Suggest> suggest(Query query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
Assert.notNull(entityType, "entityType must not be null");
Assert.notNull(index, "index must not be null");
return doFindForResponse(query, entityType, index).mapNotNull(searchDocumentResponse -> {
Suggest suggest = searchDocumentResponse.getSuggest();
SearchHitMapping.mappingFor(entityType, converter).mapHitsInCompletionSuggestion(suggest);
return suggest;
});
}
@Override
@Deprecated
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
return doSuggest(suggestion, getIndexCoordinatesFor(entityType));
}
@Override
public Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
@Deprecated
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
return doSuggest(suggestion, index);
}
private Flux<Suggest> doSuggest(SuggestBuilder suggestion, IndexCoordinates index) {
@Deprecated
private Flux<org.elasticsearch.search.suggest.Suggest> doSuggest(SuggestBuilder suggestion, IndexCoordinates index) {
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(suggestion, index);
return Flux.from(execute(client -> client.suggest(request)));
});
}
private Flux<Aggregation> doAggregate(Query query, Class<?> entityType, IndexCoordinates index) {
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
request = prepareSearchRequest(request);
return doAggregate(request);
});
}
@Override
public Mono<Long> count(Query query, Class<?> entityType) {
return count(query, entityType, getIndexCoordinatesFor(entityType));
@@ -801,7 +871,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
return Mono.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
request = prepareSearchRequest(request);
request = prepareSearchRequest(request, false);
return doCount(request);
});
}
@@ -826,31 +896,19 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
* Customization hook on the actual execution result {@link Mono}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @param suggestEntityCreator
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
*/
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request) {
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
Function<SearchDocument, ? extends Object> suggestEntityCreator) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doFindForResponse: {}", request);
}
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(SearchDocumentResponse::from);
}
/**
* Customization hook on the actual execution result {@link Publisher}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @return a {@link Flux} emitting the result of the operation.
*/
protected Flux<Aggregation> doAggregate(SearchRequest request) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doCount: {}", request);
}
return Flux.from(execute(client -> client.aggregate(request))) //
.onErrorResume(NoSuchIndexException.class, it -> Flux.empty());
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(searchResponse -> {
return SearchDocumentResponse.from(searchResponse, suggestEntityCreator);
});
}
/**
@@ -886,19 +944,25 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. E.g. by setting the
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
*
* @param request the generated {@link SearchRequest}.
* @param useScroll
* @return never {@literal null}.
*/
protected SearchRequest prepareSearchRequest(SearchRequest request) {
protected SearchRequest prepareSearchRequest(SearchRequest request, boolean useScroll) {
if (indicesOptions == null) {
return request;
if (indicesOptions != null) {
request = request.indicesOptions(indicesOptions);
}
return request.indicesOptions(indicesOptions);
// request_cache is not allowed on scroll requests.
if (useScroll) {
request = request.requestCache(null);
}
return request;
}
// endregion
@@ -906,11 +970,41 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
// region Helper methods
protected Mono<String> getClusterVersion() {
try {
return Mono.from(execute(client -> client.info())).map(mainResponse -> mainResponse.getVersion().toString());
return Mono.from(execute(ReactiveElasticsearchClient::info))
.map(mainResponse -> mainResponse.getVersion().toString());
} catch (Exception ignored) {}
return Mono.empty();
}
/**
* @return the vendor name of the used cluster and client library
* @since 4.3
*/
protected Mono<String> getVendor() {
return Mono.just("Elasticsearch");
}
/**
* @return the version of the used client runtime library.
* @since 4.3
*/
protected Mono<String> getRuntimeLibraryVersion() {
return Mono.just(Version.CURRENT.toString());
}
@Override
public Query matchAllQuery() {
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build();
}
@Override
public Query idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.idsQuery().addIds(ids.toArray(new String[] {})))
.build();
}
// endregion
@Override
@@ -935,12 +1029,12 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
@Override
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
return new DefaultReactiveIndexOperations(this, index);
return new ReactiveIndexTemplate(this, index);
}
@Override
public ReactiveIndexOperations indexOps(Class<?> clazz) {
return new DefaultReactiveIndexOperations(this, clazz);
return new ReactiveIndexTemplate(this, clazz);
}
@Override
@@ -1089,17 +1183,19 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
T entity = reader.read(type, document);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
document.getVersion());
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallAfterConvert(entity, document, index);
}
}
protected interface SearchDocumentCallback<T> {
@NonNull
Mono<T> toEntity(@NonNull SearchDocument response);
Mono<T> toEntity(SearchDocument response);
@NonNull
Mono<SearchHit<T>> toSearchHit(@NonNull SearchDocument response);
Mono<SearchHit<T>> toSearchHit(SearchDocument response);
}
protected class ReadSearchDocumentCallback<T> implements SearchDocumentCallback<T> {
@@ -1142,7 +1238,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
private T entityAt(long index) {
// it's safe to cast to int because the original indexed colleciton was fitting in memory
// it's safe to cast to int because the original indexed collection was fitting in memory
int intIndex = (int) index;
return entities.get(intIndex);
}
@@ -49,8 +49,8 @@ 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.ReactiveMappingBuilder;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
@@ -63,9 +63,9 @@ import org.springframework.util.Assert;
* @author George Popides
* @since 4.1
*/
class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
class ReactiveIndexTemplate implements ReactiveIndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReactiveIndexOperations.class);
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveIndexTemplate.class);
@Nullable private final Class<?> boundClass;
private final IndexCoordinates boundIndex;
@@ -73,7 +73,7 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
private final ReactiveElasticsearchOperations operations;
private final ElasticsearchConverter converter;
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, IndexCoordinates index) {
public ReactiveIndexTemplate(ReactiveElasticsearchOperations operations, IndexCoordinates index) {
Assert.notNull(operations, "operations must not be null");
Assert.notNull(index, "index must not be null");
@@ -85,7 +85,7 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
this.boundIndex = index;
}
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, Class<?> clazz) {
public ReactiveIndexTemplate(ReactiveElasticsearchOperations operations, Class<?> clazz) {
Assert.notNull(operations, "operations must not be null");
Assert.notNull(clazz, "clazz must not be null");
@@ -183,11 +183,14 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
if (mappingAnnotation != null) {
return loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
String mappingPath = mappingAnnotation.mappingPath();
if (hasText(mappingPath)) {
return loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
}
}
String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz);
return Mono.just(Document.parse(mapping));
return new ReactiveMappingBuilder(converter).buildReactivePropertyMapping(clazz).map(Document::parse);
}
@Override
@@ -63,7 +63,7 @@ public abstract class ReactiveResourceUtil {
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append(line).append('\n');
}
sink.next(sb.toString());
@@ -18,14 +18,13 @@ package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
import java.util.List;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
/**
* The reactive operations for the
@@ -45,7 +44,7 @@ public interface ReactiveSearchOperations {
* @return a {@link Mono} emitting the nr of matching documents.
*/
default Mono<Long> count(Class<?> entityType) {
return count(new StringQuery(QueryBuilders.matchAllQuery().toString()), entityType);
return count(matchAllQuery(), entityType);
}
/**
@@ -185,7 +184,7 @@ public interface ReactiveSearchOperations {
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<Aggregation> aggregate(Query query, Class<?> entityType);
Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType);
/**
* Perform an aggregation specified by the given {@link Query query}. <br />
@@ -196,23 +195,73 @@ public interface ReactiveSearchOperations {
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<Aggregation> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Does a suggest query
*
* @param suggestion the query
* @param entityType must not be {@literal null}.
* @return the suggest response
* @return the suggest response (Elasticsearch library classes)
* @deprecated since 4.3, use {@link #suggest(Query, Class)}
*/
Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType);
@Deprecated
Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType);
/**
* Does a suggest query
*
* @param suggestion the query
* @param index the index to run the query against
* @return the suggest response
* @return the suggest response (Elasticsearch library classes)
* @deprecated since 4.3, use {@link #suggest(Query, Class, IndexCoordinates)}
*/
Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index);
@Deprecated
Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index);
/**
* Does a suggest query.
*
* @param query the Query containing the suggest definition. Must be currently a
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQuery}, must not be {@literal null}.
* @param entityType the type of the entities that might be returned for a completion suggestion, must not be
* {@literal null}.
* @return suggest data
* @since 4.3
*/
Mono<Suggest> suggest(Query query, Class<?> entityType);
/**
* Does a suggest query.
*
* @param query the Query containing the suggest definition. Must be currently a
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQuery}, must not be {@literal null}.
* @param entityType the type of the entities that might be returned for a completion suggestion, must not be
* {@literal null}.
* @param index the index to run the query against, must not be {@literal null}.
* @return suggest data
* @since 4.3
*/
Mono<Suggest> suggest(Query query, Class<?> entityType, IndexCoordinates index);
// region helper
/**
* Creates a {@link Query} to find all documents. Must be implemented by the concrete implementations to provide an
* appropriate query using the respective client.
*
* @return a query to find all documents
* @since 4.3
*/
Query matchAllQuery();
/**
* Creates a {@link Query} to find get all documents with given ids. Must be implemented by the concrete
* implementations to provide an appropriate query using the respective client.
*
* @param ids the list of ids must not be {@literal null}
* @return query returning the documents with the given ids
* @since 4.3
*/
Query idsQuery(List<String> ids);
// endregion
}
@@ -18,14 +18,17 @@ package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.util.CollectionUtils.*;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.admin.indices.alias.Alias;
@@ -51,6 +54,7 @@ import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
@@ -66,7 +70,7 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
@@ -78,7 +82,6 @@ import org.elasticsearch.index.reindex.UpdateByQueryAction;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
@@ -136,37 +139,6 @@ class RequestFactory {
}
// region alias
@Deprecated
public IndicesAliasesRequest.AliasActions aliasAction(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
String[] indexNames = index.getIndexNames();
IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.add()
.alias(query.getAliasName()).indices(indexNames);
if (query.getFilterBuilder() != null) {
aliasAction.filter(query.getFilterBuilder());
} else if (query.getFilter() != null) {
aliasAction.filter(query.getFilter());
}
if (!StringUtils.isEmpty(query.getRouting())) {
aliasAction.routing(query.getRouting());
}
if (!StringUtils.isEmpty(query.getSearchRouting())) {
aliasAction.searchRouting(query.getSearchRouting());
}
if (!StringUtils.isEmpty(query.getIndexRouting())) {
aliasAction.indexRouting(query.getIndexRouting());
}
return aliasAction;
}
public GetAliasesRequest getAliasesRequest(IndexCoordinates index) {
String[] indexNames = index.getIndexNames();
@@ -182,14 +154,6 @@ class RequestFactory {
return getAliasesRequest;
}
@Deprecated
public IndicesAliasesRequest indicesAddAliasesRequest(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest.AliasActions aliasAction = aliasAction(query, index);
IndicesAliasesRequest request = new IndicesAliasesRequest();
request.addAliasAction(aliasAction);
return request;
}
public IndicesAliasesRequest indicesAliasesRequest(AliasActions aliasActions) {
IndicesAliasesRequest request = new IndicesAliasesRequest();
@@ -257,27 +221,6 @@ class RequestFactory {
indicesAliasesRequest(aliasActions).getAliasActions().forEach(requestBuilder::addAliasAction);
return requestBuilder;
}
@Deprecated
public IndicesAliasesRequest indicesRemoveAliasesRequest(AliasQuery query, IndexCoordinates index) {
String[] indexNames = index.getIndexNames();
IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.remove() //
.indices(indexNames) //
.alias(query.getAliasName());
return Requests.indexAliasesRequest() //
.addAliasAction(aliasAction);
}
@Deprecated
IndicesAliasesRequestBuilder indicesRemoveAliasesRequestBuilder(Client client, AliasQuery query,
IndexCoordinates index) {
String indexName = index.getIndexName();
return client.admin().indices().prepareAliases().removeAlias(indexName, query.getAliasName());
}
// endregion
// region bulk
@@ -285,15 +228,15 @@ class RequestFactory {
BulkRequest bulkRequest = new BulkRequest();
if (bulkOptions.getTimeout() != null) {
bulkRequest.timeout(bulkOptions.getTimeout());
bulkRequest.timeout(TimeValue.timeValueMillis(bulkOptions.getTimeout().toMillis()));
}
if (bulkOptions.getRefreshPolicy() != null) {
bulkRequest.setRefreshPolicy(bulkOptions.getRefreshPolicy());
bulkRequest.setRefreshPolicy(toElasticsearchRefreshPolicy(bulkOptions.getRefreshPolicy()));
}
if (bulkOptions.getWaitForActiveShards() != null) {
bulkRequest.waitForActiveShards(bulkOptions.getWaitForActiveShards());
bulkRequest.waitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().getValue()));
}
if (bulkOptions.getPipeline() != null) {
@@ -320,15 +263,15 @@ class RequestFactory {
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
if (bulkOptions.getTimeout() != null) {
bulkRequestBuilder.setTimeout(bulkOptions.getTimeout());
bulkRequestBuilder.setTimeout(TimeValue.timeValueMillis(bulkOptions.getTimeout().toMillis()));
}
if (bulkOptions.getRefreshPolicy() != null) {
bulkRequestBuilder.setRefreshPolicy(bulkOptions.getRefreshPolicy());
bulkRequestBuilder.setRefreshPolicy(toElasticsearchRefreshPolicy(bulkOptions.getRefreshPolicy()));
}
if (bulkOptions.getWaitForActiveShards() != null) {
bulkRequestBuilder.setWaitForActiveShards(bulkOptions.getWaitForActiveShards());
bulkRequestBuilder.setWaitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().getValue()));
}
if (bulkOptions.getPipeline() != null) {
@@ -355,7 +298,8 @@ class RequestFactory {
// region index management
public CreateIndexRequest createIndexRequest(IndexCoordinates index, Map<String, Object> settings, @Nullable Document mapping) {
public CreateIndexRequest createIndexRequest(IndexCoordinates index, Map<String, Object> settings,
@Nullable Document mapping) {
Assert.notNull(index, "index must not be null");
Assert.notNull(settings, "settings must not be null");
@@ -733,15 +677,16 @@ class RequestFactory {
FetchSourceContext fetchSourceContext = getFetchSourceContext(searchQuery);
if (!isEmpty(searchQuery.getIds())) {
if (!isEmpty(searchQuery.getIdsWithRouting())) {
String indexName = index.getIndexName();
for (String id : searchQuery.getIds()) {
MultiGetRequest.Item item = new MultiGetRequest.Item(indexName, id);
if (searchQuery.getRoute() != null) {
item = item.routing(searchQuery.getRoute());
for (Query.IdWithRouting idWithRouting : searchQuery.getIdsWithRouting()) {
MultiGetRequest.Item item = new MultiGetRequest.Item(indexName, idWithRouting.getId());
if (idWithRouting.getRouting() != null) {
item = item.routing(idWithRouting.getRouting());
}
// note: multiGet does not have fields, need to set sourceContext to filter
if (fetchSourceContext != null) {
item.fetchSourceContext(fetchSourceContext);
}
@@ -760,15 +705,17 @@ class RequestFactory {
String indexName = index.getIndexName();
IndexRequest indexRequest;
if (query.getObject() != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId();
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
// If we have a query id and a document id, do not ask ES to generate one.
if (id != null) {
indexRequest = new IndexRequest(indexName).id(id);
} else {
indexRequest = new IndexRequest(indexName);
}
indexRequest.source(elasticsearchConverter.mapObject(query.getObject()).toJson(), Requests.INDEX_CONTENT_TYPE);
indexRequest.source(elasticsearchConverter.mapObject(queryObject).toJson(), Requests.INDEX_CONTENT_TYPE);
} else if (query.getSource() != null) {
indexRequest = new IndexRequest(indexName).id(query.getId()).source(query.getSource(),
Requests.INDEX_CONTENT_TYPE);
@@ -779,7 +726,8 @@ class RequestFactory {
if (query.getVersion() != null) {
indexRequest.version(query.getVersion());
VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass());
VersionType versionType = retrieveVersionTypeFromPersistentEntity(
queryObject != null ? queryObject.getClass() : null);
indexRequest.versionType(versionType);
}
@@ -814,15 +762,16 @@ class RequestFactory {
IndexRequestBuilder indexRequestBuilder;
if (query.getObject() != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId();
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
// If we have a query id and a document id, do not ask ES to generate one.
if (id != null) {
indexRequestBuilder = client.prepareIndex(indexName, type, id);
} else {
indexRequestBuilder = client.prepareIndex(indexName, type);
}
indexRequestBuilder.setSource(elasticsearchConverter.mapObject(query.getObject()).toJson(),
indexRequestBuilder.setSource(elasticsearchConverter.mapObject(queryObject).toJson(),
Requests.INDEX_CONTENT_TYPE);
} else if (query.getSource() != null) {
indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(query.getSource(),
@@ -834,7 +783,8 @@ class RequestFactory {
if (query.getVersion() != null) {
indexRequestBuilder.setVersion(query.getVersion());
VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass());
VersionType versionType = retrieveVersionTypeFromPersistentEntity(
queryObject != null ? queryObject.getClass() : null);
indexRequestBuilder.setVersionType(versionType);
}
@@ -857,14 +807,18 @@ class RequestFactory {
// region search
@Nullable
public HighlightBuilder highlightBuilder(Query query) {
HighlightBuilder highlightBuilder = query.getHighlightQuery().map(HighlightQuery::getHighlightBuilder).orElse(null);
HighlightBuilder highlightBuilder = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlightBuilder(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
if (highlightBuilder == null) {
if (query instanceof NativeSearchQuery) {
NativeSearchQuery searchQuery = (NativeSearchQuery) query;
if (searchQuery.getHighlightFields() != null || searchQuery.getHighlightBuilder() != null) {
if ((searchQuery.getHighlightFields() != null && searchQuery.getHighlightFields().length > 0)
|| searchQuery.getHighlightBuilder() != null) {
highlightBuilder = searchQuery.getHighlightBuilder();
if (highlightBuilder == null) {
@@ -990,11 +944,6 @@ class RequestFactory {
sourceBuilder.seqNoAndPrimaryTerm(true);
}
if (query.getSourceFilter() != null) {
SourceFilter sourceFilter = query.getSourceFilter();
sourceBuilder.fetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (query.getPageable().isPaged()) {
sourceBuilder.from((int) query.getPageable().getOffset());
sourceBuilder.size(query.getPageable().getPageSize());
@@ -1003,12 +952,18 @@ class RequestFactory {
sourceBuilder.size(INDEX_MAX_RESULT_WINDOW);
}
if (query.getSourceFilter() != null) {
sourceBuilder.fetchSource(getFetchSourceContext(query));
SourceFilter sourceFilter = query.getSourceFilter();
sourceBuilder.fetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (!query.getFields().isEmpty()) {
sourceBuilder.fetchSource(query.getFields().toArray(new String[0]), null);
query.getFields().forEach(sourceBuilder::fetchField);
}
if (query.getIndicesOptions() != null) {
request.indicesOptions(query.getIndicesOptions());
request.indicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions()));
}
if (query.isLimiting()) {
@@ -1024,7 +979,7 @@ class RequestFactory {
request.preference(query.getPreference());
}
request.searchType(query.getSearchType());
request.searchType(SearchType.fromString(query.getSearchType().name().toLowerCase()));
prepareSort(query, sourceBuilder, getPersistentEntity(clazz));
@@ -1048,9 +1003,9 @@ class RequestFactory {
request.routing(query.getRoute());
}
TimeValue timeout = query.getTimeout();
Duration timeout = query.getTimeout();
if (timeout != null) {
sourceBuilder.timeout(timeout);
sourceBuilder.timeout(new TimeValue(timeout.toMillis()));
}
sourceBuilder.explain(query.getExplain());
@@ -1061,7 +1016,25 @@ class RequestFactory {
query.getRescorerQueries().forEach(rescorer -> sourceBuilder.addRescorer(getQueryRescorerBuilder(rescorer)));
if (query.getRequestCache() != null) {
request.requestCache(query.getRequestCache());
}
if (!query.getRuntimeFields().isEmpty()) {
Map<String, Object> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
runtimeMappings.put(runtimeField.getName(), runtimeField.getMapping());
});
sourceBuilder.runtimeMappings(runtimeMappings);
}
if (query.getScrollTime() != null) {
request.scroll(TimeValue.timeValueMillis(query.getScrollTime().toMillis()));
}
request.source(sourceBuilder);
return request;
}
@@ -1073,18 +1046,13 @@ class RequestFactory {
Assert.notEmpty(indexNames, "No index defined for Query");
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(indexNames) //
.setSearchType(query.getSearchType()) //
.setSearchType(SearchType.fromString(query.getSearchType().name().toLowerCase())) //
.setVersion(true) //
.setTrackScores(query.getTrackScores());
if (hasSeqNoPrimaryTermProperty(clazz)) {
searchRequestBuilder.seqNoAndPrimaryTerm(true);
}
if (query.getSourceFilter() != null) {
SourceFilter sourceFilter = query.getSourceFilter();
searchRequestBuilder.setFetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (query.getPageable().isPaged()) {
searchRequestBuilder.setFrom((int) query.getPageable().getOffset());
searchRequestBuilder.setSize(query.getPageable().getPageSize());
@@ -1093,12 +1061,17 @@ class RequestFactory {
searchRequestBuilder.setSize(INDEX_MAX_RESULT_WINDOW);
}
if (query.getSourceFilter() != null) {
SourceFilter sourceFilter = query.getSourceFilter();
searchRequestBuilder.setFetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (!query.getFields().isEmpty()) {
searchRequestBuilder.setFetchSource(query.getFields().toArray(new String[0]), null);
query.getFields().forEach(searchRequestBuilder::addFetchField);
}
if (query.getIndicesOptions() != null) {
searchRequestBuilder.setIndicesOptions(query.getIndicesOptions());
searchRequestBuilder.setIndicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions()));
}
if (query.isLimiting()) {
@@ -1136,9 +1109,9 @@ class RequestFactory {
searchRequestBuilder.setRouting(query.getRoute());
}
TimeValue timeout = query.getTimeout();
Duration timeout = query.getTimeout();
if (timeout != null) {
searchRequestBuilder.setTimeout(timeout);
searchRequestBuilder.setTimeout(new TimeValue(timeout.toMillis()));
}
searchRequestBuilder.setExplain(query.getExplain());
@@ -1149,6 +1122,23 @@ class RequestFactory {
query.getRescorerQueries().forEach(rescorer -> searchRequestBuilder.addRescorer(getQueryRescorerBuilder(rescorer)));
if (query.getRequestCache() != null) {
searchRequestBuilder.setRequestCache(query.getRequestCache());
}
if (!query.getRuntimeFields().isEmpty()) {
Map<String, Object> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
runtimeMappings.put(runtimeField.getName(), runtimeField.getMapping());
});
searchRequestBuilder.setRuntimeMappings(runtimeMappings);
}
if (query.getScrollTime() != null) {
searchRequestBuilder.setScroll(TimeValue.timeValueMillis(query.getScrollTime().toMillis()));
}
return searchRequestBuilder;
}
@@ -1171,11 +1161,16 @@ class RequestFactory {
}
if (!isEmpty(query.getAggregations())) {
for (AbstractAggregationBuilder<?> aggregationBuilder : query.getAggregations()) {
sourceBuilder.aggregation(aggregationBuilder);
}
query.getAggregations().forEach(sourceBuilder::aggregation);
}
if (!isEmpty(query.getPipelineAggregations())) {
query.getPipelineAggregations().forEach(sourceBuilder::aggregation);
}
if (query.getSuggestBuilder() != null) {
sourceBuilder.suggest(query.getSuggestBuilder());
}
}
private void prepareNativeSearch(SearchRequestBuilder searchRequestBuilder, NativeSearchQuery nativeSearchQuery) {
@@ -1196,9 +1191,15 @@ class RequestFactory {
}
if (!isEmpty(nativeSearchQuery.getAggregations())) {
for (AbstractAggregationBuilder<?> aggregationBuilder : nativeSearchQuery.getAggregations()) {
searchRequestBuilder.addAggregation(aggregationBuilder);
}
nativeSearchQuery.getAggregations().forEach(searchRequestBuilder::addAggregation);
}
if (!isEmpty(nativeSearchQuery.getPipelineAggregations())) {
nativeSearchQuery.getPipelineAggregations().forEach(searchRequestBuilder::addAggregation);
}
if (nativeSearchQuery.getSuggestBuilder() != null) {
searchRequestBuilder.suggest(nativeSearchQuery.getSuggestBuilder());
}
}
@@ -1238,6 +1239,15 @@ class RequestFactory {
private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> entity) {
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
Order.Mode mode = Order.DEFAULT_MODE;
String unmappedType = null;
if (order instanceof Order) {
Order o = (Order) order;
mode = o.getMode();
unmappedType = o.getUnmappedType();
}
if (ScoreSortBuilder.NAME.equals(order.getProperty())) {
return SortBuilders //
.scoreSort() //
@@ -1255,14 +1265,23 @@ class RequestFactory {
geoDistanceOrder.getGeoPoint().getLon());
sort.geoDistance(GeoDistance.fromString(geoDistanceOrder.getDistanceType().name()));
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
sort.sortMode(SortMode.fromString(geoDistanceOrder.getMode().name()));
sort.sortMode(SortMode.fromString(mode.name()));
sort.unit(DistanceUnit.fromString(geoDistanceOrder.getUnit()));
if (geoDistanceOrder.getIgnoreUnmapped() != GeoDistanceOrder.DEFAULT_IGNORE_UNMAPPED) {
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
}
return sort;
} else {
FieldSortBuilder sort = SortBuilders //
.fieldSort(fieldName) //
.order(sortOrder);
.order(sortOrder) //
.sortMode(SortMode.fromString(mode.name()));
if (unmappedType != null) {
sort.unmappedType(unmappedType);
}
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
sort.missing("_first");
@@ -1276,10 +1295,10 @@ class RequestFactory {
private QueryRescorerBuilder getQueryRescorerBuilder(RescorerQuery rescorerQuery) {
QueryBuilder queryBuilder = getQuery(rescorerQuery.getQuery());
Assert.notNull("queryBuilder", "Could not build query for rescorerQuery");
QueryBuilder queryBuilder = getQuery(rescorerQuery.getQuery());
Assert.notNull("queryBuilder", "Could not build query for rescorerQuery");
QueryRescorerBuilder builder = new QueryRescorerBuilder(queryBuilder);
QueryRescorerBuilder builder = new QueryRescorerBuilder(queryBuilder);
if (rescorerQuery.getScoreMode() != ScoreMode.Default) {
builder.setScoreMode(QueryRescoreMode.valueOf(rescorerQuery.getScoreMode().name()));
@@ -1471,7 +1490,7 @@ class RequestFactory {
updateByQueryRequest.setQuery(getQuery(queryQuery));
if (queryQuery.getIndicesOptions() != null) {
updateByQueryRequest.setIndicesOptions(queryQuery.getIndicesOptions());
updateByQueryRequest.setIndicesOptions(toElasticsearchIndicesOptions(queryQuery.getIndicesOptions()));
}
if (queryQuery.getScrollTime() != null) {
@@ -1546,7 +1565,8 @@ class RequestFactory {
updateByQueryRequestBuilder.filter(getQuery(queryQuery));
if (queryQuery.getIndicesOptions() != null) {
updateByQueryRequestBuilder.source().setIndicesOptions(queryQuery.getIndicesOptions());
updateByQueryRequestBuilder.source()
.setIndicesOptions(toElasticsearchIndicesOptions(queryQuery.getIndicesOptions()));
}
if (queryQuery.getScrollTime() != null) {
@@ -1648,26 +1668,33 @@ class RequestFactory {
}
}
@Nullable
private FetchSourceContext getFetchSourceContext(Query searchQuery) {
FetchSourceContext fetchSourceContext = null;
SourceFilter sourceFilter = searchQuery.getSourceFilter();
if (!isEmpty(searchQuery.getFields())) {
if (sourceFilter == null) {
sourceFilter = new FetchSourceFilter(toArray(searchQuery.getFields()), null);
} else {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, sourceFilter.getIncludes());
sourceFilter = new FetchSourceFilter(toArray(arrayList), null);
}
fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
} else if (sourceFilter != null) {
fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
if (sourceFilter != null) {
return new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
return fetchSourceContext;
return null;
}
public org.elasticsearch.action.support.IndicesOptions toElasticsearchIndicesOptions(IndicesOptions indicesOptions) {
Assert.notNull(indicesOptions, "indicesOptions must not be null");
Set<org.elasticsearch.action.support.IndicesOptions.Option> options = indicesOptions.getOptions().stream()
.map(it -> org.elasticsearch.action.support.IndicesOptions.Option.valueOf(it.name().toUpperCase()))
.collect(Collectors.toSet());
Set<org.elasticsearch.action.support.IndicesOptions.WildcardStates> wildcardStates = indicesOptions
.getExpandWildcards().stream()
.map(it -> org.elasticsearch.action.support.IndicesOptions.WildcardStates.valueOf(it.name().toUpperCase()))
.collect(Collectors.toSet());
return new org.elasticsearch.action.support.IndicesOptions(EnumSet.copyOf(options), EnumSet.copyOf(wildcardStates));
}
// endregion
@Nullable
@@ -1695,17 +1722,23 @@ class RequestFactory {
return null;
}
private VersionType retrieveVersionTypeFromPersistentEntity(Class<?> clazz) {
private VersionType retrieveVersionTypeFromPersistentEntity(@Nullable Class<?> clazz) {
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext = elasticsearchConverter
.getMappingContext();
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(clazz);
ElasticsearchPersistentEntity<?> persistentEntity = clazz != null ? mappingContext.getPersistentEntity(clazz)
: null;
VersionType versionType = null;
if (persistentEntity != null) {
versionType = persistentEntity.getVersionType();
org.springframework.data.elasticsearch.annotations.Document.VersionType entityVersionType = persistentEntity
.getVersionType();
if (entityVersionType != null) {
versionType = VersionType.fromString(entityVersionType.name().toLowerCase());
}
}
return versionType != null ? versionType : VersionType.EXTERNAL;
@@ -28,6 +28,7 @@ import java.util.stream.Collectors;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.client.indices.GetIndexResponse;
@@ -37,11 +38,14 @@ import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.ScrollableHitSource;
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -313,4 +317,71 @@ public class ResponseConverter {
}
// endregion
// region byQueryResponse
public static ByQueryResponse byQueryResponseOf(BulkByScrollResponse bulkByScrollResponse) {
final List<ByQueryResponse.Failure> failures = bulkByScrollResponse.getBulkFailures() //
.stream() //
.map(ResponseConverter::byQueryResponseFailureOf) //
.collect(Collectors.toList()); //
final List<ByQueryResponse.SearchFailure> searchFailures = bulkByScrollResponse.getSearchFailures() //
.stream() //
.map(ResponseConverter::byQueryResponseSearchFailureOf) //
.collect(Collectors.toList());//
return ByQueryResponse.builder() //
.withTook(bulkByScrollResponse.getTook().getMillis()) //
.withTimedOut(bulkByScrollResponse.isTimedOut()) //
.withTotal(bulkByScrollResponse.getTotal()) //
.withUpdated(bulkByScrollResponse.getUpdated()) //
.withDeleted(bulkByScrollResponse.getDeleted()) //
.withBatches(bulkByScrollResponse.getBatches()) //
.withVersionConflicts(bulkByScrollResponse.getVersionConflicts()) //
.withNoops(bulkByScrollResponse.getNoops()) //
.withBulkRetries(bulkByScrollResponse.getBulkRetries()) //
.withSearchRetries(bulkByScrollResponse.getSearchRetries()) //
.withReasonCancelled(bulkByScrollResponse.getReasonCancelled()) //
.withFailures(failures) //
.withSearchFailure(searchFailures) //
.build(); //
}
/**
* Create a new {@link ByQueryResponse.Failure} from {@link BulkItemResponse.Failure}
*
* @param failure {@link BulkItemResponse.Failure} to translate
* @return a new {@link ByQueryResponse.Failure}
*/
public static ByQueryResponse.Failure byQueryResponseFailureOf(BulkItemResponse.Failure failure) {
return ByQueryResponse.Failure.builder() //
.withIndex(failure.getIndex()) //
.withType(failure.getType()) //
.withId(failure.getId()) //
.withStatus(failure.getStatus().getStatus()) //
.withAborted(failure.isAborted()) //
.withCause(failure.getCause()) //
.withSeqNo(failure.getSeqNo()) //
.withTerm(failure.getTerm()) //
.build(); //
}
/**
* Create a new {@link ByQueryResponse.SearchFailure} from {@link ScrollableHitSource.SearchFailure}
*
* @param searchFailure {@link ScrollableHitSource.SearchFailure} to translate
* @return a new {@link ByQueryResponse.SearchFailure}
*/
public static ByQueryResponse.SearchFailure byQueryResponseSearchFailureOf(
ScrollableHitSource.SearchFailure searchFailure) {
return ByQueryResponse.SearchFailure.builder() //
.withReason(searchFailure.getReason()) //
.withIndex(searchFailure.getIndex()) //
.withNodeId(searchFailure.getNodeId()) //
.withShardId(searchFailure.getShardId()) //
.withStatus(searchFailure.getStatus().getStatus()) //
.build(); //
}
// endregion
}
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -28,7 +27,6 @@ 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.RequestOptions;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
@@ -39,7 +37,6 @@ 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;
@@ -53,7 +50,6 @@ 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;
@@ -65,18 +61,18 @@ import org.springframework.util.Assert;
* @author George Popides
* @since 4.0
*/
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIndexOperations.class);
private static final Logger LOGGER = LoggerFactory.getLogger(RestIndexTemplate.class);
private final ElasticsearchRestTemplate restTemplate;
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
super(restTemplate.getElasticsearchConverter(), boundClass);
this.restTemplate = restTemplate;
}
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
super(restTemplate.getElasticsearchConverter(), boundIndex);
this.restTemplate = restTemplate;
}
@@ -141,40 +137,6 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
});
}
@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");
Assert.notNull(query.getAliasName(), "No alias defined");
IndicesAliasesRequest indicesAliasesRequest = requestFactory.indicesRemoveAliasesRequest(query, index);
return restTemplate.execute(
client -> client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected List<AliasMetadata> doQueryForAlias(IndexCoordinates index) {
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
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));
});
}
@Override
protected Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
@@ -0,0 +1,60 @@
/*
* 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.core;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* Defines a runtime field to be added to a Query
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class RuntimeField {
private final String name;
private final String type;
private final String script;
public RuntimeField(String name, String type, String script) {
Assert.notNull(name, "name must not be null");
Assert.notNull(type, "type must not be null");
Assert.notNull(script, "script must not be null");
this.name = name;
this.type = type;
this.script = script;
}
public String getName() {
return name;
}
/**
* @return the mapping as a Map like it is needed for the Elasticsearch client
*/
public Map<String, Object> getMapping() {
Map<String, Object> map = new HashMap<>();
map.put("type", type);
map.put("script", script);
return map;
}
}
@@ -22,9 +22,9 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.search.aggregations.Aggregations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
@@ -32,6 +32,8 @@ import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -44,6 +46,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Roman Puchkovskiy
* @author Matt Gilene
* @author Sascha Woo
* @since 4.0
*/
class SearchHitMapping<T> {
@@ -95,10 +98,30 @@ class SearchHitMapping<T> {
SearchHit<T> hit = mapHit(document, content);
searchHits.add(hit);
}
Aggregations aggregations = searchDocumentResponse.getAggregations();
AggregationsContainer<?> aggregations = searchDocumentResponse.getAggregations();
TotalHitsRelation totalHitsRelation = TotalHitsRelation.valueOf(searchDocumentResponse.getTotalHitsRelation());
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations);
Suggest suggest = searchDocumentResponse.getSuggest();
mapHitsInCompletionSuggestion(suggest);
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations, suggest);
}
@SuppressWarnings("unchecked")
public void mapHitsInCompletionSuggestion(@Nullable Suggest suggest) {
if (suggest != null) {
for (Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion : suggest
.getSuggestions()) {
if (suggestion instanceof CompletionSuggestion) {
CompletionSuggestion<T> completionSuggestion = (CompletionSuggestion<T>) suggestion;
for (CompletionSuggestion.Entry<T> entry : completionSuggestion.getEntries()) {
for (CompletionSuggestion.Entry.Option<T> option : entry.getOptions()) {
option.updateSearchHit(this::mapHit);
}
}
}
}
}
}
SearchHit<T> mapHit(SearchDocument searchDocument, T content) {
@@ -173,7 +196,7 @@ class SearchHitMapping<T> {
*/
private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, Class<T> type) {
if (searchHits.getTotalHits() == 0) {
if (searchHits.isEmpty()) {
return searchHits;
}
@@ -214,10 +237,11 @@ class SearchHitMapping<T> {
searchHits.getMaxScore(), //
scrollId, //
convertedSearchHits, //
searchHits.getAggregations());
searchHits.getAggregations(), //
searchHits.getSuggest());
}
} catch (Exception e) {
LOGGER.warn("Could not map inner_hits", e);
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
}
return searchHits;
@@ -225,7 +249,7 @@ class SearchHitMapping<T> {
/**
* find a {@link ElasticsearchPersistentEntity} following the property chain defined by the nested metadata
*
*
* @param persistentEntity base entity
* @param nestedMetaData nested metadata
* @return A {@link ElasticsearchPersistentEntityWithNestedMetaData} containing the found entity or null together with
@@ -18,13 +18,13 @@ package org.springframework.data.elasticsearch.core;
import java.util.Iterator;
import java.util.List;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
/**
* Encapsulates a list of {@link SearchHit}s with additional information from the search.
*
*
* @param <T> the result data class.
* @author Sascha Woo
* @since 4.0
@@ -35,7 +35,7 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
* @return the aggregations.
*/
@Nullable
Aggregations getAggregations();
AggregationsContainer<?> getAggregations();
/**
* @return the maximum score
@@ -78,6 +78,21 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
return !getSearchHits().isEmpty();
}
/**
* @return the suggest response
* @since 4.3
*/
@Nullable
Suggest getSuggest();
/**
* @return wether the {@link SearchHits} has a suggest response.
* @since 4.3
*/
default boolean hasSuggest() {
return getSuggest() != null;
}
/**
* @return an iterator for {@link SearchHit}
*/
@@ -18,7 +18,7 @@ package org.springframework.data.elasticsearch.core;
import java.util.Collections;
import java.util.List;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -39,7 +39,8 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
@Nullable private final String scrollId;
private final List<? extends SearchHit<T>> searchHits;
private final Lazy<List<SearchHit<T>>> unmodifiableSearchHits;
@Nullable private final Aggregations aggregations;
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
/**
* @param totalHits the number of total hits for the search
@@ -50,7 +51,8 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
* @param aggregations the aggregations if available
*/
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
List<? extends SearchHit<T>> searchHits, @Nullable Aggregations aggregations) {
List<? extends SearchHit<T>> searchHits, @Nullable AggregationsContainer<?> aggregations,
@Nullable Suggest suggest) {
Assert.notNull(searchHits, "searchHits must not be null");
@@ -60,6 +62,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
this.scrollId = scrollId;
this.searchHits = searchHits;
this.aggregations = aggregations;
this.suggest = suggest;
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
}
@@ -89,14 +92,23 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
public List<SearchHit<T>> getSearchHits() {
return unmodifiableSearchHits.get();
}
// endregion
// region SearchHit access
@Override
public SearchHit<T> getSearchHit(int index) {
return searchHits.get(index);
}
// endregion
@Override
@Nullable
public AggregationsContainer<?> getAggregations() {
return aggregations;
}
@Override
@Nullable
public Suggest getSuggest() {
return suggest;
}
@Override
public String toString() {
@@ -109,12 +121,4 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
", aggregations=" + aggregations + //
'}';
}
// region aggregations
@Override
@Nullable
public Aggregations getAggregations() {
return aggregations;
}
// endregion
}
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.util.CloseableIterator;
import org.springframework.lang.Nullable;
@@ -33,7 +32,7 @@ public interface SearchHitsIterator<T> extends CloseableIterator<SearchHit<T>> {
* @return the aggregations.
*/
@Nullable
Aggregations getAggregations();
AggregationsContainer<?> getAggregations();
/**
* @return the maximum score
@@ -71,7 +71,11 @@ public interface SearchOperations {
* @param clazz the entity class
* @return the suggest response
* @since 4.1
* @deprecated since 4.3 use a {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder} with
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder#withSuggestBuilder(SuggestBuilder)},
* call {@link #search(Query, Class)} and get the suggest from {@link SearchHits#getSuggest()}
*/
@Deprecated
SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz);
/**
@@ -80,7 +84,11 @@ public interface SearchOperations {
* @param suggestion the query
* @param index the index to run the query against
* @return the suggest response
* @deprecated since 4.3 use a {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder} with
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder#withSuggestBuilder(SuggestBuilder)},
* call {@link #search(Query, Class)} and get the suggest from {@link SearchHits#getSuggest()}
*/
@Deprecated
SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index);
/**
@@ -220,4 +228,23 @@ public interface SearchOperations {
* are completed.
*/
<T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index);
/**
* Creates a {@link Query} to get all documents. Must be implemented by the concrete implementations to provide an
* appropriate query using the respective client.
*
* @return a query to find all documents
* @since 4.3
*/
Query matchAllQuery();
/**
* Creates a {@link Query} to find get all documents with given ids. Must be implemented by the concrete
* implementations to provide an appropriate query using the respective client.
*
* @param ids the list of ids must not be {@literal null}
* @return query returning the documents with the given ids
* @since 4.3
*/
Query idsQuery(List<String> ids);
}
@@ -22,7 +22,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -55,7 +54,7 @@ abstract class StreamQueries {
Assert.notNull(continueScrollFunction, "continueScrollFunction must not be null.");
Assert.notNull(clearScrollConsumer, "clearScrollConsumer must not be null.");
Aggregations aggregations = searchHits.getAggregations();
AggregationsContainer<?> aggregations = searchHits.getAggregations();
float maxScore = searchHits.getMaxScore();
long totalHits = searchHits.getTotalHits();
TotalHitsRelation totalHitsRelation = searchHits.getTotalHitsRelation();
@@ -78,7 +77,7 @@ abstract class StreamQueries {
@Override
@Nullable
public Aggregations getAggregations() {
public AggregationsContainer<?> getAggregations() {
return aggregations;
}
@@ -23,7 +23,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
@@ -59,7 +58,6 @@ 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;
@@ -71,19 +69,18 @@ import org.springframework.util.Assert;
* @author George Popides
* @since 4.0
*/
class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
class TransportIndexTemplate extends AbstractIndexTemplate implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTransportIndexOperations.class);
private static final Logger LOGGER = LoggerFactory.getLogger(TransportIndexTemplate.class);
private final Client client;
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
Class<?> boundClass) {
public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
super(elasticsearchConverter, boundClass);
this.client = client;
}
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter,
IndexCoordinates boundIndex) {
super(elasticsearchConverter, boundIndex);
this.client = client;
@@ -146,31 +143,6 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp
return mappings.iterator().next().value.get(IndexCoordinates.TYPE).getSourceAsMap();
}
@Override
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest.AliasActions aliasAction = requestFactory.aliasAction(query, index);
return client.admin().indices().prepareAliases().addAliasAction(aliasAction).execute().actionGet().isAcknowledged();
}
@Override
@Deprecated
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
IndicesAliasesRequestBuilder indicesAliasesRequestBuilder = requestFactory
.indicesRemoveAliasesRequestBuilder(client, query, index);
return indicesAliasesRequestBuilder.execute().actionGet().isAcknowledged();
}
@Override
protected List<AliasMetadata> doQueryForAlias(IndexCoordinates index) {
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
return client.admin().indices().getAliases(getAliasesRequest).actionGet().getAliases().get(index.getIndexName());
}
@Override
protected Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
@@ -0,0 +1,41 @@
/*
* 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.core.clients.elasticsearch7;
import org.elasticsearch.search.aggregations.Aggregation;
import org.springframework.data.elasticsearch.core.AggregationContainer;
import org.springframework.lang.NonNull;
/**
* AggregationContainer implementation for an Elasticsearch7 aggregation.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class ElasticsearchAggregation implements AggregationContainer<Aggregation> {
private final Aggregation aggregation;
public ElasticsearchAggregation(Aggregation aggregation) {
this.aggregation = aggregation;
}
@NonNull
@Override
public Aggregation aggregation() {
return aggregation;
}
}
@@ -0,0 +1,41 @@
/*
* 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.core.clients.elasticsearch7;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.lang.NonNull;
/**
* AggregationsContainer implementation for the Elasticsearch7 aggregations.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class ElasticsearchAggregations implements AggregationsContainer<Aggregations> {
private final Aggregations aggregations;
public ElasticsearchAggregations(Aggregations aggregations) {
this.aggregations = aggregations;
}
@NonNull
@Override
public Aggregations aggregations() {
return aggregations;
}
}
@@ -0,0 +1,6 @@
/**
* Classes and interfaces used by the code that uses Elasticsearch 7 client libraries
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.core.clients.elasticsearch7;
@@ -0,0 +1,40 @@
/*
* 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.core.convert;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
/**
* @author Sascha Woo
* @since 4.3
*/
public abstract class AbstractPropertyValueConverter implements PropertyValueConverter {
private final PersistentProperty<?> property;
public AbstractPropertyValueConverter(PersistentProperty<?> property) {
Assert.notNull(property, "property must not be null.");
this.property = property;
}
protected PersistentProperty<?> getProperty() {
return property;
}
}
@@ -0,0 +1,124 @@
/*
* 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.core.convert;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.data.elasticsearch.core.Range;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
/**
* @author Sascha Woo
* @since 4.3
*/
public abstract class AbstractRangePropertyValueConverter<T> extends AbstractPropertyValueConverter {
protected static final String LT_FIELD = "lt";
protected static final String LTE_FIELD = "lte";
protected static final String GT_FIELD = "gt";
protected static final String GTE_FIELD = "gte";
public AbstractRangePropertyValueConverter(PersistentProperty<?> property) {
super(property);
}
@Override
public Object read(Object value) {
Assert.notNull(value, "value must not be null.");
Assert.isInstanceOf(Map.class, value, "value must be instance of Map.");
try {
Map<String, Object> source = (Map<String, Object>) value;
Range.Bound<T> lowerBound;
Range.Bound<T> upperBound;
if (source.containsKey(GTE_FIELD)) {
lowerBound = Range.Bound.inclusive(parse((String) source.get(GTE_FIELD)));
} else if (source.containsKey(GT_FIELD)) {
lowerBound = Range.Bound.exclusive(parse((String) source.get(GT_FIELD)));
} else {
lowerBound = Range.Bound.unbounded();
}
if (source.containsKey(LTE_FIELD)) {
upperBound = Range.Bound.inclusive(parse((String) source.get(LTE_FIELD)));
} else if (source.containsKey(LT_FIELD)) {
upperBound = Range.Bound.exclusive(parse((String) source.get(LT_FIELD)));
} else {
upperBound = Range.Bound.unbounded();
}
return Range.of(lowerBound, upperBound);
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
@Override
public Object write(Object value) {
Assert.notNull(value, "value must not be null.");
if (!Range.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
Range<T> range = (Range<T>) value;
Range.Bound<T> lowerBound = range.getLowerBound();
Range.Bound<T> upperBound = range.getUpperBound();
Map<String, Object> target = new LinkedHashMap<>();
if (lowerBound.isBounded()) {
String lowerBoundValue = format(lowerBound.getValue().get());
if (lowerBound.isInclusive()) {
target.put(GTE_FIELD, lowerBoundValue);
} else {
target.put(GT_FIELD, lowerBoundValue);
}
}
if (upperBound.isBounded()) {
String upperBoundValue = format(upperBound.getValue().get());
if (upperBound.isInclusive()) {
target.put(LTE_FIELD, upperBoundValue);
} else {
target.put(LT_FIELD, upperBoundValue);
}
}
return target;
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
protected abstract String format(T value);
protected Class<?> getGenericType() {
return getProperty().getTypeInformation().getTypeArguments().get(0).getType();
}
protected abstract T parse(String value);
}
@@ -0,0 +1,73 @@
/*
* 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.core.convert;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class DatePropertyValueConverter extends AbstractPropertyValueConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(DatePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public DatePropertyValueConverter(PersistentProperty<?> property, List<ElasticsearchDateConverter> dateConverters) {
super(property);
this.dateConverters = dateConverters;
}
@Override
public Object read(Object value) {
String s = value.toString();
for (ElasticsearchDateConverter dateConverter : dateConverters) {
try {
return dateConverter.parse(s);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s,
getProperty().getActualType().getTypeName(), getProperty().getName()));
}
@Override
public Object write(Object value) {
if (!Date.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((Date) value);
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
}
@@ -0,0 +1,62 @@
/*
* 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.core.convert;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class DateRangePropertyValueConverter extends AbstractRangePropertyValueConverter<Date> {
private static final Logger LOGGER = LoggerFactory.getLogger(DateRangePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public DateRangePropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);
this.dateConverters = dateConverters;
}
@Override
protected String format(Date value) {
return dateConverters.get(0).format(value);
}
@Override
protected Date parse(String value) {
for (ElasticsearchDateConverter converters : dateConverters) {
try {
return converters.parse(value);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
getGenericType().getTypeName(), getProperty().getName()));
}
}
@@ -1,88 +0,0 @@
/*
* Copyright 2013-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.convert;
import java.util.Date;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.joda.time.ReadableInstant;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.core.convert.converter.Converter;
/**
* DateTimeConverters
*
* @author Rizwan Idrees
* @author Mohsin Husen
*/
public final class DateTimeConverters {
private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC);
/**
* @deprecated since 4.1
*/
@Deprecated
public enum JodaDateTimeConverter implements Converter<ReadableInstant, String> {
INSTANCE;
@Override
public String convert(ReadableInstant source) {
if (source == null) {
return null;
}
return formatter.print(source);
}
}
/**
* @deprecated since 4.1
*/
@Deprecated
public enum JodaLocalDateTimeConverter implements Converter<LocalDateTime, String> {
INSTANCE;
@Override
public String convert(LocalDateTime source) {
if (source == null) {
return null;
}
return formatter.print(source.toDateTime(DateTimeZone.UTC));
}
}
/**
* @deprecated since 4.1
*/
@Deprecated
public enum JavaDateConverter implements Converter<Date, String> {
INSTANCE;
@Override
public String convert(Date source) {
if (source == null) {
return null;
}
return formatter.print(source.getTime());
}
}
}
@@ -19,6 +19,7 @@ import org.springframework.data.convert.EntityConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -93,8 +94,7 @@ public interface ElasticsearchConverter
/**
* Updates a {@link Query} by renaming the property names in the query to the correct mapped field names and the
* values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If
* domainClass is null it's a noop.
* {@link PropertyValueConverter}. If domainClass is null it's a noop.
*
* @param query the query that is internally updated, must not be {@literal null}
* @param domainClass the class of the object that is searched with the query
@@ -47,7 +47,7 @@ final public class ElasticsearchDateConverter {
/**
* Creates an ElasticsearchDateConverter for the given {@link DateFormat}.
*
*
* @param dateFormat must not be @{literal null}
* @return converter
*/
@@ -135,7 +135,7 @@ final public class ElasticsearchDateConverter {
/**
* Creates a {@link DateFormatter} for a given pattern. The pattern can be the name of a {@link DateFormat} enum value
* or a literal pattern.
*
*
* @param pattern the pattern to use
* @return DateFormatter
*/
@@ -172,8 +172,73 @@ final public class ElasticsearchDateConverter {
return new PatternDateFormatter(dateTimeFormatter);
}
@SuppressWarnings("unchecked")
private static <T extends TemporalAccessor> TemporalQuery<T> getTemporalQuery(Class<T> type) {
return temporal -> {
// no reflection for java.time classes (GraalVM native)
if (type == java.time.chrono.HijrahDate.class) {
return (T) java.time.chrono.HijrahDate.from(temporal);
}
if (type == java.time.chrono.JapaneseDate.class) {
return (T) java.time.chrono.JapaneseDate.from(temporal);
}
if (type == java.time.ZonedDateTime.class) {
return (T) java.time.ZonedDateTime.from(temporal);
}
if (type == java.time.LocalDateTime.class) {
return (T) java.time.LocalDateTime.from(temporal);
}
if (type == java.time.chrono.ThaiBuddhistDate.class) {
return (T) java.time.chrono.ThaiBuddhistDate.from(temporal);
}
if (type == java.time.LocalTime.class) {
return (T) java.time.LocalTime.from(temporal);
}
if (type == java.time.ZoneOffset.class) {
return (T) java.time.ZoneOffset.from(temporal);
}
if (type == java.time.OffsetTime.class) {
return (T) java.time.OffsetTime.from(temporal);
}
if (type == java.time.chrono.ChronoLocalDate.class) {
return (T) java.time.chrono.ChronoLocalDate.from(temporal);
}
if (type == java.time.Month.class) {
return (T) java.time.Month.from(temporal);
}
if (type == java.time.chrono.ChronoLocalDateTime.class) {
return (T) java.time.chrono.ChronoLocalDateTime.from(temporal);
}
if (type == java.time.MonthDay.class) {
return (T) java.time.MonthDay.from(temporal);
}
if (type == java.time.Instant.class) {
return (T) java.time.Instant.from(temporal);
}
if (type == java.time.OffsetDateTime.class) {
return (T) java.time.OffsetDateTime.from(temporal);
}
if (type == java.time.chrono.ChronoZonedDateTime.class) {
return (T) java.time.chrono.ChronoZonedDateTime.from(temporal);
}
if (type == java.time.chrono.MinguoDate.class) {
return (T) java.time.chrono.MinguoDate.from(temporal);
}
if (type == java.time.Year.class) {
return (T) java.time.Year.from(temporal);
}
if (type == java.time.DayOfWeek.class) {
return (T) java.time.DayOfWeek.from(temporal);
}
if (type == java.time.LocalDate.class) {
return (T) java.time.LocalDate.from(temporal);
}
if (type == java.time.YearMonth.class) {
return (T) java.time.YearMonth.from(temporal);
}
// for implementations not covered until here use reflection to check for the existence of a static
// from(TemporalAccessor) method
try {
Method method = type.getMethod("from", TemporalAccessor.class);
Object o = method.invoke(null, temporal);
@@ -184,7 +249,8 @@ final public class ElasticsearchDateConverter {
throw new ConversionException("could not create object of class " + type.getName(), e);
}
};
} // endregion
}
// endregion
/**
* a DateFormatter to convert epoch milliseconds
@@ -0,0 +1,53 @@
/*
* 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.core.convert;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class NumberRangePropertyValueConverter extends AbstractRangePropertyValueConverter<Number> {
public NumberRangePropertyValueConverter(PersistentProperty<?> property) {
super(property);
}
@Override
protected String format(Number number) {
return String.valueOf(number);
}
@Override
protected Number parse(String value) {
Class<?> type = getGenericType();
if (Integer.class.isAssignableFrom(type)) {
return Integer.valueOf(value);
} else if (Float.class.isAssignableFrom(type)) {
return Float.valueOf(value);
} else if (Long.class.isAssignableFrom(type)) {
return Long.valueOf(value);
} else if (Double.class.isAssignableFrom(type)) {
return Double.valueOf(value);
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
type.getTypeName(), getProperty().getName()));
}
}
@@ -0,0 +1,76 @@
/*
* 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.core.convert;
import java.time.temporal.TemporalAccessor;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class TemporalPropertyValueConverter extends AbstractPropertyValueConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalPropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public TemporalPropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);
this.dateConverters = dateConverters;
}
@SuppressWarnings("unchecked")
@Override
public Object read(Object value) {
String s = value.toString();
Class<?> actualType = getProperty().getActualType();
for (ElasticsearchDateConverter dateConverter : dateConverters) {
try {
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) actualType);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s,
getProperty().getActualType().getTypeName(), getProperty().getName()));
}
@Override
public Object write(Object value) {
if (!TemporalAccessor.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((TemporalAccessor) value);
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
}
@@ -0,0 +1,66 @@
/*
* 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.core.convert;
import java.time.temporal.TemporalAccessor;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
/**
* @author Sascha Woo
* @since 4.3
*/
public class TemporalRangePropertyValueConverter extends AbstractRangePropertyValueConverter<TemporalAccessor> {
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalRangePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public TemporalRangePropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);
Assert.notEmpty(dateConverters, "dateConverters must not be empty.");
this.dateConverters = dateConverters;
}
@Override
protected String format(TemporalAccessor temporal) {
return dateConverters.get(0).format(temporal);
}
@Override
protected TemporalAccessor parse(String value) {
Class<?> type = getGenericType();
for (ElasticsearchDateConverter converters : dateConverters) {
try {
return converters.parse(value, (Class<? extends TemporalAccessor>) type);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
type.getTypeName(), getProperty().getName()));
}
}
@@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -63,36 +62,38 @@ import com.fasterxml.jackson.core.JsonGenerator;
* @author Matt Gilene
* @since 4.0
*/
public class DocumentAdapters {
public final class DocumentAdapters {
private DocumentAdapters() {}
/**
* Create a {@link Document} from {@link GetResponse}.
* <p>
* Returns a {@link Document} using the source if available.
* Returns a {@link Document} using the getResponse if available.
*
* @param source the source {@link GetResponse}.
* @return the adapted {@link Document}, null if source.isExists() returns false.
* @param getResponse the getResponse {@link GetResponse}.
* @return the adapted {@link Document}, null if getResponse.isExists() returns false.
*/
@Nullable
public static Document from(GetResponse source) {
public static Document from(GetResponse getResponse) {
Assert.notNull(source, "GetResponse must not be null");
Assert.notNull(getResponse, "GetResponse must not be null");
if (!source.isExists()) {
if (!getResponse.isExists()) {
return null;
}
if (source.isSourceEmpty()) {
return fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
source.getPrimaryTerm());
if (getResponse.isSourceEmpty()) {
return fromDocumentFields(getResponse, getResponse.getIndex(), getResponse.getId(), getResponse.getVersion(),
getResponse.getSeqNo(), getResponse.getPrimaryTerm());
}
Document document = Document.from(source.getSourceAsMap());
document.setIndex(source.getIndex());
document.setId(source.getId());
document.setVersion(source.getVersion());
document.setSeqNo(source.getSeqNo());
document.setPrimaryTerm(source.getPrimaryTerm());
Document document = Document.from(getResponse.getSourceAsMap());
document.setIndex(getResponse.getIndex());
document.setId(getResponse.getId());
document.setVersion(getResponse.getVersion());
document.setSeqNo(getResponse.getSeqNo());
document.setPrimaryTerm(getResponse.getPrimaryTerm());
return document;
}
@@ -176,8 +177,8 @@ public class DocumentAdapters {
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();
if (sourceInnerHits != null) {
sourceInnerHits
.forEach((name, searchHits) -> innerHits.put(name, SearchDocumentResponse.from(searchHits, null, null)));
sourceInnerHits.forEach((name, searchHits) -> innerHits.put(name,
SearchDocumentResponse.from(searchHits, null, null, null, searchDocument -> null)));
}
NestedMetaData nestedMetaData = from(source.getNestedIdentity());
@@ -185,12 +186,14 @@ public class DocumentAdapters {
List<String> matchedQueries = from(source.getMatchedQueries());
BytesReference sourceRef = source.getSourceRef();
Map<String, DocumentField> sourceFields = source.getFields();
if (sourceRef == null || sourceRef.length() == 0) {
return new SearchDocumentAdapter(
source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source,
source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()),
innerHits, nestedMetaData, explanation, matchedQueries);
fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
source.getPrimaryTerm()),
source.getScore(), source.getSortValues(), sourceFields, highlightFields, innerHits, nestedMetaData,
explanation, matchedQueries);
}
Document document = Document.from(source.getSourceAsMap());
@@ -203,8 +206,8 @@ public class DocumentAdapters {
document.setSeqNo(source.getSeqNo());
document.setPrimaryTerm(source.getPrimaryTerm());
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
document, innerHits, nestedMetaData, explanation, matchedQueries);
return new SearchDocumentAdapter(document, source.getScore(), source.getSortValues(), sourceFields, highlightFields,
innerHits, nestedMetaData, explanation, matchedQueries);
}
@Nullable
@@ -243,6 +246,10 @@ public class DocumentAdapters {
*
* @param documentFields the {@link DocumentField}s backing the {@link Document}.
* @param index the index where the Document was found
* @param id the document id
* @param version the document version
* @param seqNo the seqNo if the document
* @param primaryTerm the primaryTerm of the document
* @return the adapted {@link Document}.
*/
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String index, String id,
@@ -261,10 +268,13 @@ public class DocumentAdapters {
return new DocumentFieldAdapter(fields, index, id, version, seqNo, primaryTerm);
}
// TODO: Performance regarding keys/values/entry-set
/**
* Adapter for a collection of {@link DocumentField}s.
*/
static class DocumentFieldAdapter implements Document {
private final Collection<DocumentField> documentFields;
private final Map<String, DocumentField> documentFieldMap;
private final String index;
private final String id;
private final long version;
@@ -274,6 +284,8 @@ public class DocumentAdapters {
DocumentFieldAdapter(Collection<DocumentField> documentFields, String index, String id, long version, long seqNo,
long primaryTerm) {
this.documentFields = documentFields;
this.documentFieldMap = new LinkedHashMap<>(documentFields.size());
documentFields.forEach(documentField -> documentFieldMap.put(documentField.getName(), documentField));
this.index = index;
this.id = id;
this.version = version;
@@ -353,14 +365,7 @@ public class DocumentAdapters {
@Override
public boolean containsKey(Object key) {
for (DocumentField documentField : documentFields) {
if (documentField.getName().equals(key)) {
return true;
}
}
return false;
return documentFieldMap.containsKey(key);
}
@Override
@@ -380,11 +385,9 @@ public class DocumentAdapters {
@Override
@Nullable
public Object get(Object key) {
return documentFields.stream() //
.filter(documentField -> documentField.getName().equals(key)) //
.map(DocumentField::getValue).findFirst() //
.orElse(null); //
DocumentField documentField = documentFieldMap.get(key);
return documentField != null ? documentField.getValue() : null;
}
@Override
@@ -409,17 +412,18 @@ public class DocumentAdapters {
@Override
public Set<String> keySet() {
return documentFields.stream().map(DocumentField::getName).collect(Collectors.toCollection(LinkedHashSet::new));
return documentFieldMap.keySet();
}
@Override
public Collection<Object> values() {
return documentFields.stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList());
return documentFieldMap.values().stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList());
}
@Override
public Set<Entry<String, Object>> entrySet() {
return documentFields.stream().collect(Collectors.toMap(DocumentField::getName, DocumentFieldAdapter::getValue))
return documentFieldMap.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> DocumentFieldAdapter.getValue(entry.getValue())))
.entrySet();
}
@@ -458,7 +462,6 @@ public class DocumentAdapters {
}
}
@Override
public String toString() {
return getClass().getSimpleName() + '@' + this.id + '#' + this.version + ' ' + toJson();
@@ -494,14 +497,14 @@ public class DocumentAdapters {
@Nullable private final Explanation explanation;
@Nullable private final List<String> matchedQueries;
SearchDocumentAdapter(float score, Object[] sortValues, Map<String, DocumentField> fields,
Map<String, List<String>> highlightFields, Document delegate, Map<String, SearchDocumentResponse> innerHits,
SearchDocumentAdapter(Document delegate, float score, Object[] sortValues, Map<String, DocumentField> fields,
Map<String, List<String>> highlightFields, Map<String, SearchDocumentResponse> innerHits,
@Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation,
@Nullable List<String> matchedQueries) {
this.delegate = delegate;
this.score = score;
this.sortValues = sortValues;
this.delegate = delegate;
fields.forEach((name, documentField) -> this.fields.put(name, documentField.getValues()));
this.highlightFields.putAll(highlightFields);
this.innerHits.putAll(innerHits);
@@ -646,7 +649,13 @@ public class DocumentAdapters {
@Override
public Object get(Object key) {
return delegate.get(key);
if (delegate.containsKey(key)) {
return delegate.get(key);
}
// fallback to fields
return fields.get(key);
}
@Override
@@ -17,19 +17,29 @@ package org.springframework.data.elasticsearch.core.document;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregations;
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.PhraseSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.SortBy;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.core.suggest.response.TermSuggestion;
import org.springframework.data.elasticsearch.support.ScoreDoc;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* This represents the complete search response from Elasticsearch, including the returned documents. Instances must be
* created with the {@link #from(SearchResponse)} method.
*
* created with the {@link #from(SearchResponse,Function)} method.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
@@ -40,16 +50,18 @@ public class SearchDocumentResponse {
private final float maxScore;
private final String scrollId;
private final List<SearchDocument> searchDocuments;
private final Aggregations aggregations;
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
private SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, String scrollId,
List<SearchDocument> searchDocuments, Aggregations aggregations) {
List<SearchDocument> searchDocuments, @Nullable Aggregations aggregations, @Nullable Suggest suggest) {
this.totalHits = totalHits;
this.totalHitsRelation = totalHitsRelation;
this.maxScore = maxScore;
this.scrollId = scrollId;
this.searchDocuments = searchDocuments;
this.aggregations = aggregations;
this.aggregations = aggregations != null ? new ElasticsearchAggregations(aggregations) : null;
this.suggest = suggest;
}
public long getTotalHits() {
@@ -72,40 +84,53 @@ public class SearchDocumentResponse {
return searchDocuments;
}
public Aggregations getAggregations() {
@Nullable
public AggregationsContainer<?> getAggregations() {
return aggregations;
}
@Nullable
public Suggest getSuggest() {
return suggest;
}
/**
* creates a SearchDocumentResponse from the {@link SearchResponse}
*
* @param searchResponse must not be {@literal null}
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type
* @return the SearchDocumentResponse
*/
public static SearchDocumentResponse from(SearchResponse searchResponse) {
public static <T> SearchDocumentResponse from(SearchResponse searchResponse,
Function<SearchDocument, T> suggestEntityCreator) {
Assert.notNull(searchResponse, "searchResponse must not be null");
Aggregations aggregations = searchResponse.getAggregations();
String scrollId = searchResponse.getScrollId();
SearchHits searchHits = searchResponse.getHits();
String scrollId = searchResponse.getScrollId();
Aggregations aggregations = searchResponse.getAggregations();
org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest();
SearchDocumentResponse searchDocumentResponse = from(searchHits, scrollId, aggregations);
return searchDocumentResponse;
return from(searchHits, scrollId, aggregations, suggest, suggestEntityCreator);
}
/**
* creates a {@link SearchDocumentResponse} from {@link SearchHits} with the given scrollId and aggregations
*
* creates a {@link SearchDocumentResponse} from {@link SearchHits} with the given scrollId aggregations and suggest
*
* @param searchHits the {@link SearchHits} to process
* @param scrollId scrollId
* @param aggregations aggregations
* @param suggestES the suggestion response from Elasticsearch
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type
* @return the {@link SearchDocumentResponse}
* @since 4.1
* @since 4.3
*/
public static SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
@Nullable Aggregations aggregations) {
public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
@Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> suggestEntityCreator) {
TotalHits responseTotalHits = searchHits.getTotalHits();
long totalHits;
@@ -128,7 +153,105 @@ public class SearchDocumentResponse {
}
}
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations);
Suggest suggest = suggestFrom(suggestES, suggestEntityCreator);
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations,
suggest);
}
@Nullable
private static <T> Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> entityCreator) {
if (suggestES == null) {
return null;
}
List<Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>>> suggestions = new ArrayList<>();
for (org.elasticsearch.search.suggest.Suggest.Suggestion<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option>> suggestionES : suggestES) {
if (suggestionES instanceof org.elasticsearch.search.suggest.term.TermSuggestion) {
org.elasticsearch.search.suggest.term.TermSuggestion termSuggestionES = (org.elasticsearch.search.suggest.term.TermSuggestion) suggestionES;
List<TermSuggestion.Entry> entries = new ArrayList<>();
for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry entryES : termSuggestionES) {
List<TermSuggestion.Entry.Option> options = new ArrayList<>();
for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option optionES : entryES) {
options.add(new TermSuggestion.Entry.Option(textToString(optionES.getText()),
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
optionES.getFreq()));
}
entries.add(new TermSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(),
entryES.getLength(), options));
}
suggestions.add(new TermSuggestion(termSuggestionES.getName(), termSuggestionES.getSize(), entries,
suggestFrom(termSuggestionES.getSort())));
}
if (suggestionES instanceof org.elasticsearch.search.suggest.phrase.PhraseSuggestion) {
org.elasticsearch.search.suggest.phrase.PhraseSuggestion phraseSuggestionES = (org.elasticsearch.search.suggest.phrase.PhraseSuggestion) suggestionES;
List<PhraseSuggestion.Entry> entries = new ArrayList<>();
for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry entryES : phraseSuggestionES) {
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry.Option optionES : entryES) {
options.add(new PhraseSuggestion.Entry.Option(textToString(optionES.getText()),
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch()));
}
entries.add(new PhraseSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(),
entryES.getLength(), options, entryES.getCutoffScore()));
}
suggestions.add(new PhraseSuggestion(phraseSuggestionES.getName(), phraseSuggestionES.getSize(), entries));
}
if (suggestionES instanceof org.elasticsearch.search.suggest.completion.CompletionSuggestion) {
org.elasticsearch.search.suggest.completion.CompletionSuggestion completionSuggestionES = (org.elasticsearch.search.suggest.completion.CompletionSuggestion) suggestionES;
List<CompletionSuggestion.Entry<T>> entries = new ArrayList<>();
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry entryES : completionSuggestionES) {
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) {
SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null;
T hitEntity = searchDocument != null ? entityCreator.apply(searchDocument) : null;
options.add(new CompletionSuggestion.Entry.Option<T>(textToString(optionES.getText()),
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity));
}
entries.add(new CompletionSuggestion.Entry<T>(textToString(entryES.getText()), entryES.getOffset(),
entryES.getLength(), options));
}
suggestions.add(
new CompletionSuggestion<T>(completionSuggestionES.getName(), completionSuggestionES.getSize(), entries));
}
}
return new Suggest(suggestions, suggestES.hasScoreDocs());
}
private static SortBy suggestFrom(org.elasticsearch.search.suggest.SortBy sort) {
return SortBy.valueOf(sort.name().toUpperCase());
}
@Nullable
private static ScoreDoc scoreDocFrom(@Nullable org.apache.lucene.search.ScoreDoc scoreDoc) {
if (scoreDoc == null) {
return null;
}
return new ScoreDoc(scoreDoc.score, scoreDoc.doc, scoreDoc.shardIndex);
}
private static String textToString(@Nullable Text text) {
return text != null ? text.string() : "";
}
}
@@ -11,26 +11,6 @@ import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.data.geo.Point;
/**
* @author Artur Konaczak
* @deprecated since 4.1, not used anymore
*/
@Deprecated
public class CustomGeoModule extends SimpleModule {
private static final long serialVersionUID = 1L;
/**
* Creates a new {@link org.springframework.data.elasticsearch.core.geo.CustomGeoModule} registering serializers and deserializers for spring data commons geo-spatial types.
*/
public CustomGeoModule() {
super("Spring Data Elasticsearch Geo", new Version(1, 0, 0, null, "org.springframework.data.elasticsearch", "spring-data-elasticsearch-geo"));
addSerializer(Point.class, new PointSerializer());
addDeserializer(Point.class, new PointDeserializer());
}
}
class PointSerializer extends JsonSerializer<Point> {
@Override
public void serialize(Point value, JsonGenerator gen, SerializerProvider serializers) throws IOException {

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