diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java index a4a40a15e..284543220 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java @@ -19,7 +19,6 @@ import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; -import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; @@ -64,7 +63,13 @@ default String convertId(Object idValue) { return idValue.toString(); } - return getConversionService().convert(idValue, String.class); + String converted = getConversionService().convert(idValue, String.class); + + if (converted == null) { + return idValue.toString(); + } + + return converted; } /** @@ -86,29 +91,15 @@ default Document mapObject(@Nullable Object source) { // region query /** - * Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and - * the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a + * Updates a {@link Query} by renaming the property names in the query to the correct mapped field names and the + * values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a * {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If - * domainClass is null or query is not a {@link CriteriaQuery}, it's a noop. + * domainClass is null it's a noop. * - * @param query the query that is internally updated + * @param query the query that is internally updated, must not be {@literal null} * @param domainClass the class of the object that is searched with the query */ - default void updateQuery(Query query, @Nullable Class domainClass) { - - if (domainClass != null && query instanceof CriteriaQuery) { - updateCriteriaQuery((CriteriaQuery) query, domainClass); - } - } + void updateQuery(Query query, @Nullable Class domainClass); - /** - * Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and - * the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a - * {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. - * - * @param criteriaQuery the query that is internally updated, must not be {@literal null} - * @param domainClass the class of the object that is searched with the query, must not be {@literal null} - */ - void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass); // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index 18286bb1c..c080d1e1f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -42,8 +42,11 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; import org.springframework.data.elasticsearch.core.query.Field; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; +import org.springframework.data.elasticsearch.core.query.SourceFilter; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor; @@ -1024,7 +1027,61 @@ private static Collection asCollection(Object source) { // region queries @Override - public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass) { + public void updateQuery(Query query, @Nullable Class domainClass) { + + Assert.notNull(query, "query must not be null"); + + if (domainClass != null) { + + updateFieldsAndSourceFilter(query, domainClass); + + if (query instanceof CriteriaQuery) { + updateCriteriaQuery((CriteriaQuery) query, domainClass); + } + } + } + + private void updateFieldsAndSourceFilter(Query query, Class domainClass) { + + ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(domainClass); + + if (persistentEntity != null) { + List fields = query.getFields(); + + if (!fields.isEmpty()) { + query.setFields(updateFieldNames(fields, persistentEntity)); + } + + SourceFilter sourceFilter = query.getSourceFilter(); + + if (sourceFilter != null) { + + String[] includes = null; + String[] excludes = null; + + if (sourceFilter.getIncludes() != null) { + includes = updateFieldNames(Arrays.asList(sourceFilter.getIncludes()), persistentEntity) + .toArray(new String[] {}); + } + + if (sourceFilter.getExcludes() != null) { + excludes = updateFieldNames(Arrays.asList(sourceFilter.getExcludes()), persistentEntity) + .toArray(new String[] {}); + } + + query.addSourceFilter(new FetchSourceFilter(includes, excludes)); + } + } + } + + private List updateFieldNames(List fields, ElasticsearchPersistentEntity persistentEntity) { + return fields.stream().map(fieldName -> { + ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName); + return persistentProperty != null ? persistentProperty.getFieldName() : fieldName; + }).collect(Collectors.toList()); + } + + private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass) { Assert.notNull(criteriaQuery, "criteriaQuery must not be null"); Assert.notNull(domainClass, "domainClass must not be null"); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index 8384d757a..1d3e9fb49 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -96,6 +96,15 @@ public List getFields() { return fields; } + @Override + public void setFields(List fields) { + + Assert.notNull(fields, "fields must not be null"); + + this.fields.clear(); + this.fields.addAll(fields); + } + @Override public void addSourceFilter(SourceFilter sourceFilter) { this.sourceFilter = sourceFilter; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java index 25aeb0bf0..cbb10eb98 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java @@ -103,6 +103,13 @@ static Query findAll() { */ List getFields(); + /** + * Set fields to be returned as part of search request + * @param fields must not be {@literal null} + * @since 4.3 + */ + void setFields(List fields); + /** * Add source filter to be added as part of search request * diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index a54ae7b63..e27eedead 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -15,7 +15,6 @@ */ package org.springframework.data.elasticsearch.core; -import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; import java.time.LocalDate; @@ -25,6 +24,7 @@ import java.util.List; import java.util.Objects; +import org.assertj.core.api.SoftAssertions; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -44,6 +44,9 @@ import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.SourceFilter; import org.springframework.lang.Nullable; /** @@ -356,9 +359,7 @@ void shouldMapNamesAndValueInNestedEntities() throws JSONException { " }\n" + // "}\n"; // - CriteriaQuery criteriaQuery = new CriteriaQuery( - new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)) - ); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3))); mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class); String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); @@ -389,9 +390,7 @@ void shouldMapNamesAndValueInNestedEntitiesWithSubfields() throws JSONException " }\n" + // "}\n"; // - CriteriaQuery criteriaQuery = new CriteriaQuery( - new Criteria("persons.nickName.keyword").is("Foobar") - ); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.nickName.keyword").is("Foobar")); mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class); String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); @@ -417,14 +416,33 @@ void shouldMapNamesAndValueInObjectEntities() throws JSONException { " }\n" + // "}\n"; // - CriteriaQuery criteriaQuery = new CriteriaQuery( - new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)) - ); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3))); mappingElasticsearchConverter.updateQuery(criteriaQuery, ObjectWithPerson.class); String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); assertEquals(expected, queryString, false); } + + @Test // #1778 + @DisplayName("should map names in source fields and SourceFilters") + void shouldMapNamesInSourceFieldsAndSourceFilters() { + + Query query = Query.findAll(); + // Note: we don't care if these filters make sense here, this test is only about name mapping + query.addFields("firstName", "lastName"); + query.addSourceFilter(new FetchSourceFilter(new String[] { "firstName" }, new String[] { "lastName" })); + + mappingElasticsearchConverter.updateQuery(query, Person.class); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(query.getFields()).containsExactly("first-name", "last-name"); + SourceFilter sourceFilter = query.getSourceFilter(); + softly.assertThat(sourceFilter).isNotNull(); + softly.assertThat(sourceFilter.getIncludes()).containsExactly("first-name"); + softly.assertThat(sourceFilter.getExcludes()).containsExactly("last-name"); + softly.assertAll(); + } + // endregion // region helper functions @@ -442,7 +460,8 @@ static class Person { @Nullable @Id String id; @Nullable @Field(name = "first-name") String firstName; @Nullable @Field(name = "last-name") String lastName; - @Nullable @MultiField(mainField = @Field(name="nick-name"), otherFields = {@InnerField(suffix = "keyword", type = FieldType.Keyword)}) String nickName; + @Nullable @MultiField(mainField = @Field(name = "nick-name"), + otherFields = { @InnerField(suffix = "keyword", type = FieldType.Keyword) }) String nickName; @Nullable @Field(name = "created-date", type = FieldType.Date, format = DateFormat.epoch_millis) Date createdDate; @Nullable @Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate; @@ -450,16 +469,12 @@ static class Person { static class House { @Nullable @Id String id; - @Nullable - @Field(name = "per-sons", type = FieldType.Nested) - List persons; + @Nullable @Field(name = "per-sons", type = FieldType.Nested) List persons; } static class ObjectWithPerson { @Nullable @Id String id; - @Nullable - @Field(name = "per-sons", type = FieldType.Object) - List persons; + @Nullable @Field(name = "per-sons", type = FieldType.Object) List persons; } static class GeoShapeEntity {