Remove org.elasticsearch dependencies from API classes.

Original Pull Request #1913
Closes #1884
Closes #1885
This commit is contained in:
Peter-Josef Meisch 2021-08-30 21:37:04 +02:00 committed by GitHub
parent e688fc70e0
commit 305d930870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2105 additions and 840 deletions

View File

@ -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,11 +165,13 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
.withWebClientConfigurer(webClient -> { <.>
.withClientConfigurer( <.>
(ReactiveRestClients.WebClientConfigurationCallback) webClient -> {
// ...
return webClient;
})
.withHttpClientConfigurer(clientBuilder -> { <.>
.withClientConfigurer( <.>
(RestClients.RestClientConfigurationCallback) clientBuilder -> {
// ...
return clientBuilder;
})
@ -174,13 +179,16 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.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]

View File

@ -3,12 +3,53 @@
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
[[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`.
=== 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`.
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`.

View File

@ -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,16 @@ public @interface CompletionContext {
String name();
ContextMapping.Type type();
ContextMappingType type();
String precision() default "";
String path() default "";
/**
* @since 4.3
*/
enum ContextMappingType {
CATEGORY, GEO
}
}

View File

@ -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;
/**
@ -121,4 +120,10 @@ public @interface Document {
*/
Dynamic dynamic() default Dynamic.INHERIT;
/**
* @since 4.3
*/
enum VersionType {
INTERNAL, EXTERNAL, EXTERNAL_GTE
}
}

View File

@ -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();
@ -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 #getClientConfigurer()}
*/
@Deprecated
HttpClientConfigCallback getHttpClientConfigurer();
/**
* @return the client configuration callback
* @since 4.3
*/
<T> ClientConfigurationCallback<T> getClientConfigurer();
/**
* @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}
@ -345,7 +354,10 @@ public interface ClientConfiguration {
*
* @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);
/**
@ -354,9 +366,21 @@ public interface ClientConfiguration {
* @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);
}
}

View File

@ -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 ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer = t -> t;
/*
* (non-Javadoc)
@ -206,6 +208,9 @@ class ClientConfigurationBuilder
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
this.webClientConfigurer = webClientConfigurer;
// noinspection NullableProblems
this.clientConfigurer = (ReactiveRestClients.WebClientConfigurationCallback) webClientConfigurer::apply;
return this;
}
@ -215,6 +220,19 @@ class ClientConfigurationBuilder
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
this.httpClientConfigurer = httpClientConfigurer;
// noinspection NullableProblems
this.clientConfigurer = (RestClients.RestClientConfigurationCallback) httpClientConfigurer::customizeHttpClient;
return this;
}
@Override
public TerminalClientConfigurationBuilder withClientConfigurer(
ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer) {
Assert.notNull(clientConfigurer, "clientConfigurer must not be null");
this.clientConfigurer = clientConfigurer;
return this;
}
@ -242,7 +260,7 @@ class ClientConfigurationBuilder
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, headersSupplier);
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, clientConfigurer, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {

View File

@ -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");
@ -76,7 +79,6 @@ class ClusterNodes implements Streamable<TransportAddress> {
* 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);

View File

@ -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 ClientConfigurationCallback<?> clientConfigurer;
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) {
ClientConfigurationCallback<?> clientConfigurer, 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.clientConfigurer = clientConfigurer;
this.headersSupplier = headersSupplier;
}
@ -132,6 +134,12 @@ class DefaultClientConfiguration implements ClientConfiguration {
return httpClientConfigurer;
}
@SuppressWarnings("unchecked")
@Override
public <T> ClientConfigurationCallback<T> getClientConfigurer() {
return (ClientConfigurationCallback<T>) clientConfigurer;
}
@Override
public Supplier<HttpHeaders> getHeadersSupplier() {
return headersSupplier;

View File

@ -36,6 +36,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;
@ -119,7 +120,7 @@ public final class RestClients {
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
clientBuilder = clientConfiguration.getHttpClientConfigurer().customizeHttpClient(clientBuilder);
clientBuilder = clientConfiguration.<HttpAsyncClientBuilder> getClientConfigurer().configure(clientBuilder);
return clientBuilder;
});
@ -198,7 +199,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(), "");
}
@ -233,4 +234,13 @@ 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> {}
}

View File

@ -111,6 +111,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;
@ -289,7 +290,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
provider = provider //
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()) //
.withWebClientConfigurer(clientConfiguration.<WebClient> getClientConfigurer()::configure) //
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> {
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
@ -485,7 +486,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

View File

@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.client.reactive;
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 +62,12 @@ 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> {}
}

View File

@ -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

View File

@ -0,0 +1,208 @@
/*
* Copyright 2021-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.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);
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;
}
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();
}
// endregion
}

View File

@ -15,32 +15,19 @@
*/
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 +44,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,7 +59,13 @@ 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
@ -84,9 +76,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@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
@ -176,7 +168,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
* @since 4.3
*/
public void logVersions() {
VersionInfo.logVersions(getClusterVersion());
VersionInfo.logVersions(getVendor(), getRuntimeLibraryVersion(), getClusterVersion());
}
// endregion
@ -364,40 +356,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
@ -414,8 +372,7 @@ 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();
long scrollTimeInMillis = Duration.ofMinutes(1).toMillis();
// noinspection ConstantConditions
int maxCount = query.isLimiting() ? query.getMaxResults() : 0;
@ -436,98 +393,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));
@ -557,8 +432,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));
@ -600,37 +473,6 @@ 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 <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
@ -749,6 +591,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

View File

@ -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;
@ -48,8 +44,6 @@ import org.springframework.util.Assert;
*/
abstract class AbstractIndexTemplate implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIndexTemplate.class);
protected final ElasticsearchConverter elasticsearchConverter;
protected final RequestFactory requestFactory;
@ -175,8 +169,6 @@ abstract class AbstractIndexTemplate implements IndexOperations {
protected abstract void doRefresh(IndexCoordinates indexCoordinates);
protected abstract List<AliasMetadata> doQueryForAlias(IndexCoordinates index);
@Override
public Map<String, Set<AliasData>> getAliases(String... aliasNames) {

View File

@ -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;
}
}

View File

@ -33,6 +33,7 @@ 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;
@ -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);
@ -223,7 +224,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
@ -261,7 +263,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,
@ -272,6 +274,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

View File

@ -36,6 +36,8 @@ 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;
@ -90,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);
@ -246,7 +248,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
@ -288,7 +291,7 @@ 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,
@ -309,6 +312,24 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
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

View File

@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
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,6 +40,7 @@ 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;
@ -75,6 +77,7 @@ 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;
@ -164,7 +167,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
* @since 4.3
*/
public Mono<Void> logVersions() {
return getClusterVersion().doOnNext(VersionInfo::logVersions).then();
return getVendor() //
.doOnNext(vendor -> getRuntimeLibraryVersion() //
.doOnNext(runtimeLibraryVersion -> getClusterVersion() //
.doOnNext(clusterVersion -> VersionInfo.logVersions(vendor, runtimeLibraryVersion, clusterVersion)))) //
.then(); //
}
@Override
@ -557,7 +564,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
@ -939,6 +946,35 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
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

View File

@ -18,14 +18,14 @@ package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.elasticsearch.index.query.QueryBuilders;
import java.util.List;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
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;
/**
* The reactive operations for the
@ -45,7 +45,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);
}
/**
@ -215,4 +215,25 @@ public interface ReactiveSearchOperations {
* @return the suggest response
*/
Flux<Suggest> suggest(SuggestBuilder suggestion, 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
}

View File

@ -18,13 +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.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.admin.indices.alias.Alias;
@ -50,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;
@ -223,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) {
@ -258,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) {
@ -802,7 +807,10 @@ 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) {
@ -954,7 +962,7 @@ class RequestFactory {
}
if (query.getIndicesOptions() != null) {
request.indicesOptions(query.getIndicesOptions());
request.indicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions()));
}
if (query.isLimiting()) {
@ -970,7 +978,7 @@ class RequestFactory {
request.preference(query.getPreference());
}
request.searchType(query.getSearchType());
request.searchType(SearchType.fromString(query.getSearchType().name().toLowerCase()));
prepareSort(query, sourceBuilder, getPersistentEntity(clazz));
@ -994,9 +1002,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());
@ -1023,7 +1031,7 @@ 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)) {
@ -1048,7 +1056,7 @@ class RequestFactory {
}
if (query.getIndicesOptions() != null) {
searchRequestBuilder.setIndicesOptions(query.getIndicesOptions());
searchRequestBuilder.setIndicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions()));
}
if (query.isLimiting()) {
@ -1086,9 +1094,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());
@ -1429,7 +1437,7 @@ class RequestFactory {
updateByQueryRequest.setQuery(getQuery(queryQuery));
if (queryQuery.getIndicesOptions() != null) {
updateByQueryRequest.setIndicesOptions(queryQuery.getIndicesOptions());
updateByQueryRequest.setIndicesOptions(toElasticsearchIndicesOptions(queryQuery.getIndicesOptions()));
}
if (queryQuery.getScrollTime() != null) {
@ -1504,7 +1512,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) {
@ -1618,6 +1627,21 @@ class RequestFactory {
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
@ -1656,7 +1680,12 @@ class RequestFactory {
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;

View File

@ -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
}

View File

@ -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;
@ -140,19 +137,6 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations
});
}
@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) {

View File

@ -220,4 +220,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);
}

View File

@ -75,8 +75,7 @@ class TransportIndexTemplate extends AbstractIndexTemplate implements IndexOpera
private final Client client;
public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter,
Class<?> boundClass) {
public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
super(elasticsearchConverter, boundClass);
this.client = client;
}
@ -144,13 +143,6 @@ class TransportIndexTemplate extends AbstractIndexTemplate implements IndexOpera
return mappings.iterator().next().value.get(IndexCoordinates.TYPE).getSourceAsMap();
}
@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) {

View File

@ -17,11 +17,12 @@ package org.springframework.data.elasticsearch.core.index;
import java.io.IOException;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.data.elasticsearch.annotations.GeoShapeField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* @author Peter-Josef Meisch
*/
@ -63,27 +64,42 @@ final class GeoShapeMappingParameters {
this.orientation = orientation;
}
public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException {
public boolean isCoerce() {
return coerce;
}
Assert.notNull(builder, "builder must ot be null");
public boolean isIgnoreMalformed() {
return ignoreMalformed;
}
public boolean isIgnoreZValue() {
return ignoreZValue;
}
public GeoShapeField.Orientation getOrientation() {
return orientation;
}
public void writeTypeAndParametersTo(ObjectNode objectNode) throws IOException {
Assert.notNull(objectNode, "objectNode must not be null");
if (coerce) {
builder.field(FIELD_PARAM_COERCE, coerce);
objectNode.put(FIELD_PARAM_COERCE, coerce);
}
if (ignoreMalformed) {
builder.field(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed);
objectNode.put(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed);
}
if (!ignoreZValue) {
builder.field(FIELD_PARAM_IGNORE_Z_VALUE, ignoreZValue);
objectNode.put(FIELD_PARAM_IGNORE_Z_VALUE, ignoreZValue);
}
if (orientation != GeoShapeField.Orientation.ccw) {
builder.field(FIELD_PARAM_ORIENTATION, orientation.name());
objectNode.put(FIELD_PARAM_ORIENTATION, orientation.name());
}
builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_SHAPE);
objectNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_SHAPE);
}
}

View File

@ -15,18 +15,16 @@
*/
package org.springframework.data.elasticsearch.core.index;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.springframework.data.elasticsearch.core.index.MappingParameters.*;
import static org.springframework.util.StringUtils.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Iterator;
import java.util.stream.Collectors;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
@ -35,16 +33,22 @@ import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ResourceUtil;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.databind.util.RawValue;
/**
* @author Rizwan Idrees
@ -75,6 +79,7 @@ public class MappingBuilder {
private static final String FIELD_CONTEXT_PATH = "path";
private static final String FIELD_CONTEXT_PRECISION = "precision";
private static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates";
private static final String FIELD_INCLUDE_IN_PARENT = "include_in_parent";
private static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators";
private static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments";
@ -86,6 +91,7 @@ public class MappingBuilder {
private static final String TYPE_DYNAMIC = "dynamic";
private static final String TYPE_VALUE_KEYWORD = "keyword";
private static final String TYPE_VALUE_GEO_POINT = "geo_point";
private static final String TYPE_VALUE_GEO_SHAPE = "geo_shape";
private static final String TYPE_VALUE_JOIN = "join";
private static final String TYPE_VALUE_COMPLETION = "completion";
@ -98,6 +104,7 @@ public class MappingBuilder {
private static final String RUNTIME = "runtime";
protected final ElasticsearchConverter elasticsearchConverter;
private final ObjectMapper objectMapper = new ObjectMapper();
private boolean writeTypeHints = true;
@ -126,61 +133,58 @@ public class MappingBuilder {
writeTypeHints = entity.writeTypeHints();
XContentBuilder builder = jsonBuilder().startObject();
ObjectNode objectNode = objectMapper.createObjectNode();
// Dynamic templates
addDynamicTemplatesMapping(builder, entity);
addDynamicTemplatesMapping(objectNode, entity);
mapEntity(builder, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class),
mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class),
runtimeFields);
builder.endObject() // root object
.close();
return builder.getOutputStream().toString();
return objectMapper.writer().writeValueAsString(objectNode);
} catch (IOException e) {
throw new MappingException("could not build mapping", e);
}
}
private void writeTypeHintMapping(XContentBuilder builder) throws IOException {
private void writeTypeHintMapping(ObjectNode propertiesNode) throws IOException {
if (writeTypeHints) {
builder.startObject(TYPEHINT_PROPERTY) //
.field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) //
.field(FIELD_PARAM_INDEX, false) //
.field(FIELD_PARAM_DOC_VALUES, false) //
.endObject();
propertiesNode.set(TYPEHINT_PROPERTY, objectMapper.createObjectNode() //
.put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) //
.put(FIELD_PARAM_INDEX, false) //
.put(FIELD_PARAM_DOC_VALUES, false));
}
}
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity<?> entity,
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping,
@Nullable org.springframework.data.elasticsearch.core.document.Document runtimeFields) throws IOException {
private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity, boolean isRootObject,
String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping, @Nullable Document runtimeFields)
throws IOException {
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
if (!mappingAnnotation.enabled()) {
builder.field(MAPPING_ENABLED, false);
objectNode.put(MAPPING_ENABLED, false);
return;
}
if (mappingAnnotation.dateDetection() != Mapping.Detection.DEFAULT) {
builder.field(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name()));
objectNode.put(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name()));
}
if (mappingAnnotation.numericDetection() != Mapping.Detection.DEFAULT) {
builder.field(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name()));
objectNode.put(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name()));
}
if (mappingAnnotation.dynamicDateFormats().length > 0) {
builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats());
objectNode.putArray(DYNAMIC_DATE_FORMATS).addAll(
Arrays.stream(mappingAnnotation.dynamicDateFormats()).map(TextNode::valueOf).collect(Collectors.toList()));
}
if (runtimeFields != null) {
builder.field(RUNTIME, runtimeFields);
objectNode.set(RUNTIME, objectMapper.convertValue(runtimeFields, JsonNode.class));
}
}
@ -189,23 +193,29 @@ public class MappingBuilder {
String type = nestedOrObjectField ? fieldType.toString().toLowerCase()
: FieldType.Object.toString().toLowerCase();
builder.startObject(nestedObjectFieldName).field(FIELD_PARAM_TYPE, type);
ObjectNode nestedObjectNode = objectMapper.createObjectNode();
nestedObjectNode.put(FIELD_PARAM_TYPE, type);
if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null
&& parentFieldAnnotation.includeInParent()) {
builder.field("include_in_parent", true);
nestedObjectNode.put(FIELD_INCLUDE_IN_PARENT, true);
}
objectNode.set(nestedObjectFieldName, nestedObjectNode);
// now go on with the nested one
objectNode = nestedObjectNode;
}
if (entity != null && entity.dynamic() != Dynamic.INHERIT) {
builder.field(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase());
objectNode.put(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase());
} else if (dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
}
builder.startObject(FIELD_PROPERTIES);
ObjectNode propertiesNode = objectNode.putObject(FIELD_PROPERTIES);
writeTypeHintMapping(builder);
writeTypeHintMapping(propertiesNode);
if (entity != null) {
entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
@ -223,19 +233,12 @@ public class MappingBuilder {
return;
}
buildPropertyMapping(builder, isRootObject, property);
buildPropertyMapping(propertiesNode, isRootObject, property);
} catch (IOException e) {
logger.warn("error mapping property with name {}", property.getName(), e);
}
});
}
builder.endObject();
if (writeNestedProperties) {
builder.endObject();
}
}
@Nullable
@ -256,7 +259,7 @@ public class MappingBuilder {
return null;
}
private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject,
private void buildPropertyMapping(ObjectNode propertiesNode, boolean isRootObject,
ElasticsearchPersistentProperty property) throws IOException {
if (property.isAnnotationPresent(Mapping.class)) {
@ -270,27 +273,28 @@ public class MappingBuilder {
ClassPathResource mappings = new ClassPathResource(mappingPath);
if (mappings.exists()) {
builder.rawField(property.getFieldName(), mappings.getInputStream(), XContentType.JSON);
propertiesNode.putRawValue(property.getFieldName(),
new RawValue(StreamUtils.copyToString(mappings.getInputStream(), Charset.defaultCharset())));
return;
}
}
} else {
applyDisabledPropertyMapping(builder, property);
applyDisabledPropertyMapping(propertiesNode, property);
return;
}
}
if (property.isGeoPointProperty()) {
applyGeoPointFieldMapping(builder, property);
applyGeoPointFieldMapping(propertiesNode, property);
return;
}
if (property.isGeoShapeProperty()) {
applyGeoShapeMapping(builder, property);
applyGeoShapeMapping(propertiesNode, property);
}
if (property.isJoinFieldProperty()) {
addJoinFieldMapping(builder, property);
addJoinFieldMapping(propertiesNode, property);
}
Field fieldAnnotation = property.findAnnotation(Field.class);
@ -310,7 +314,7 @@ public class MappingBuilder {
? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next())
: null;
mapEntity(builder, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
fieldAnnotation, dynamicMapping, null);
return;
}
@ -320,15 +324,15 @@ public class MappingBuilder {
if (isCompletionProperty) {
CompletionField completionField = property.findAnnotation(CompletionField.class);
applyCompletionFieldMapping(builder, property, completionField);
applyCompletionFieldMapping(propertiesNode, property, completionField);
}
if (isRootObject && fieldAnnotation != null && property.isIdProperty()) {
applyDefaultIdFieldMapping(builder, property);
applyDefaultIdFieldMapping(propertiesNode, property);
} else if (multiField != null) {
addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty, dynamicMapping);
addMultiFieldMapping(propertiesNode, property, multiField, isNestedOrObjectProperty, dynamicMapping);
} else if (fieldAnnotation != null) {
addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping);
addSingleFieldMapping(propertiesNode, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping);
}
}
@ -339,73 +343,70 @@ public class MappingBuilder {
|| property.findAnnotation(CompletionField.class) != null;
}
private void applyGeoPointFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
private void applyGeoPointFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT).endObject();
propertiesNode.set(property.getFieldName(),
objectMapper.createObjectNode().put(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT));
}
private void applyGeoShapeMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
private void applyGeoShapeMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName());
GeoShapeMappingParameters.from(property.findAnnotation(GeoShapeField.class)).writeTypeAndParametersTo(builder);
builder.endObject();
ObjectNode shapeNode = propertiesNode.putObject(property.getFieldName());
GeoShapeMappingParameters mappingParameters = GeoShapeMappingParameters
.from(property.findAnnotation(GeoShapeField.class));
mappingParameters.writeTypeAndParametersTo(shapeNode);
}
private void applyCompletionFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
private void applyCompletionFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property,
@Nullable CompletionField annotation) throws IOException {
builder.startObject(property.getFieldName());
builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION);
ObjectNode completionNode = propertyNode.putObject(property.getFieldName());
completionNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION);
if (annotation != null) {
completionNode.put(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength());
completionNode.put(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
completionNode.put(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
builder.field(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength());
builder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
builder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
if (!StringUtils.isEmpty(annotation.searchAnalyzer())) {
builder.field(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer());
if (StringUtils.hasLength(annotation.searchAnalyzer())) {
completionNode.put(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer());
}
if (!StringUtils.isEmpty(annotation.analyzer())) {
builder.field(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer());
if (StringUtils.hasLength(annotation.analyzer())) {
completionNode.put(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer());
}
if (annotation.contexts().length > 0) {
builder.startArray(COMPLETION_CONTEXTS);
ArrayNode contextsNode = completionNode.putArray(COMPLETION_CONTEXTS);
for (CompletionContext context : annotation.contexts()) {
builder.startObject();
builder.field(FIELD_CONTEXT_NAME, context.name());
builder.field(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
ObjectNode contextNode = contextsNode.addObject();
contextNode.put(FIELD_CONTEXT_NAME, context.name());
contextNode.put(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
if (context.precision().length() > 0) {
builder.field(FIELD_CONTEXT_PRECISION, context.precision());
contextNode.put(FIELD_CONTEXT_PRECISION, context.precision());
}
if (StringUtils.hasText(context.path())) {
builder.field(FIELD_CONTEXT_PATH, context.path());
contextNode.put(FIELD_CONTEXT_PATH, context.path());
}
}
}
}
}
builder.endObject();
}
builder.endArray();
}
}
builder.endObject();
}
private void applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
private void applyDefaultIdFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()) //
.field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) //
.field(FIELD_INDEX, true) //
.endObject(); //
propertyNode.set(property.getFieldName(), objectMapper.createObjectNode()//
.put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) //
.put(FIELD_INDEX, true) //
);
}
private void applyDisabledPropertyMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
private void applyDisabledPropertyMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) {
try {
Field field = property.getRequiredAnnotation(Field.class);
@ -414,10 +415,11 @@ public class MappingBuilder {
throw new IllegalArgumentException("Field type must be 'object");
}
builder.startObject(property.getFieldName()) //
.field(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) //
.field(MAPPING_ENABLED, false) //
.endObject(); //
propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() //
.put(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) //
.put(MAPPING_ENABLED, false) //
);
} catch (Exception e) {
throw new MappingException("Could not write enabled: false mapping for " + property.getFieldName(), e);
}
@ -428,33 +430,29 @@ public class MappingBuilder {
*
* @throws IOException
*/
private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
private void addSingleFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property,
Field annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException {
// build the property json, if empty skip it as this is no valid mapping
XContentBuilder propertyBuilder = jsonBuilder().startObject();
addFieldMappingParameters(propertyBuilder, annotation, nestedOrObjectField);
propertyBuilder.endObject().close();
ObjectNode fieldNode = objectMapper.createObjectNode();
addFieldMappingParameters(fieldNode, annotation, nestedOrObjectField);
if ("{}".equals(propertyBuilder.getOutputStream().toString())) {
if (fieldNode.isEmpty()) {
return;
}
builder.startObject(property.getFieldName());
propertiesNode.set(property.getFieldName(), fieldNode);
if (nestedOrObjectField) {
if (annotation.dynamic() != Dynamic.INHERIT) {
builder.field(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase());
fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase());
} else if (dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
}
}
}
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
builder.endObject();
}
private void addJoinFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
private void addJoinFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property)
throws IOException {
JoinTypeRelation[] joinTypeRelations = property.getRequiredAnnotation(JoinTypeRelations.class).relations();
@ -464,24 +462,23 @@ public class MappingBuilder {
property.getFieldName());
return;
}
builder.startObject(property.getFieldName());
builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_JOIN);
ObjectNode propertyNode = propertiesNode.putObject(property.getFieldName());
propertyNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_JOIN);
builder.startObject(JOIN_TYPE_RELATIONS);
ObjectNode relationsNode = propertyNode.putObject(JOIN_TYPE_RELATIONS);
for (JoinTypeRelation joinTypeRelation : joinTypeRelations) {
String parent = joinTypeRelation.parent();
String[] children = joinTypeRelation.children();
if (children.length > 1) {
builder.array(parent, children);
relationsNode.putArray(parent)
.addAll(Arrays.stream(children).map(TextNode::valueOf).collect(Collectors.toList()));
} else if (children.length == 1) {
builder.field(parent, children[0]);
relationsNode.put(parent, children[0]);
}
}
builder.endObject();
builder.endObject();
}
/**
@ -489,43 +486,43 @@ public class MappingBuilder {
*
* @throws IOException
*/
private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
private void addMultiFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property,
MultiField annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException {
// main field
builder.startObject(property.getFieldName());
ObjectNode mainFieldNode = objectMapper.createObjectNode();
propertyNode.set(property.getFieldName(), mainFieldNode);
if (nestedOrObjectField) {
if (annotation.mainField().dynamic() != Dynamic.INHERIT) {
builder.field(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase());
mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase());
} else if (dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
}
}
addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField);
addFieldMappingParameters(mainFieldNode, annotation.mainField(), nestedOrObjectField);
// inner fields
builder.startObject("fields");
ObjectNode innerFieldsNode = mainFieldNode.putObject("fields");
for (InnerField innerField : annotation.otherFields()) {
builder.startObject(innerField.suffix());
addFieldMappingParameters(builder, innerField, false);
builder.endObject();
}
builder.endObject();
builder.endObject();
ObjectNode innerFieldNode = innerFieldsNode.putObject(innerField.suffix());
addFieldMappingParameters(innerFieldNode, innerField, false);
}
}
private void addFieldMappingParameters(XContentBuilder builder, Annotation annotation, boolean nestedOrObjectField)
private void addFieldMappingParameters(ObjectNode fieldNode, Annotation annotation, boolean nestedOrObjectField)
throws IOException {
MappingParameters mappingParameters = MappingParameters.from(annotation);
if (!nestedOrObjectField && mappingParameters.isStore()) {
builder.field(FIELD_PARAM_STORE, true);
fieldNode.put(FIELD_PARAM_STORE, true);
}
mappingParameters.writeTypeAndParametersTo(builder);
mappingParameters.writeTypeAndParametersTo(fieldNode);
}
/**
@ -533,7 +530,7 @@ public class MappingBuilder {
*
* @throws IOException
*/
private void addDynamicTemplatesMapping(XContentBuilder builder, ElasticsearchPersistentEntity<?> entity)
private void addDynamicTemplatesMapping(ObjectNode objectNode, ElasticsearchPersistentEntity<?> entity)
throws IOException {
if (entity.isAnnotationPresent(DynamicTemplates.class)) {
@ -543,11 +540,9 @@ public class MappingBuilder {
String jsonString = ResourceUtil.readFileFromClasspath(mappingPath);
if (hasText(jsonString)) {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(jsonString).get("dynamic_templates");
if (jsonNode != null && jsonNode.isArray()) {
String json = objectMapper.writeValueAsString(jsonNode);
builder.rawField(FIELD_DYNAMIC_TEMPLATES, new ByteArrayInputStream(json.getBytes()), XContentType.JSON);
objectNode.set(FIELD_DYNAMIC_TEMPLATES, jsonNode);
}
}
}

View File

@ -18,11 +18,11 @@ package org.springframework.data.elasticsearch.core.index;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@ -32,9 +32,13 @@ import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.NullValueType;
import org.springframework.data.elasticsearch.annotations.Similarity;
import org.springframework.data.elasticsearch.annotations.TermVector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
/**
* A class to hold the mapping parameters that might be set on
* {@link org.springframework.data.elasticsearch.annotations.Field } or
@ -58,6 +62,7 @@ public final class MappingParameters {
static final String FIELD_PARAM_FORMAT = "format";
static final String FIELD_PARAM_IGNORE_ABOVE = "ignore_above";
static final String FIELD_PARAM_IGNORE_MALFORMED = "ignore_malformed";
static final String FIELD_PARAM_IGNORE_Z_VALUE = "ignore_z_value";
static final String FIELD_PARAM_INDEX = "index";
static final String FIELD_PARAM_INDEX_OPTIONS = "index_options";
static final String FIELD_PARAM_INDEX_PHRASES = "index_phrases";
@ -70,6 +75,7 @@ public final class MappingParameters {
static final String FIELD_PARAM_NORMS = "norms";
static final String FIELD_PARAM_NULL_VALUE = "null_value";
static final String FIELD_PARAM_POSITION_INCREMENT_GAP = "position_increment_gap";
static final String FIELD_PARAM_ORIENTATION = "orientation";
static final String FIELD_PARAM_POSITIVE_SCORE_IMPACT = "positive_score_impact";
static final String FIELD_PARAM_DIMS = "dims";
static final String FIELD_PARAM_SCALING_FACTOR = "scaling_factor";
@ -163,7 +169,8 @@ public final class MappingParameters {
positiveScoreImpact = field.positiveScoreImpact();
dims = field.dims();
if (type == FieldType.Dense_Vector) {
Assert.isTrue(dims >= 1 && dims <= 2048, "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
Assert.isTrue(dims >= 1 && dims <= 2048,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
}
Assert.isTrue(field.enabled() || type == FieldType.Object, "enabled false is only allowed for field type object");
enabled = field.enabled();
@ -205,7 +212,8 @@ public final class MappingParameters {
positiveScoreImpact = field.positiveScoreImpact();
dims = field.dims();
if (type == FieldType.Dense_Vector) {
Assert.isTrue(dims >= 1 && dims <= 2048, "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
Assert.isTrue(dims >= 1 && dims <= 2048,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
}
enabled = true;
eagerGlobalOrdinals = field.eagerGlobalOrdinals();
@ -216,20 +224,20 @@ public final class MappingParameters {
}
/**
* writes the different fields to the builder.
* writes the different fields to an {@link ObjectNode}.
*
* @param builder must not be {@literal null}.
* @param objectNode must not be {@literal null}
*/
public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException {
public void writeTypeAndParametersTo(ObjectNode objectNode) throws IOException {
Assert.notNull(builder, "builder must ot be null");
Assert.notNull(objectNode, "objectNode must not be null");
if (fielddata) {
builder.field(FIELD_PARAM_DATA, fielddata);
objectNode.put(FIELD_PARAM_DATA, fielddata);
}
if (type != FieldType.Auto) {
builder.field(FIELD_PARAM_TYPE, type.name().toLowerCase());
objectNode.put(FIELD_PARAM_TYPE, type.toString().toLowerCase());
if (type == FieldType.Date) {
List<String> formats = new ArrayList<>();
@ -246,125 +254,123 @@ public final class MappingParameters {
Collections.addAll(formats, dateFormatPatterns);
if (!formats.isEmpty()) {
builder.field(FIELD_PARAM_FORMAT, String.join("||", formats));
objectNode.put(FIELD_PARAM_FORMAT, String.join("||", formats));
}
}
}
if (!index) {
builder.field(FIELD_PARAM_INDEX, index);
objectNode.put(FIELD_PARAM_INDEX, index);
}
if (!StringUtils.isEmpty(analyzer)) {
builder.field(FIELD_PARAM_INDEX_ANALYZER, analyzer);
if (StringUtils.hasLength(analyzer)) {
objectNode.put(FIELD_PARAM_INDEX_ANALYZER, analyzer);
}
if (!StringUtils.isEmpty(searchAnalyzer)) {
builder.field(FIELD_PARAM_SEARCH_ANALYZER, searchAnalyzer);
if (StringUtils.hasLength(searchAnalyzer)) {
objectNode.put(FIELD_PARAM_SEARCH_ANALYZER, searchAnalyzer);
}
if (!StringUtils.isEmpty(normalizer)) {
builder.field(FIELD_PARAM_NORMALIZER, normalizer);
if (StringUtils.hasLength(normalizer)) {
objectNode.put(FIELD_PARAM_NORMALIZER, normalizer);
}
if (copyTo != null && copyTo.length > 0) {
builder.field(FIELD_PARAM_COPY_TO, copyTo);
objectNode.putArray(FIELD_PARAM_COPY_TO)
.addAll(Arrays.stream(copyTo).map(TextNode::valueOf).collect(Collectors.toList()));
}
if (ignoreAbove != null) {
Assert.isTrue(ignoreAbove >= 0, "ignore_above must be a positive value");
builder.field(FIELD_PARAM_IGNORE_ABOVE, ignoreAbove);
objectNode.put(FIELD_PARAM_IGNORE_ABOVE, ignoreAbove);
}
if (!coerce) {
builder.field(FIELD_PARAM_COERCE, coerce);
objectNode.put(FIELD_PARAM_COERCE, coerce);
}
if (!docValues) {
builder.field(FIELD_PARAM_DOC_VALUES, docValues);
objectNode.put(FIELD_PARAM_DOC_VALUES, docValues);
}
if (ignoreMalformed) {
builder.field(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed);
objectNode.put(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed);
}
if (indexOptions != IndexOptions.none) {
builder.field(FIELD_PARAM_INDEX_OPTIONS, indexOptions);
objectNode.put(FIELD_PARAM_INDEX_OPTIONS, indexOptions.toString());
}
if (indexPhrases) {
builder.field(FIELD_PARAM_INDEX_PHRASES, indexPhrases);
objectNode.put(FIELD_PARAM_INDEX_PHRASES, indexPhrases);
}
if (indexPrefixes != null) {
builder.startObject(FIELD_PARAM_INDEX_PREFIXES);
ObjectNode prefixNode = objectNode.putObject(FIELD_PARAM_INDEX_PREFIXES);
if (indexPrefixes.minChars() != IndexPrefixes.MIN_DEFAULT) {
builder.field(FIELD_PARAM_INDEX_PREFIXES_MIN_CHARS, indexPrefixes.minChars());
prefixNode.put(FIELD_PARAM_INDEX_PREFIXES_MIN_CHARS, indexPrefixes.minChars());
}
if (indexPrefixes.maxChars() != IndexPrefixes.MAX_DEFAULT) {
builder.field(FIELD_PARAM_INDEX_PREFIXES_MAX_CHARS, indexPrefixes.maxChars());
prefixNode.put(FIELD_PARAM_INDEX_PREFIXES_MAX_CHARS, indexPrefixes.maxChars());
}
builder.endObject();
}
if (!norms) {
builder.field(FIELD_PARAM_NORMS, norms);
objectNode.put(FIELD_PARAM_NORMS, norms);
}
if (!StringUtils.isEmpty(nullValue)) {
Object value;
if (StringUtils.hasLength(nullValue)) {
switch (nullValueType) {
case Integer:
value = Integer.valueOf(nullValue);
objectNode.put(FIELD_PARAM_NULL_VALUE, Integer.valueOf(nullValue));
break;
case Long:
value = Long.valueOf(nullValue);
objectNode.put(FIELD_PARAM_NULL_VALUE, Long.valueOf(nullValue));
break;
case Double:
value = Double.valueOf(nullValue);
objectNode.put(FIELD_PARAM_NULL_VALUE, Double.valueOf(nullValue));
break;
case String:
default:
value = nullValue;
objectNode.put(FIELD_PARAM_NULL_VALUE, nullValue);
break;
}
builder.field(FIELD_PARAM_NULL_VALUE, value);
}
if (positionIncrementGap != null && positionIncrementGap >= 0) {
builder.field(FIELD_PARAM_POSITION_INCREMENT_GAP, positionIncrementGap);
objectNode.put(FIELD_PARAM_POSITION_INCREMENT_GAP, positionIncrementGap);
}
if (similarity != Similarity.Default) {
builder.field(FIELD_PARAM_SIMILARITY, similarity);
objectNode.put(FIELD_PARAM_SIMILARITY, similarity.toString());
}
if (termVector != TermVector.none) {
builder.field(FIELD_PARAM_TERM_VECTOR, termVector);
objectNode.put(FIELD_PARAM_TERM_VECTOR, termVector.toString());
}
if (type == FieldType.Scaled_Float) {
builder.field(FIELD_PARAM_SCALING_FACTOR, scalingFactor);
objectNode.put(FIELD_PARAM_SCALING_FACTOR, scalingFactor);
}
if (maxShingleSize != null) {
builder.field(FIELD_PARAM_MAX_SHINGLE_SIZE, maxShingleSize);
objectNode.put(FIELD_PARAM_MAX_SHINGLE_SIZE, maxShingleSize);
}
if (!positiveScoreImpact) {
builder.field(FIELD_PARAM_POSITIVE_SCORE_IMPACT, positiveScoreImpact);
objectNode.put(FIELD_PARAM_POSITIVE_SCORE_IMPACT, positiveScoreImpact);
}
if (type == FieldType.Dense_Vector) {
builder.field(FIELD_PARAM_DIMS, dims);
objectNode.put(FIELD_PARAM_DIMS, dims);
}
if (!enabled) {
builder.field(FIELD_PARAM_ENABLED, enabled);
objectNode.put(FIELD_PARAM_ENABLED, enabled);
}
if (eagerGlobalOrdinals) {
builder.field(FIELD_PARAM_EAGER_GLOBAL_ORDINALS, eagerGlobalOrdinals);
objectNode.put(FIELD_PARAM_EAGER_GLOBAL_ORDINALS, eagerGlobalOrdinals);
}
}
}

View File

@ -28,7 +28,7 @@ import org.springframework.data.mapping.MappingException;
import org.springframework.lang.Nullable;
/**
* Subclass of {@link MappingBuilder} with specialized methods TO inhibit blocking CALLS
* Subclass of {@link MappingBuilder} with specialized methods To inhibit blocking calls
*
* @author Peter-Josef Meisch
* @since 4.3

View File

@ -15,7 +15,7 @@
*/
package org.springframework.data.elasticsearch.core.mapping;
import org.elasticsearch.index.VersionType;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Dynamic;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.index.Settings;
@ -61,7 +61,7 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
String settingPath();
@Nullable
VersionType getVersionType();
Document.VersionType getVersionType();
boolean isCreateIndexAndMapping();

View File

@ -19,7 +19,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import org.elasticsearch.index.VersionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
@ -72,7 +71,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
private final Lazy<SettingsParameter> settingsParameter;
private @Nullable ElasticsearchPersistentProperty seqNoPrimaryTermProperty;
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
private @Nullable VersionType versionType;
private @Nullable Document.VersionType versionType;
private boolean createIndexAndMapping;
private final Dynamic dynamic;
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
@ -156,7 +155,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
@Nullable
@Override
public VersionType getVersionType() {
public Document.VersionType getVersionType() {
return versionType;
}

View File

@ -23,9 +23,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.unit.TimeValue;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.lang.Nullable;
@ -52,7 +49,7 @@ abstract class AbstractQuery implements Query {
protected float minScore;
@Nullable protected Collection<String> ids;
@Nullable protected String route;
protected SearchType searchType = SearchType.DFS_QUERY_THEN_FETCH;
protected SearchType searchType = SearchType.QUERY_THEN_FETCH;
@Nullable protected IndicesOptions indicesOptions;
protected boolean trackScores;
@Nullable protected String preference;
@ -61,7 +58,7 @@ abstract class AbstractQuery implements Query {
@Nullable private Boolean trackTotalHits;
@Nullable private Integer trackTotalHitsUpTo;
@Nullable private Duration scrollTime;
@Nullable private TimeValue timeout;
@Nullable private Duration timeout;
private boolean explain = false;
@Nullable private List<Object> searchAfter;
protected List<RescorerQuery> rescorerQueries = new ArrayList<>();
@ -271,7 +268,7 @@ abstract class AbstractQuery implements Query {
@Nullable
@Override
public TimeValue getTimeout() {
public Duration getTimeout() {
return timeout;
}
@ -281,7 +278,7 @@ abstract class AbstractQuery implements Query {
* @param timeout
* @since 4.2
*/
public void setTimeout(@Nullable TimeValue timeout) {
public void setTimeout(@Nullable Duration timeout) {
this.timeout = timeout;
}

View File

@ -15,18 +15,20 @@
*/
package org.springframework.data.elasticsearch.core.query;
import java.time.Duration;
import java.util.List;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.springframework.data.elasticsearch.core.ActiveShardCount;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.lang.Nullable;
/**
* Options that may be passed to an
* {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkIndex(List, BulkOptions, IndexCoordinates)} or
* {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkUpdate(List, BulkOptions, IndexCoordinates)} call. <br/>
* {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkIndex(List, BulkOptions, IndexCoordinates)}
* or
* {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkUpdate(List, BulkOptions, IndexCoordinates)}
* call. <br/>
* Use {@link BulkOptions#builder()} to obtain a builder, then set the desired properties and call
* {@link BulkOptionsBuilder#build()} to get the BulkOptions object.
*
@ -38,13 +40,13 @@ public class BulkOptions {
private static final BulkOptions defaultOptions = builder().build();
private final @Nullable TimeValue timeout;
private final @Nullable WriteRequest.RefreshPolicy refreshPolicy;
private final @Nullable Duration timeout;
private final @Nullable RefreshPolicy refreshPolicy;
private final @Nullable ActiveShardCount waitForActiveShards;
private final @Nullable String pipeline;
private final @Nullable String routingId;
private BulkOptions(@Nullable TimeValue timeout, @Nullable WriteRequest.RefreshPolicy refreshPolicy,
private BulkOptions(@Nullable Duration timeout, @Nullable RefreshPolicy refreshPolicy,
@Nullable ActiveShardCount waitForActiveShards, @Nullable String pipeline, @Nullable String routingId) {
this.timeout = timeout;
this.refreshPolicy = refreshPolicy;
@ -54,12 +56,12 @@ public class BulkOptions {
}
@Nullable
public TimeValue getTimeout() {
public Duration getTimeout() {
return timeout;
}
@Nullable
public WriteRequest.RefreshPolicy getRefreshPolicy() {
public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}
@ -101,20 +103,20 @@ public class BulkOptions {
*/
public static class BulkOptionsBuilder {
private @Nullable TimeValue timeout;
private @Nullable WriteRequest.RefreshPolicy refreshPolicy;
private @Nullable Duration timeout;
private @Nullable RefreshPolicy refreshPolicy;
private @Nullable ActiveShardCount waitForActiveShards;
private @Nullable String pipeline;
private @Nullable String routingId;
private BulkOptionsBuilder() {}
public BulkOptionsBuilder withTimeout(TimeValue timeout) {
public BulkOptionsBuilder withTimeout(Duration timeout) {
this.timeout = timeout;
return this;
}
public BulkOptionsBuilder withRefreshPolicy(WriteRequest.RefreshPolicy refreshPolicy) {
public BulkOptionsBuilder withRefreshPolicy(RefreshPolicy refreshPolicy) {
this.refreshPolicy = refreshPolicy;
return this;
}

View File

@ -17,11 +17,7 @@ package org.springframework.data.elasticsearch.core.query;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.ScrollableHitSource;
import org.springframework.lang.Nullable;
/**
@ -47,8 +43,8 @@ public class ByQueryResponse {
private final List<SearchFailure> searchFailures;
private ByQueryResponse(long took, boolean timedOut, long total, long updated, long deleted, int batches,
long versionConflicts, long noops, long bulkRetries, long searchRetries,
@Nullable String reasonCancelled, List<Failure> failures, List<SearchFailure> searchFailures) {
long versionConflicts, long noops, long bulkRetries, long searchRetries, @Nullable String reasonCancelled,
List<Failure> failures, List<SearchFailure> searchFailures) {
this.took = took;
this.timedOut = timedOut;
this.total = total;
@ -167,34 +163,6 @@ public class ByQueryResponse {
return new ByQueryResponseBuilder();
}
public static ByQueryResponse of(BulkByScrollResponse bulkByScrollResponse) {
final List<Failure> failures = bulkByScrollResponse.getBulkFailures() //
.stream() //
.map(Failure::of) //
.collect(Collectors.toList()); //
final List<SearchFailure> searchFailures = bulkByScrollResponse.getSearchFailures() //
.stream() //
.map(SearchFailure::of) //
.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(); //
}
public static class Failure {
@Nullable private final String index;
@ -267,25 +235,6 @@ public class ByQueryResponse {
return new FailureBuilder();
}
/**
* Create a new {@link Failure} from {@link BulkItemResponse.Failure}
*
* @param failure {@link BulkItemResponse.Failure} to translate
* @return a new {@link Failure}
*/
public static Failure of(BulkItemResponse.Failure failure) {
return 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(); //
}
/**
* Builder for {@link Failure}
*/
@ -354,8 +303,8 @@ public class ByQueryResponse {
@Nullable private final Integer shardId;
@Nullable private final String nodeId;
private SearchFailure(Throwable reason, @Nullable Integer status, @Nullable String index,
@Nullable Integer shardId, @Nullable String nodeId) {
private SearchFailure(Throwable reason, @Nullable Integer status, @Nullable String index, @Nullable Integer shardId,
@Nullable String nodeId) {
this.reason = reason;
this.status = status;
this.index = index;
@ -396,22 +345,6 @@ public class ByQueryResponse {
return new SearchFailureBuilder();
}
/**
* Create a new {@link SearchFailure} from {@link ScrollableHitSource.SearchFailure}
*
* @param searchFailure {@link ScrollableHitSource.SearchFailure} to translate
* @return a new {@link SearchFailure}
*/
public static SearchFailure of(ScrollableHitSource.SearchFailure searchFailure) {
return builder() //
.withReason(searchFailure.getReason()) //
.withIndex(searchFailure.getIndex()) //
.withNodeId(searchFailure.getNodeId()) //
.withShardId(searchFailure.getShardId()) //
.withStatus(searchFailure.getStatus().getStatus()) //
.build(); //
}
/**
* Builder for {@link SearchFailure}
*/

View File

@ -15,23 +15,35 @@
*/
package org.springframework.data.elasticsearch.core.query;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Encapsulates an Elasticsearch {@link org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder} to prevent
* leaking of Elasticsearch classes into the query API.
* Combines a {@link Highlight} definition with the type of the entity where it's present on a method.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
public class HighlightQuery {
private final HighlightBuilder highlightBuilder;
public HighlightQuery(HighlightBuilder highlightBuilder) {
this.highlightBuilder = highlightBuilder;
private final Highlight highlight;
@Nullable private final Class<?> type;
public HighlightQuery(Highlight highlight, @Nullable Class<?> type) {
Assert.notNull(highlight, "highlight must not be null");
this.highlight = highlight;
this.type = type;
}
public HighlightBuilder getHighlightBuilder() {
return highlightBuilder;
public Highlight getHighlight() {
return highlight;
}
@Nullable
public Class<?> getType() {
return type;
}
}

View File

@ -20,14 +20,15 @@ import java.util.stream.Collectors;
import org.elasticsearch.search.fetch.subphase.highlight.AbstractHighlighterBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.HighlightParameters;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightCommonParameters;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@ -45,115 +46,114 @@ public class HighlightQueryBuilder {
}
/**
* creates a HighlightBuilder from an annotation
* creates an Elasticsearch HighlightBuilder from an annotation.
*
* @param highlight, must not be {@literal null}
* @param type the entity type, used to map field names. If null, field names are not mapped.
* @return the builder for the highlight
*/
public HighlightQuery getHighlightQuery(Highlight highlight, @Nullable Class<?> type) {
Assert.notNull(highlight, "highlight must not be null");
public HighlightBuilder getHighlightBuilder(Highlight highlight, @Nullable Class<?> type) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
addParameters(highlight.parameters(), highlightBuilder, type);
addParameters(highlight.getParameters(), highlightBuilder, type);
for (HighlightField highlightField : highlight.fields()) {
String mappedName = mapFieldName(highlightField.name(), type);
for (HighlightField highlightField : highlight.getFields()) {
String mappedName = mapFieldName(highlightField.getName(), type);
HighlightBuilder.Field field = new HighlightBuilder.Field(mappedName);
addParameters(highlightField.parameters(), field, type);
addParameters(highlightField.getParameters(), field, type);
highlightBuilder.field(field);
}
return new HighlightQuery(highlightBuilder);
return highlightBuilder;
}
private void addParameters(HighlightParameters parameters, AbstractHighlighterBuilder<?> builder, Class<?> type) {
private <P extends HighlightCommonParameters> void addParameters(P parameters, AbstractHighlighterBuilder<?> builder,
@Nullable Class<?> type) {
if (StringUtils.hasLength(parameters.boundaryChars())) {
builder.boundaryChars(parameters.boundaryChars().toCharArray());
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
builder.boundaryChars(parameters.getBoundaryChars().toCharArray());
}
if (parameters.boundaryMaxScan() > -1) {
builder.boundaryMaxScan(parameters.boundaryMaxScan());
if (parameters.getBoundaryMaxScan() > -1) {
builder.boundaryMaxScan(parameters.getBoundaryMaxScan());
}
if (StringUtils.hasLength(parameters.boundaryScanner())) {
builder.boundaryScannerType(parameters.boundaryScanner());
if (StringUtils.hasLength(parameters.getBoundaryScanner())) {
builder.boundaryScannerType(parameters.getBoundaryScanner());
}
if (StringUtils.hasLength(parameters.boundaryScannerLocale())) {
builder.boundaryScannerLocale(parameters.boundaryScannerLocale());
if (StringUtils.hasLength(parameters.getBoundaryScannerLocale())) {
builder.boundaryScannerLocale(parameters.getBoundaryScannerLocale());
}
if (parameters.forceSource()) { // default is false
builder.forceSource(parameters.forceSource());
if (parameters.getForceSource()) { // default is false
builder.forceSource(true);
}
if (StringUtils.hasLength(parameters.fragmenter())) {
builder.fragmenter(parameters.fragmenter());
if (StringUtils.hasLength(parameters.getFragmenter())) {
builder.fragmenter(parameters.getFragmenter());
}
if (parameters.fragmentSize() > -1) {
builder.fragmentSize(parameters.fragmentSize());
if (parameters.getFragmentSize() > -1) {
builder.fragmentSize(parameters.getFragmentSize());
}
if (parameters.noMatchSize() > -1) {
builder.noMatchSize(parameters.noMatchSize());
if (parameters.getNoMatchSize() > -1) {
builder.noMatchSize(parameters.getNoMatchSize());
}
if (parameters.numberOfFragments() > -1) {
builder.numOfFragments(parameters.numberOfFragments());
if (parameters.getNumberOfFragments() > -1) {
builder.numOfFragments(parameters.getNumberOfFragments());
}
if (StringUtils.hasLength(parameters.order())) {
builder.order(parameters.order());
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(parameters.getOrder());
}
if (parameters.phraseLimit() > -1) {
builder.phraseLimit(parameters.phraseLimit());
if (parameters.getPhraseLimit() > -1) {
builder.phraseLimit(parameters.getPhraseLimit());
}
if (parameters.preTags().length > 0) {
builder.preTags(parameters.preTags());
if (parameters.getPreTags().length > 0) {
builder.preTags(parameters.getPreTags());
}
if (parameters.postTags().length > 0) {
builder.postTags(parameters.postTags());
if (parameters.getPostTags().length > 0) {
builder.postTags(parameters.getPostTags());
}
if (!parameters.requireFieldMatch()) { // default is true
builder.requireFieldMatch(parameters.requireFieldMatch());
if (!parameters.getRequireFieldMatch()) { // default is true
builder.requireFieldMatch(false);
}
if (StringUtils.hasLength(parameters.type())) {
builder.highlighterType(parameters.type());
if (StringUtils.hasLength(parameters.getType())) {
builder.highlighterType(parameters.getType());
}
if (builder instanceof HighlightBuilder) {
if (builder instanceof HighlightBuilder && parameters instanceof HighlightParameters) {
HighlightBuilder highlightBuilder = (HighlightBuilder) builder;
HighlightParameters highlightParameters = (HighlightParameters) parameters;
if (StringUtils.hasLength(parameters.encoder())) {
highlightBuilder.encoder(parameters.encoder());
if (StringUtils.hasLength(highlightParameters.getEncoder())) {
highlightBuilder.encoder(highlightParameters.getEncoder());
}
if (StringUtils.hasLength(parameters.tagsSchema())) {
highlightBuilder.tagsSchema(parameters.tagsSchema());
if (StringUtils.hasLength(highlightParameters.getTagsSchema())) {
highlightBuilder.tagsSchema(highlightParameters.getTagsSchema());
}
}
if (builder instanceof HighlightBuilder.Field) {
if (builder instanceof HighlightBuilder.Field && parameters instanceof HighlightFieldParameters) {
HighlightBuilder.Field field = (HighlightBuilder.Field) builder;
HighlightFieldParameters fieldParameters = (HighlightFieldParameters) parameters;
if (parameters.fragmentOffset() > -1) {
field.fragmentOffset(parameters.fragmentOffset());
if ((fieldParameters).getFragmentOffset() > -1) {
field.fragmentOffset(fieldParameters.getFragmentOffset());
}
if (parameters.matchedFields().length > 0) {
field.matchedFields(Arrays.stream(parameters.matchedFields()) //
if (fieldParameters.getMatchedFields().length > 0) {
field.matchedFields(Arrays.stream(fieldParameters.getMatchedFields()) //
.map(fieldName -> mapFieldName(fieldName, type)) //
.collect(Collectors.toList()) //
.toArray(new String[] {})); //

View File

@ -0,0 +1,89 @@
/*
* 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.query;
import java.util.EnumSet;
/**
* Class mirroring the IndicesOptions from Elasticsearch in Spring Data Elasticsearch API.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class IndicesOptions {
private EnumSet<Option> options;
private EnumSet<WildcardStates> expandWildcards;
public static final IndicesOptions STRICT_EXPAND_OPEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES), EnumSet.of(IndicesOptions.WildcardStates.OPEN));
public static final IndicesOptions STRICT_EXPAND_OPEN_HIDDEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES),
EnumSet.of(IndicesOptions.WildcardStates.OPEN, IndicesOptions.WildcardStates.HIDDEN));
public static final IndicesOptions LENIENT_EXPAND_OPEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.IGNORE_UNAVAILABLE),
EnumSet.of(IndicesOptions.WildcardStates.OPEN));
public static final IndicesOptions LENIENT_EXPAND_OPEN_HIDDEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.IGNORE_UNAVAILABLE),
EnumSet.of(IndicesOptions.WildcardStates.OPEN, IndicesOptions.WildcardStates.HIDDEN));
public static final IndicesOptions LENIENT_EXPAND_OPEN_CLOSED = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.IGNORE_UNAVAILABLE),
EnumSet.of(IndicesOptions.WildcardStates.OPEN, IndicesOptions.WildcardStates.CLOSED));
public static final IndicesOptions LENIENT_EXPAND_OPEN_CLOSED_HIDDEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.IGNORE_UNAVAILABLE),
EnumSet.of(IndicesOptions.WildcardStates.OPEN, IndicesOptions.WildcardStates.CLOSED,
IndicesOptions.WildcardStates.HIDDEN));
public static final IndicesOptions STRICT_EXPAND_OPEN_CLOSED = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES),
EnumSet.of(IndicesOptions.WildcardStates.OPEN, IndicesOptions.WildcardStates.CLOSED));
public static final IndicesOptions STRICT_EXPAND_OPEN_CLOSED_HIDDEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES), EnumSet.of(IndicesOptions.WildcardStates.OPEN,
IndicesOptions.WildcardStates.CLOSED, IndicesOptions.WildcardStates.HIDDEN));
public static final IndicesOptions STRICT_EXPAND_OPEN_FORBID_CLOSED = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.FORBID_CLOSED_INDICES),
EnumSet.of(IndicesOptions.WildcardStates.OPEN));
public static final IndicesOptions STRICT_EXPAND_OPEN_HIDDEN_FORBID_CLOSED = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.FORBID_CLOSED_INDICES),
EnumSet.of(IndicesOptions.WildcardStates.OPEN, IndicesOptions.WildcardStates.HIDDEN));
public static final IndicesOptions STRICT_EXPAND_OPEN_FORBID_CLOSED_IGNORE_THROTTLED = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES, IndicesOptions.Option.FORBID_CLOSED_INDICES,
IndicesOptions.Option.IGNORE_THROTTLED),
EnumSet.of(IndicesOptions.WildcardStates.OPEN));
public static final IndicesOptions STRICT_SINGLE_INDEX_NO_EXPAND_FORBID_CLOSED = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.FORBID_ALIASES_TO_MULTIPLE_INDICES, IndicesOptions.Option.FORBID_CLOSED_INDICES),
EnumSet.noneOf(IndicesOptions.WildcardStates.class));
public IndicesOptions(EnumSet<Option> options, EnumSet<WildcardStates> expandWildcards) {
this.options = options;
this.expandWildcards = expandWildcards;
}
public EnumSet<Option> getOptions() {
return options;
}
public EnumSet<WildcardStates> getExpandWildcards() {
return expandWildcards;
}
public enum WildcardStates {
OPEN, CLOSED, HIDDEN;
}
public enum Option {
IGNORE_UNAVAILABLE, IGNORE_ALIASES, ALLOW_NO_INDICES, FORBID_ALIASES_TO_MULTIPLE_INDICES, FORBID_CLOSED_INDICES, IGNORE_THROTTLED;
}
}

View File

@ -17,14 +17,13 @@ package org.springframework.data.elasticsearch.core.query;
import static org.springframework.util.CollectionUtils.*;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
@ -75,7 +74,7 @@ public class NativeSearchQueryBuilder {
@Nullable private String preference;
@Nullable private Integer maxResults;
@Nullable private Boolean trackTotalHits;
@Nullable private TimeValue timeout;
@Nullable private Duration timeout;
private final List<RescorerQuery> rescorerQueries = new ArrayList<>();
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {
@ -302,7 +301,7 @@ public class NativeSearchQueryBuilder {
return this;
}
public NativeSearchQueryBuilder withTimeout(TimeValue timeout) {
public NativeSearchQueryBuilder withTimeout(Duration timeout) {
this.timeout = timeout;
return this;
}
@ -369,7 +368,7 @@ public class NativeSearchQueryBuilder {
}
if (searchType != null) {
nativeSearchQuery.setSearchType(searchType);
nativeSearchQuery.setSearchType(Query.SearchType.valueOf(searchType.name()));
}
if (indicesOptions != null) {

View File

@ -21,10 +21,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@ -54,10 +50,9 @@ public interface Query {
*
* @return new instance of {@link Query}.
* @since 3.2
* @see QueryBuilders#matchAllQuery()
*/
static Query findAll() {
return new StringQuery(QueryBuilders.matchAllQuery().toString());
return new StringQuery("{\"match_all\":{}}");
}
/**
@ -295,7 +290,7 @@ public interface Query {
* @since 4.2
*/
@Nullable
TimeValue getTimeout();
Duration getTimeout();
/**
* @return {@literal true} when the query has the explain parameter set, defaults to {@literal false}
@ -360,4 +355,11 @@ public interface Query {
*/
@Nullable
Boolean getRequestCache();
/**
* @since 4.3
*/
enum SearchType {
QUERY_THEN_FETCH, DFS_QUERY_THEN_FETCH
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.query.highlight;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @since 4.3
*/
public class Highlight {
private final HighlightParameters parameters;
private final List<HighlightField> fields;
public Highlight(HighlightParameters parameters, List<HighlightField> fields) {
Assert.notNull(parameters, "parameters must not be null");
Assert.notNull(fields, "fields must not be null");
this.parameters = parameters;
this.fields = fields;
}
public HighlightParameters getParameters() {
return parameters;
}
public List<HighlightField> getFields() {
return fields;
}
/**
* Creates a {@link Highlight} from an Annotation instance.
*
* @param highlight must not be {@literal null}
* @return highlight definition
*/
public static Highlight of(org.springframework.data.elasticsearch.annotations.Highlight highlight) {
Assert.notNull(highlight, "highlight must not be null");
org.springframework.data.elasticsearch.annotations.HighlightParameters parameters = highlight.parameters();
HighlightParameters highlightParameters = HighlightParameters.builder() //
.withBoundaryChars(parameters.boundaryChars()) //
.withBoundaryMaxScan(parameters.boundaryMaxScan()) //
.withBoundaryScanner(parameters.boundaryScanner()) //
.withBoundaryScannerLocale(parameters.boundaryScannerLocale()) //
.withEncoder(parameters.encoder()) //
.withForceSource(parameters.forceSource()) //
.withFragmenter(parameters.fragmenter()) //
.withFragmentSize(parameters.fragmentSize()) //
.withNoMatchSize(parameters.noMatchSize()) //
.withNumberOfFragments(parameters.numberOfFragments()) //
.withOrder(parameters.order()) //
.withPhraseLimit(parameters.phraseLimit()) //
.withPreTags(parameters.preTags()) //
.withPostTags(parameters.postTags()) //
.withRequireFieldMatch(parameters.requireFieldMatch()) //
.withTagsSchema(parameters.tagsSchema()) //
.withType(parameters.type()) //
.build();
List<HighlightField> highlightFields = Arrays.stream(highlight.fields()) //
.map(HighlightField::of) //
.collect(Collectors.toList());
return new Highlight(highlightParameters, highlightFields);
}
}

View File

@ -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.query.highlight;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @since 4.3
*/
abstract public class HighlightCommonParameters {
private final String boundaryChars;
private final int boundaryMaxScan;
private final String boundaryScanner;
private final String boundaryScannerLocale;
private final boolean forceSource;
private final String fragmenter;
private final int fragmentSize;
private final int noMatchSize;
private final int numberOfFragments;
private final String order;
private final int phraseLimit;
private final String[] preTags;
private final String[] postTags;
private final boolean requireFieldMatch;
private final String type;
protected HighlightCommonParameters(HighlightCommonParametersBuilder<?> builder) {
Assert.notNull(builder, "builder must not be null");
boundaryChars = builder.boundaryChars;
boundaryMaxScan = builder.boundaryMaxScan;
boundaryScanner = builder.boundaryScanner;
boundaryScannerLocale = builder.boundaryScannerLocale;
forceSource = builder.forceSource;
fragmenter = builder.fragmenter;
fragmentSize = builder.fragmentSize;
noMatchSize = builder.noMatchSize;
numberOfFragments = builder.numberOfFragments;
order = builder.order;
phraseLimit = builder.phraseLimit;
preTags = builder.preTags;
postTags = builder.postTags;
requireFieldMatch = builder.requireFieldMatch;
type = builder.type;
}
public String getBoundaryChars() {
return boundaryChars;
}
public int getBoundaryMaxScan() {
return boundaryMaxScan;
}
public String getBoundaryScanner() {
return boundaryScanner;
}
public String getBoundaryScannerLocale() {
return boundaryScannerLocale;
}
public boolean getForceSource() {
return forceSource;
}
public String getFragmenter() {
return fragmenter;
}
public int getFragmentSize() {
return fragmentSize;
}
public int getNoMatchSize() {
return noMatchSize;
}
public int getNumberOfFragments() {
return numberOfFragments;
}
public String getOrder() {
return order;
}
public int getPhraseLimit() {
return phraseLimit;
}
public String[] getPreTags() {
return preTags;
}
public String[] getPostTags() {
return postTags;
}
public boolean getRequireFieldMatch() {
return requireFieldMatch;
}
public String getType() {
return type;
}
@SuppressWarnings("unchecked")
public static abstract class HighlightCommonParametersBuilder<SELF extends HighlightCommonParametersBuilder<SELF>> {
private String boundaryChars = "";
private int boundaryMaxScan = -1;
private String boundaryScanner = "";
private String boundaryScannerLocale = "";
private boolean forceSource = false;
private String fragmenter = "";
private int fragmentSize = -1;
private int noMatchSize = -1;
private int numberOfFragments = -1;
private String order = "";
private int phraseLimit = -1;
private String[] preTags = new String[0];
private String[] postTags = new String[0];
private boolean requireFieldMatch = true;
private String type = "";
protected HighlightCommonParametersBuilder() {}
public SELF withBoundaryChars(String boundaryChars) {
this.boundaryChars = boundaryChars;
return (SELF) this;
}
public SELF withBoundaryMaxScan(int boundaryMaxScan) {
this.boundaryMaxScan = boundaryMaxScan;
return (SELF) this;
}
public SELF withBoundaryScanner(String boundaryScanner) {
this.boundaryScanner = boundaryScanner;
return (SELF) this;
}
public SELF withBoundaryScannerLocale(String boundaryScannerLocale) {
this.boundaryScannerLocale = boundaryScannerLocale;
return (SELF) this;
}
public SELF withForceSource(boolean forceSource) {
this.forceSource = forceSource;
return (SELF) this;
}
public SELF withFragmenter(String fragmenter) {
this.fragmenter = fragmenter;
return (SELF) this;
}
public SELF withFragmentSize(int fragmentSize) {
this.fragmentSize = fragmentSize;
return (SELF) this;
}
public SELF withNoMatchSize(int noMatchSize) {
this.noMatchSize = noMatchSize;
return (SELF) this;
}
public SELF withNumberOfFragments(int numberOfFragments) {
this.numberOfFragments = numberOfFragments;
return (SELF) this;
}
public SELF withOrder(String order) {
this.order = order;
return (SELF) this;
}
public SELF withPhraseLimit(int phraseLimit) {
this.phraseLimit = phraseLimit;
return (SELF) this;
}
public SELF withPreTags(String[] preTags) {
this.preTags = preTags;
return (SELF) this;
}
public SELF withPostTags(String[] postTags) {
this.postTags = postTags;
return (SELF) this;
}
public SELF withRequireFieldMatch(boolean requireFieldMatch) {
this.requireFieldMatch = requireFieldMatch;
return (SELF) this;
}
public SELF withType(String type) {
this.type = type;
return (SELF) this;
}
public abstract HighlightCommonParameters build();
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.query.highlight;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @since 4.3
*/
public class HighlightField {
private final String name;
private final HighlightFieldParameters parameters;
public HighlightField(String name, HighlightFieldParameters parameters) {
Assert.notNull(name, "name must not be null");
Assert.notNull(parameters, "parameters must not be null");
this.name = name;
this.parameters = parameters;
}
public String getName() {
return name;
}
public HighlightFieldParameters getParameters() {
return parameters;
}
public static HighlightField of(org.springframework.data.elasticsearch.annotations.HighlightField field) {
org.springframework.data.elasticsearch.annotations.HighlightParameters parameters = field.parameters();
HighlightFieldParameters highlightParameters = HighlightFieldParameters.builder() //
.withBoundaryChars(parameters.boundaryChars()) //
.withBoundaryMaxScan(parameters.boundaryMaxScan()) //
.withBoundaryScanner(parameters.boundaryScanner()) //
.withBoundaryScannerLocale(parameters.boundaryScannerLocale()) //
.withForceSource(parameters.forceSource()) //
.withFragmenter(parameters.fragmenter()) //
.withFragmentOffset(parameters.fragmentOffset()) //
.withFragmentSize(parameters.fragmentSize()) //
.withMatchedFields(parameters.matchedFields()) //
.withNoMatchSize(parameters.noMatchSize()) //
.withNumberOfFragments(parameters.numberOfFragments()) //
.withOrder(parameters.order()) //
.withPhraseLimit(parameters.phraseLimit()) //
.withPreTags(parameters.preTags()) //
.withPostTags(parameters.postTags()) //
.withRequireFieldMatch(parameters.requireFieldMatch()) //
.withType(parameters.type()) //
.build();
return new HighlightField(field.name(), highlightParameters);
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.query.highlight;
/**
* @author Peter-Josef Meisch
* @since 4.3
*/
public class HighlightFieldParameters extends HighlightCommonParameters {
private final int fragmentOffset;
private final String[] matchedFields;
private HighlightFieldParameters(HighlightFieldParametersBuilder builder) {
super(builder);
fragmentOffset = builder.fragmentOffset;
matchedFields = builder.matchedFields;
}
public int getFragmentOffset() {
return fragmentOffset;
}
public String[] getMatchedFields() {
return matchedFields;
}
public static HighlightFieldParametersBuilder builder() {
return new HighlightFieldParametersBuilder();
}
public static final class HighlightFieldParametersBuilder
extends HighlightCommonParametersBuilder<HighlightFieldParametersBuilder> {
private int fragmentOffset = -1;
private String[] matchedFields = new String[0];
public HighlightFieldParametersBuilder withFragmentOffset(int fragmentOffset) {
this.fragmentOffset = fragmentOffset;
return this;
}
public HighlightFieldParametersBuilder withMatchedFields(String[] matchedFields) {
this.matchedFields = matchedFields;
return this;
}
@Override
public HighlightFieldParameters build() {
return new HighlightFieldParameters(this);
}
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.query.highlight;
/**
* @author Peter-Josef Meisch
* @since 4.3
*/
public class HighlightParameters extends HighlightCommonParameters {
private final String encoder;
private final String tagsSchema;
private HighlightParameters(HighlightParametersBuilder builder) {
super(builder);
encoder = builder.encoder;
tagsSchema = builder.tagsSchema;
}
public String getEncoder() {
return encoder;
}
public String getTagsSchema() {
return tagsSchema;
}
public static HighlightParametersBuilder builder() {
return new HighlightParametersBuilder();
}
public static final class HighlightParametersBuilder
extends HighlightCommonParametersBuilder<HighlightParametersBuilder> {
private String encoder = "";
private String tagsSchema = "";
public HighlightParametersBuilder withEncoder(String encoder) {
this.encoder = encoder;
return this;
}
public HighlightParametersBuilder withTagsSchema(String tagsSchema) {
this.tagsSchema = tagsSchema;
return this;
}
@Override
public HighlightParameters build() {
return new HighlightParameters(this);
}
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.
*/
/**
* classes to define highlight settings parameters of a query
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.core.query.highlight;

View File

@ -30,7 +30,6 @@ import org.springframework.data.elasticsearch.core.SearchPage;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.HighlightQueryBuilder;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
@ -119,7 +118,9 @@ public class ElasticsearchQueryMethod extends QueryMethod {
}
private HighlightQuery createAnnotatedHighlightQuery() {
return new HighlightQueryBuilder(mappingContext).getHighlightQuery(highlightAnnotation, getDomainClass());
return new HighlightQuery(
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation),
getDomainClass());
}
/**

View File

@ -15,7 +15,7 @@
*/
package org.springframework.data.elasticsearch.repository.support;
import org.elasticsearch.index.VersionType;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.lang.Nullable;
@ -40,5 +40,5 @@ public interface ElasticsearchEntityInformation<T, ID> extends EntityInformation
Long getVersion(T entity);
@Nullable
VersionType getVersionType();
Document.VersionType getVersionType();
}

View File

@ -15,7 +15,7 @@
*/
package org.springframework.data.elasticsearch.repository.support;
import org.elasticsearch.index.VersionType;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@ -70,7 +70,7 @@ public class MappingElasticsearchEntityInformation<T, ID> extends PersistentEnti
}
@Override
public VersionType getVersionType() {
public Document.VersionType getVersionType() {
return persistentEntity.getVersionType();
}

View File

@ -23,7 +23,6 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
@ -250,19 +249,18 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
Assert.notNull(ids, "Cannot delete 'null' list.");
IndexCoordinates indexCoordinates = getIndexCoordinates();
IdsQueryBuilder idsQueryBuilder = idsQuery();
List<String> idStrings = new ArrayList<>();
for (ID id : ids) {
idsQueryBuilder.addIds(stringIdRepresentation(id));
idStrings.add(stringIdRepresentation(id));
}
if (idsQueryBuilder.ids().isEmpty()) {
if (idStrings.isEmpty()) {
return;
}
Query query = operations.idsQuery(idStrings);
executeAndRefresh((OperationsCallback<Void>) operations -> {
operations.delete(new NativeSearchQueryBuilder().withQuery(idsQueryBuilder).build(), entityClass,
indexCoordinates);
operations.delete(query, entityClass, getIndexCoordinates());
return null;
});
}

View File

@ -18,7 +18,6 @@ package org.springframework.data.elasticsearch.repository.support;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@ -205,9 +204,9 @@ public class SimpleReactiveElasticsearchRepository<T, ID> implements ReactiveEla
return Flux.fromIterable(ids) //
.map(this::convertId) //
.collectList() //
.map(it -> new NativeSearchQueryBuilder().withQuery(new IdsQueryBuilder().addIds(it.toArray(new String[0])))
.build())
.flatMap(it -> operations.delete(it, entityInformation.getJavaType(), entityInformation.getIndexCoordinates())) //
.map(idList -> operations.idsQuery(idList)) //
.flatMap(
query -> operations.delete(query, entityInformation.getJavaType(), entityInformation.getIndexCoordinates())) //
.then(doRefresh());
}
@ -226,8 +225,7 @@ public class SimpleReactiveElasticsearchRepository<T, ID> implements ReactiveEla
.map(entityInformation::getRequiredId) //
.map(this::convertId) //
.collectList() //
.map(it -> new NativeSearchQueryBuilder().withQuery(new IdsQueryBuilder().addIds(it.toArray(new String[0])))
.build())
.map(idList -> operations.idsQuery(idList))
.flatMap(
query -> operations.delete(query, entityInformation.getJavaType(), entityInformation.getIndexCoordinates())) //
.then(doRefresh());

View File

@ -0,0 +1,84 @@
/*
* 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.support;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.Assert;
/**
* A version defined by 3 parts: major minor and revision number.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class Version {
private static final Pattern PATTERN = Pattern.compile("^(\\d+)(\\.(\\d+))?(\\.(\\d+))?.*$");
private final int major;
private final int minor;
private final int revision;
public Version(int major, int minor, int revision) {
this.major = major;
this.minor = minor;
this.revision = revision;
}
@Override
public String toString() {
return "" + major + "." + minor + "." + revision;
}
/**
* Creates a version from a String that matches {@link #PATTERN}; major, minor and revision numbers separated by dots
* with optional trailing characters. A missing revision is treated as 0.
*
* @param s the String to parse
* @return the Version
* @throws IllegalArgumentException if the input is null or cannot be parsed.
*/
public static Version fromString(String s) {
Assert.notNull(s, "s must not be null");
Matcher matcher = PATTERN.matcher(s);
if (!matcher.matches()) {
throw new IllegalArgumentException("invalid input pattern");
}
int major = Integer.parseInt(matcher.group(1));
int minor = matcher.group(3) != null ? Integer.parseInt(matcher.group(3)) : 0;
int revision = matcher.group(5) != null ? Integer.parseInt(matcher.group(5)) : 0;
return new Version(major, minor, revision);
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
public int getRevision() {
return revision;
}
}

View File

@ -19,7 +19,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.elasticsearch.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
@ -36,7 +35,8 @@ public final class VersionInfo {
private static final Logger LOG = LoggerFactory.getLogger(VersionInfo.class);
protected static final String VERSION_PROPERTIES = "versions.properties";
private static final String VERSION_PROPERTIES = "versions.properties";
public static final String VERSION_SPRING_DATA_ELASTICSEARCH = "version.spring-data-elasticsearch";
public static final String VERSION_ELASTICSEARCH_CLIENT = "version.elasticsearch-client";
@ -62,28 +62,29 @@ public final class VersionInfo {
*
* @param clusterVersion the version of the cluster
*/
public static void logVersions(@Nullable String clusterVersion) {
public static void logVersions(String vendor, String runtimeLibraryVersion, @Nullable String clusterVersion) {
try {
String versionSpringDataElasticsearch = versionProperties.getProperty(VERSION_SPRING_DATA_ELASTICSEARCH);
Version versionESBuilt = Version.fromString(versionProperties.getProperty(VERSION_ELASTICSEARCH_CLIENT));
Version versionESUsed = Version.CURRENT;
Version versionESCluster = clusterVersion != null ? Version.fromString(clusterVersion) : null;
Version versionBuiltLibraryES = Version.fromString(versionProperties.getProperty(VERSION_ELASTICSEARCH_CLIENT));
Version versionRuntimeLibrary = Version.fromString(runtimeLibraryVersion);
Version versionCluster = clusterVersion != null ? Version.fromString(clusterVersion) : null;
LOG.info("Version Spring Data Elasticsearch: {}", versionSpringDataElasticsearch.toString());
LOG.info("Version Elasticsearch Client in build: {}", versionESBuilt.toString());
LOG.info("Version Elasticsearch Client used: {}", versionESUsed.toString());
LOG.info("Version Elasticsearch client in build: {}", versionBuiltLibraryES.toString());
LOG.info("Version runtime client used: {} - {}", vendor, versionRuntimeLibrary.toString());
if (differInMajorOrMinor(versionESBuilt, versionESUsed)) {
LOG.warn("Version mismatch in between Elasticsearch Clients build/use: {} - {}", versionESBuilt, versionESUsed);
if (differInMajorOrMinor(versionBuiltLibraryES, versionRuntimeLibrary)) {
LOG.warn("Version mismatch in between Elasticsearch Clients build/use: {} - {}", versionBuiltLibraryES,
versionRuntimeLibrary);
}
if (versionESCluster != null) {
LOG.info("Version Elasticsearch cluster: {}", versionESCluster.toString());
if (versionCluster != null) {
LOG.info("Version cluster: {} - {}", vendor, versionCluster.toString());
if (differInMajorOrMinor(versionESUsed, versionESCluster)) {
LOG.warn("Version mismatch in between Elasticsearch Client and Cluster: {} - {}", versionESUsed,
versionESCluster);
if (differInMajorOrMinor(versionRuntimeLibrary, versionCluster)) {
LOG.warn("Version mismatch in between Client and Cluster: {} - {} - {}", vendor, versionRuntimeLibrary,
versionCluster);
}
}
} catch (Exception e) {
@ -109,7 +110,7 @@ public final class VersionInfo {
}
private static boolean differInMajorOrMinor(Version version1, Version version2) {
return version1.major != version2.major || version1.minor != version2.minor;
return version1.getMajor() != version2.getMajor() || version1.getMinor() != version2.getMinor();
}
private VersionInfo() {}

View File

@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.annotations;
import static org.assertj.core.api.Assertions.*;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import static org.springframework.data.elasticsearch.annotations.Document.VersionType.*;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@ -26,7 +27,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.LocalDate;
import org.elasticsearch.index.VersionType;
import org.json.JSONException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -59,7 +59,7 @@ public class ComposableAnnotationsUnitTest {
assertThat(entity.getIndexCoordinates()).isEqualTo(IndexCoordinates.of("test-no-create"));
assertThat(entity.isCreateIndexAndMapping()).isFalse();
assertThat(entity.getVersionType()).isEqualTo(VersionType.INTERNAL);
assertThat(entity.getVersionType()).isEqualTo(Document.VersionType.INTERNAL);
}
@Test // DATAES-362
@ -120,7 +120,7 @@ public class ComposableAnnotationsUnitTest {
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Document(indexName = "", createIndex = false, versionType = VersionType.INTERNAL)
@Document(indexName = "", createIndex = false, versionType = INTERNAL)
public @interface DocumentNoCreate {
@AliasFor(value = "indexName", annotation = Document.class)

View File

@ -20,12 +20,14 @@ import static org.mockito.Mockito.*;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.elasticsearch.client.RestClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
@ -156,35 +158,73 @@ public class ClientConfigurationUnitTests {
assertThat(clientConfiguration.getWebClientConfigurer()).isEqualTo(Function.identity());
}
@Test // DATAES-719
void shouldUseConfiguredWebClientConfigurer() {
Function<WebClient, WebClient> webClientConfigurer = webClient -> webClient;
@Test // #1885
@DisplayName("should use configured httpClientConfigurer as client configurer")
void shouldUseConfiguredHttpClientConfigurerAsClientConfigurer() {
AtomicInteger callCounter = new AtomicInteger();
ClientConfiguration clientConfiguration = ClientConfiguration.builder() //
.connectedTo("foo", "bar") //
.withWebClientConfigurer(webClientConfigurer) //
.withHttpClientConfigurer(httpClientBuilder -> {
callCounter.incrementAndGet();
return httpClientBuilder;
}) //
.build();
assertThat(clientConfiguration.getWebClientConfigurer()).isEqualTo(webClientConfigurer);
ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> clientConfigurer = clientConfiguration
.getClientConfigurer();
clientConfigurer.configure(HttpAsyncClientBuilder.create());
assertThat(callCounter.get()).isEqualTo(1);
}
@Test // DATAES-588
@DisplayName("should use configured httpClientConfigurer")
void shouldUseConfiguredHttpClientConfigurer() {
@Test // #1885
@DisplayName("should use configured webClientConfigurer as client configurer")
void shouldUseConfiguredWebClientConfigurerAsClientConfigurer() {
RestClientBuilder.HttpClientConfigCallback callback = httpClientBuilder -> httpClientBuilder;
AtomicInteger callCounter = new AtomicInteger();
ClientConfiguration clientConfiguration = ClientConfiguration.builder() //
.connectedTo("foo", "bar") //
.withHttpClientConfigurer(callback) //
.withWebClientConfigurer(webClientConfigurer -> {
callCounter.incrementAndGet();
return webClientConfigurer;
}) //
.build();
assertThat(clientConfiguration.getHttpClientConfigurer()).isEqualTo(callback);
ClientConfiguration.ClientConfigurationCallback<WebClient> clientConfigurer = clientConfiguration
.getClientConfigurer();
clientConfigurer.configure(WebClient.builder().build());
assertThat(callCounter.get()).isEqualTo(1);
}
@Test // #1885
@DisplayName("should use configured client configurer")
void shouldUseConfiguredClientConfigurer() {
AtomicInteger callCounter = new AtomicInteger();
ClientConfiguration clientConfiguration = ClientConfiguration.builder() //
.connectedTo("foo", "bar") //
.withClientConfigurer(clientConfigurer -> {
callCounter.incrementAndGet();
return clientConfigurer;
}) //
.build();
ClientConfiguration.ClientConfigurationCallback<Object> clientConfigurer = clientConfiguration
.getClientConfigurer();
clientConfigurer.configure(new Object());
assertThat(callCounter.get()).isEqualTo(1);
}
private static String buildBasicAuth(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(username, password);
return headers.getFirst(HttpHeaders.AUTHORIZATION);
return Objects.requireNonNull(headers.getFirst(HttpHeaders.AUTHORIZATION));
}
}

View File

@ -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.client;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
@ -18,7 +33,6 @@ import java.util.function.Consumer;
import java.util.stream.Stream;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.ExtendWith;
@ -91,13 +105,8 @@ public class RestClientsTest {
AtomicInteger supplierCount = new AtomicInteger(1);
AtomicInteger clientConfigurerCount = new AtomicInteger(0);
RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> {
clientConfigurerCount.incrementAndGet();
return httpClientBuilder;
};
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
ClientConfiguration clientConfiguration = configurationBuilder //
configurationBuilder //
.connectedTo("localhost:" + server.port()) //
.withBasicAuth("user", "password") //
.withDefaultHeaders(defaultHeaders) //
@ -106,8 +115,21 @@ public class RestClientsTest {
httpHeaders.add("supplied", "val0");
httpHeaders.add("supplied", "val" + supplierCount.getAndIncrement());
return httpHeaders;
}) //
.withHttpClientConfigurer(configCallback).build();
});
if (clientUnderTestFactory instanceof RestClientUnderTestFactory) {
configurationBuilder.withClientConfigurer((RestClients.RestClientConfigurationCallback) httpClientBuilder -> {
clientConfigurerCount.incrementAndGet();
return httpClientBuilder;
});
} else if (clientUnderTestFactory instanceof ReactiveElasticsearchClientUnderTestFactory) {
configurationBuilder.withClientConfigurer((ReactiveRestClients.WebClientConfigurationCallback) webClient -> {
clientConfigurerCount.incrementAndGet();
return webClient;
});
}
ClientConfiguration clientConfiguration = configurationBuilder.build();
ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration);
@ -125,10 +147,7 @@ public class RestClientsTest {
);
}
// clientConfigurer is only used in non-reactive setup
if (!(clientUnderTestFactory instanceof ReactiveElasticsearchClientUnderTestFactory)) {
assertThat(clientConfigurerCount).hasValue(1);
}
});
}

View File

@ -26,7 +26,6 @@ import java.util.HashMap;
import java.util.Map;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.unit.TimeValue;
@ -40,6 +39,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
@ -128,7 +128,7 @@ public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests {
void shouldUseAllOptionsFromUpdateByQuery() throws JSONException {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) //
.withIndicesOptions(IndicesOptions.lenientExpandOpen()) //
.withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN) //
.build(); //
searchQuery.setScrollTime(Duration.ofMillis(1000));
@ -159,7 +159,7 @@ public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests {
// then
assertThat(request).isNotNull();
assertThat(request.getSearchRequest().indicesOptions()).usingRecursiveComparison()
.isEqualTo(IndicesOptions.lenientExpandOpen());
.isEqualTo(IndicesOptions.LENIENT_EXPAND_OPEN);
assertThat(request.getScrollTime().getMillis()).isEqualTo(1000);
assertEquals(request.getSearchRequest().source().toString(), expectedSearchRequest, false);
assertThat(request.isAbortOnVersionConflict()).isTrue();

View File

@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.annotations.Document.VersionType.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.Integer;
import static org.springframework.data.elasticsearch.core.document.Document.*;
@ -44,11 +45,9 @@ import java.util.stream.IntStream;
import org.assertj.core.api.SoftAssertions;
import org.assertj.core.util.Lists;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.query.InnerHitBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
@ -398,7 +397,7 @@ public abstract class ElasticsearchTemplateTests {
operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh();
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withIndicesOptions(IndicesOptions.lenientExpandOpen()).build();
.withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN).build();
// when
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
@ -1651,7 +1650,7 @@ public abstract class ElasticsearchTemplateTests {
// when
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withIndicesOptions(IndicesOptions.lenientExpandOpen()).build();
.withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN).build();
SearchScrollHits<SampleEntity> scroll = ((AbstractElasticsearchTemplate) operations)
.searchScrollStart(scrollTimeInMillis, searchQuery, SampleEntity.class, index);
@ -2450,7 +2449,7 @@ public abstract class ElasticsearchTemplateTests {
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
// when
Query query = new NativeSearchQueryBuilder().withQuery(idsQuery().addIds(documentIdToDelete)).build();
Query query = operations.idsQuery(Arrays.asList(documentIdToDelete));
operations.delete(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName()));
// then
@ -3983,7 +3982,7 @@ public abstract class ElasticsearchTemplateTests {
}
}
@Document(indexName = "#{@indexNameProvider.indexName()}", versionType = VersionType.EXTERNAL_GTE)
@Document(indexName = "#{@indexNameProvider.indexName()}", versionType = EXTERNAL_GTE)
private static class GTEVersionEntity {
@Nullable @Version private Long version;
@Nullable @Id private String id;

View File

@ -29,7 +29,6 @@ import java.util.Map;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.Client;
@ -51,6 +50,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
@ -162,7 +162,7 @@ public class ElasticsearchTransportTemplateTests extends ElasticsearchTemplateTe
void shouldUseAllOptionsFromUpdateByQuery() throws JSONException {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) //
.withIndicesOptions(IndicesOptions.lenientExpandOpen()) //
.withIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN) //
.build(); //
searchQuery.setScrollTime(Duration.ofMillis(1000));
@ -195,7 +195,7 @@ public class ElasticsearchTransportTemplateTests extends ElasticsearchTemplateTe
// then
assertThat(request).isNotNull();
assertThat(request.request().getSearchRequest().indicesOptions()).usingRecursiveComparison()
.isEqualTo(IndicesOptions.lenientExpandOpen());
.isEqualTo(IndicesOptions.LENIENT_EXPAND_OPEN);
assertThat(request.request().getScrollTime().getMillis()).isEqualTo(1000);
assertEquals(request.request().getSearchRequest().source().toString(), expectedSearchRequest, false);
assertThat(request.request().isAbortOnVersionConflict()).isTrue();

View File

@ -21,6 +21,7 @@ import static org.mockito.Mockito.*;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import java.io.IOException;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
@ -36,7 +37,6 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
@ -490,25 +490,24 @@ class RequestFactoryTests {
@Test // DATAES-1003
@DisplayName("should set timeout to request")
void shouldSetTimeoutToRequest() {
Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withTimeout(TimeValue.timeValueSeconds(1))
.build();
Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withTimeout(Duration.ofSeconds(1)).build();
SearchRequest searchRequest = requestFactory.searchRequest(query, Person.class, IndexCoordinates.of("persons"));
assertThat(searchRequest.source().timeout()).isEqualTo(TimeValue.timeValueSeconds(1));
assertThat(searchRequest.source().timeout().getMillis()).isEqualTo(Duration.ofSeconds(1).toMillis());
}
@Test // DATAES-1003
@DisplayName("should set timeout to requestbuilder")
void shouldSetTimeoutToRequestBuilder() {
when(client.prepareSearch(any())).thenReturn(new SearchRequestBuilder(client, SearchAction.INSTANCE));
Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withTimeout(TimeValue.timeValueSeconds(1))
.build();
Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withTimeout(Duration.ofSeconds(1)).build();
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, Person.class,
IndexCoordinates.of("persons"));
assertThat(searchRequestBuilder.request().source().timeout()).isEqualTo(TimeValue.timeValueSeconds(1));
assertThat(searchRequestBuilder.request().source().timeout().getMillis())
.isEqualTo(Duration.ofSeconds(1).toMillis());
}
private String requestToString(ToXContent request) throws IOException {

View File

@ -32,7 +32,6 @@ import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -251,8 +250,8 @@ public class ElasticsearchTemplateCompletionWithContextsTests {
@Nullable @Id private String id;
@Nullable private String name;
@Nullable @CompletionField(maxInputLength = 100, contexts = {
@CompletionContext(name = LANGUAGE_CATEGORY, type = ContextMapping.Type.CATEGORY) }) private Completion suggest;
@Nullable @CompletionField(maxInputLength = 100, contexts = { @CompletionContext(name = LANGUAGE_CATEGORY,
type = CompletionContext.ContextMappingType.CATEGORY) }) private Completion suggest;
private ContextCompletionEntity() {}

View File

@ -37,7 +37,6 @@ import java.util.Objects;
import java.util.Set;
import org.assertj.core.data.Percentage;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@ -841,8 +840,8 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Document(indexName = "completion")
static class CompletionDocument {
@Nullable @Id private String id;
@Nullable @CompletionField(contexts = { @CompletionContext(name = "location", type = ContextMapping.Type.GEO,
path = "proppath") }) private Completion suggest;
@Nullable @CompletionField(contexts = { @CompletionContext(name = "location",
type = CompletionContext.ContextMappingType.GEO, path = "proppath") }) private Completion suggest;
@Nullable
public String getId() {

View File

@ -35,7 +35,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import org.json.JSONException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -250,9 +249,10 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
public void shouldUseFieldNameOnCompletion() throws JSONException {
// given
String expected = "{\"properties\":{" + "\"id-property\":{\"type\":\"keyword\",\"index\":true},"
+ "\"completion-property\":{\"type\":\"completion\",\"max_input_length\":100,\"preserve_position_increments\":true,\"preserve_separators\":true,\"search_analyzer\":\"simple\",\"analyzer\":\"simple\"},\"completion-property\":{}"
+ "}}";
String expected = "{\"properties\":{" + "\"id-property\":{\"type\":\"keyword\",\"index\":true}," + //
"\"completion-property\":{\"type\":\"completion\",\"max_input_length\":100,\"preserve_position_increments\":true,\"preserve_separators\":true,\"search_analyzer\":\"simple\",\"analyzer\":\"simple\"},\"completion-property\":{}"
+ //
"}}";
// when
String mapping = getMappingBuilder().buildPropertyMapping(FieldNameEntity.CompletionEntity.class);
@ -1564,8 +1564,8 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
@Document(indexName = "completion")
static class CompletionDocument {
@Nullable @Id private String id;
@Nullable @CompletionField(contexts = { @CompletionContext(name = "location", type = ContextMapping.Type.GEO,
path = "proppath") }) private Completion suggest;
@Nullable @CompletionField(contexts = { @CompletionContext(name = "location",
type = CompletionContext.ContextMappingType.GEO, path = "proppath") }) private Completion suggest;
@Nullable
public String getId() {

View File

@ -41,15 +41,15 @@ class HighlightQueryBuilderTests {
private final SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext();
private HighlightQueryBuilder highlightQueryBuilder = new HighlightQueryBuilder(context);
private final HighlightQueryBuilder highlightQueryBuilder = new HighlightQueryBuilder(context);
@Test
void shouldProcessAnnotationWithNoParameters() throws NoSuchMethodException, JSONException {
Highlight highlight = getAnnotation("annotatedMethod");
String expected = ResourceUtil.readFileFromClasspath("/highlights/highlights.json");
HighlightBuilder highlightBuilder = highlightQueryBuilder.getHighlightQuery(highlight, HighlightEntity.class)
.getHighlightBuilder();
HighlightBuilder highlightBuilder = highlightQueryBuilder.getHighlightBuilder(
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlight), HighlightEntity.class);
String actualStr = highlightBuilder.toString();
assertEquals(expected, actualStr, false);
@ -60,8 +60,8 @@ class HighlightQueryBuilderTests {
Highlight highlight = getAnnotation("annotatedMethodWithManyValue");
String expected = ResourceUtil.readFileFromClasspath("/highlights/highlights-with-parameters.json");
HighlightBuilder highlightBuilder = highlightQueryBuilder.getHighlightQuery(highlight, HighlightEntity.class)
.getHighlightBuilder();
HighlightBuilder highlightBuilder = highlightQueryBuilder.getHighlightBuilder(
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlight), HighlightEntity.class);
String actualStr = highlightBuilder.toString();
assertEquals(expected, actualStr, true);
@ -77,8 +77,8 @@ class HighlightQueryBuilderTests {
/**
* The annotation values on this method are just random values. The field has just one common parameters and the field
* specific, the whole bunch pf parameters is tested on the top level. tagsSchema cannot be tested together with
* preTags and postTags, ist it sets it's own values for these.
* specific, the bunch of parameters is tested on the top level. tagsSchema cannot be tested together with preTags and
* postTags, ist it sets its own values for these.
*/
// region test data
@Highlight(fields = { @HighlightField(name = "someField") })

View File

@ -0,0 +1,108 @@
/*
* 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.support;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* @author Peter-Josef Meisch
*/
class VersionUnitTest {
@Test // #1885
@DisplayName("shouldThrowOnNullInput")
void shouldThrowOnNullInput() {
// noinspection ConstantConditions
assertThatThrownBy(() -> Version.fromString(null)).isInstanceOf(IllegalArgumentException.class);
}
@Test // #1885
@DisplayName("shouldThrowOnInvalidInput")
void shouldThrowOnInvalidInput() {
assertThatThrownBy(() -> Version.fromString("no-version")).isInstanceOf(IllegalArgumentException.class);
}
@Test // #1885
@DisplayName("should match major only")
void shouldMatchMajorOnly() {
Version version = Version.fromString("12");
assertThat(version.getMajor()).isEqualTo(12);
assertThat(version.getMinor()).isEqualTo(0);
assertThat(version.getRevision()).isEqualTo(0);
}
@Test // #1885
@DisplayName("should match major only with details")
void shouldMatchMajorOnlyWithDetails() {
Version version = Version.fromString("12-alpha");
assertThat(version.getMajor()).isEqualTo(12);
assertThat(version.getMinor()).isEqualTo(0);
assertThat(version.getRevision()).isEqualTo(0);
}
@Test // #1885
@DisplayName("should match major and minor only")
void shouldMatchMajorAndMinorOnly() {
Version version = Version.fromString("12.34");
assertThat(version.getMajor()).isEqualTo(12);
assertThat(version.getMinor()).isEqualTo(34);
assertThat(version.getRevision()).isEqualTo(0);
}
@Test // #1885
@DisplayName("should match major and minor only with details")
void shouldMatchMajorAndMinorOnlyWithDetails() {
Version version = Version.fromString("12.34-alpha");
assertThat(version.getMajor()).isEqualTo(12);
assertThat(version.getMinor()).isEqualTo(34);
assertThat(version.getRevision()).isEqualTo(0);
}
@Test // #1885
@DisplayName("should match all")
void shouldMatchAll() {
Version version = Version.fromString("12.34.56");
assertThat(version.getMajor()).isEqualTo(12);
assertThat(version.getMinor()).isEqualTo(34);
assertThat(version.getRevision()).isEqualTo(56);
}
@Test // #1885
@DisplayName("should match all with details")
void shouldMatchAllWithDetails() {
Version version = Version.fromString("12.34.56-alpha");
assertThat(version.getMajor()).isEqualTo(12);
assertThat(version.getMinor()).isEqualTo(34);
assertThat(version.getRevision()).isEqualTo(56);
}
}