diff --git a/spring-data-mongodb/Spring Data MongoDB.sonargraph b/spring-data-mongodb/Spring Data MongoDB.sonargraph index 02c36b2bd..b44d23e41 100644 --- a/spring-data-mongodb/Spring Data MongoDB.sonargraph +++ b/spring-data-mongodb/Spring Data MongoDB.sonargraph @@ -1,5 +1,5 @@ - + @@ -32,6 +32,7 @@ + diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 063dbbb64..8170898c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1616,7 +1616,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { CursorPreparer preparer, DbObjectCallback objectCallback) { MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity); + + DBObject mappedFields = queryMapper.getMappedFields(fields, entity); DBObject mappedQuery = queryMapper.getMappedObject(query, entity); if (LOGGER.isDebugEnabled()) { @@ -1969,8 +1970,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { return null; } - MongoPersistentEntity entity = mappingContext.getPersistentEntity(type); - return queryMapper.getMappedObject(query.getSortObject(), entity); + return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type)); } // Callback implementations @@ -2030,7 +2030,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { } public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (fields == null) { + if (fields == null || fields.toMap().isEmpty()) { return collection.find(query); } else { return collection.find(query, fields); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java index a40728a4f..1c630abd3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java @@ -45,6 +45,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverters.DBObjectToS import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigIntegerConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToURLConverter; +import org.springframework.data.mongodb.core.convert.MongoConverters.TermToStringConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.URLToStringConverter; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.util.Assert; @@ -58,6 +59,7 @@ import org.springframework.util.Assert; * * @author Oliver Gierke * @author Thomas Darimont + * @author Christoph Strobl */ public class CustomConversions { @@ -106,7 +108,8 @@ public class CustomConversions { toRegister.add(URLToStringConverter.INSTANCE); toRegister.add(StringToURLConverter.INSTANCE); toRegister.add(DBObjectToStringConverter.INSTANCE); - + toRegister.add(TermToStringConverter.INSTANCE); + toRegister.addAll(JodaTimeConverters.getConvertersToRegister()); toRegister.addAll(GeoConverters.getConvertersToRegister()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java index 126a8f4c7..afabef255 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java @@ -25,6 +25,8 @@ import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.mongodb.core.query.Term; import org.springframework.util.StringUtils; import com.mongodb.DBObject; @@ -34,6 +36,7 @@ import com.mongodb.DBObject; * * @author Oliver Gierke * @author Thomas Darimont + * @author Christoph Strobl */ abstract class MongoConverters { @@ -160,4 +163,19 @@ abstract class MongoConverters { return source == null ? null : source.toString(); } } + + /** + * @author Christoph Strobl + * @since 1.6 + */ + @WritingConverter + public static enum TermToStringConverter implements Converter { + + INSTANCE; + + @Override + public String convert(Term source) { + return source == null ? null : source.getFormatted(); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index 283e98772..536f97c87 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -57,6 +57,11 @@ import com.mongodb.DBRef; public class QueryMapper { private static final List DEFAULT_ID_NAMES = Arrays.asList("id", "_id"); + private static final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore"); + + private enum MetaMapping { + FORCE, WHEN_PRESENT, IGNORE; + } private final ConversionService conversionService; private final MongoConverter converter; @@ -119,6 +124,61 @@ public class QueryMapper { return result; } + /** + * Maps fields used for sorting to the {@link MongoPersistentEntity}s properties.
+ * Also converts properties to their {@code $meta} representation if present. + * + * @param sortObject + * @param entity + * @return + * @since 1.6 + */ + public DBObject getMappedSort(DBObject sortObject, MongoPersistentEntity entity) { + + if (sortObject == null) { + return null; + } + + DBObject mappedSort = getMappedObject(sortObject, entity); + mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT); + return mappedSort; + } + + /** + * Maps fields to retrieve to the {@link MongoPersistentEntity}s properties.
+ * Also onverts and potentially adds missing property {@code $meta} representation. + * + * @param fieldsObject + * @param entity + * @return + * @since 1.6 + */ + public DBObject getMappedFields(DBObject fieldsObject, MongoPersistentEntity entity) { + + DBObject mappedFields = fieldsObject != null ? getMappedObject(fieldsObject, entity) : new BasicDBObject(); + mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE); + return mappedFields.keySet().isEmpty() ? null : mappedFields; + } + + private void mapMetaAttributes(DBObject source, MongoPersistentEntity entity, MetaMapping metaMapping) { + + if (entity == null || source == null) { + return; + } + + if (entity.hasTextScoreProperty() && !MetaMapping.IGNORE.equals(metaMapping)) { + MongoPersistentProperty textScoreProperty = entity.getTextScoreProperty(); + if (MetaMapping.FORCE.equals(metaMapping) + || (MetaMapping.WHEN_PRESENT.equals(metaMapping) && source.containsField(textScoreProperty.getFieldName()))) { + source.putAll(getMappedTextScoreField(textScoreProperty)); + } + } + } + + private DBObject getMappedTextScoreField(MongoPersistentProperty property) { + return new BasicDBObject(property.getFieldName(), META_TEXT_SCORE); + } + /** * Extracts the mapped object value for given field out of rawValue taking nested {@link Keyword}s into account * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java index 8768d5591..9fb64be3f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java @@ -276,7 +276,7 @@ public class TextIndexDefinition implements IndexDefinition { * @return */ public TextIndexDefinitionBuilder onField(String fieldname) { - return onField(fieldname, Float.NaN); + return onField(fieldname, 1F); } /** 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 7abb020fa..c99c0eef8 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 @@ -114,6 +114,24 @@ public class BasicMongoPersistentEntity extends BasicPersistentEntity extends PersistentEntity> restrictedTypes = new HashSet>(); - private final Map criteria = new LinkedHashMap(); + private final Map criteria = new LinkedHashMap(); private Field fieldSpec; private Sort sort; private int skip; @@ -53,42 +54,48 @@ public class Query { private String hint; /** - * Static factory method to create a {@link Query} using the provided {@link Criteria}. + * Static factory method to create a {@link Query} using the provided {@link CriteriaDefinition}. * - * @param criteria must not be {@literal null}. + * @param criteriaDefinition must not be {@literal null}. * @return + * @since 1.6 */ - public static Query query(Criteria criteria) { - return new Query(criteria); + public static Query query(CriteriaDefinition criteriaDefinition) { + return new Query(criteriaDefinition); } public Query() {} /** - * Creates a new {@link Query} using the given {@link Criteria}. + * Creates a new {@link Query} using the given {@link CriteriaDefinition}. * - * @param criteria must not be {@literal null}. + * @param criteriaDefinition must not be {@literal null}. + * @since 1.6 */ - public Query(Criteria criteria) { - addCriteria(criteria); + public Query(CriteriaDefinition criteriaDefinition) { + addCriteria(criteriaDefinition); } /** - * Adds the given {@link Criteria} to the current {@link Query}. + * Adds the given {@link CriteriaDefinition} to the current {@link Query}. * - * @param criteria must not be {@literal null}. + * @param criteriaDefinition must not be {@literal null}. * @return + * @since 1.6 */ - public Query addCriteria(Criteria criteria) { - CriteriaDefinition existing = this.criteria.get(criteria.getKey()); - String key = criteria.getKey(); + public Query addCriteria(CriteriaDefinition criteriaDefinition) { + + CriteriaDefinition existing = this.criteria.get(criteriaDefinition.getKey()); + String key = criteriaDefinition.getKey(); + if (existing == null) { - this.criteria.put(key, criteria); + this.criteria.put(key, criteriaDefinition); } else { throw new InvalidMongoDbApiUsageException("Due to limitations of the com.mongodb.BasicDBObject, " + "you can't add a second '" + key + "' criteria. " + "Query already contains '" + existing.getCriteriaObject() + "'."); } + return this; } @@ -268,8 +275,8 @@ public class Query { return hint; } - protected List getCriteria() { - return new ArrayList(this.criteria.values()); + protected List getCriteria() { + return new ArrayList(this.criteria.values()); } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/Term.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Term.java similarity index 96% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/Term.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Term.java index 1b1db21cf..c7a2edffb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/Term.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Term.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.query.text; +package org.springframework.data.mongodb.core.query; /** * A {@link Term} defines one or multiple words {@link Type#WORD} or phrases {@link Type#PHRASE} to be used in the @@ -24,7 +24,7 @@ package org.springframework.data.mongodb.core.query.text; */ public class Term { - enum Type { + public enum Type { WORD, PHRASE; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/TextCriteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java similarity index 66% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/TextCriteria.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java index ba1cdb805..8f6f9aced 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/TextCriteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java @@ -13,16 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.query.text; +package org.springframework.data.mongodb.core.query; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.mongodb.BasicDBObject; @@ -30,42 +26,59 @@ import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; /** - * Implementation of {@link CriteriaDefinition} to be used for full text search . + * Implementation of {@link CriteriaDefinition} to be used for full text search. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.6 */ -public class TextCriteria extends Criteria { +public class TextCriteria implements CriteriaDefinition { + private final List terms; private String language; - private List terms; + /** + * Creates a new {@link TextCriteria}. + * + * @see #forDefaultLanguage() + * @see #forLanguage(String) + */ public TextCriteria() { + this(null); + } + + private TextCriteria(String language) { + + this.language = language; this.terms = new ArrayList(); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getCriteriaObject() + /** + * Returns a new {@link TextCriteria} for the default language. + * + * @return */ - @Override - public DBObject getCriteriaObject() { - - BasicDBObjectBuilder builder = new BasicDBObjectBuilder(); - - if (StringUtils.hasText(language)) { - builder.add("$language", language); - } - - if (!CollectionUtils.isEmpty(terms)) { - builder.add("$search", join(terms.iterator())); - } - - return new BasicDBObject("$text", builder.get()); + public static TextCriteria forDefaultLanguage() { + return new TextCriteria(); } /** - * @param words + * For a full list of supported languages see the mongdodb reference manual for Text Search Languages. + * + * @param language + * @return + */ + public static TextCriteria forLanguage(String language) { + + Assert.hasText(language, "Language must not be null or empty!"); + return new TextCriteria(language); + } + + /** + * Configures the {@link TextCriteria} to match any of the given words. + * + * @param words the words to match. * @return */ public TextCriteria matchingAny(String... words) { @@ -73,22 +86,21 @@ public class TextCriteria extends Criteria { for (String word : words) { matching(word); } + return this; } /** - * Add given {@link Term} to criteria. + * Adds given {@link Term} to criteria. * - * @param term must not be null. + * @param term must not be {@literal null}. */ - public void matching(Term term) { + public TextCriteria matching(Term term) { Assert.notNull(term, "Term to add must not be null."); - this.terms.add(term); - } - private void notMatching(Term term) { - matching(term.negate()); + this.terms.add(term); + return this; } /** @@ -110,7 +122,7 @@ public class TextCriteria extends Criteria { public TextCriteria notMatching(String term) { if (StringUtils.hasText(term)) { - notMatching(new Term(term, Term.Type.WORD)); + matching(new Term(term, Term.Type.WORD).negate()); } return this; } @@ -136,7 +148,7 @@ public class TextCriteria extends Criteria { public TextCriteria notMatchingPhrase(String phrase) { if (StringUtils.hasText(phrase)) { - notMatching(new Term(phrase, Term.Type.PHRASE)); + matching(new Term(phrase, Term.Type.PHRASE).negate()); } return this; } @@ -155,69 +167,45 @@ public class TextCriteria extends Criteria { return this; } - /** - * @return + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getKey() */ - public static TextCriteria forDefaultLanguage() { - return new TextCriteriaBuilder().build(); - } - - /** - * For a full list of supported languages see the mongdodb reference manual for Text Search Languages. - * - * @param language - * @return - */ - public static TextCriteria forLanguage(String language) { - return new TextCriteriaBuilder().withLanguage(language).build(); - } - - private static String join(Iterator iterator) { - - Term first = iterator.next(); - if (!iterator.hasNext()) { - return first.getFormatted(); - } - - StringBuilder buf = new StringBuilder(256); - if (first != null) { - buf.append(first); - } - - while (iterator.hasNext()) { - buf.append(' '); - Term obj = iterator.next(); - if (obj != null) { - buf.append(obj.getFormatted()); - } - } - - return buf.toString(); - } - - public static class TextCriteriaBuilder { - - private TextCriteria instance; - - public TextCriteriaBuilder() { - this.instance = new TextCriteria(); - } - - public TextCriteriaBuilder withLanguage(String language) { - this.instance.language = language; - return this; - } - - public TextCriteria build() { - return this.instance; - } - - } - @Override public String getKey() { return "$text"; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.CriteriaDefinition#getCriteriaObject() + */ + @Override + public DBObject getCriteriaObject() { + + BasicDBObjectBuilder builder = new BasicDBObjectBuilder(); + + if (StringUtils.hasText(language)) { + builder.add("$language", language); + } + + if (!terms.isEmpty()) { + builder.add("$search", join(terms)); + } + + return new BasicDBObject("$text", builder.get()); + } + + private String join(Iterable terms) { + + List result = new ArrayList(); + + for (Term term : terms) { + if (term != null) { + result.add(term.getFormatted()); + } + } + + return StringUtils.collectionToDelimitedString(result, " "); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/TextQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java similarity index 94% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/TextQuery.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java index e56776e79..8e54122ca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/text/TextQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.query.text; +package org.springframework.data.mongodb.core.query; import java.util.Locale; -import org.springframework.data.mongodb.core.query.Query; - import com.mongodb.BasicDBObject; import com.mongodb.DBObject; @@ -147,6 +145,10 @@ public class TextQuery extends Query { return scoreFieldName; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Query#getFieldsObject() + */ @Override public DBObject getFieldsObject() { @@ -155,24 +157,32 @@ public class TextQuery extends Query { } DBObject fields = super.getFieldsObject(); + if (fields == null) { fields = new BasicDBObject(); } + fields.put(getScoreFieldName(), META_TEXT_SCORE); return fields; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Query#getSortObject() + */ @Override public DBObject getSortObject() { DBObject sort = new BasicDBObject(); + if (this.sortByScore) { sort.put(getScoreFieldName(), META_TEXT_SCORE); } + if (super.getSortObject() != null) { sort.putAll(super.getSortObject()); } + return sort; } - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java index 62a20ad00..8b32178ca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 the original author or authors. + * Copyright 2010-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. @@ -26,6 +26,7 @@ import org.springframework.data.repository.PagingAndSortingRepository; * Mongo specific {@link org.springframework.data.repository.Repository} interface. * * @author Oliver Gierke + * @author Christoph Strobl */ @NoRepositoryBean public interface MongoRepository extends PagingAndSortingRepository { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index f517e86d6..c59b74be8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -27,6 +27,7 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.convert.MongoWriter; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; @@ -38,6 +39,7 @@ import com.mongodb.DBRef; * Custom {@link ParameterAccessor} that uses a {@link MongoWriter} to serialize parameters into Mongo format. * * @author Oliver Gierke + * @author Christoph Strobl */ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -110,6 +112,14 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { return delegate.getGeoNearLocation(); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getFullText() + */ + public TextCriteria getFullText() { + return delegate.getFullText(); + } + /** * Converts the given value with the underlying {@link MongoWriter}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java index 1b38d68eb..51b0f4d1c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java @@ -17,12 +17,14 @@ package org.springframework.data.mongodb.repository.query; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.ParameterAccessor; /** * Mongo-specific {@link ParameterAccessor} exposing a maximum distance parameter. * * @author Oliver Gierke + * @author Christoph Strobl */ public interface MongoParameterAccessor extends ParameterAccessor { @@ -40,4 +42,12 @@ public interface MongoParameterAccessor extends ParameterAccessor { * @return */ Point getGeoNearLocation(); + + /** + * Returns the {@link TextCriteria} to be used for full text query. + * + * @return null if not set. + * @since 1.6 + */ + TextCriteria getFullText(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java index 9d17e2cfd..8e1004e01 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -22,6 +22,7 @@ import java.util.List; import org.springframework.core.MethodParameter; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.repository.Near; import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter; import org.springframework.data.repository.query.Parameter; @@ -31,10 +32,13 @@ import org.springframework.data.repository.query.Parameters; * Custom extension of {@link Parameters} discovering additional * * @author Oliver Gierke + * @author Christoph Strobl */ public class MongoParameters extends Parameters { private final Integer distanceIndex; + private final Integer fullTextIndex; + private Integer nearIndex; /** @@ -48,6 +52,7 @@ public class MongoParameters extends Parameters super(method); List> parameterTypes = Arrays.asList(method.getParameterTypes()); this.distanceIndex = parameterTypes.indexOf(Distance.class); + this.fullTextIndex = parameterTypes.indexOf(TextCriteria.class); if (this.nearIndex == null && isGeoNearMethod) { this.nearIndex = getNearIndex(parameterTypes); @@ -56,12 +61,14 @@ public class MongoParameters extends Parameters } } - private MongoParameters(List parameters, Integer distanceIndex, Integer nearIndex) { + private MongoParameters(List parameters, Integer distanceIndex, Integer nearIndex, + Integer fullTextIndex) { super(parameters); this.distanceIndex = distanceIndex; this.nearIndex = nearIndex; + this.fullTextIndex = fullTextIndex; } @SuppressWarnings({ "unchecked", "deprecation" }) @@ -124,13 +131,31 @@ public class MongoParameters extends Parameters return nearIndex; } + /** + * Returns ths inde of the parameter to be used as a textquery param + * + * @return + * @since 1.6 + */ + public int getFullTextParameterIndex() { + return fullTextIndex != null ? fullTextIndex.intValue() : -1; + } + + /** + * @return + * @since 1.6 + */ + public boolean hasFullTextParameter() { + return this.fullTextIndex != null && this.fullTextIndex.intValue() >= 0; + } + /* * (non-Javadoc) * @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List) */ @Override protected MongoParameters createFrom(List parameters) { - return new MongoParameters(parameters, this.distanceIndex, this.nearIndex); + return new MongoParameters(parameters, this.distanceIndex, this.nearIndex, this.fullTextIndex); } /** @@ -162,7 +187,8 @@ public class MongoParameters extends Parameters */ @Override public boolean isSpecialParameter() { - return super.isSpecialParameter() || Distance.class.isAssignableFrom(getType()) || isNearParameter(); + return super.isSpecialParameter() || Distance.class.isAssignableFrom(getType()) || isNearParameter() + || TextCriteria.class.isAssignableFrom(getType()); } private boolean isNearParameter() { @@ -181,5 +207,7 @@ public class MongoParameters extends Parameters private boolean hasNearAnnotation() { return parameter.getParameterAnnotation(Near.class) != null; } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java index 5f0bafa85..df74089fd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java @@ -17,12 +17,17 @@ package org.springframework.data.mongodb.repository.query; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.query.Term; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Mongo-specific {@link ParametersParameterAccessor} to allow access to the {@link Distance} parameter. * * @author Oliver Gierke + * @author Christoph Strobl */ public class MongoParametersParameterAccessor extends ParametersParameterAccessor implements MongoParameterAccessor { @@ -77,4 +82,35 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso return (Point) value; } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getFullText() + */ + @Override + public TextCriteria getFullText() { + int index = method.getParameters().getFullTextParameterIndex(); + return index >= 0 ? potentiallyConvertFullText(getValue(index)) : null; + } + + protected TextCriteria potentiallyConvertFullText(Object fullText) { + + Assert.notNull(fullText, "Fulltext parameter must not be 'null'."); + + if (fullText instanceof String) { + return TextCriteria.forDefaultLanguage().matching((String) fullText); + } + + if (fullText instanceof Term) { + return TextCriteria.forDefaultLanguage().matching((Term) fullText); + } + + if (fullText instanceof TextCriteria) { + return ((TextCriteria) fullText); + } + + throw new IllegalArgumentException(String.format( + "Expected full text parameter to be one of String, Term or TextCriteria but found %s.", + ClassUtils.getShortName(fullText.getClass()))); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 4a687de36..ead1d642e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -146,11 +146,7 @@ class MongoQueryCreator extends AbstractQueryCreator { @Override protected Query complete(Criteria criteria, Sort sort) { - if (criteria == null) { - return null; - } - - Query query = new Query(criteria).with(sort); + Query query = (criteria == null ? new Query() : new Query(criteria)).with(sort); if (LOG.isDebugEnabled()) { LOG.debug("Created query " + query); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java index 1da693d44..0b020c9ae 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -39,6 +39,7 @@ import org.springframework.util.StringUtils; * Mongo specific implementation of {@link QueryMethod}. * * @author Oliver Gierke + * @author Christoph Strobl */ public class MongoQueryMethod extends QueryMethod { @@ -143,7 +144,7 @@ public class MongoQueryMethod extends QueryMethod { } /** - * Returns whether te query is a geo near query. + * Returns whether the query is a geo near query. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index 29eeb2418..b52510939 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -21,6 +21,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.parser.PartTree; @@ -77,6 +78,11 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { query.limit(tree.getMaxResults()); } + TextCriteria textCriteria = accessor.getFullText(); + if (textCriteria != null) { + query.addCriteria(textCriteria); + } + String fieldSpec = this.getQueryMethod().getFieldSpecification(); if (!StringUtils.hasText(fieldSpec)) { @@ -87,6 +93,7 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { BasicQuery result = new BasicQuery(query.getQueryObject().toString(), fieldSpec); result.setSortObject(query.getSortObject()); + return result; } catch (JSONParseException o_O) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java index ba1b5b95e..42ee34565 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2012 the original author or authors. + * Copyright 2010-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. @@ -40,6 +40,7 @@ import org.springframework.util.Assert; * Repository base implementation for Mongo. * * @author Oliver Gierke + * @author Christoph Strobl */ public class SimpleMongoRepository implements MongoRepository { @@ -232,4 +233,5 @@ public class SimpleMongoRepository implements MongoR protected MongoEntityInformation getEntityInformation() { return entityInformation; } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index 67f0c3a13..e8431fdff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -45,6 +45,7 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.TextScore; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; @@ -600,6 +601,63 @@ public class QueryMapperUnitTests { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("foo", -1).get())); } + /** + * @see DATAMONGO-973 + */ + @Test + public void getMappedFieldsAppendsTextScoreFieldProperlyCorrectlyWhenNotPresent() { + + Query query = new Query(); + + DBObject dbo = mapper.getMappedFields(query.getFieldsObject(), + context.getPersistentEntity(WithTextScoreProperty.class)); + + assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void getMappedFieldsReplacesTextScoreFieldProperlyCorrectlyWhenPresent() { + + Query query = new Query(); + query.fields().include("textScore"); + + DBObject dbo = mapper.getMappedFields(query.getFieldsObject(), + context.getPersistentEntity(WithTextScoreProperty.class)); + + assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void getMappedSortAppendsTextScoreProperlyWhenSortedByScore() { + + Query query = new Query().with(new Sort("textScore")); + + DBObject dbo = mapper + .getMappedSort(query.getSortObject(), context.getPersistentEntity(WithTextScoreProperty.class)); + + assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void getMappedSortIgnoresTextScoreWhenNotSortedByScore() { + + Query query = new Query().with(new Sort("id")); + + DBObject dbo = mapper + .getMappedSort(query.getSortObject(), context.getPersistentEntity(WithTextScoreProperty.class)); + + assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("_id", 1).get())); + } + @Document public class Foo { @Id private ObjectId id; @@ -675,4 +733,10 @@ public class QueryMapperUnitTests { @DBRef Map mapWithDBRef; } + + class WithTextScoreProperty { + + @Id String id; + @TextScore @Field("score") Float textScore; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java new file mode 100644 index 000000000..c7971154d --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java @@ -0,0 +1,50 @@ +/* + * 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.convert; + +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.convert.MongoConverters.TermToStringConverter; +import org.springframework.data.mongodb.core.query.Term; +import org.springframework.data.mongodb.core.query.Term.Type; + +/** + * @author Christoph Strobl + */ +public class TermToStringConverterUnitTests { + + /** + * @DATAMONGO-973 + */ + @Test + public void shouldNotConvertNull() { + assertThat(TermToStringConverter.INSTANCE.convert(null), nullValue()); + } + + /** + * @DATAMONGO-973 + */ + @Test + public void shouldUseFormattedRepresentationForConversion() { + + Term term = spy(new Term("foo", Type.WORD)); + TermToStringConverter.INSTANCE.convert(term); + verify(term, times(1)).getFormatted(); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntityTestDummy.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntityTestDummy.java index 7577edd13..a07cd1002 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntityTestDummy.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntityTestDummy.java @@ -214,4 +214,14 @@ public class MongoPersistentEntityTestDummy implements MongoPersistentEntity< public String getLanguage() { return null; } + + @Override + public MongoPersistentProperty getTextScoreProperty() { + return null; + } + + @Override + public boolean hasTextScoreProperty() { + return false; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IsQuery.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IsQuery.java index bcf379c70..399a55a19 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IsQuery.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IsQuery.java @@ -91,6 +91,12 @@ public class IsQuery extends TypeSafeMatcher { return this; } + public IsQuery where(Criteria criteria) { + + this.query.putAll(criteria.getCriteriaObject()); + return this; + } + @Override public void describeTo(Description description) { @@ -117,8 +123,10 @@ public class IsQuery extends TypeSafeMatcher { return false; } - if (!new IsEqual(sort).matches(item.getSortObject())) { - return false; + if (item.getSortObject() == null && !sort.toMap().isEmpty()) { + if (!new IsEqual(sort).matches(item.getSortObject())) { + return false; + } } if (!new IsEqual(fields).matches(item.getFieldsObject())) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/IsTextQuery.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IsTextQuery.java similarity index 72% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/IsTextQuery.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IsTextQuery.java index 94b3e1928..6e7d9403c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/IsTextQuery.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IsTextQuery.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.query.text; +package org.springframework.data.mongodb.core.query; import org.hamcrest.TypeSafeMatcher; -import org.springframework.data.mongodb.core.query.IsQuery; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.TextQuery; import org.springframework.util.StringUtils; import com.mongodb.BasicDBObject; @@ -28,7 +30,7 @@ import com.mongodb.DBObject; * @author Christoph Strobl * @param */ -public class IsTextQuery extends IsQuery { +public class IsTextQuery extends IsQuery { private final String SCORE_DEFAULT_FIELDNAME = "score"; private final DBObject META_TEXT_SCORE = new BasicDBObject("$meta", "textScore"); @@ -39,7 +41,7 @@ public class IsTextQuery extends IsQuery { super(); } - public static IsTextQuery isTextQuery() { + public static IsTextQuery isTextQuery() { return new IsTextQuery(); } @@ -77,6 +79,41 @@ public class IsTextQuery extends IsQuery { return this; } + @Override + public IsTextQuery where(Criteria criteria) { + + super.where(criteria); + return this; + } + + @Override + public IsTextQuery excludingField(String fieldname) { + + super.excludingField(fieldname); + return this; + } + + @Override + public IsTextQuery includingField(String fieldname) { + + super.includingField(fieldname); + return this; + } + + @Override + public IsTextQuery limitingTo(int limit) { + + super.limitingTo(limit); + return this; + } + + @Override + public IsQuery skippig(int skip) { + + super.skippig(skip); + return this; + } + private void appendLanguage(String language) { DBObject dbo = getOrCreateTextDbo(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextCriteriaUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java similarity index 97% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextCriteriaUnitTests.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java index 47c17e166..9a14e7d42 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextCriteriaUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.query.text; +package org.springframework.data.mongodb.core.query; import org.hamcrest.core.IsEqual; import org.junit.Assert; @@ -25,6 +25,8 @@ import com.mongodb.DBObject; import com.mongodb.util.JSON; /** + * Unit tests for {@link TextCriteria}. + * * @author Christoph Strobl */ public class TextCriteriaUnitTests { 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/TextQueryTests.java similarity index 97% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryTests.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java index 7618226a0..94b0637ef 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/TextQueryTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.query.text; +package org.springframework.data.mongodb.core.query; import static org.hamcrest.collection.IsCollectionWithSize.*; import static org.hamcrest.collection.IsEmptyCollection.*; @@ -40,7 +40,9 @@ 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.core.query.TextCriteria; +import org.springframework.data.mongodb.core.query.TextQuery; +import org.springframework.data.mongodb.core.query.TextQueryTests.FullTextDoc.FullTextDocBuilder; import org.springframework.data.mongodb.test.util.MongoVersionRule; import org.springframework.data.util.Version; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java similarity index 93% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryUnitTests.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java index 0ad4d45b3..2a0fa9c0b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/text/TextQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.core.query.text; +package org.springframework.data.mongodb.core.query; import static org.junit.Assert.*; -import static org.springframework.data.mongodb.core.query.text.IsTextQuery.*; +import static org.springframework.data.mongodb.core.query.IsTextQuery.*; import org.junit.Test; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; -import org.springframework.data.mongodb.core.query.Query; /** + * Unit tests for {@link TextQuery}. + * * @author Christoph Strobl */ public class TextQueryUnitTests { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java new file mode 100644 index 000000000..05305d9a9 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java @@ -0,0 +1,319 @@ +/* + * 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.repository; + +import static org.hamcrest.collection.IsCollectionWithSize.*; +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsCollectionContaining.*; +import static org.hamcrest.core.IsEqual.*; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.config.AbstractMongoConfiguration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder; +import org.springframework.data.mongodb.core.index.TextIndexed; +import org.springframework.data.mongodb.core.mapping.TextScore; +import org.springframework.data.mongodb.core.query.TextCriteria; +import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory; +import org.springframework.data.mongodb.test.util.MongoVersionRule; +import org.springframework.data.util.Version; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +import com.mongodb.Mongo; +import com.mongodb.MongoClient; + +/** + * Integration tests for text searches on repository. + * + * @author Christoph Strobl + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class MongoRepositoryTextSearchIntegrationTests { + + public static @ClassRule MongoVersionRule versionRule = MongoVersionRule.atLeast(new Version(2, 6, 0)); + + private static final FullTextDocument PASSENGER_57 = new FullTextDocument("1", "Passenger 57", + "Passenger 57 is an action film that stars Wesley Snipes and Bruce Payne."); + private static final FullTextDocument DEMOLITION_MAN = new FullTextDocument("2", "Demolition Man", + "Demolition Man is a science fiction action comedy film staring Wesley Snipes and Sylvester Stallone."); + private static final FullTextDocument DROP_ZONE = new FullTextDocument("3", "Drop Zone", + "Drop Zone is an action film featuring Wesley Snipes and Gary Busey."); + + @Autowired MongoTemplate template; + FullTextRepository repo; + + @Before + public void setUp() { + + template.indexOps(FullTextDocument.class).ensureIndex( + new TextIndexDefinitionBuilder().onField("title").onField("content").build()); + this.repo = new MongoRepositoryFactory(this.template).getRepository(FullTextRepository.class); + } + + @After + public void tearDown() { + template.dropCollection(FullTextDocument.class); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void findAllByTextCriteriaShouldReturnMatchingDocuments() { + + initRepoWithDefaultDocuments(); + + List result = repo.findAllBy(TextCriteria.forDefaultLanguage().matchingAny("stallone", "payne")); + + assertThat(result, hasSize(2)); + assertThat(result, hasItems(PASSENGER_57, DEMOLITION_MAN)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void derivedFinderWithTextCriteriaReturnsCorrectResult() { + + initRepoWithDefaultDocuments(); + FullTextDocument blade = new FullTextDocument( + "4", + "Blade", + "Blade is a 1998 American vampire-superhero-vigilante action film starring Wesley Snipes and Stephen Dorff, loosely based on the Marvel Comics character Blade"); + blade.nonTextIndexProperty = "foo"; + repo.save(blade); + + List result = repo.findByNonTextIndexProperty("foo", + TextCriteria.forDefaultLanguage().matching("snipes")); + + assertThat(result, hasSize(1)); + assertThat(result, hasItems(blade)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void findByWithPaginationWorksCorrectlyWhenUsingTextCriteria() { + + initRepoWithDefaultDocuments(); + + Page page = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("film"), new PageRequest(1, + 1, Direction.ASC, "id")); + + assertThat(page.hasNext(), is(true)); + assertThat(page.hasPrevious(), is(true)); + assertThat(page.getTotalElements(), is(3L)); + assertThat(page.getContent().get(0), equalTo(DEMOLITION_MAN)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void findAllByTextCriteriaWithSortWorksCorrectly() { + + initRepoWithDefaultDocuments(); + FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer."); + repo.save(snipes); + + List result = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("snipes"), new Sort( + "score")); + + assertThat(result.size(), is(4)); + assertThat(result.get(0), equalTo(snipes)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void findByWithSortByScoreViaPageRequestTriggersSortingCorrectly() { + + initRepoWithDefaultDocuments(); + FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer."); + repo.save(snipes); + + Page page = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("snipes"), new PageRequest( + 0, 10, Direction.ASC, "score")); + + assertThat(page.getTotalElements(), is(4L)); + assertThat(page.getContent().get(0), equalTo(snipes)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void findByWithSortViaPageRequestIgnoresTextScoreWhenSortedByOtherProperty() { + + initRepoWithDefaultDocuments(); + FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer."); + repo.save(snipes); + + Page page = repo.findAllBy(TextCriteria.forDefaultLanguage().matching("snipes"), new PageRequest( + 0, 10, Direction.ASC, "id")); + + assertThat(page.getTotalElements(), is(4L)); + assertThat(page.getContent().get(0), equalTo(PASSENGER_57)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void derivedSortForTextScorePropertyWorksCorrectly() { + + initRepoWithDefaultDocuments(); + FullTextDocument snipes = new FullTextDocument("4", "Snipes", "Wesley Trent Snipes is an actor and film producer."); + repo.save(snipes); + + List result = repo.findByNonTextIndexPropertyIsNullOrderByScoreDesc(TextCriteria + .forDefaultLanguage().matching("snipes")); + assertThat(result.get(0), equalTo(snipes)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void derivedFinderMethodWithoutFullTextShouldNoCauseTroubleWhenHavingEntityWithTextScoreProperty() { + + initRepoWithDefaultDocuments(); + List result = repo.findByTitle(DROP_ZONE.getTitle()); + assertThat(result.get(0), equalTo(DROP_ZONE)); + assertThat(result.get(0).score, equalTo(0.0F)); + } + + private void initRepoWithDefaultDocuments() { + repo.save(Arrays.asList(PASSENGER_57, DEMOLITION_MAN, DROP_ZONE)); + } + + @org.springframework.context.annotation.Configuration + public static class Configuration extends AbstractMongoConfiguration { + + @Override + protected String getDatabaseName() { + return ClassUtils.getShortNameAsProperty(MongoRepositoryTextSearchIntegrationTests.class); + } + + @Override + public Mongo mongo() throws Exception { + return new MongoClient(); + } + + } + + static class FullTextDocument { + + private @Id String id; + private @TextIndexed String title; + private @TextIndexed String content; + String nonTextIndexProperty; + @TextScore Float score; + + public FullTextDocument() { + + } + + public FullTextDocument(String id, String title, String content) { + + this.id = id; + this.title = title; + this.content = content; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof FullTextDocument)) { + return false; + } + FullTextDocument other = (FullTextDocument) obj; + return ObjectUtils.nullSafeEquals(this.id, other.id); + } + + } + + static interface FullTextRepository extends MongoRepository { + + List findByNonTextIndexProperty(String nonTextIndexProperty, TextCriteria criteria); + + List findByNonTextIndexPropertyIsNullOrderByScoreDesc(TextCriteria criteria); + + List findByTitle(String title); + + List findAllBy(TextCriteria textCriteria); + + List findAllBy(TextCriteria textCriteria, Sort sort); + + Page findAllBy(TextCriteria textCriteria, Pageable pageable); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java index eff893fc0..7b089b044 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java @@ -21,11 +21,13 @@ import static org.junit.Assert.*; import java.lang.reflect.Method; import java.util.List; +import org.hamcrest.core.IsNull; import org.junit.Test; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.repository.Person; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; @@ -35,6 +37,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat * Unit tests for {@link MongoParametersParameterAccessor}. * * @author Oliver Gierke + * @author Christoph Strobl */ public class MongoParametersParameterAccessorUnitTests { @@ -64,10 +67,41 @@ public class MongoParametersParameterAccessorUnitTests { assertThat(accessor.getMaxDistance(), is(DISTANCE)); } + /** + * @see DATAMONGO-973 + */ + @Test + public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuchMethodException, SecurityException { + + Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context); + + MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { + new Point(10, 20), DISTANCE }); + assertThat(accessor.getFullText(), IsNull.nullValue()); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, SecurityException { + + Method method = PersonRepository.class.getMethod("findByFirstname", String.class, TextCriteria.class); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context); + + MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { "spring", + TextCriteria.forDefaultLanguage().matching("data") }); + assertThat(accessor.getFullText().getCriteriaObject().toString(), + equalTo("{ \"$text\" : { \"$search\" : \"data\"}}")); + } + interface PersonRepository extends Repository { List findByLocationNear(Point point); List findByLocationNear(Point point, Distance distance); + + List findByFirstname(String firstname, TextCriteria fullText); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java index e9386918c..2213bc684 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java @@ -28,6 +28,7 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.geo.Distance; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.repository.Near; import org.springframework.data.mongodb.repository.Person; import org.springframework.data.repository.query.Parameter; @@ -36,6 +37,7 @@ import org.springframework.data.repository.query.Parameter; * Unit tests for {@link MongoParameters}. * * @author Oliver Gierke + * @author Christoph Strobl */ @RunWith(MockitoJUnitRunner.class) public class MongoParametersUnitTests { @@ -97,6 +99,28 @@ public class MongoParametersUnitTests { assertThat(parameters.getNearIndex(), is(1)); } + /** + * @see DATAMONGO-973 + */ + @Test + public void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchMethodException { + + Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); + MongoParameters parameters = new MongoParameters(method, false); + assertThat(parameters.getFullTextParameterIndex(), is(1)); + } + + /** + * @see DATAMONGO-973 + */ + @Test + public void shouldTreatTextCriteriaParameterAsSpecialParameter() throws SecurityException, NoSuchMethodException { + + Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); + MongoParameters parameters = new MongoParameters(method, false); + assertThat(parameters.getParameter(parameters.getFullTextParameterIndex()).isSpecialParameter(), is(true)); + } + interface PersonRepository { List findByLocationNear(Point point, Distance distance); @@ -110,5 +134,7 @@ public class MongoParametersUnitTests { GeoResults findByOtherLocationAndLocationNear(Point point, @Near Point anotherLocation); GeoResults validDoubleArrays(double[] first, @Near double[] second); + + List findByNameAndText(String name, TextCriteria text); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java index 3d51d992f..126e944b8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java @@ -42,6 +42,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat * Unit test for {@link MongoQueryMethod}. * * @author Oliver Gierke + * @author Christoph Strobl */ public class MongoQueryMethodUnitTests { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java index edeecbd5f..62c3b113b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import static org.springframework.data.mongodb.core.query.IsTextQuery.*; import java.lang.reflect.Method; @@ -36,6 +37,8 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Person; import org.springframework.data.mongodb.repository.Query; @@ -120,6 +123,18 @@ public class PartTreeMongoQueryUnitTests { assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 0).add("lastname", 0).get())); } + /** + * @see DATAMOGO-973 + */ + @Test + public void shouldAddFullTextParamCorrectlyToDerivedQuery() { + + org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstname", + new Object[] { "text", TextCriteria.forDefaultLanguage().matching("search") }); + + assertThat(query, isTextQuery().searchingFor("search").where(new Criteria("firstname").is("text"))); + } + private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object[] args) { Class[] types = new Class[args.length]; @@ -162,5 +177,7 @@ public class PartTreeMongoQueryUnitTests { @Query(fields = "{ 'firstname' : 0, 'lastname' : 0 }") Person findPersonByFirstnameAndLastname(String firstname, String lastname); + + Person findPersonByFirstname(String firstname, TextCriteria fullText); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java index 2578b870d..ab11cde67 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java @@ -23,6 +23,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.convert.MongoWriter; +import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.ParameterAccessor; /** @@ -105,4 +106,13 @@ class StubParameterAccessor implements MongoParameterAccessor { public Point getGeoNearLocation() { return null; } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getFullText() + */ + @Override + public TextCriteria getFullText() { + return null; + } } diff --git a/src/main/asciidoc/reference/mongo-repositories.adoc b/src/main/asciidoc/reference/mongo-repositories.adoc index e1189fd26..0312eaaf1 100644 --- a/src/main/asciidoc/reference/mongo-repositories.adoc +++ b/src/main/asciidoc/reference/mongo-repositories.adoc @@ -296,6 +296,45 @@ public interface PersonRepository extends MongoRepository, Query We think you will find this an extremely powerful tool for writing MongoDB queries. +[[mongodb.repositories.queries.full-text]] +=== Full-text search queries +MongoDBs full text search feature is very store specic and therefore can rather be found on `MongoRepository` than on the more general `CrudRepository`. What we need is a document with a full-text index defined for (Please see section <> for creating). + +Additional methods on `MongoRepository` take `TextCriteria` as input parameter. In addition to those explicit methods, it is also possible to add a `TextCriteria` derived repository method. The criteria will added as an additional `AND` criteria. Once the entity contains a `@TextScore` annotated property the documents full-text score will be retrieved. Furthermore the `@TextScore` annotated property will also make it possible to sort by the documents score. + +[source, java] +---- +@Document +class FullTextDocument { + + @Id String id; + @TextIndexed String title; + @TextIndexed String content; + @TextScore Float score; +} + +interface FullTextRepository extends Repository { + + // Execute a full-text search and define sorting dynamically + List findAllBy(TextCriteria criteria, Sort sort); + + // Paginate over a full-text search result + Page findAllBy(TextCriteria criteria, Pageable pageable); + + // Combine a derived query with a full-text search + List findByTitleOrderByScoreDesc(String title, TextCriteria criteria); +} + + +Sort sort = new Sort("score"); +TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("spring", "data"); +List result = repository.findAllBy(criteria, sort); + +criteria = TextCriteria.forDefaultLanguage().matching("film"); +Page page = repository.findAllBy(criteria, new PageRequest(1, 1, sort)); +List result = repository.findByTitleOrderByScoreDesc("mongodb", criteria); +---- + [[mongodb.repositories.misc]] == Miscellaneous