Refactoring to prepare for removal of the old deprecated RestHighLevelClient integration.

Original Pull Request #2559 
Closes #2557
This commit is contained in:
Peter-Josef Meisch 2023-05-13 08:22:10 +02:00 committed by GitHub
parent f897dd4318
commit 73e9a6f5c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 205 additions and 852 deletions

View File

@ -9,7 +9,7 @@
[[new-features.5-1-0]]
== New in Spring Data Elasticsearch 5.1
* Upgrade to Elasticsearch 8.7.0
* Upgrade to Elasticsearch 8.7.1
* Allow specification of the TLS certificate when connecting to an Elasticsearch 8 cluster
[[new-features.5-0-0]]

View File

@ -17,7 +17,6 @@ package org.springframework.data.elasticsearch.core;
import org.reactivestreams.Publisher;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
@ -28,11 +27,6 @@ import org.springframework.lang.Nullable;
/**
* Interface that specifies a basic set of Elasticsearch operations executed in a reactive way.
* <p>
* Implemented by {@link ReactiveElasticsearchTemplate}. Not often used but a useful option for extensibility and
* testability (as it can be easily mocked, stubbed, or be the target of a JDK proxy). Command execution using
* {@link ReactiveElasticsearchOperations} is deferred until a {@link org.reactivestreams.Subscriber} subscribes to the
* {@link Publisher}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch

View File

@ -1,111 +0,0 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import static org.mockito.Mockito.*;
import java.util.HashMap;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.bytes.BytesArray;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate;
/**
* @author Roman Puchkovskiy
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class ElasticsearchRestTemplateCallbackTests extends ElasticsearchTemplateCallbackTests {
@Mock private RestHighLevelClient client;
@Mock private IndexResponse indexResponse;
@Mock private BulkResponse bulkResponse;
@Mock private BulkItemResponse bulkItemResponse;
@Mock private GetResponse getResponse;
@Mock private MultiGetResponse multiGetResponse;
@Mock private MultiGetItemResponse multiGetItemResponse;
@Mock private MultiSearchResponse.Item multiSearchResponseItem;
@SuppressWarnings("deprecation") // we know what we test
@BeforeEach
public void setUp() throws Exception {
initTemplate(new ElasticsearchRestTemplate(client));
doReturn(indexResponse).when(client).index(any(IndexRequest.class), any(RequestOptions.class));
doReturn("response-id").when(indexResponse).getId();
doReturn(bulkResponse).when(client).bulk(any(BulkRequest.class), any(RequestOptions.class));
doReturn(new BulkItemResponse[] { bulkItemResponse, bulkItemResponse }).when(bulkResponse).getItems();
doReturn("response-id").when(bulkItemResponse).getId();
doReturn(getResponse).when(client).get(any(GetRequest.class), any(RequestOptions.class));
doReturn(true).when(getResponse).isExists();
doReturn(false).when(getResponse).isSourceEmpty();
doReturn(new HashMap<String, Object>() {
{
put("id", "init");
put("firstname", "luke");
}
}).when(getResponse).getSourceAsMap();
doReturn(multiGetResponse).when(client).mget(any(MultiGetRequest.class), any(RequestOptions.class));
doReturn(new MultiGetItemResponse[] { multiGetItemResponse, multiGetItemResponse }).when(multiGetResponse)
.getResponses();
doReturn(getResponse).when(multiGetItemResponse).getResponse();
doReturn(searchResponse).when(client).search(any(SearchRequest.class), any(RequestOptions.class));
doReturn(nSearchHits(2)).when(searchResponse).getHits();
doReturn("scroll-id").when(searchResponse).getScrollId();
doReturn(new BytesArray(new byte[8])).when(searchHit).getSourceRef();
doReturn(new HashMap<String, Object>() {
{
put("id", "init");
put("firstname", "luke");
}
}).when(searchHit).getSourceAsMap();
MultiSearchResponse multiSearchResponse = new MultiSearchResponse(
new MultiSearchResponse.Item[] { multiSearchResponseItem }, 1L);
doReturn(multiSearchResponse).when(client).multiSearch(any(MultiSearchRequest.class), any());
doReturn(multiSearchResponse).when(client).msearch(any(MultiSearchRequest.class), any());
doReturn(searchResponse).when(multiSearchResponseItem).getResponse();
doReturn(searchResponse).when(client).scroll(any(SearchScrollRequest.class), any(RequestOptions.class));
}
}

View File

@ -1,561 +0,0 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Spy;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.client.erhlc.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.event.AfterConvertCallback;
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.util.CloseableIterator;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* @author Roman Puchkovskiy
*/
abstract class ElasticsearchTemplateCallbackTests {
protected AbstractElasticsearchTemplate template;
@Mock protected SearchResponse searchResponse;
@Mock protected org.elasticsearch.search.SearchHit searchHit;
private final IndexCoordinates index = IndexCoordinates.of("index");
@Spy private ValueCapturingAfterSaveCallback afterSaveCallback = new ValueCapturingAfterSaveCallback();
@Spy private ValueCapturingAfterConvertCallback afterConvertCallback = new ValueCapturingAfterConvertCallback();
@Spy private ValueCapturingBeforeConvertCallback beforeConvertCallback = new ValueCapturingBeforeConvertCallback();
protected final void initTemplate(AbstractElasticsearchTemplate template) {
this.template = template;
}
protected final org.elasticsearch.search.SearchHits nSearchHits(int count) {
org.elasticsearch.search.SearchHit[] hits = new org.elasticsearch.search.SearchHit[count];
Arrays.fill(hits, searchHit);
return new org.elasticsearch.search.SearchHits(hits, new TotalHits(count, TotalHits.Relation.EQUAL_TO), 1.0f);
}
@Test // DATAES-771
void saveOneShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity = new Person("init", "luke");
Person saved = template.save(entity);
verify(afterSaveCallback).onAfterSave(eq(entity), any());
assertThat(saved.firstname).isEqualTo("after-save");
}
@Test // DATAES-771
void saveWithIndexCoordinatesShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity = new Person("init", "luke");
Person saved = template.save(entity, index);
verify(afterSaveCallback).onAfterSave(eq(entity), eq(index));
assertThat(saved.firstname).isEqualTo("after-save");
}
@Test // DATAES-771
void saveArrayShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity1 = new Person("init1", "luke1");
Person entity2 = new Person("init2", "luke2");
Iterable<Person> saved = template.save(entity1, entity2);
verify(afterSaveCallback, times(2)).onAfterSave(any(), any());
Iterator<Person> savedIterator = saved.iterator();
assertThat(savedIterator.next().firstname).isEqualTo("after-save");
assertThat(savedIterator.next().firstname).isEqualTo("after-save");
}
@Test // DATAES-771
void saveIterableShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity1 = new Person("init1", "luke1");
Person entity2 = new Person("init2", "luke2");
Iterable<Person> saved = template.save(Arrays.asList(entity1, entity2));
verify(afterSaveCallback, times(2)).onAfterSave(any(), any());
Iterator<Person> savedIterator = saved.iterator();
assertThat(savedIterator.next().firstname).isEqualTo("after-save");
assertThat(savedIterator.next().firstname).isEqualTo("after-save");
}
@Test // DATAES-771
void saveIterableWithIndexCoordinatesShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity1 = new Person("init1", "luke1");
Person entity2 = new Person("init2", "luke2");
Iterable<Person> saved = template.save(Arrays.asList(entity1, entity2), index);
verify(afterSaveCallback, times(2)).onAfterSave(any(), eq(index));
Iterator<Person> savedIterator = saved.iterator();
assertThat(savedIterator.next().firstname).isEqualTo("after-save");
assertThat(savedIterator.next().firstname).isEqualTo("after-save");
}
@Test // DATAES-771
void indexShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity = new Person("init", "luke");
IndexQuery indexQuery = indexQueryForEntity(entity);
template.index(indexQuery, index);
verify(afterSaveCallback).onAfterSave(eq(entity), eq(index));
Person savedPerson = (Person) indexQuery.getObject();
assertThat(savedPerson.firstname).isEqualTo("after-save");
}
private IndexQuery indexQueryForEntity(Person entity) {
IndexQuery indexQuery = new IndexQuery();
indexQuery.setObject(entity);
return indexQuery;
}
@Test // DATAES-771
void bulkIndexShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity1 = new Person("init1", "luke1");
Person entity2 = new Person("init2", "luke2");
IndexQuery query1 = indexQueryForEntity(entity1);
IndexQuery query2 = indexQueryForEntity(entity2);
template.bulkIndex(Arrays.asList(query1, query2), index);
verify(afterSaveCallback, times(2)).onAfterSave(any(), eq(index));
Person savedPerson1 = (Person) query1.getObject();
Person savedPerson2 = (Person) query2.getObject();
assertThat(savedPerson1.firstname).isEqualTo("after-save");
assertThat(savedPerson2.firstname).isEqualTo("after-save");
}
@Test // DATAES-771
void bulkIndexWithOptionsShouldInvokeAfterSaveCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
Person entity1 = new Person("init1", "luke1");
Person entity2 = new Person("init2", "luke2");
IndexQuery query1 = indexQueryForEntity(entity1);
IndexQuery query2 = indexQueryForEntity(entity2);
template.bulkIndex(Arrays.asList(query1, query2), BulkOptions.defaultOptions(), index);
verify(afterSaveCallback, times(2)).onAfterSave(any(), eq(index));
Person savedPerson1 = (Person) query1.getObject();
Person savedPerson2 = (Person) query2.getObject();
assertThat(savedPerson1.firstname).isEqualTo("after-save");
assertThat(savedPerson2.firstname).isEqualTo("after-save");
}
@Test // DATAES-772
void getShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
Person result = template.get("init", Person.class);
verify(afterConvertCallback).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), any());
assertThat(result.firstname).isEqualTo("after-convert");
}
private Document lukeDocument() {
return Document.create().append("id", "init").append("firstname", "luke");
}
@Test // DATAES-772
void getWithCoordinatesShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
Person result = template.get("init", Person.class, index);
verify(afterConvertCallback).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), eq(index));
assertThat(result.firstname).isEqualTo("after-convert");
}
@Test // DATAES-772, #1678
void multiGetShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
List<MultiGetItem<Person>> results = template.multiGet(queryForTwo(), Person.class, index);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()),
eq(index));
assertThat(results.get(0).getItem().firstname).isEqualTo("after-convert");
assertThat(results.get(1).getItem().firstname).isEqualTo("after-convert");
}
private Query queryForTwo() {
return new NativeSearchQueryBuilder().withIds(Arrays.asList("init1", "init2")).build();
}
private Query queryForOne() {
return new NativeSearchQueryBuilder().withIds(singletonList("init")).build();
}
private void skipItemsFromScrollStart(CloseableIterator<Person> results) {
results.next();
results.next();
}
@Test // DATAES-772
void moreLikeThisShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
SearchHits<Person> results = template.search(moreLikeThisQuery(), Person.class, index);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()),
eq(index));
assertThat(results.getSearchHit(0).getContent().firstname).isEqualTo("after-convert");
assertThat(results.getSearchHit(1).getContent().firstname).isEqualTo("after-convert");
}
private MoreLikeThisQuery moreLikeThisQuery() {
MoreLikeThisQuery query = new MoreLikeThisQuery();
query.setId("init");
query.addFields("id");
return query;
}
@Test // DATAES-772
void searchOneShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
doReturn(nSearchHits(1)).when(searchResponse).getHits();
SearchHit<Person> result = template.searchOne(queryForOne(), Person.class);
verify(afterConvertCallback).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), any());
assertThat(result.getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void searchOneWithIndexCoordinatesShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
doReturn(nSearchHits(1)).when(searchResponse).getHits();
SearchHit<Person> result = template.searchOne(queryForOne(), Person.class, index);
verify(afterConvertCallback).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), eq(index));
assertThat(result.getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void multiSearchShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
List<SearchHits<Person>> results = template.multiSearch(singletonList(queryForTwo()), Person.class, index);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()),
eq(index));
List<SearchHit<Person>> hits = results.get(0).getSearchHits();
assertThat(hits.get(0).getContent().firstname).isEqualTo("after-convert");
assertThat(hits.get(1).getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void multiSearchWithMultipleEntityClassesShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
List<SearchHits<?>> results = template.multiSearch(singletonList(queryForTwo()), singletonList(Person.class),
index);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()),
eq(index));
List<? extends SearchHit<?>> hits = results.get(0).getSearchHits();
assertThat(((Person) hits.get(0).getContent()).firstname).isEqualTo("after-convert");
assertThat(((Person) hits.get(1).getContent()).firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void searchShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
SearchHits<Person> results = template.search(queryForTwo(), Person.class);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), any());
List<SearchHit<Person>> hits = results.getSearchHits();
assertThat(hits.get(0).getContent().firstname).isEqualTo("after-convert");
assertThat(hits.get(1).getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void searchWithIndexCoordinatesShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
SearchHits<Person> results = template.search(queryForTwo(), Person.class, index);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()),
eq(index));
List<SearchHit<Person>> hits = results.getSearchHits();
assertThat(hits.get(0).getContent().firstname).isEqualTo("after-convert");
assertThat(hits.get(1).getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void searchViaMoreLikeThisShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
SearchHits<Person> results = template.search(moreLikeThisQuery(), Person.class);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), any());
List<SearchHit<Person>> hits = results.getSearchHits();
assertThat(hits.get(0).getContent().firstname).isEqualTo("after-convert");
assertThat(hits.get(1).getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void searchViaMoreLikeThisWithIndexCoordinatesShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
SearchHits<Person> results = template.search(moreLikeThisQuery(), Person.class, index);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()),
eq(index));
List<SearchHit<Person>> hits = results.getSearchHits();
assertThat(hits.get(0).getContent().firstname).isEqualTo("after-convert");
assertThat(hits.get(1).getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void searchForStreamShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
SearchHitsIterator<Person> results = template.searchForStream(queryForTwo(), Person.class);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), any());
assertThat(results.next().getContent().firstname).isEqualTo("after-convert");
assertThat(results.next().getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-772
void searchForStreamWithIndexCoordinatesShouldInvokeAfterConvertCallback() {
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
SearchHitsIterator<Person> results = template.searchForStream(queryForTwo(), Person.class, index);
verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()),
eq(index));
assertThat(results.next().getContent().firstname).isEqualTo("after-convert");
assertThat(results.next().getContent().firstname).isEqualTo("after-convert");
}
@Test // DATAES-785
void saveOneShouldInvokeBeforeConvertCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(beforeConvertCallback));
Person entity = new Person("init1", "luke1");
Person saved = template.save(entity, index);
verify(beforeConvertCallback).onBeforeConvert(any(), eq(index));
assertThat(saved.firstname).isEqualTo("before-convert");
}
@Test // DATAES-785
void saveAllShouldInvokeBeforeConvertCallbacks() {
template.setEntityCallbacks(EntityCallbacks.create(beforeConvertCallback));
Person entity1 = new Person("init1", "luke1");
Person entity2 = new Person("init2", "luke2");
Iterable<Person> saved = template.save(Arrays.asList(entity1, entity2), index);
verify(beforeConvertCallback, times(2)).onBeforeConvert(any(), eq(index));
Iterator<Person> iterator = saved.iterator();
assertThat(iterator.next().firstname).isEqualTo("before-convert");
assertThat(iterator.next().firstname).isEqualTo("before-convert");
}
static class Person {
@Nullable
@Id String id;
@Nullable String firstname;
public Person(@Nullable String id, @Nullable String firstname) {
this.id = id;
this.firstname = firstname;
}
public Person() {}
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public String getFirstname() {
return firstname;
}
public void setFirstname(@Nullable String firstname) {
this.firstname = firstname;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Person person = (Person) o;
if (!Objects.equals(id, person.id))
return false;
return Objects.equals(firstname, person.firstname);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (firstname != null ? firstname.hashCode() : 0);
return result;
}
}
static class ValueCapturingEntityCallback<T> {
private final List<T> values = new ArrayList<>(1);
protected void capture(T value) {
values.add(value);
}
public List<T> getValues() {
return values;
}
@Nullable
public T getValue() {
return CollectionUtils.lastElement(values);
}
}
static class ValueCapturingAfterSaveCallback extends ValueCapturingEntityCallback<Person>
implements AfterSaveCallback<Person> {
@Override
public Person onAfterSave(Person entity, IndexCoordinates index) {
capture(entity);
return new Person() {
{
id = entity.id;
firstname = "after-save";
}
};
}
}
static class ValueCapturingAfterConvertCallback extends ValueCapturingEntityCallback<Person>
implements AfterConvertCallback<Person> {
@Override
public Person onAfterConvert(Person entity, Document document, IndexCoordinates indexCoordinates) {
capture(entity);
return new Person() {
{
id = entity.id;
firstname = "after-convert";
}
};
}
}
static class ValueCapturingBeforeConvertCallback extends ValueCapturingEntityCallback<Person>
implements BeforeConvertCallback<Person> {
@Override
public Person onBeforeConvert(Person entity, IndexCoordinates indexCoordinates) {
capture(entity);
return new Person() {
{
id = entity.id;
firstname = "before-convert";
}
};
}
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2023 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.suggest;
import co.elastic.clients.elasticsearch.core.search.CompletionContext;
import co.elastic.clients.elasticsearch.core.search.FieldSuggester;
import co.elastic.clients.elasticsearch.core.search.SuggestFuzziness;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
* @since 5.1
*/
@ContextConfiguration(classes = { CompletionWithContextsELCIntegrationTests.Config.class })
public class CompletionWithContextsELCIntegrationTests extends CompletionWithContextsIntegrationTests {
@Configuration
@Import({ ElasticsearchTemplateConfiguration.class })
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("completion-context");
}
}
@Override
protected Query getSearchQuery(String suggestionName, String category) {
return NativeQuery.builder() //
.withSuggester(Suggester.of(s -> s //
.suggesters(//
suggestionName, //
FieldSuggester.of(fs -> fs //
.prefix("m") //
.completion(cs -> cs //
.field("suggest") //
.fuzzy(SuggestFuzziness.of(f -> f.fuzziness("AUTO"))) //
.contexts( //
ContextCompletionEntity.LANGUAGE_CATEGORY, //
List.of(CompletionContext.of(cc -> cc //
.context(cb -> cb.category(category)) //
)) //
) //
) //
) //
))) //
.build();
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2019-2023 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.suggest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext;
import org.elasticsearch.xcontent.ToXContent;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.client.erhlc.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
*/
@SuppressWarnings("deprecation")
@ContextConfiguration(classes = { CompletionWithContextsERHLCIntegrationTests.Config.class })
public class CompletionWithContextsERHLCIntegrationTests extends CompletionWithContextsIntegrationTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("completion-context-es7");
}
}
@Override
@NotNull
protected Query getSearchQuery(String suggestionName, String category) {
CompletionSuggestionBuilder completionSuggestionFuzzyBuilder = SuggestBuilders.completionSuggestion("suggest")
.prefix("m", Fuzziness.AUTO);
Map<String, List<? extends ToXContent>> contextMap = new HashMap<>();
List<CategoryQueryContext> contexts = new ArrayList<>(1);
CategoryQueryContext queryContext = CategoryQueryContext.builder().setCategory(category).build();
contexts.add(queryContext);
contextMap.put(ContextCompletionEntity.LANGUAGE_CATEGORY, contexts);
completionSuggestionFuzzyBuilder.contexts(contextMap);
var suggestBuilder = new SuggestBuilder().addSuggestion(suggestionName, completionSuggestionFuzzyBuilder);
return new NativeSearchQueryBuilder().withSuggestBuilder(suggestBuilder).build();
}
}

