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.
This commit is contained in:
Oliver Gierke 2018-06-13 18:56:12 +02:00
parent d996406113
commit 5ddb46c435
15 changed files with 160 additions and 26 deletions

View File

@ -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 {}

View File

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

View File

@ -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<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
public DefaultResultMapper() {
this(new SimpleElasticsearchMappingContext());
}
public DefaultResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, 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<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
EntityMapper entityMapper) {
super(entityMapper);
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext;
}
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> 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 <T> void setPersistentEntityId(T result, String id, Class<T> 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 <T> void setPersistentEntityVersion(T result, long version, Class<T> 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 <T> void setPersistentEntityScore(T result, float score, Class<T> 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);
}
}
}

View File

@ -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<T> extends Page<T> {
float getMaxScore();
}

View File

@ -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<T> extends PersistentEntity<T, ElasticsearchPersistentProperty> {
@ -57,6 +58,7 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
* {@literal true}, {@link #getScoreProperty()} will return a non-{@literal null} value.
*
* @return false when {@link ElasticsearchPersistentEntity} does not define a score property.
* @since 3.1
*/
boolean hasScoreProperty();
@ -66,6 +68,7 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
*
* @return the score {@link ElasticsearchPersistentProperty} of the {@link PersistentEntity} or {@literal null} if not
* defined.
* @since 3.1
*/
@Nullable
ElasticsearchPersistentProperty getScoreProperty();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013 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.
@ -24,8 +24,8 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Sascha Woo
* @author Oliver Gierke
*/
public interface ElasticsearchPersistentProperty extends PersistentProperty<ElasticsearchPersistentProperty> {
String getFieldName();
@ -38,6 +38,7 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
* current property is the version property of that {@link ElasticsearchPersistentEntity} under consideration.
*
* @return
* @since 3.1
*/
boolean isScoreProperty();

View File

@ -183,6 +183,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
}
if (property.isScoreProperty()) {
ElasticsearchPersistentProperty scoreProperty = this.scoreProperty;
if (scoreProperty != null) {
@ -191,8 +192,6 @@ public class SimpleElasticsearchPersistentEntity<T> 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;
}
}

View File

@ -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<ElasticsearchPersistentProperty> implements ElasticsearchPersistentProperty {
@ -40,7 +42,7 @@ public class SimpleElasticsearchPersistentProperty extends
private static final Set<Class<?>> SUPPORTED_ID_TYPES = new HashSet<>();
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = new HashSet<>();
private final Lazy<Boolean> 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<?, ElasticsearchPersistentProperty> 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;
}
}

View File

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

View File

@ -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;

View File

@ -132,6 +132,7 @@ public interface Query {
* Get if scores will be computed and tracked, regardless of whether sorting on a field. Defaults to <tt>false</tt>.
*
* @return
* @since 3.1
*/
boolean getTrackScores();

View File

@ -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.*;

View File

@ -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<IndexQuery> indexQueries = new ArrayList<>();

View File

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

View File

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