DATAMONGO-976 - Add support for reading $meta projection on textScore into document.

We introduced @TextScore that can be used to mark a property to take { $meta : “textScore” }. In contrast to @Transient the value will be considered when reading documents.
The value can and will only get picked up if the score field is retrieved from the store.

Original pull request: #198.
This commit is contained in:
Christoph Strobl
2014-07-02 11:24:54 +02:00
committed by Thomas Darimont
parent 83ffbb00e8
commit 89a42c5648
10 changed files with 256 additions and 12 deletions

View File

@@ -385,7 +385,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
public void doWithPersistentProperty(MongoPersistentProperty prop) {
if (prop.equals(idProperty)) {
if (prop.equals(idProperty) || prop.isCalculatedProperty()) {
return;
}

View File

@@ -38,6 +38,7 @@ import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -120,12 +121,22 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
@Override
public void verify() {
verifyFieldUniqueness();
verifyFieldTypes();
}
private void verifyFieldUniqueness() {
AssertFieldNameUniquenessHandler handler = new AssertFieldNameUniquenessHandler();
doWithProperties(handler);
doWithAssociations(handler);
}
private void verifyFieldTypes() {
doWithProperties(new PropertyTypeAssertionHandler());
}
/**
* {@link Comparator} implementation inspecting the {@link MongoPersistentProperty}'s order.
*
@@ -240,4 +251,45 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
}
}
/**
* @author Christoph Strobl
* @since 1.6
*/
private static class PropertyTypeAssertionHandler implements PropertyHandler<MongoPersistentProperty> {
@Override
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
potentiallyAssertTextScoreType(persistentProperty);
potentiallyAssertLanguageType(persistentProperty);
}
private void potentiallyAssertLanguageType(MongoPersistentProperty persistentProperty) {
if (persistentProperty.isLanguageProperty()) {
assertPropertyType(persistentProperty, String.class);
}
}
private void potentiallyAssertTextScoreType(MongoPersistentProperty persistentProperty) {
if (persistentProperty.isTextScoreProperty()) {
assertPropertyType(persistentProperty, Float.class, Double.class);
}
}
private void assertPropertyType(MongoPersistentProperty persistentProperty, Class<?>... validMatches) {
for (Class<?> potentialMatch : validMatches) {
if (ClassUtils.isAssignable(potentialMatch, persistentProperty.getActualType())) {
return;
}
}
throw new MappingException(String.format("Missmatching types for %s. Found %s expected one of %s.",
persistentProperty.getField(), persistentProperty.getActualType(),
StringUtils.arrayToCommaDelimitedString(validMatches)));
}
}
}

View File

