DATAES-749 - Introduce SearchPage as return type for repository methods.

Original PR: #397
This commit is contained in:
Peter-Josef Meisch 2020-02-26 21:13:30 +01:00 committed by GitHub
parent dc795eb7ee
commit 6a4a7483aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 4 deletions

View File

@ -21,9 +21,12 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.support.ReactiveSupport;
import org.springframework.lang.Nullable;
/**
* Utility class with helper methods for working with {@link SearchHit}.
@ -75,9 +78,12 @@ public final class SearchHitSupport {
return unwrapSearchHits(searchHits.getSearchHits());
}
if (result instanceof Flux) {
Flux<?> flux = (Flux<?>) result;
return flux.map(SearchHitSupport::unwrapSearchHits);
if (ReactiveSupport.isReactorAvailable()) {
if (result instanceof Flux) {
Flux<?> flux = (Flux<?>) result;
return flux.map(SearchHitSupport::unwrapSearchHits);
}
}
return result;
@ -94,4 +100,28 @@ public final class SearchHitSupport {
return new AggregatedPageImpl<>(searchHits.getSearchHits(), pageable, searchHits.getTotalHits(),
searchHits.getAggregations(), searchHits.getScrollId(), searchHits.getMaxScore());
}
public static <T> SearchPage<T> searchPageFor(SearchHits<T> searchHits, @Nullable Pageable pageable) {
return new SearchPageImpl<>(searchHits, (pageable != null) ? pageable : Pageable.unpaged());
}
/**
* SearchPage implementation.
*
* @param <T>
*/
static class SearchPageImpl<T> extends PageImpl<SearchHit<T>> implements SearchPage<T> {
private final SearchHits<T> searchHits;
public SearchPageImpl(SearchHits<T> searchHits, Pageable pageable) {
super(searchHits.getSearchHits(), pageable, searchHits.getTotalHits());
this.searchHits = searchHits;
}
@Override
public SearchHits<T> getSearchHits() {
return searchHits;
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 org.springframework.data.domain.Page;
/**
* Page definition for repositories that need to return a paged SearchHits.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
public interface SearchPage<T> extends Page<SearchHit<T>> {
SearchHits<T> getSearchHits();
}

View File

@ -86,7 +86,11 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
} else if (queryMethod.isPageQuery()) {
query.setPageable(accessor.getPageable());
SearchHits<?> searchHits = elasticsearchOperations.search(query, clazz, index);
result = SearchHitSupport.page(searchHits, query.getPageable());
if (queryMethod.isSearchPageMethod()) {
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
} else {
result = SearchHitSupport.page(searchHits, query.getPageable());
}
} else if (queryMethod.isStreamQuery()) {
if (accessor.getPageable().isUnpaged()) {
query.setPageable(PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));

View File

@ -25,6 +25,7 @@ import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
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;
@ -171,6 +172,25 @@ public class ElasticsearchQueryMethod extends QueryMethod {
return false;
}
/**
* checks if the return type is {@link SearchPage}.
*
* @since 4.0
*/
public boolean isSearchPageMethod() {
return SearchPage.class.isAssignableFrom(methodReturnType());
}
/**
* retusn the declared return type for this method.
*
* @return the return type
* @since 4.0
*/
public Class<?> methodReturnType() {
return method.getReturnType();
}
protected boolean isAllowedGenericType(ParameterizedType methodGenericReturnType) {
return Collection.class.isAssignableFrom((Class<?>) methodGenericReturnType.getRawType())
|| Stream.class.isAssignableFrom((Class<?>) methodGenericReturnType.getRawType());

View File

@ -0,0 +1,39 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.concurrent.atomic.AtomicBoolean;
import org.springframework.data.repository.util.ClassUtils;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public final class ReactiveSupport {
private ReactiveSupport() {}
/**
* @return true if project reactor is on the classpath
*/
public static boolean isReactorAvailable() {
AtomicBoolean available = new AtomicBoolean(false);
ClassUtils.ifPresent("reactor.core.publisher.Flux", null, aClass -> {
available.set(true);
});
return available.get();
}
}

View File

@ -53,6 +53,7 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchPage;
import org.springframework.data.elasticsearch.core.geo.GeoBox;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
@ -1479,6 +1480,23 @@ public abstract class CustomMethodRepositoryBaseTests {
assertThat(searchHits.getSearchHit(2).getId()).isEqualTo("oslo");
}
@Test // DATAES-749
void shouldReturnSearchPage() {
List<SampleEntity> entities = createSampleEntities("abc", 20);
repository.saveAll(entities);
// when
SearchPage<SampleEntity> searchPage = repository.searchByMessage("Message", PageRequest.of(0, 10));
assertThat(searchPage).isNotNull();
SearchHits<SampleEntity> searchHits = searchPage.getSearchHits();
assertThat(searchHits).isNotNull();
assertThat((searchHits.getTotalHits())).isEqualTo(20);
assertThat(searchHits.getSearchHits()).hasSize(10);
Pageable nextPageable = searchPage.nextPageable();
assertThat((nextPageable.getPageNumber())).isEqualTo(1);
}
private List<SampleEntity> createSampleEntities(String type, int numberOfEntities) {
List<SampleEntity> entities = new ArrayList<>();
@ -1627,6 +1645,8 @@ public abstract class CustomMethodRepositoryBaseTests {
Stream<SearchHit<SampleEntity>> readByMessage(String message);
SearchHits<SampleEntity> searchBy(Sort sort);
SearchPage<SampleEntity> searchByMessage(String message, Pageable pageable);
}
/**