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. * Specifies that this field is used for storing the document score.
* *
* @author Sascha Woo * @author Sascha Woo
* @since 3.1
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Documented @Documented
@Inherited @Inherited
@ReadOnlyProperty @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 java.io.IOException;
import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.util.Assert;
/** /**
* @author Artur Konczak * @author Artur Konczak
@ -29,6 +30,9 @@ public abstract class AbstractResultMapper implements ResultsMapper {
private EntityMapper entityMapper; private EntityMapper entityMapper;
public AbstractResultMapper(EntityMapper entityMapper) { public AbstractResultMapper(EntityMapper entityMapper) {
Assert.notNull(entityMapper, "EntityMapper must not be null!");
this.entityMapper = entityMapper; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 { public class DefaultResultMapper extends AbstractResultMapper {
private MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext; private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
public DefaultResultMapper() { public DefaultResultMapper() {
this(new SimpleElasticsearchMappingContext()); this(new SimpleElasticsearchMappingContext());
} }
public DefaultResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) { public DefaultResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
super(new DefaultEntityMapper(mappingContext)); super(new DefaultEntityMapper(mappingContext));
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
} }
public DefaultResultMapper(EntityMapper entityMapper) { public DefaultResultMapper(EntityMapper entityMapper) {
super(entityMapper); this(new SimpleElasticsearchMappingContext(), entityMapper);
} }
public DefaultResultMapper( public DefaultResultMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
EntityMapper entityMapper) { EntityMapper entityMapper) {
super(entityMapper); super(entityMapper);
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
} }
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
long totalHits = response.getHits().getTotalHits(); long totalHits = response.getHits().getTotalHits();
float maxScore = response.getHits().getMaxScore(); float maxScore = response.getHits().getMaxScore();
@ -98,6 +107,7 @@ public class DefaultResultMapper extends AbstractResultMapper {
setPersistentEntityId(result, hit.getId(), clazz); setPersistentEntityId(result, hit.getId(), clazz);
setPersistentEntityVersion(result, hit.getVersion(), clazz); setPersistentEntityVersion(result, hit.getVersion(), clazz);
setPersistentEntityScore(result, hit.getScore(), clazz); setPersistentEntityScore(result, hit.getScore(), clazz);
populateScriptFields(result, hit); populateScriptFields(result, hit);
results.add(result); results.add(result);
} }
@ -184,7 +194,9 @@ public class DefaultResultMapper extends AbstractResultMapper {
} }
private <T> void setPersistentEntityId(T result, String id, Class<T> clazz) { 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); ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
@ -196,7 +208,9 @@ public class DefaultResultMapper extends AbstractResultMapper {
} }
private <T> void setPersistentEntityVersion(T result, long version, Class<T> clazz) { 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); ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(clazz);
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
@ -211,14 +225,17 @@ public class DefaultResultMapper extends AbstractResultMapper {
} }
private <T> void setPersistentEntityScore(T result, float score, Class<T> clazz) { 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)) { if (clazz.isAnnotationPresent(Document.class)) {
persistentEntity.getPropertyAccessor(result).setProperty(scoreProperty, score);
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; package org.springframework.data.elasticsearch.core;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@ -12,5 +26,4 @@ import org.springframework.data.domain.Page;
public interface ScoredPage<T> extends Page<T> { public interface ScoredPage<T> extends Page<T> {
float getMaxScore(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Mohsin Husen
* @author Mark Paluch * @author Mark Paluch
* @author Sascha Woo * @author Sascha Woo
* @author Oliver Gierke
*/ */
public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, ElasticsearchPersistentProperty> { 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. * {@literal true}, {@link #getScoreProperty()} will return a non-{@literal null} value.
* *
* @return false when {@link ElasticsearchPersistentEntity} does not define a score property. * @return false when {@link ElasticsearchPersistentEntity} does not define a score property.
* @since 3.1
*/ */
boolean hasScoreProperty(); 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 * @return the score {@link ElasticsearchPersistentProperty} of the {@link PersistentEntity} or {@literal null} if not
* defined. * defined.
* @since 3.1
*/ */
@Nullable @Nullable
ElasticsearchPersistentProperty getScoreProperty(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Sascha Woo * @author Sascha Woo
* @author Oliver Gierke
*/ */
public interface ElasticsearchPersistentProperty extends PersistentProperty<ElasticsearchPersistentProperty> { public interface ElasticsearchPersistentProperty extends PersistentProperty<ElasticsearchPersistentProperty> {
String getFieldName(); String getFieldName();
@ -38,6 +38,7 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
* current property is the version property of that {@link ElasticsearchPersistentEntity} under consideration. * current property is the version property of that {@link ElasticsearchPersistentEntity} under consideration.
* *
* @return * @return
* @since 3.1
*/ */
boolean isScoreProperty(); boolean isScoreProperty();

View File

@ -183,6 +183,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
} }
if (property.isScoreProperty()) { if (property.isScoreProperty()) {
ElasticsearchPersistentProperty scoreProperty = this.scoreProperty; ElasticsearchPersistentProperty scoreProperty = this.scoreProperty;
if (scoreProperty != null) { if (scoreProperty != null) {
@ -191,8 +192,6 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
+ "as version. Check your mapping configuration!", property.getField(), scoreProperty.getField())); + "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; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; package org.springframework.data.elasticsearch.core.mapping;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.springframework.data.elasticsearch.annotations.Score; import org.springframework.data.elasticsearch.annotations.Score;
import org.springframework.data.mapping.Association; import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.Lazy;
/** /**
* Elasticsearch specific {@link org.springframework.data.mapping.PersistentProperty} implementation processing * Elasticsearch specific {@link org.springframework.data.mapping.PersistentProperty} implementation processing
@ -33,6 +34,7 @@ import org.springframework.data.util.Lazy;
* @author Mohsin Husen * @author Mohsin Husen
* @author Mark Paluch * @author Mark Paluch
* @author Sascha Woo * @author Sascha Woo
* @author Oliver Gierke
*/ */
public class SimpleElasticsearchPersistentProperty extends public class SimpleElasticsearchPersistentProperty extends
AnnotationBasedPersistentProperty<ElasticsearchPersistentProperty> implements ElasticsearchPersistentProperty { 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<Class<?>> SUPPORTED_ID_TYPES = new HashSet<>();
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = 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 { static {
SUPPORTED_ID_TYPES.add(String.class); SUPPORTED_ID_TYPES.add(String.class);
@ -50,7 +52,14 @@ public class SimpleElasticsearchPersistentProperty extends
public SimpleElasticsearchPersistentProperty(Property property, public SimpleElasticsearchPersistentProperty(Property property,
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) { PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(property, owner, 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 @Override
@ -68,8 +77,12 @@ public class SimpleElasticsearchPersistentProperty extends
return null; return null;
} }
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty#isScoreProperty()
*/
@Override @Override
public boolean isScoreProperty() { public boolean isScoreProperty() {
return isScore.get(); return isScore;
} }
} }

View File

@ -165,11 +165,21 @@ abstract class AbstractQuery implements Query {
this.indicesOptions = indicesOptions; this.indicesOptions = indicesOptions;
} }
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.query.Query#getTrackScores()
*/
@Override @Override
public boolean getTrackScores() { public boolean getTrackScores() {
return trackScores; return trackScores;
} }
/**
* Configures whether to track scores.
*
* @param trackScores
* @since 3.1
*/
public void setTrackScores(boolean trackScores) { public void setTrackScores(boolean trackScores) {
this.trackScores = trackScores; this.trackScores = trackScores;
} }

View File

@ -130,6 +130,11 @@ public class NativeSearchQueryBuilder {
return this; return this;
} }
/**
* @param trackScores whether to track scores.
* @return
* @since 3.1
*/
public NativeSearchQueryBuilder withTrackScores(boolean trackScores) { public NativeSearchQueryBuilder withTrackScores(boolean trackScores) {
this.trackScores = trackScores; this.trackScores = trackScores;
return this; 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>. * Get if scores will be computed and tracked, regardless of whether sorting on a field. Defaults to <tt>false</tt>.
* *
* @return * @return
* @since 3.1
*/ */
boolean getTrackScores(); boolean getTrackScores();

View File

@ -46,6 +46,7 @@ import org.springframework.data.elasticsearch.entities.Car;
import org.springframework.data.elasticsearch.entities.SampleEntity; import org.springframework.data.elasticsearch.entities.SampleEntity;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.*;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.*; 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.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
@ -1516,6 +1515,7 @@ public class ElasticsearchTemplateTests {
@Test // DATAES-462 @Test // DATAES-462
public void shouldReturnScores() { public void shouldReturnScores() {
// given // given
List<IndexQuery> indexQueries = new ArrayList<>(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; package org.springframework.data.elasticsearch.core.mapping;
import static org.assertj.core.api.Assertions.*;
import java.beans.IntrospectionException; import java.beans.IntrospectionException;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.annotation.Version; import org.springframework.data.annotation.Version;
import org.springframework.data.elasticsearch.annotations.Score;
import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mapping.model.SimpleTypeHolder;
@ -30,6 +33,7 @@ import org.springframework.util.ReflectionUtils;
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Mark Paluch * @author Mark Paluch
* @author Oliver Gierke
*/ */
public class SimpleElasticsearchPersistentEntityTests { public class SimpleElasticsearchPersistentEntityTests {
@ -63,6 +67,17 @@ public class SimpleElasticsearchPersistentEntityTests {
entity.addPersistentProperty(persistentProperty2); 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, private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasticsearchPersistentEntity<?> entity,
String field) { String field) {
@ -106,4 +121,12 @@ public class SimpleElasticsearchPersistentEntityTests {
this.version2 = version2; 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;
}
}