From 5ddb46c435f8daa4839084870e8eeb53270af159 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 13 Jun 2018 18:56:12 +0200 Subject: [PATCH] DATAES-462 - Polishing. SimpleElasticsearchPersistentProperty now already checks for the correct type of score properties. Added unit tests for that. Also added unit tests for SimpleElasticsearchPersistentEntity rejecting more than one score property being present. Additional non-null assertions on components that are required so that we can remove superfluous null checks. A bit o formatting, Javadoc, missing @since tags and license headers. Original pull request: #207. --- .../data/elasticsearch/annotations/Score.java | 4 +- .../core/AbstractResultMapper.java | 4 ++ .../core/DefaultResultMapper.java | 39 +++++++++++----- .../data/elasticsearch/core/ScoredPage.java | 17 ++++++- .../ElasticsearchPersistentEntity.java | 5 ++- .../ElasticsearchPersistentProperty.java | 5 ++- .../SimpleElasticsearchPersistentEntity.java | 3 +- ...SimpleElasticsearchPersistentProperty.java | 21 +++++++-- .../core/query/AbstractQuery.java | 10 +++++ .../core/query/NativeSearchQueryBuilder.java | 5 +++ .../data/elasticsearch/core/query/Query.java | 1 + .../core/DefaultResultMapperTests.java | 1 + .../core/ElasticsearchTemplateTests.java | 2 +- ...pleElasticsearchPersistentEntityTests.java | 25 ++++++++++- ...sticsearchPersistentPropertyUnitTests.java | 44 +++++++++++++++++++ 15 files changed, 160 insertions(+), 26 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Score.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Score.java index b64bfe120..1696626c6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Score.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Score.java @@ -13,11 +13,11 @@ import org.springframework.data.annotation.ReadOnlyProperty; * Specifies that this field is used for storing the document score. * * @author Sascha Woo + * @since 3.1 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @Documented @Inherited @ReadOnlyProperty -public @interface Score { -} +public @interface Score {} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractResultMapper.java index 0f8a16829..c558bb757 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractResultMapper.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractResultMapper.java @@ -20,6 +20,7 @@ import static org.apache.commons.lang.StringUtils.*; import java.io.IOException; import org.springframework.data.elasticsearch.ElasticsearchException; +import org.springframework.util.Assert; /** * @author Artur Konczak @@ -29,6 +30,9 @@ public abstract class AbstractResultMapper implements ResultsMapper { private EntityMapper entityMapper; public AbstractResultMapper(EntityMapper entityMapper) { + + Assert.notNull(entityMapper, "EntityMapper must not be null!"); + this.entityMapper = entityMapper; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java index 3a20182a3..efedfaa1d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 the original author or authors. + * Copyright 2014-2018 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. @@ -58,30 +58,39 @@ import com.fasterxml.jackson.core.JsonGenerator; */ public class DefaultResultMapper extends AbstractResultMapper { - private MappingContext, ElasticsearchPersistentProperty> mappingContext; + private final MappingContext, ElasticsearchPersistentProperty> mappingContext; public DefaultResultMapper() { this(new SimpleElasticsearchMappingContext()); } public DefaultResultMapper(MappingContext, ElasticsearchPersistentProperty> mappingContext) { + super(new DefaultEntityMapper(mappingContext)); + + Assert.notNull(mappingContext, "MappingContext must not be null!"); + this.mappingContext = mappingContext; } public DefaultResultMapper(EntityMapper entityMapper) { - super(entityMapper); + this(new SimpleElasticsearchMappingContext(), entityMapper); } public DefaultResultMapper( MappingContext, ElasticsearchPersistentProperty> mappingContext, EntityMapper entityMapper) { + super(entityMapper); + + Assert.notNull(mappingContext, "MappingContext must not be null!"); + this.mappingContext = mappingContext; } @Override public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { + long totalHits = response.getHits().getTotalHits(); float maxScore = response.getHits().getMaxScore(); @@ -98,6 +107,7 @@ public class DefaultResultMapper extends AbstractResultMapper { setPersistentEntityId(result, hit.getId(), clazz); setPersistentEntityVersion(result, hit.getVersion(), clazz); setPersistentEntityScore(result, hit.getScore(), clazz); + populateScriptFields(result, hit); results.add(result); } @@ -184,7 +194,9 @@ public class DefaultResultMapper extends AbstractResultMapper { } private void setPersistentEntityId(T result, String id, Class clazz) { - if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) { + + if (clazz.isAnnotationPresent(Document.class)) { + ElasticsearchPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(clazz); ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); @@ -196,7 +208,9 @@ public class DefaultResultMapper extends AbstractResultMapper { } private void setPersistentEntityVersion(T result, long version, Class clazz) { - if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) { + + if (clazz.isAnnotationPresent(Document.class)) { + ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(clazz); ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); @@ -211,14 +225,17 @@ public class DefaultResultMapper extends AbstractResultMapper { } private void setPersistentEntityScore(T result, float score, Class clazz) { - if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) { - ElasticsearchPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(clazz); - ElasticsearchPersistentProperty scoreProperty = persistentEntity.getScoreProperty(); - Class type = scoreProperty.getType(); - if (scoreProperty != null && (type == Float.class || type == Float.TYPE)) { - persistentEntity.getPropertyAccessor(result).setProperty(scoreProperty, score); + if (clazz.isAnnotationPresent(Document.class)) { + + ElasticsearchPersistentEntity entity = mappingContext.getRequiredPersistentEntity(clazz); + + if (!entity.hasScoreProperty()) { + return; } + + entity.getPropertyAccessor(result) // + .setProperty(entity.getScoreProperty(), score); } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ScoredPage.java b/src/main/java/org/springframework/data/elasticsearch/core/ScoredPage.java index 64e61a221..aa8d28f8e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ScoredPage.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ScoredPage.java @@ -1,4 +1,18 @@ - +/* + * Copyright 2018 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 + * + * http://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; @@ -12,5 +26,4 @@ import org.springframework.data.domain.Page; public interface ScoredPage extends Page { float getMaxScore(); - } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index d78263acc..6d07c3b1c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 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. @@ -25,6 +25,7 @@ import org.springframework.lang.Nullable; * @author Mohsin Husen * @author Mark Paluch * @author Sascha Woo + * @author Oliver Gierke */ public interface ElasticsearchPersistentEntity extends PersistentEntity { @@ -57,6 +58,7 @@ public interface ElasticsearchPersistentEntity extends PersistentEntity extends PersistentEntity { String getFieldName(); @@ -38,6 +38,7 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty extends BasicPersistentEntit } if (property.isScoreProperty()) { + ElasticsearchPersistentProperty scoreProperty = this.scoreProperty; if (scoreProperty != null) { @@ -191,8 +192,6 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit + "as version. Check your mapping configuration!", property.getField(), scoreProperty.getField())); } - Assert.isTrue(property.getType() == Float.class || property.getType() == Float.TYPE, "Score property must be of type float!"); - this.scoreProperty = property; } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 21afb1e27..c10318ee0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 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. @@ -15,16 +15,17 @@ */ package org.springframework.data.elasticsearch.core.mapping; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.springframework.data.elasticsearch.annotations.Score; import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; -import org.springframework.data.util.Lazy; /** * Elasticsearch specific {@link org.springframework.data.mapping.PersistentProperty} implementation processing @@ -33,6 +34,7 @@ import org.springframework.data.util.Lazy; * @author Mohsin Husen * @author Mark Paluch * @author Sascha Woo + * @author Oliver Gierke */ public class SimpleElasticsearchPersistentProperty extends AnnotationBasedPersistentProperty implements ElasticsearchPersistentProperty { @@ -40,7 +42,7 @@ public class SimpleElasticsearchPersistentProperty extends private static final Set> SUPPORTED_ID_TYPES = new HashSet<>(); private static final Set SUPPORTED_ID_PROPERTY_NAMES = new HashSet<>(); - private final Lazy isScore = Lazy.of(() -> isAnnotationPresent(Score.class)); + private final boolean isScore; static { SUPPORTED_ID_TYPES.add(String.class); @@ -50,7 +52,14 @@ public class SimpleElasticsearchPersistentProperty extends public SimpleElasticsearchPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(property, owner, simpleTypeHolder); + + this.isScore = isAnnotationPresent(Score.class); + + if (isScore && !Arrays.asList(Float.TYPE, Float.class).contains(getType())) { + throw new MappingException(String.format("Score property %s must be either of type float or Float!", property.getName())); + } } @Override @@ -68,8 +77,12 @@ public class SimpleElasticsearchPersistentProperty extends return null; } + /* + * (non-Javadoc) + * @see org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty#isScoreProperty() + */ @Override public boolean isScoreProperty() { - return isScore.get(); + return isScore; } } 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 c1917b8ba..b8bcfd52b 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 @@ -165,11 +165,21 @@ abstract class AbstractQuery implements Query { this.indicesOptions = indicesOptions; } + /* + * (non-Javadoc) + * @see org.springframework.data.elasticsearch.core.query.Query#getTrackScores() + */ @Override public boolean getTrackScores() { return trackScores; } + /** + * Configures whether to track scores. + * + * @param trackScores + * @since 3.1 + */ public void setTrackScores(boolean trackScores) { this.trackScores = trackScores; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 2b9fb73f5..2c39ccdf6 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -130,6 +130,11 @@ public class NativeSearchQueryBuilder { return this; } + /** + * @param trackScores whether to track scores. + * @return + * @since 3.1 + */ public NativeSearchQueryBuilder withTrackScores(boolean trackScores) { this.trackScores = trackScores; return this; 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 5f82e4d2a..27d2aeae7 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 @@ -132,6 +132,7 @@ public interface Query { * Get if scores will be computed and tracked, regardless of whether sorting on a field. Defaults to false. * * @return + * @since 3.1 */ boolean getTrackScores(); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java b/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java index a33270561..27acb2dc3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java @@ -46,6 +46,7 @@ import org.springframework.data.elasticsearch.entities.Car; import org.springframework.data.elasticsearch.entities.SampleEntity; import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 08ef1b390..07d438549 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -36,7 +36,6 @@ import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.sort.FieldSortBuilder; -import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.hamcrest.Matchers; @@ -1516,6 +1515,7 @@ public class ElasticsearchTemplateTests { @Test // DATAES-462 public void shouldReturnScores() { + // given List indexQueries = new ArrayList<>(); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java index 281a1db9c..c6bf94095 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 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. @@ -15,10 +15,13 @@ */ package org.springframework.data.elasticsearch.core.mapping; +import static org.assertj.core.api.Assertions.*; + import java.beans.IntrospectionException; import org.junit.Test; import org.springframework.data.annotation.Version; +import org.springframework.data.elasticsearch.annotations.Score; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; @@ -30,6 +33,7 @@ import org.springframework.util.ReflectionUtils; * @author Rizwan Idrees * @author Mohsin Husen * @author Mark Paluch + * @author Oliver Gierke */ public class SimpleElasticsearchPersistentEntityTests { @@ -62,6 +66,17 @@ public class SimpleElasticsearchPersistentEntityTests { // when entity.addPersistentProperty(persistentProperty2); } + + @Test // DATAES-462 + public void rejectsMultipleScoreProperties() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + + assertThatExceptionOfType(MappingException.class) // + .isThrownBy(() -> context.getRequiredPersistentEntity(TwoScoreProperties.class)) // + .withMessageContaining("first") // + .withMessageContaining("second"); + } private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasticsearchPersistentEntity entity, String field) { @@ -106,4 +121,12 @@ public class SimpleElasticsearchPersistentEntityTests { this.version2 = version2; } } + + // DATAES-462 + + static class TwoScoreProperties { + + @Score float first; + @Score float second; + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java new file mode 100644 index 000000000..d79274926 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 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 + * + * http://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.mapping; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; +import org.springframework.data.elasticsearch.annotations.Score; +import org.springframework.data.mapping.MappingException; + +/** + * Unit tests for {@link SimpleElasticsearchPersistentProperty}. + * + * @author Oliver Gierke + */ +public class SimpleElasticsearchPersistentPropertyUnitTests { + + @Test // DATAES-462 + public void rejectsScorePropertyOfTypeOtherthanFloat() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + + assertThatExceptionOfType(MappingException.class) // + .isThrownBy(() -> context.getRequiredPersistentEntity(InvalidScoreProperty.class)) // + .withMessageContaining("scoreProperty"); + } + + static class InvalidScoreProperty { + @Score String scoreProperty; + } +}