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; + } +}