View File

@ -15,38 +15,25 @@
*/
package org.springframework.data.elasticsearch.core.suggest;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
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.xcontent.ToXContent;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.CompletionContext;
import org.springframework.data.elasticsearch.annotations.CompletionField;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.client.erhlc.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.utils.IndexInitializer;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
/**
@ -56,24 +43,25 @@ import org.springframework.lang.Nullable;
@SpringIntegrationTest
public abstract class CompletionWithContextsIntegrationTests {
private static final String SUGGESTION_NAME = "test-suggest";
@Autowired private ElasticsearchOperations operations;
private IndexOperations indexOperations;
@Autowired private IndexNameProvider indexNameProvider;
@BeforeEach
void setup() {
indexOperations = operations.indexOps(ContextCompletionEntity.class);
indexOperations.delete();
indexNameProvider.increment();
operations.indexOps(ContextCompletionEntity.class).createWithMapping();
}
@AfterEach
void after() {
indexOperations.delete();
@Test
@Order(java.lang.Integer.MAX_VALUE)
void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
}
private void loadContextCompletionObjectEntities() {
IndexInitializer.init(indexOperations);
NonDocumentEntity nonDocumentEntity = new NonDocumentEntity();
nonDocumentEntity.setSomeField1("foo");
nonDocumentEntity.setSomeField2("bar");
@ -100,105 +88,74 @@ public abstract class CompletionWithContextsIntegrationTests {
indexQueries.add(new ContextCompletionEntityBuilder("4").name("Artur Konczak")
.suggest(new String[] { "Artur", "Konczak" }, context4).buildIndex());
operations.bulkIndex(indexQueries, IndexCoordinates.of("test-index-context-completion"));
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
operations.indexOps(ContextCompletionEntity.class).refresh();
}
abstract protected Query getSearchQuery(String suggestionName, String category);
@Test // DATAES-536
public void shouldFindSuggestionsForGivenCriteriaQueryUsingContextCompletionEntityOfMongo() {
// given
loadContextCompletionObjectEntities();
CompletionSuggestionBuilder completionSuggestionFuzzyBuilder = SuggestBuilders.completionSuggestion("suggest")
.prefix("m", Fuzziness.AUTO);
Query query = getSearchQuery(SUGGESTION_NAME, "mongo");
Map<String, List<? extends ToXContent>> contextMap = new HashMap<>();
List<CategoryQueryContext> contexts = new ArrayList<>(1);
var searchHits = operations.search(query, ContextCompletionEntity.class);
CategoryQueryContext.Builder builder = CategoryQueryContext.builder();
builder.setCategory("mongo");
CategoryQueryContext queryContext = builder.build();
contexts.add(queryContext);
contextMap.put(ContextCompletionEntity.LANGUAGE_CATEGORY, contexts);
completionSuggestionFuzzyBuilder.contexts(contextMap);
// when
SearchResponse suggestResponse = ((ElasticsearchRestTemplate) operations).suggest(
new SuggestBuilder().addSuggestion("test-suggest", completionSuggestionFuzzyBuilder),
IndexCoordinates.of("test-index-context-completion"));
assertThat(suggestResponse.getSuggest()).isNotNull();
CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("test-suggest");
List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getEntries().get(0).getOptions();
// then
assertThat(searchHits.hasSuggest()).isTrue();
Suggest suggest = searchHits.getSuggest();
Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest
.getSuggestion(SUGGESTION_NAME);
assertThat(suggestion).isNotNull();
assertThat(suggestion)
.isInstanceOf(org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion.class);
List<org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion.Entry.Option<CompletionIntegrationTests.AnnotatedCompletionEntity>> options = ((org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion<CompletionIntegrationTests.AnnotatedCompletionEntity>) suggestion)
.getEntries().get(0).getOptions();
assertThat(options).hasSize(1);
assertThat(options.get(0).getText().string()).isEqualTo("Marchand");
assertThat(options.get(0).getText()).isEqualTo("Marchand");
}
@Test // DATAES-536
public void shouldFindSuggestionsForGivenCriteriaQueryUsingContextCompletionEntityOfElastic() {
// given
loadContextCompletionObjectEntities();
SuggestionBuilder completionSuggestionFuzzyBuilder = SuggestBuilders.completionSuggestion("suggest").prefix("m",
Fuzziness.AUTO);
Query query = getSearchQuery(SUGGESTION_NAME, "elastic");
Map<String, List<? extends ToXContent>> contextMap = new HashMap<>();
List<CategoryQueryContext> contexts = new ArrayList<>(1);
var searchHits = operations.search(query, ContextCompletionEntity.class);
CategoryQueryContext.Builder builder = CategoryQueryContext.builder();
builder.setCategory("elastic");
CategoryQueryContext queryContext = builder.build();
contexts.add(queryContext);
contextMap.put(ContextCompletionEntity.LANGUAGE_CATEGORY, contexts);
((CompletionSuggestionBuilder) completionSuggestionFuzzyBuilder).contexts(contextMap);
// when
SearchResponse suggestResponse = ((ElasticsearchRestTemplate) operations).suggest(
new SuggestBuilder().addSuggestion("test-suggest", completionSuggestionFuzzyBuilder),
IndexCoordinates.of("test-index-context-completion"));
assertThat(suggestResponse.getSuggest()).isNotNull();
CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("test-suggest");
List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getEntries().get(0).getOptions();
// then
assertThat(searchHits.hasSuggest()).isTrue();
Suggest suggest = searchHits.getSuggest();
Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest
.getSuggestion(SUGGESTION_NAME);
assertThat(suggestion).isNotNull();
assertThat(suggestion)
.isInstanceOf(org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion.class);
List<org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion.Entry.Option<CompletionIntegrationTests.AnnotatedCompletionEntity>> options = ((org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion<CompletionIntegrationTests.AnnotatedCompletionEntity>) suggestion)
.getEntries().get(0).getOptions();
assertThat(options).hasSize(1);
assertThat(options.get(0).getText().string()).isEqualTo("Mohsin");
assertThat(options.get(0).getText()).isEqualTo("Mohsin");
}
@Test // DATAES-536
public void shouldFindSuggestionsForGivenCriteriaQueryUsingContextCompletionEntityOfKotlin() {
// given
loadContextCompletionObjectEntities();
SuggestionBuilder completionSuggestionFuzzyBuilder = SuggestBuilders.completionSuggestion("suggest").prefix("m",
Fuzziness.AUTO);
Query query = getSearchQuery(SUGGESTION_NAME, "kotlin");
Map<String, List<? extends ToXContent>> contextMap = new HashMap<>();
List<CategoryQueryContext> contexts = new ArrayList<>(1);
var searchHits = operations.search(query, ContextCompletionEntity.class);
CategoryQueryContext.Builder builder = CategoryQueryContext.builder();
builder.setCategory("kotlin");
CategoryQueryContext queryContext = builder.build();
contexts.add(queryContext);
contextMap.put(ContextCompletionEntity.LANGUAGE_CATEGORY, contexts);
((CompletionSuggestionBuilder) completionSuggestionFuzzyBuilder).contexts(contextMap);
// when
SearchResponse suggestResponse = ((ElasticsearchRestTemplate) operations).suggest(
new SuggestBuilder().addSuggestion("test-suggest", completionSuggestionFuzzyBuilder),
IndexCoordinates.of("test-index-context-completion"));
assertThat(suggestResponse.getSuggest()).isNotNull();
CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("test-suggest");
List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getEntries().get(0).getOptions();
// then
assertThat(searchHits.hasSuggest()).isTrue();
Suggest suggest = searchHits.getSuggest();
Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest
.getSuggestion(SUGGESTION_NAME);
assertThat(suggestion).isNotNull();
assertThat(suggestion)
.isInstanceOf(org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion.class);
List<org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion.Entry.Option<CompletionIntegrationTests.AnnotatedCompletionEntity>> options = ((org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion<CompletionIntegrationTests.AnnotatedCompletionEntity>) suggestion)
.getEntries().get(0).getOptions();
assertThat(options).hasSize(2);
assertThat(options.get(0).getText().string()).isIn("Marchand", "Mohsin");
assertThat(options.get(1).getText().string()).isIn("Marchand", "Mohsin");
assertThat(options.get(0).getText()).isIn("Marchand", "Mohsin");
assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin");
}
/**
@ -235,7 +192,7 @@ public abstract class CompletionWithContextsIntegrationTests {
* @author Mewes Kochheim
* @author Robert Gruendler
*/
@Document(indexName = "test-index-context-completion")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class ContextCompletionEntity {
public static final String LANGUAGE_CATEGORY = "language";

View File

@ -1,31 +0,0 @@
/*
* Copyright 2019-2023 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.suggest;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
*/
@ContextConfiguration(classes = { CompletionWithContextsRestTemplateIntegrationTests.Config.class })
public class CompletionWithContextsRestTemplateIntegrationTests extends CompletionWithContextsIntegrationTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
static class Config {}
}