@@ -192,4 +192,22 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
public boolean isLanguageProperty() {
return getFieldName().equals(LANGUAGE_FIELD_NAME) || isAnnotationPresent(Language.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isTextScoreProperty()
*/
@Override
public boolean isTextScoreProperty() {
return isAnnotationPresent(TextScore.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isCalculatedProperty()
*/
@Override
public boolean isCalculatedProperty() {
return isAnnotationPresent(Calculated.class);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2014 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.mongodb.core.mapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Meta-annotation to be used to annotate {@link java.lang.annotation.Annotation}s that mark properties either
* calculated internally or on server. <br/>
* <b>NOTE</b>: Different to {@link org.springframework.data.annotation.Transient}, {@link Calculated} properties are
* considered when reading from the store.
*
* @author Christoph Strobl
* @since 1.6
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Calculated {
}

View File

@@ -69,6 +69,24 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
*/
boolean isLanguageProperty();
/**
* Returns whether the property holds the documents score calculated by text search. <br/>
* It's marked with {@link TextScore}.
*
* @return
* @since 1.6
*/
boolean isTextScoreProperty();
/**
* Returns wheter the property is calculated eiter internally or on the server and therefore must not be written when
* saved.
*
* @return
* @since 1.6
*/
boolean isCalculatedProperty();
/**
* Returns the {@link DBRef} if the property is a reference.
*

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2014 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.mongodb.core.mapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@link TextScore} marks the property to be considered as the on server calculated {@literal textScore} when doing
* full text search. <br />
* <b>NOTE</b> Property will not be written when saving entity.
*
* @author Christoph Strobl
* @since 1.6
*/
@Calculated
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TextScore {
}

View File

@@ -73,6 +73,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.test.util.ReflectionTestUtils;
@@ -1807,6 +1808,32 @@ public class MappingMongoConverterUnitTests {
assertThat(result.shape, is((Shape) sphere));
}
/**
* @see DATAMONGO-976
*/
@Test
public void shouldIgnoreTextScorePropertyWhenWriting() {
ClassWithTextScoreProperty source = new ClassWithTextScoreProperty();
source.score = Float.MAX_VALUE;
BasicDBObject dbo = new BasicDBObject();
converter.write(source, dbo);
assertThat(dbo.get("score"), nullValue());
}
/**
* @see DATAMONGO-976
*/
@Test
public void shouldIncludeTextScorePropertyWhenReading() {
ClassWithTextScoreProperty entity = converter
.read(ClassWithTextScoreProperty.class, new BasicDBObject("score", 5F));
assertThat(entity.score, equalTo(5F));
}
static class GenericType<T> {
T content;
}
@@ -2057,4 +2084,9 @@ public class MappingMongoConverterUnitTests {
Shape shape;
}
class ClassWithTextScoreProperty {
@TextScore Float score;
}
}

View File

@@ -150,6 +150,32 @@ public class BasicMongoPersistentPropertyUnitTests {
assertThat(property.isLanguageProperty(), is(true));
}
/**
* @see DATAMONGO-976
*/
@Test
public void shouldDetectTextScorePropertyCorrectly() {
BasicMongoPersistentEntity<DocumentWithTextScoreProperty> persistentEntity = new BasicMongoPersistentEntity<DocumentWithTextScoreProperty>(
ClassTypeInformation.from(DocumentWithTextScoreProperty.class));
MongoPersistentProperty property = getPropertyFor(persistentEntity, "score");
assertThat(property.isTextScoreProperty(), is(true));
}
/**
* @see DATAMONGO-976
*/
@Test
public void shouldDetectTextScoreAsCalculatedProperty() {
BasicMongoPersistentEntity<DocumentWithTextScoreProperty> persistentEntity = new BasicMongoPersistentEntity<DocumentWithTextScoreProperty>(
ClassTypeInformation.from(DocumentWithTextScoreProperty.class));
MongoPersistentProperty property = getPropertyFor(persistentEntity, "score");
assertThat(property.isCalculatedProperty(), is(true));
}
private MongoPersistentProperty getPropertyFor(Field field) {
return getPropertyFor(entity, field);
}
@@ -200,4 +226,8 @@ public class BasicMongoPersistentPropertyUnitTests {
String language;
}
static class DocumentWithTextScoreProperty {
@TextScore Float score;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2013 by the original author(s).
* Copyright 2011-2014 by the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import com.mongodb.DBRef;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoMappingContextUnitTests {
@@ -179,6 +180,21 @@ public class MongoMappingContextUnitTests {
context.getPersistentEntity(ClassWithMultipleImplicitIds.class);
}
/**
* @see DATAMONGO-976
*/
@Test
public void shouldRejectClassWithInvalidTextScoreProperty() {
exception.expect(MappingException.class);
exception.expectMessage("score");
exception.expectMessage("Float");
exception.expectMessage("Double");
MongoMappingContext context = new MongoMappingContext();
context.getPersistentEntity(ClassWithInvalidTextScoreProperty.class);
}
public class SampleClass {
Map<String, SampleClass> children;
@@ -240,4 +256,9 @@ public class MongoMappingContextUnitTests {
String _id;
String id;
}
class ClassWithInvalidTextScoreProperty {
@TextScore Locale score;
}
}

View File

@@ -39,6 +39,7 @@ import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.Language;
import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.mongodb.core.query.text.TextQueryTests.FullTextDoc.FullTextDocBuilder;
import org.springframework.data.mongodb.test.util.MongoVersionRule;
import org.springframework.data.util.Version;
@@ -183,18 +184,18 @@ public class TextQueryTests extends AbstractIntegrationTests {
}
/**
* @see DATAMONGO-850
* @see DATAMONGO-976
*/
@Test
public void shouldInlcudeScoreCorreclty() {
initWithDefaultDocuments();
List<FullTextDocWithScore> result = template.find(new TextQuery("bake coffee -cake").includeScore().sortByScore(),
FullTextDocWithScore.class);
List<FullTextDoc> result = template.find(new TextQuery("bake coffee -cake").includeScore().sortByScore(),
FullTextDoc.class);
assertThat(result, hasSize(2));
for (FullTextDocWithScore scoredDoc : result) {
for (FullTextDoc scoredDoc : result) {
assertTrue(scoredDoc.score > 0F);
}
}
@@ -259,12 +260,6 @@ public class TextQueryTests extends AbstractIntegrationTests {
this.template.save(MILK_AND_SUGAR);
}
static class FullTextDocWithScore extends FullTextDoc {
public Float score;
}
@Document(collection = "fullTextDoc")
static class FullTextDoc {
@@ -276,6 +271,8 @@ public class TextQueryTests extends AbstractIntegrationTests {
private String subheadline;
private String body;
private @TextScore Float score;
@Override
public int hashCode() {
final int prime = 31;