diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java
index c8107a62f..ef34cc849 100644
--- a/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java
+++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Query.java
@@ -15,8 +15,14 @@
*/
package org.springframework.data.elasticsearch.annotations;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
import org.springframework.data.annotation.QueryAnnotation;
-import java.lang.annotation.*;
/**
* Query
@@ -34,10 +40,18 @@ import java.lang.annotation.*;
public @interface Query {
/**
- * @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
+ * @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for query.
*/
+ @AliasFor("query")
String value() default "";
+ /**
+ * @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for value
+ * @since 5.0
+ */
+ @AliasFor("value")
+ String query() default "";
+
/**
* Named Query Named looked up by repository.
*
diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/SourceFilters.java b/src/main/java/org/springframework/data/elasticsearch/annotations/SourceFilters.java
new file mode 100644
index 000000000..30319bdf8
--- /dev/null
+++ b/src/main/java/org/springframework/data/elasticsearch/annotations/SourceFilters.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.data.elasticsearch.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be placed on repository methods to define the properties that should be requested from
+ * Elasticsearch when the method is run.
+ *
+ * @author Alexander Torres
+ * @author Peter-Josef Meisch
+ * @since 5.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+@Documented
+public @interface SourceFilters {
+
+ /**
+ * Properties to be requested from Elasticsearch to be included in the response. These can be passed in as literals
+ * like
+ *
+ *
+ * {@code @SourceFilters(includes = {"property1", "property2"})}
+ *
+ *
+ * or as a parameterized value
+ *
+ *
+ * {@code @SourceFilters(includes = "?0")}
+ *
+ *
+ * when the list of properties is passed as a function parameter.
+ */
+ String[] includes() default "";
+
+ /**
+ * Properties to be requested from Elasticsearch to be excluded in the response. These can be passed in as literals
+ * like
+ *
+ *
+ * {@code @SourceFilters(excludes = {"property1", "property2"})}
+ *
+ *
+ * or as a parameterized value
+ *
+ *
+ * {@code @SourceFilters(excludes = "?0")}
+ *
+ *
+ * when the list of properties is passed as a function parameter.
+ */
+ String[] excludes() default "";
+}
diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java
index 0805f1d91..635c194ba 100644
--- a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java
+++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java
@@ -16,6 +16,9 @@
package org.springframework.data.elasticsearch.repository.query;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
+import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
+import org.springframework.data.elasticsearch.core.query.Query;
+import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
@@ -31,12 +34,14 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
protected static final int DEFAULT_STREAM_BATCH_SIZE = 500;
protected ElasticsearchQueryMethod queryMethod;
- protected ElasticsearchOperations elasticsearchOperations;
+ protected final ElasticsearchOperations elasticsearchOperations;
+ protected final ElasticsearchConverter elasticsearchConverter;
public AbstractElasticsearchRepositoryQuery(ElasticsearchQueryMethod queryMethod,
ElasticsearchOperations elasticsearchOperations) {
this.queryMethod = queryMethod;
this.elasticsearchOperations = elasticsearchOperations;
+ this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
}
@Override
@@ -49,4 +54,19 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
* @since 4.2
*/
public abstract boolean isCountQuery();
+
+ protected void prepareQuery(Query query, Class> clazz, ParameterAccessor parameterAccessor) {
+
+ elasticsearchConverter.updateQuery(query, clazz);
+
+ if (queryMethod.hasAnnotatedHighlight()) {
+ query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
+ }
+
+ var sourceFilter = queryMethod.getSourceFilter(parameterAccessor,
+ elasticsearchOperations.getElasticsearchConverter());
+ if (sourceFilter != null) {
+ query.addSourceFilter(sourceFilter);
+ }
+ }
}
diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java
index 6e49eb651..cbd7de8d3 100644
--- a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java
+++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java
@@ -27,6 +27,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.Query;
+import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingConverter;
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingExecution;
import org.springframework.data.mapping.context.MappingContext;
@@ -88,6 +89,12 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
}
+ var sourceFilter = queryMethod.getSourceFilter(parameterAccessor,
+ elasticsearchOperations.getElasticsearchConverter());
+ if (sourceFilter != null) {
+ query.addSourceFilter(sourceFilter);
+ }
+
Class> targetType = processor.getReturnedType().getTypeToRead();
String indexName = queryMethod.getEntityInformation().getIndexName();
IndexCoordinates index = IndexCoordinates.of(indexName);
diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java
index cea66fe49..57ea3f73d 100644
--- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java
+++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java
@@ -23,7 +23,6 @@ import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
-import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
@@ -49,13 +48,11 @@ import org.springframework.util.ClassUtils;
public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery {
private final PartTree tree;
- private final ElasticsearchConverter elasticsearchConverter;
private final MappingContext, ElasticsearchPersistentProperty> mappingContext;
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) {
super(method, elasticsearchOperations);
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
- this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
this.mappingContext = elasticsearchConverter.getMappingContext();
}
@@ -66,18 +63,16 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
@Override
public Object execute(Object[] parameters) {
- Class> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
- ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
- CriteriaQuery query = createQuery(accessor);
+ Class> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
+ ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(queryMethod.getParameters(),
+ parameters);
+
+ CriteriaQuery query = createQuery(parameterAccessor);
Assert.notNull(query, "unsupported query");
- elasticsearchConverter.updateQuery(query, clazz);
-
- if (queryMethod.hasAnnotatedHighlight()) {
- query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
- }
+ prepareQuery(query, clazz, parameterAccessor);
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
@@ -89,11 +84,11 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
}
if (tree.isDelete()) {
- result = countOrGetDocumentsForDelete(query, accessor);
+ result = countOrGetDocumentsForDelete(query, parameterAccessor);
elasticsearchOperations.delete(query, clazz, index);
elasticsearchOperations.indexOps(index).refresh();
} else if (queryMethod.isPageQuery()) {
- query.setPageable(accessor.getPageable());
+ query.setPageable(parameterAccessor.getPageable());
SearchHits> searchHits = elasticsearchOperations.search(query, clazz, index);
if (queryMethod.isSearchPageMethod()) {
result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
@@ -101,15 +96,15 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
}
} else if (queryMethod.isStreamQuery()) {
- if (accessor.getPageable().isUnpaged()) {
+ if (parameterAccessor.getPageable().isUnpaged()) {
query.setPageable(PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
} else {
- query.setPageable(accessor.getPageable());
+ query.setPageable(parameterAccessor.getPageable());
}
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
} else if (queryMethod.isCollectionQuery()) {
- if (accessor.getPageable().isUnpaged()) {
+ if (parameterAccessor.getPageable().isUnpaged()) {
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
if (itemCount == 0) {
@@ -119,7 +114,7 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
}
} else {
- query.setPageable(accessor.getPageable());
+ query.setPageable(parameterAccessor.getPageable());
}
if (result == null) {
diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java
index 4e6259d99..a70c4b23b 100644
--- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java
+++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java
@@ -17,22 +17,31 @@ package org.springframework.data.elasticsearch.repository.query;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.stream.Stream;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.Query;
+import org.springframework.data.elasticsearch.annotations.SourceFilters;
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.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
+import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
+import org.springframework.data.elasticsearch.core.query.SourceFilter;
+import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
@@ -49,16 +58,19 @@ import org.springframework.util.ClassUtils;
* @author Mark Paluch
* @author Christoph Strobl
* @author Peter-Josef Meisch
+ * @author Alexander Torres
*/
public class ElasticsearchQueryMethod extends QueryMethod {
private final MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext;
- private @Nullable ElasticsearchEntityMetadata> metadata;
+ @Nullable private ElasticsearchEntityMetadata> metadata;
protected final Method method; // private in base class, but needed here and in derived classes as well
@Nullable private final Query queryAnnotation;
@Nullable private final Highlight highlightAnnotation;
private final Lazy highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
+ @Nullable private final SourceFilters sourceFilters;
+
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext) {
@@ -70,6 +82,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
this.mappingContext = mappingContext;
this.queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
this.highlightAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Highlight.class);
+ this.sourceFilters = AnnotatedElementUtils.findMergedAnnotation(method, SourceFilters.class);
verifyCountQueryTypes();
}
@@ -92,8 +105,9 @@ public class ElasticsearchQueryMethod extends QueryMethod {
/**
* @return the query String. Must not be {@literal null} when {@link #hasAnnotatedQuery()} returns true
*/
+ @Nullable
public String getAnnotatedQuery() {
- return queryAnnotation.value();
+ return queryAnnotation != null ? queryAnnotation.value() : null;
}
/**
@@ -246,4 +260,86 @@ public class ElasticsearchQueryMethod extends QueryMethod {
return queryAnnotation != null && queryAnnotation.count();
}
+ /**
+ * @return {@literal true} if the method is annotated with {@link SourceFilters}.
+ * @since 5.0
+ */
+ public boolean hasSourceFilters() {
+ return sourceFilters != null;
+ }
+
+ /**
+ * @return the {@link SourceFilters} annotation for this method.
+ * @since 5.0
+ */
+ @Nullable
+ public SourceFilters getSourceFilters() {
+ return sourceFilters;
+ }
+
+ /**
+ * Uses the sourceFilters property to create a {@link SourceFilter} to be added to a
+ * {@link org.springframework.data.elasticsearch.core.query.Query}
+ *
+ * @param parameterAccessor the accessor with the query method parameter details
+ * @param converter {@link ElasticsearchConverter} needed to convert entity property names to the Elasticsearch field
+ * names and for parameter conversion when the includes or excludes are defined as parameters
+ * @return source filter with includes and excludes for a query, {@literal null} when no {@link SourceFilters}
+ * annotation was set on the method.
+ * @since 5.0
+ */
+ @Nullable
+ SourceFilter getSourceFilter(ParameterAccessor parameterAccessor, ElasticsearchConverter converter) {
+
+ if (sourceFilters == null || (sourceFilters.includes().length == 0 && sourceFilters.excludes().length == 0)) {
+ return null;
+ }
+
+ ElasticsearchPersistentEntity> persistentEntity = converter.getMappingContext()
+ .getPersistentEntity(getEntityInformation().getJavaType());
+
+ StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService());
+ FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder();
+
+ if (sourceFilters.includes().length > 0) {
+ fetchSourceFilterBuilder
+ .withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil, persistentEntity));
+ }
+
+ if (sourceFilters.excludes().length > 0) {
+ fetchSourceFilterBuilder
+ .withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil, persistentEntity));
+ }
+
+ return fetchSourceFilterBuilder.build();
+ }
+
+ private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor, StringQueryUtil stringQueryUtil,
+ @Nullable ElasticsearchPersistentEntity> persistentEntity) {
+
+ List unmappedFieldNames = new ArrayList<>();
+
+ for (String s : source) {
+
+ if (!s.isBlank()) {
+ String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor);
+ // this could be "[\"foo\",\"bar\"]", must be split
+ if (fieldName.startsWith("[") && fieldName.endsWith("]")) {
+ unmappedFieldNames.addAll( //
+ Arrays.asList(fieldName.substring(1, fieldName.length() - 2) //
+ .replaceAll("\\\"", "") //
+ .split(","))); //
+ } else {
+ unmappedFieldNames.add(fieldName);
+ }
+ }
+ }
+
+ return unmappedFieldNames.stream().map(fieldName -> {
+ ElasticsearchPersistentProperty property = persistentEntity != null
+ ? persistentEntity.getPersistentProperty(fieldName)
+ : null;
+ return property != null ? property.getFieldName() : fieldName;
+ }).toArray(String[]::new);
+ }
}
diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java
index 0875d8579..0d857b302 100644
--- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java
+++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java
@@ -21,6 +21,7 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
+import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -38,13 +39,13 @@ import org.springframework.util.Assert;
*/
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
- private String query;
+ private final String queryString;
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
- String query) {
+ String queryString) {
super(queryMethod, elasticsearchOperations);
- Assert.notNull(query, "Query cannot be empty");
- this.query = query;
+ Assert.notNull(queryString, "Query cannot be empty");
+ this.queryString = queryString;
}
@Override
@@ -56,40 +57,42 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
public Object execute(Object[] parameters) {
Class> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
- ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
+ ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(queryMethod.getParameters(),
+ parameters);
- StringQuery stringQuery = createQuery(accessor);
-
- Assert.notNull(stringQuery, "unsupported query");
+ Query query = createQuery(parameterAccessor);
+ Assert.notNull(query, "unsupported query");
if (queryMethod.hasAnnotatedHighlight()) {
- stringQuery.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
+ query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
}
+ prepareQuery(query, clazz, parameterAccessor);
+
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
- Object result = null;
+ Object result;
if (isCountQuery()) {
- result = elasticsearchOperations.count(stringQuery, clazz, index);
+ result = elasticsearchOperations.count(query, clazz, index);
} else if (queryMethod.isPageQuery()) {
- stringQuery.setPageable(accessor.getPageable());
- SearchHits> searchHits = elasticsearchOperations.search(stringQuery, clazz, index);
+ query.setPageable(parameterAccessor.getPageable());
+ SearchHits> searchHits = elasticsearchOperations.search(query, clazz, index);
if (queryMethod.isSearchPageMethod()) {
- result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable());
+ result = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
} else {
- result = SearchHitSupport
- .unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable()));
+ result = SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
}
} else if (queryMethod.isStreamQuery()) {
- stringQuery.setPageable(
- accessor.getPageable().isPaged() ? accessor.getPageable() : PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
- result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(stringQuery, clazz, index));
+ query.setPageable(parameterAccessor.getPageable().isPaged() ? parameterAccessor.getPageable()
+ : PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
+ result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
} else if (queryMethod.isCollectionQuery()) {
- stringQuery.setPageable(accessor.getPageable().isPaged() ? accessor.getPageable() : Pageable.unpaged());
- result = elasticsearchOperations.search(stringQuery, clazz, index);
+ query.setPageable(
+ parameterAccessor.getPageable().isPaged() ? parameterAccessor.getPageable() : Pageable.unpaged());
+ result = elasticsearchOperations.search(query, clazz, index);
} else {
- result = elasticsearchOperations.searchOne(stringQuery, clazz, index);
+ result = elasticsearchOperations.searchOne(query, clazz, index);
}
return (queryMethod.isNotSearchHitMethod() && queryMethod.isNotSearchPageMethod())
@@ -99,7 +102,7 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) {
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
- .replacePlaceholders(this.query, parameterAccessor);
+ .replacePlaceholders(this.queryString, parameterAccessor);
return new StringQuery(queryString);
}
diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java
index 1d3feef59..580bb1d96 100644
--- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java
+++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryIntegrationTests.java
@@ -48,6 +48,7 @@ import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.Query;
+import org.springframework.data.elasticsearch.annotations.SourceFilters;
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
@@ -80,12 +81,11 @@ import org.springframework.lang.Nullable;
@SpringIntegrationTest
public abstract class CustomMethodRepositoryIntegrationTests implements NewElasticsearchClientDevelopment {
+ @Autowired ElasticsearchOperations operations;
@Autowired private IndexNameProvider indexNameProvider;
@Autowired private SampleCustomMethodRepository repository;
@Autowired private SampleStreamingCustomMethodRepository streamingRepository;
- @Autowired ElasticsearchOperations operations;
-
boolean rhlcWithCluster8() {
var clusterVersion = ((AbstractElasticsearchTemplate) operations).getClusterVersion();
return (oldElasticsearchClient() && clusterVersion != null && clusterVersion.startsWith("8"));
@@ -1675,6 +1675,94 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
assertThat(returnedIds).containsAll(ids);
}
+ @Test // #2146
+ @DisplayName("should use sourceIncludes from annotation")
+ void shouldUseSourceIncludesFromAnnotation() {
+
+ SampleEntity entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity);
+
+ var searchHits = repository.searchWithSourceFilterIncludesAnnotation();
+
+ assertThat(searchHits.hasSearchHits()).isTrue();
+ var foundEntity = searchHits.getSearchHit(0).getContent();
+ assertThat(foundEntity.getMessage()).isEqualTo("message");
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage");
+ assertThat(foundEntity.getType()).isNull();
+ assertThat(foundEntity.getKeyword()).isNull();
+ }
+
+ @Test // #2146
+ @DisplayName("should use sourceIncludes from parameter")
+ void shouldUseSourceIncludesFromParameter() {
+
+ SampleEntity entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity);
+
+ var searchHits = repository.searchBy(List.of("message", "customFieldNameMessage"));
+
+ assertThat(searchHits.hasSearchHits()).isTrue();
+ var foundEntity = searchHits.getSearchHit(0).getContent();
+ assertThat(foundEntity.getMessage()).isEqualTo("message");
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage");
+ assertThat(foundEntity.getType()).isNull();
+ assertThat(foundEntity.getKeyword()).isNull();
+ }
+
+ @Test // #2146
+ @DisplayName("should use sourceExcludes from annotation")
+ void shouldUseSourceExcludesFromAnnotation() {
+
+ SampleEntity entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity);
+
+ var searchHits = repository.searchWithSourceFilterExcludesAnnotation();
+
+ assertThat(searchHits.hasSearchHits()).isTrue();
+ var foundEntity = searchHits.getSearchHit(0).getContent();
+ assertThat(foundEntity.getMessage()).isEqualTo("message");
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage");
+ assertThat(foundEntity.getType()).isNull();
+ assertThat(foundEntity.getKeyword()).isNull();
+ }
+
+ @Test // #2146
+ @DisplayName("should use source excludes from parameter")
+ void shouldUseSourceExcludesFromParameter() {
+
+ SampleEntity entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity);
+
+ var searchHits = repository.findBy(List.of("type", "keyword"));
+
+ assertThat(searchHits.hasSearchHits()).isTrue();
+ var foundEntity = searchHits.getSearchHit(0).getContent();
+ assertThat(foundEntity.getMessage()).isEqualTo("message");
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage");
+ assertThat(foundEntity.getType()).isNull();
+ assertThat(foundEntity.getKeyword()).isNull();
+ }
+
private List createSampleEntities(String type, int numberOfEntities) {
List entities = new ArrayList<>();
@@ -1690,93 +1778,6 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
return entities;
}
- @Document(indexName = "#{@indexNameProvider.indexName()}")
- static class SampleEntity {
- @Nullable
- @Id private String id;
- @Nullable
- @Field(type = Text, store = true, fielddata = true) private String type;
- @Nullable
- @Field(type = Text, store = true, fielddata = true) private String message;
- @Nullable
- @Field(type = Keyword) private String keyword;
- @Nullable private int rate;
- @Nullable private boolean available;
- @Nullable private GeoPoint location;
- @Nullable
- @Version private Long version;
-
- @Nullable
- public String getId() {
- return id;
- }
-
- public void setId(@Nullable String id) {
- this.id = id;
- }
-
- @Nullable
- public String getType() {
- return type;
- }
-
- public void setType(@Nullable String type) {
- this.type = type;
- }
-
- @Nullable
- public String getMessage() {
- return message;
- }
-
- public void setMessage(@Nullable String message) {
- this.message = message;
- }
-
- @Nullable
- public String getKeyword() {
- return keyword;
- }
-
- public void setKeyword(@Nullable String keyword) {
- this.keyword = keyword;
- }
-
- public int getRate() {
- return rate;
- }
-
- public void setRate(int rate) {
- this.rate = rate;
- }
-
- public boolean isAvailable() {
- return available;
- }
-
- public void setAvailable(boolean available) {
- this.available = available;
- }
-
- @Nullable
- public GeoPoint getLocation() {
- return location;
- }
-
- public void setLocation(@Nullable GeoPoint location) {
- this.location = location;
- }
-
- @Nullable
- public java.lang.Long getVersion() {
- return version;
- }
-
- public void setVersion(@Nullable java.lang.Long version) {
- this.version = version;
- }
- }
-
/**
* @author Rizwan Idrees
* @author Mohsin Husen
@@ -1911,11 +1912,30 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
@Query("{\"ids\" : {\"values\" : ?0 }}")
List getByIds(Collection ids);
+
+ @Query("""
+ {
+ "match_all": {}
+ }
+ """)
+ @SourceFilters(includes = { "message", "customFieldNameMessage" })
+ SearchHits searchWithSourceFilterIncludesAnnotation();
+
+ @SourceFilters(includes = "?0")
+ SearchHits searchBy(Collection sourceIncludes);
+
+ @Query("""
+ {
+ "match_all": {}
+ }
+ """)
+ @SourceFilters(excludes = { "type", "keyword" })
+ SearchHits searchWithSourceFilterExcludesAnnotation();
+
+ @SourceFilters(excludes = "?0")
+ SearchHits findBy(Collection sourceExcludes);
}
- /**
- * @author Rasmus Faber-Espensen
- */
public interface SampleStreamingCustomMethodRepository extends ElasticsearchRepository {
Stream findByType(String type);
@@ -1928,4 +1948,103 @@ public abstract class CustomMethodRepositoryIntegrationTests implements NewElast
Stream> streamSearchHitsByType(String type);
}
+
+ @Document(indexName = "#{@indexNameProvider.indexName()}")
+ static class SampleEntity {
+ @Nullable
+ @Id private String id;
+ @Nullable
+ @Field(type = Text, store = true, fielddata = true) private String type;
+ @Nullable
+ @Field(type = Text, store = true, fielddata = true) private String message;
+ @Nullable
+ @Field(type = Keyword) private String keyword;
+ @Nullable private int rate;
+ @Nullable private boolean available;
+ @Nullable private GeoPoint location;
+ @Nullable
+ @Version private Long version;
+
+ @Field(name = "custom_field_name", type = Text)
+ @Nullable private String customFieldNameMessage;
+
+ @Nullable
+ public String getId() {
+ return id;
+ }
+
+ public void setId(@Nullable String id) {
+ this.id = id;
+ }
+
+ @Nullable
+ public String getType() {
+ return type;
+ }
+
+ public void setType(@Nullable String type) {
+ this.type = type;
+ }
+
+ @Nullable
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(@Nullable String message) {
+ this.message = message;
+ }
+
+ @Nullable
+ public String getCustomFieldNameMessage() {
+ return customFieldNameMessage;
+ }
+
+ public void setCustomFieldNameMessage(@Nullable String customFieldNameMessage) {
+ this.customFieldNameMessage = customFieldNameMessage;
+ }
+
+ @Nullable
+ public String getKeyword() {
+ return keyword;
+ }
+
+ public void setKeyword(@Nullable String keyword) {
+ this.keyword = keyword;
+ }
+
+ public int getRate() {
+ return rate;
+ }
+
+ public void setRate(int rate) {
+ this.rate = rate;
+ }
+
+ public boolean isAvailable() {
+ return available;
+ }
+
+ public void setAvailable(boolean available) {
+ this.available = available;
+ }
+
+ @Nullable
+ public GeoPoint getLocation() {
+ return location;
+ }
+
+ public void setLocation(@Nullable GeoPoint location) {
+ this.location = location;
+ }
+
+ @Nullable
+ public java.lang.Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(@Nullable java.lang.Long version) {
+ this.version = version;
+ }
+ }
}
diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java
index 3368732f0..e995d1a80 100644
--- a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java
+++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryIntegrationTests.java
@@ -23,6 +23,7 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -47,6 +48,7 @@ import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.Query;
+import org.springframework.data.elasticsearch.annotations.SourceFilters;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -634,6 +636,98 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
}
+ @Test // #2146
+ @DisplayName("should use sourceIncludes from annotation")
+ void shouldUseSourceIncludesFromAnnotation() {
+
+ var entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity).block();
+
+ repository.searchWithSourceFilterIncludesAnnotation() //
+ .as(StepVerifier::create) //
+ .consumeNextWith(foundEntity -> { //
+ assertThat(foundEntity.getMessage()).isEqualTo("message"); //
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); //
+ assertThat(foundEntity.getType()).isNull(); //
+ assertThat(foundEntity.getKeyword()).isNull(); //
+ }) //
+ .verifyComplete();
+ }
+
+ @Test // #2146
+ @DisplayName("should use sourceIncludes from parameter")
+ void shouldUseSourceIncludesFromParameter() {
+
+ var entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity).block();
+
+ repository.searchBy(List.of("message", "customFieldNameMessage")) //
+ .as(StepVerifier::create) //
+ .consumeNextWith(foundEntity -> { //
+ assertThat(foundEntity.getMessage()).isEqualTo("message"); //
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); //
+ assertThat(foundEntity.getType()).isNull(); //
+ assertThat(foundEntity.getKeyword()).isNull(); //
+ }) //
+ .verifyComplete();
+ }
+
+ @Test // #2146
+ @DisplayName("should use sourceExcludes from annotation")
+ void shouldUseSourceExcludesFromAnnotation() {
+
+ var entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity).block();
+
+ repository.searchWithSourceFilterExcludesAnnotation() //
+ .as(StepVerifier::create) //
+ .consumeNextWith(foundEntity -> { //
+ assertThat(foundEntity.getMessage()).isEqualTo("message"); //
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); //
+ assertThat(foundEntity.getType()).isNull(); //
+ assertThat(foundEntity.getKeyword()).isNull(); //
+ }) //
+ .verifyComplete();
+ }
+
+ @Test // #2146
+ @DisplayName("should use source excludes from parameter")
+ void shouldUseSourceExcludesFromParameter() {
+
+ var entity = new SampleEntity();
+ entity.setId("42");
+ entity.setMessage("message");
+ entity.setCustomFieldNameMessage("customFieldNameMessage");
+ entity.setType("type");
+ entity.setKeyword("keyword");
+ repository.save(entity).block();
+
+ repository.findBy(List.of("type", "keyword")) //
+ .as(StepVerifier::create) //
+ .consumeNextWith(foundEntity -> { //
+ assertThat(foundEntity.getMessage()).isEqualTo("message"); //
+ assertThat(foundEntity.getCustomFieldNameMessage()).isEqualTo("customFieldNameMessage"); //
+ assertThat(foundEntity.getType()).isNull(); //
+ assertThat(foundEntity.getKeyword()).isNull(); //
+ }) //
+ .verifyComplete();
+ }
+
Mono bulkIndex(SampleEntity... entities) {
return operations.saveAll(Arrays.asList(entities), IndexCoordinates.of(indexNameProvider.indexName())).then();
}
@@ -683,6 +777,27 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
@Query("{\"bool\": {\"must\": [{ \"terms\": { \"message\": ?0 } }, { \"terms\": { \"rate\": ?1 } }] } }")
Flux findAllViaAnnotatedQueryByMessageInAndRatesIn(List messages, List rates);
+ @Query(query = """
+ {
+ "match_all": {}
+ }
+ """)
+ @SourceFilters(includes = { "message", "customFieldNameMessage" })
+ Flux searchWithSourceFilterIncludesAnnotation();
+
+ @SourceFilters(includes = "?0")
+ Flux searchBy(Collection sourceIncludes);
+
+ @Query("""
+ {
+ "match_all": {}
+ }
+ """)
+ @SourceFilters(excludes = { "type", "keyword" })
+ Flux searchWithSourceFilterExcludesAnnotation();
+
+ @SourceFilters(excludes = "?0")
+ Flux findBy(Collection sourceExcludes);
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
@@ -693,10 +808,15 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true) private String message;
+ @Nullable
+ @Field(type = FieldType.Keyword) private String keyword;
+
@Nullable private int rate;
@Nullable private boolean available;
@Nullable
@Version private Long version;
+ @Field(name = "custom_field_name", type = FieldType.Text)
+ @Nullable private String customFieldNameMessage;
public SampleEntity() {}
@@ -733,6 +853,15 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
this.type = type;
}
+ @Nullable
+ public String getKeyword() {
+ return keyword;
+ }
+
+ public void setKeyword(@Nullable String keyword) {
+ this.keyword = keyword;
+ }
+
@Nullable
public String getMessage() {
return message;
@@ -766,5 +895,14 @@ abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
public void setVersion(@Nullable java.lang.Long version) {
this.version = version;
}
+
+ @Nullable
+ public String getCustomFieldNameMessage() {
+ return customFieldNameMessage;
+ }
+
+ public void setCustomFieldNameMessage(@Nullable String customFieldNameMessage) {
+ this.customFieldNameMessage = customFieldNameMessage;
+ }
}
}