View File

@ -18,7 +18,7 @@ package org.springframework.data.elasticsearch.repositories.setting.dynamic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
@ -32,7 +32,7 @@ public class DynamicSettingAndMappingEntityRepositoryELCIntegrationTests
extends DynamicSettingAndMappingEntityRepositoryIntegrationTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
@Import({ ElasticsearchTemplateConfiguration.class })
@EnableElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean

View File

@ -71,7 +71,7 @@ public abstract class DynamicSettingAndMappingEntityRepositoryIntegrationTests {
public void shouldCreateGivenDynamicSettingsForGivenIndex() {
assertThat(indexOperations.exists()).isTrue();
Map<String, Object> map = indexOperations.getSettings();
Map<String, Object> map = indexOperations.getSettings().flatten();
assertThat(map.containsKey("index.number_of_replicas")).isTrue();
assertThat(map.containsKey("index.number_of_shards")).isTrue();
assertThat(map.containsKey("index.analysis.analyzer.emailAnalyzer.tokenizer")).isTrue();

View File

@ -1,40 +0,0 @@
/*
* Copyright 2019-2023 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.utils;
import org.springframework.data.elasticsearch.core.IndexOperations;
/**
* Utility to initialize indexes.
*
* @author Peter-Josef Meisch
*/
public class IndexInitializer {
private IndexInitializer() {}
/**
* Initialize a fresh index with mappings for {@link Class}. Drops the index if it exists before creation.
*
* @param indexOperations
*/
public static void init(IndexOperations indexOperations) {
indexOperations.delete();
indexOperations.create();
indexOperations.putMapping(indexOperations.createMapping());
indexOperations.refresh();
}
}