diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index fde98209d..2902b9c91 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -385,7 +385,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App entity.doWithProperties(new PropertyHandler() { public void doWithPersistentProperty(MongoPersistentProperty prop) { - if (prop.equals(idProperty)) { + if (prop.equals(idProperty) || prop.isCalculatedProperty()) { return; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java index 2f9eac702..7abb020fa 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java @@ -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 extends BasicPersistentEntity extends BasicPersistentEntity { + + @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))); + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java index 793f52afc..c44e78014 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java @@ -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); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Calculated.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Calculated.java new file mode 100644 index 000000000..a38cc9e12 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Calculated.java @@ -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.
+ * NOTE: 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 { + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java index 5473310db..9b1ce2a00 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentProperty.java @@ -69,6 +69,24 @@ public interface MongoPersistentProperty extends PersistentProperty + * 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. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/TextScore.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/TextScore.java new file mode 100644 index 000000000..bf138e4fa --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/TextScore.java @@ -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.
+ * NOTE 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 { + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index f4218a274..aff93e606 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -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 content; } @@ -2057,4 +2084,9 @@ public class MappingMongoConverterUnitTests { Shape shape; } + + class ClassWithTextScoreProperty { + + @TextScore Float score; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java index d8fe99fbc..89388ed77 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java @@ -150,6 +150,32 @@ public class BasicMongoPersistentPropertyUnitTests { assertThat(property.isLanguageProperty(), is(true)); } + /** + * @see DATAMONGO-976 + */ + @Test + public void shouldDetectTextScorePropertyCorrectly() { + + BasicMongoPersistentEntity persistentEntity = new BasicMongoPersistentEntity( + ClassTypeInformation.from(DocumentWithTextScoreProperty.class)); + + MongoPersistentProperty property = getPropertyFor(persistentEntity, "score"); + assertThat(property.isTextScoreProperty(), is(true)); + } + + /** + * @see DATAMONGO-976 + */ + @Test + public void shouldDetectTextScoreAsCalculatedProperty() { + + BasicMongoPersistentEntity persistentEntity = new BasicMongoPersistentEntity( + 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; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java index abaa66c4a..5eccf368b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java @@ -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 children; @@ -240,4 +256,9 @@ public class MongoMappingContextUnitTests { String _id; String id; } + + class ClassWithInvalidTextScoreProperty { + + @TextScore Locale score; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryTests.java index db7fe8fda..7618226a0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryTests.java @@ -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 result = template.find(new TextQuery("bake coffee -cake").includeScore().sortByScore(), - FullTextDocWithScore.class); + List 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;