diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index a09e25418..434862aaa 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.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. @@ -36,6 +36,7 @@ import com.mongodb.MongoException; * @author Mark Pollack * @author Oliver Gierke * @author Komi Innocent + * @author Christoph Strobl */ public class DefaultIndexOperations implements IndexOperations { @@ -142,6 +143,13 @@ public class DefaultIndexOperations implements IndexOperations { if ("2d".equals(value)) { indexFields.add(IndexField.geo(key)); + } else if ("text".equals(value)) { + + DBObject weights = (DBObject) ix.get("weights"); + for (String fieldName : weights.keySet()) { + indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString()))); + } + } else { Double keyValue = new Double(value.toString()); @@ -159,8 +167,8 @@ public class DefaultIndexOperations implements IndexOperations { boolean unique = ix.containsField("unique") ? (Boolean) ix.get("unique") : false; boolean dropDuplicates = ix.containsField("dropDups") ? (Boolean) ix.get("dropDups") : false; boolean sparse = ix.containsField("sparse") ? (Boolean) ix.get("sparse") : false; - - indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse)); + String language = ix.containsField("default_language") ? (String) ix.get("default_language") : ""; + indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language)); } return indexInfoList; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java index 79cddfff3..83bf35436 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-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. @@ -24,22 +24,33 @@ import org.springframework.util.ObjectUtils; * Value object for an index field. * * @author Oliver Gierke + * @author Christoph Strobl */ @SuppressWarnings("deprecation") public final class IndexField { + enum Type { + GEO, TEXT, DEFAULT; + } + private final String key; private final Direction direction; - private final boolean isGeo; + private final Type type; + private final Float weight; - private IndexField(String key, Direction direction, boolean isGeo) { + private IndexField(String key, Direction direction, Type type) { + this(key, direction, type, Float.NaN); + } + + private IndexField(String key, Direction direction, Type type, Float weight) { Assert.hasText(key); - Assert.isTrue(direction != null ^ isGeo); + Assert.isTrue(direction != null ^ (Type.GEO.equals(type) || Type.TEXT.equals(type))); this.key = key; this.direction = direction; - this.isGeo = isGeo; + this.type = type == null ? Type.DEFAULT : type; + this.weight = weight == null ? Float.NaN : weight; } /** @@ -53,12 +64,12 @@ public final class IndexField { @Deprecated public static IndexField create(String key, Order order) { Assert.notNull(order); - return new IndexField(key, order.toDirection(), false); + return new IndexField(key, order.toDirection(), Type.DEFAULT); } public static IndexField create(String key, Direction order) { Assert.notNull(order); - return new IndexField(key, order, false); + return new IndexField(key, order, Type.DEFAULT); } /** @@ -68,7 +79,16 @@ public final class IndexField { * @return */ public static IndexField geo(String key) { - return new IndexField(key, null, true); + return new IndexField(key, null, Type.GEO); + } + + /** + * Creates a text {@link IndexField} for the given key. + * + * @since 1.6 + */ + public static IndexField text(String key, Float weight) { + return new IndexField(key, null, Type.TEXT, weight); } /** @@ -101,10 +121,20 @@ public final class IndexField { /** * Returns whether the {@link IndexField} is a geo index field. * - * @return the isGeo + * @return true if type is {@link Type#GEO}. */ public boolean isGeo() { - return isGeo; + return Type.GEO.equals(type); + } + + /** + * Returns wheter the {@link IndexField} is a text index field. + * + * @return true if type is {@link Type#TEXT} + * @since 1.6 + */ + public boolean isText() { + return Type.TEXT.equals(type); } /* @@ -125,7 +155,7 @@ public final class IndexField { IndexField that = (IndexField) obj; return this.key.equals(that.key) && ObjectUtils.nullSafeEquals(this.direction, that.direction) - && this.isGeo == that.isGeo; + && this.type == that.type; } /* @@ -138,7 +168,8 @@ public final class IndexField { int result = 17; result += 31 * ObjectUtils.nullSafeHashCode(key); result += 31 * ObjectUtils.nullSafeHashCode(direction); - result += 31 * ObjectUtils.nullSafeHashCode(isGeo); + result += 31 * ObjectUtils.nullSafeHashCode(type); + result += 31 * ObjectUtils.nullSafeHashCode(weight); return result; } @@ -148,6 +179,7 @@ public final class IndexField { */ @Override public String toString() { - return String.format("IndexField [ key: %s, direction: %s, isGeo: %s]", key, direction, isGeo); + return String.format("IndexField [ key: %s, direction: %s, type: %s, weight: %s]", key, direction, type, weight); } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index 332a25d10..2073fc1c2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-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. @@ -23,6 +23,11 @@ import java.util.List; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +/** + * @author Mark Pollack + * @author Oliver Gierke + * @author Christoph Strobl + */ public class IndexInfo { private final List indexFields; @@ -31,14 +36,30 @@ public class IndexInfo { private final boolean unique; private final boolean dropDuplicates; private final boolean sparse; + private final String language; + /** + * @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)} + * @param indexFields + * @param name + * @param unique + * @param dropDuplicates + * @param sparse + */ + @Deprecated public IndexInfo(List indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse) { + this(indexFields, name, unique, dropDuplicates, sparse, ""); + } + + public IndexInfo(List indexFields, String name, boolean unique, boolean dropDuplicates, boolean sparse, + String language) { this.indexFields = Collections.unmodifiableList(indexFields); this.name = name; this.unique = unique; this.dropDuplicates = dropDuplicates; this.sparse = sparse; + this.language = language; } /** @@ -84,14 +105,23 @@ public class IndexInfo { return sparse; } + /** + * @return + * @since 1.6 + */ + public String getLanguage() { + return language; + } + @Override public String toString() { return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates=" - + dropDuplicates + ", sparse=" + sparse + "]"; + + dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]"; } @Override public int hashCode() { + final int prime = 31; int result = 1; result = prime * result + (dropDuplicates ? 1231 : 1237); @@ -99,6 +129,7 @@ public class IndexInfo { result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + (sparse ? 1231 : 1237); result = prime * result + (unique ? 1231 : 1237); + result = prime * result + ObjectUtils.nullSafeHashCode(language); return result; } @@ -137,6 +168,9 @@ public class IndexInfo { if (unique != other.unique) { return false; } + if (!ObjectUtils.nullSafeEquals(language, other.language)) { + return false; + } return true; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java index 9c299125c..d51089a35 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core.index; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -29,6 +30,9 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mongodb.core.index.Index.Duplicates; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy; +import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder; +import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; @@ -93,6 +97,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { final List indexInformation = new ArrayList(); indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType())); + indexInformation.addAll(potentiallyCreateTextIndexDefinition(root)); final CycleGuard guard = new CycleGuard(); @@ -188,6 +193,82 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { return createCompoundIndexDefinitions(dotPath, collection, type); } + private Collection potentiallyCreateTextIndexDefinition(MongoPersistentEntity root) { + + TextIndexDefinitionBuilder indexDefinitionBuilder = new TextIndexDefinitionBuilder().named(root.getType() + .getSimpleName() + "_TextIndex"); + + if (StringUtils.hasText(root.getLanguage())) { + indexDefinitionBuilder.withDefaultLanguage(root.getLanguage()); + } + + try { + appendTextIndexInformation("", indexDefinitionBuilder, root, + new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard()); + } catch (CyclicPropertyReferenceException e) { + LOGGER.warn(e.getMessage()); + } + + TextIndexDefinition indexDefinition = indexDefinitionBuilder.build(); + + if (!indexDefinition.hasFieldSpec()) { + return Collections.emptyList(); + } + + IndexDefinitionHolder holder = new IndexDefinitionHolder("", indexDefinition, root.getCollection()); + return Collections.singletonList(holder); + + } + + private void appendTextIndexInformation(final String dotPath, + final TextIndexDefinitionBuilder indexDefinitionBuilder, MongoPersistentEntity entity, + final TextIndexIncludeOptions includeOptions, final CycleGuard guard) { + + entity.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) { + + guard.protect(persistentProperty, dotPath); + + if (persistentProperty.isLanguageProperty()) { + indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName()); + } + + TextIndexed indexed = persistentProperty.findAnnotation(TextIndexed.class); + + if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) { + + String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") + + persistentProperty.getFieldName(); + + Float weight = indexed != null ? indexed.weight() + : (includeOptions.getParentFieldSpec() != null ? includeOptions.getParentFieldSpec().getWeight() : 1.0F); + + if (persistentProperty.isEntity()) { + + TextIndexIncludeOptions optionsForNestedType = includeOptions; + if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed != null) { + optionsForNestedType = new TextIndexIncludeOptions(IncludeStrategy.FORCE, new TextIndexedFieldSpec( + propertyDotPath, weight)); + } + + try { + appendTextIndexInformation(propertyDotPath, indexDefinitionBuilder, + mappingContext.getPersistentEntity(persistentProperty.getActualType()), optionsForNestedType, guard); + } catch (CyclicPropertyReferenceException e) { + LOGGER.warn(e.getMessage(), e); + } + } else if (includeOptions.isForce() || indexed != null) { + indexDefinitionBuilder.onField(propertyDotPath, weight); + } + } + + } + }); + + } + /** * Create {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} for {@link CompoundIndexes} of given type. * @@ -549,4 +630,41 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { return indexDefinition.getIndexOptions(); } } + + /** + * @author Christoph Strobl + * @since 1.6 + */ + static class TextIndexIncludeOptions { + + enum IncludeStrategy { + FORCE, DEFAULT; + } + + private final IncludeStrategy strategy; + + private final TextIndexedFieldSpec parentFieldSpec; + + public TextIndexIncludeOptions(IncludeStrategy strategy, TextIndexedFieldSpec parentFieldSpec) { + this.strategy = strategy; + this.parentFieldSpec = parentFieldSpec; + } + + public TextIndexIncludeOptions(IncludeStrategy strategy) { + this(strategy, null); + } + + public IncludeStrategy getStrategy() { + return strategy; + } + + public TextIndexedFieldSpec getParentFieldSpec() { + return parentFieldSpec; + } + + public boolean isForce() { + return IncludeStrategy.FORCE.equals(strategy); + } + + } } 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 new file mode 100644 index 000000000..8768d5591 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java @@ -0,0 +1,336 @@ +/* + * 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.index; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * {@link IndexDefinition} to span multiple keys for text search. + * + * @author Christoph Strobl + * @since 1.6 + */ +public class TextIndexDefinition implements IndexDefinition { + + private String name; + private Set fieldSpecs; + private String defaultLanguage; + private String languageOverride; + + TextIndexDefinition() { + fieldSpecs = new LinkedHashSet(); + } + + /** + * Creates a {@link TextIndexDefinition} for all fields in the document. + * + * @return + */ + public static TextIndexDefinition forAllFields() { + return new TextIndexDefinitionBuilder().onAllFields().build(); + } + + /** + * Get {@link TextIndexDefinitionBuilder} to create {@link TextIndexDefinition}. + * + * @return + */ + public static TextIndexDefinitionBuilder builder() { + return new TextIndexDefinitionBuilder(); + } + + /** + * @param fieldSpec + */ + public void addFieldSpec(TextIndexedFieldSpec fieldSpec) { + this.fieldSpecs.add(fieldSpec); + } + + /** + * @param fieldSpecs + */ + public void addFieldSpecs(Collection fieldSpecs) { + this.fieldSpecs.addAll(fieldSpecs); + } + + /** + * Returns if the {@link TextIndexDefinition} has fields assigned. + * + * @return + */ + public boolean hasFieldSpec() { + return !fieldSpecs.isEmpty(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys() + */ + @Override + public DBObject getIndexKeys() { + + DBObject keys = new BasicDBObject(); + for (TextIndexedFieldSpec fieldSpec : fieldSpecs) { + keys.put(fieldSpec.fieldname, "text"); + } + + return keys; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexOptions() + */ + @Override + public DBObject getIndexOptions() { + + DBObject options = new BasicDBObject(); + if (StringUtils.hasText(name)) { + options.put("name", name); + } + if (StringUtils.hasText(defaultLanguage)) { + options.put("default_language", defaultLanguage); + } + + BasicDBObject weightsDbo = new BasicDBObject(); + for (TextIndexedFieldSpec fieldSpec : fieldSpecs) { + if (fieldSpec.isWeighted()) { + weightsDbo.put(fieldSpec.getFieldname(), fieldSpec.getWeight()); + } + } + + if (!weightsDbo.isEmpty()) { + options.put("weights", weightsDbo); + } + if (StringUtils.hasText(languageOverride)) { + options.put("language_override", languageOverride); + } + + return options; + } + + /** + * @author Christoph Strobl + * @since 1.6 + */ + public static class TextIndexedFieldSpec { + + private final String fieldname; + private final Float weight; + + /** + * Create new {@link TextIndexedFieldSpec} for given fieldname without any weight. + * + * @param fieldname + */ + public TextIndexedFieldSpec(String fieldname) { + this(fieldname, 1.0F); + } + + /** + * Create new {@link TextIndexedFieldSpec} for given fieldname and weight. + * + * @param fieldname + * @param weight + */ + public TextIndexedFieldSpec(String fieldname, Float weight) { + + Assert.hasText(fieldname, "Text index field cannot be blank."); + this.fieldname = fieldname; + this.weight = weight != null ? weight : 1.0F; + } + + /** + * Get the fieldname associated with the {@link TextIndexedFieldSpec}. + * + * @return + */ + public String getFieldname() { + return fieldname; + } + + /** + * Get the weight associated with the {@link TextIndexedFieldSpec}. + * + * @return + */ + public Float getWeight() { + return weight; + } + + /** + * @return true if {@link #weight} has a value that is a valid number. + */ + public boolean isWeighted() { + return this.weight != null && this.weight.compareTo(1.0F) != 0; + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(fieldname); + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof TextIndexedFieldSpec)) { + return false; + } + + TextIndexedFieldSpec other = (TextIndexedFieldSpec) obj; + + return ObjectUtils.nullSafeEquals(this.fieldname, other.fieldname); + } + + } + + /** + * {@link TextIndexDefinitionBuilder} helps defining options for creating {@link TextIndexDefinition}. + * + * @author Christoph Strobl + * @since 1.6 + */ + public static class TextIndexDefinitionBuilder { + + private TextIndexDefinition instance; + private static final TextIndexedFieldSpec ALL_FIELDS = new TextIndexedFieldSpec("$**"); + + public TextIndexDefinitionBuilder() { + this.instance = new TextIndexDefinition(); + } + + /** + * Define the name to be used when creating the index in the store. + * + * @param name + * @return + */ + public TextIndexDefinitionBuilder named(String name) { + this.instance.name = name; + return this; + } + + /** + * Define the index to span all fields using wilcard.
+ * NOTE {@link TextIndexDefinition} cannot contain any other fields when defined with wildcard. + * + * @return + */ + public TextIndexDefinitionBuilder onAllFields() { + + if (!instance.fieldSpecs.isEmpty()) { + throw new InvalidDataAccessApiUsageException("Cannot add wildcard fieldspect to non empty."); + } + + this.instance.fieldSpecs.add(ALL_FIELDS); + return this; + } + + /** + * Include given fields with default weight. + * + * @param fieldnames + * @return + */ + public TextIndexDefinitionBuilder onFields(String... fieldnames) { + + for (String fieldname : fieldnames) { + onField(fieldname); + } + return this; + } + + /** + * Include given field with default weight. + * + * @param fieldname + * @return + */ + public TextIndexDefinitionBuilder onField(String fieldname) { + return onField(fieldname, Float.NaN); + } + + /** + * Include given field with weight. + * + * @param fieldname + * @return + */ + public TextIndexDefinitionBuilder onField(String fieldname, Float weight) { + + if (this.instance.fieldSpecs.contains(ALL_FIELDS)) { + throw new InvalidDataAccessApiUsageException(String.format("Cannot add %s to field spec for all fields.", + fieldname)); + } + + this.instance.fieldSpecs.add(new TextIndexedFieldSpec(fieldname, weight)); + return this; + } + + /** + * Define the default language to be used when indexing documents. + * + * @param language + * @see http://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/#specify-default-language-text-index + * @return + */ + public TextIndexDefinitionBuilder withDefaultLanguage(String language) { + + this.instance.defaultLanguage = language; + return this; + } + + /** + * Define field for language override. + * + * @param fieldname + * @return + */ + public TextIndexDefinitionBuilder withLanguageOverride(String fieldname) { + + if (StringUtils.hasText(this.instance.languageOverride)) { + throw new InvalidDataAccessApiUsageException(String.format( + "Cannot set language override on %s as it is already defined on %s.", fieldname, + this.instance.languageOverride)); + } + + this.instance.languageOverride = fieldname; + return this; + } + + public TextIndexDefinition build() { + return this.instance; + } + + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexed.java new file mode 100644 index 000000000..9348aacd4 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexed.java @@ -0,0 +1,44 @@ +/* + * 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.index; + +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 TextIndexed} marks a field to be part of the text index. As there can be only one text index per collection + * all fields marked with {@link TextIndexed} are combined into one single index.
+ * + * @author Christoph Strobl + * @since 1.6 + */ +@Documented +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface TextIndexed { + + /** + * Defines the significance of the filed relative to other indexed fields. The value directly influences the documents + * score.
+ * Defaulted to {@literal 1.0}. + * + * @return + */ + float weight() default 1.0F; +} 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 dfa1e7465..2f9eac702 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 @@ -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. @@ -47,12 +47,14 @@ import org.springframework.util.StringUtils; * @author Jon Brisbin * @author Oliver Gierke * @author Thomas Darimont + * @author Christoph Strobl */ public class BasicMongoPersistentEntity extends BasicPersistentEntity implements MongoPersistentEntity, ApplicationContextAware { private static final String AMBIGUOUS_FIELD_MAPPING = "Ambiguous field mapping detected! Both %s and %s map to the same field name %s! Disambiguate using @DocumentField annotation!"; private final String collection; + private final String language; private final SpelExpressionParser parser; private final StandardEvaluationContext context; @@ -75,8 +77,10 @@ public class BasicMongoPersistentEntity extends BasicPersistentEntity extends BasicPersistentEntity extends BasicPersistentEntity implements MongoPersistentProperty { @@ -48,6 +49,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope private static final Logger LOG = LoggerFactory.getLogger(BasicMongoPersistentProperty.class); private static final String ID_FIELD_NAME = "_id"; + private static final String LANGUAGE_FIELD_NAME = "language"; private static final Set> SUPPORTED_ID_TYPES = new HashSet>(); private static final Set SUPPORTED_ID_PROPERTY_NAMES = new HashSet(); @@ -181,4 +183,13 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope public DBRef getDBRef() { return findAnnotation(DBRef.class); } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isLanguageProperty() + */ + @Override + public boolean isLanguageProperty() { + return getFieldName().equals(LANGUAGE_FIELD_NAME) || isAnnotationPresent(Language.class); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java index cb14e0f0d..3ff03c639 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright (c) 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. @@ -29,6 +29,7 @@ import org.springframework.data.annotation.Persistent; * * @author Jon Brisbin * @author Oliver Gierke ogierke@vmware.com + * @author Christoph Strobl */ @Persistent @Inherited @@ -37,4 +38,13 @@ import org.springframework.data.annotation.Persistent; public @interface Document { String collection() default ""; + + /** + * Defines the default language to be used with this document. + * + * @since 1.6 + * @return + */ + String language() default ""; + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Language.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Language.java new file mode 100644 index 000000000..dc763264b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Language.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Mark property as language field. + * + * @author Christoph Strobl + * @since 1.6 + */ +@Documented +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Language { + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java index bca73c585..b4085f8d9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 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. @@ -21,6 +21,7 @@ import org.springframework.data.mapping.PersistentEntity; * MongoDB specific {@link PersistentEntity} abstraction. * * @author Oliver Gierke + * @author Christoph Strobl */ public interface MongoPersistentEntity extends PersistentEntity { @@ -30,4 +31,13 @@ public interface MongoPersistentEntity extends PersistentEntity { @@ -59,6 +60,15 @@ public interface MongoPersistentProperty extends PersistentProperty indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnSinglePropertyInRoot.class); + assertThat(indexDefinitions.size(), equalTo(1)); + assertIndexPathAndCollection("bar", "textIndexOnSinglePropertyInRoot", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldResolveMultiFieldTextIndexCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnMutiplePropertiesInRoot.class); + assertThat(indexDefinitions.size(), equalTo(1)); + assertIndexPathAndCollection(new String[] { "foo", "bar" }, "textIndexOnMutiplePropertiesInRoot", + indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldResolveTextIndexOnElementCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnNestedRoot.class); + assertThat(indexDefinitions.size(), equalTo(1)); + assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedRoot", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldResolveTextIndexOnElementWithWeightCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnNestedWithWeightRoot.class); + assertThat(indexDefinitions.size(), equalTo(1)); + assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedWithWeightRoot", + indexDefinitions.get(0)); + + DBObject weights = DBObjectTestUtils.getAsDBObject(indexDefinitions.get(0).getIndexOptions(), "weights"); + assertThat(weights.get("nested.foo"), IsEqual. equalTo(5F)); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(TextIndexOnNestedWithMostSpecificValueRoot.class); + assertThat(indexDefinitions.size(), equalTo(1)); + assertIndexPathAndCollection(new String[] { "nested.foo", "nested.bar" }, + "textIndexOnNestedWithMostSpecificValueRoot", indexDefinitions.get(0)); + + DBObject weights = DBObjectTestUtils.getAsDBObject(indexDefinitions.get(0).getIndexOptions(), "weights"); + assertThat(weights.get("nested.foo"), IsEqual. equalTo(5F)); + assertThat(weights.get("nested.bar"), IsEqual. equalTo(10F)); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldSetDefaultLanguageCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithDefaultLanguage.class); + assertThat(indexDefinitions.get(0).getIndexOptions().get("default_language"), IsEqual. equalTo("spanish")); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldResolveTextIndexLanguageOverrideCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(DocumentWithLanguageOverrideOnNestedElementRoot.class); + assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), IsEqual. equalTo("lang")); + } + + @Document + static class TextIndexOnSinglePropertyInRoot { + + String foo; + + @TextIndexed String bar; + } + + @Document + static class TextIndexOnMutiplePropertiesInRoot { + + @TextIndexed String foo; + + @TextIndexed(weight = 5) String bar; + } + + @Document + static class TextIndexOnNestedRoot { + + String bar; + + @TextIndexed TextIndexOnNested nested; + } + + static class TextIndexOnNested { + + String foo; + + } + + @Document + static class TextIndexOnNestedWithWeightRoot { + + @TextIndexed(weight = 5) TextIndexOnNested nested; + + } + + @Document + static class TextIndexOnNestedWithMostSpecificValueRoot { + @TextIndexed(weight = 5) TextIndexOnNestedWithMostSpecificValue nested; + } + + static class TextIndexOnNestedWithMostSpecificValue { + + String foo; + @TextIndexed(weight = 10) String bar; + } + + @Document(language = "spanish") + static class DocumentWithDefaultLanguage { + @TextIndexed String foo; + } + + @Document + static class DocumentWithLanguageOverrideOnNestedElementRoot { + + DocumentWithLanguageOverrideOnNestedElement nested; + } + + static class DocumentWithLanguageOverrideOnNestedElement { + + @TextIndexed String foo; + + @Language String lang; + } + + } + public static class MixedIndexResolutionTests { /** @@ -561,16 +722,23 @@ public class MongoPersistentEntityIndexResolverUnitTests { public String collection() { return null; } + + @Override + public String language() { + return null; + } }; MongoPersistentProperty propertyMock = mock(MongoPersistentProperty.class); when(propertyMock.isEntity()).thenReturn(true); + when(propertyMock.getOwner()).thenReturn( + (PersistentEntity) MongoPersistentEntityDummyBuilder.forClass(Object.class).build()); when(propertyMock.getActualType()).thenThrow( new MongoPersistentEntityIndexResolver.CyclicPropertyReferenceException("foo", Object.class, "bar")); MongoPersistentEntity dummy = MongoPersistentEntityDummyBuilder - .forClass(SelfCyclingViaCollectionType.class).withCollection("foo").and(propertyMock) - .and(documentDummy).build(); + .forClass(SelfCyclingViaCollectionType.class).withCollection("foo").and(propertyMock).and(documentDummy) + .build(); new MongoPersistentEntityIndexResolver(prepareMappingContext(SelfCyclingViaCollectionType.class)) .resolveIndexForEntity(dummy); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java new file mode 100644 index 000000000..8047f0fe0 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java @@ -0,0 +1,96 @@ +/* + * 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.index; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.config.AbstractIntegrationTests; +import org.springframework.data.mongodb.core.IndexOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Language; +import org.springframework.data.mongodb.test.util.MongoVersionRule; +import org.springframework.data.util.Version; + +import com.mongodb.WriteConcern; + +/** + * @author Christoph Strobl + */ +public class TextIndexTests extends AbstractIntegrationTests { + + public static @ClassRule MongoVersionRule version = MongoVersionRule.atLeast(new Version(2, 6)); + + private @Autowired MongoTemplate template; + private IndexOperations indexOps; + + @Before + public void setUp() throws Exception { + + template.setWriteConcern(WriteConcern.FSYNC_SAFE); + this.indexOps = template.indexOps(TextIndexedDocumentRoot.class); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void indexInfoShouldHaveBeenCreatedCorrectly() { + + List indexInfos = indexOps.getIndexInfo(); + + assertThat(indexInfos.size(), is(2)); + + List fields = indexInfos.get(0).getIndexFields(); + assertThat(fields.size(), is(1)); + assertThat(fields, hasItem(IndexField.create("_id", Direction.ASC))); + + IndexInfo textIndexInfo = indexInfos.get(1); + List textIndexFields = textIndexInfo.getIndexFields(); + assertThat(textIndexFields.size(), is(4)); + assertThat(textIndexFields, hasItem(IndexField.text("textIndexedPropertyWithDefaultWeight", 1F))); + assertThat(textIndexFields, hasItem(IndexField.text("textIndexedPropertyWithWeight", 5F))); + assertThat(textIndexFields, hasItem(IndexField.text("nestedDocument.textIndexedPropertyInNestedDocument", 1F))); + assertThat(textIndexFields, hasItem(IndexField.create("_ftsx", Direction.ASC))); + assertThat(textIndexInfo.getLanguage(), is("spanish")); + } + + @Document(language = "spanish") + static class TextIndexedDocumentRoot { + + @TextIndexed String textIndexedPropertyWithDefaultWeight; + @TextIndexed(weight = 5) String textIndexedPropertyWithWeight; + + TextIndexedDocumentWihtLanguageOverride nestedDocument; + } + + static class TextIndexedDocumentWihtLanguageOverride { + + @Language String lang; + + @TextIndexed String textIndexedPropertyInNestedDocument; + + String nonTextIndexedProperty; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java index abf624c6e..ac2ff1f9e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.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. @@ -30,12 +30,12 @@ import org.springframework.data.util.ClassTypeInformation; * Unit tests for {@link BasicMongoPersistentEntity}. * * @author Oliver Gierke + * @author Christoph Strobl */ @RunWith(MockitoJUnitRunner.class) public class BasicMongoPersistentEntityUnitTests { - @Mock - ApplicationContext context; + @Mock ApplicationContext context; @Test public void subclassInheritsAtDocumentAnnotation() { @@ -69,6 +69,17 @@ public class BasicMongoPersistentEntityUnitTests { assertThat(entity.getCollection(), is("reference")); } + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldDetectLanguageCorrectly() { + + BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( + ClassTypeInformation.from(DocumentWithLanguage.class)); + assertThat(entity.getLanguage(), is("spanish")); + } + @Document(collection = "contacts") class Contact { @@ -95,4 +106,9 @@ public class BasicMongoPersistentEntityUnitTests { return collectionName; } } + + @Document(language = "spanish") + static class DocumentWithLanguage { + + } } 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 eaade63a4..d8fe99fbc 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 @@ -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. @@ -38,6 +38,7 @@ import org.springframework.util.ReflectionUtils; * Unit test for {@link BasicMongoPersistentProperty}. * * @author Oliver Gierke + * @author Christoph Strobl */ public class BasicMongoPersistentPropertyUnitTests { @@ -123,8 +124,42 @@ public class BasicMongoPersistentPropertyUnitTests { property.getFieldName(); } + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldDetectAnnotatedLanguagePropertyCorrectly() { + + BasicMongoPersistentEntity persistentEntity = new BasicMongoPersistentEntity( + ClassTypeInformation.from(DocumentWithLanguageProperty.class)); + + MongoPersistentProperty property = getPropertyFor(persistentEntity, "lang"); + assertThat(property.isLanguageProperty(), is(true)); + } + + /** + * @see DATAMONGO-937 + */ + @Test + public void shouldDetectIplicitLanguagePropertyCorrectly() { + + BasicMongoPersistentEntity persistentEntity = new BasicMongoPersistentEntity( + ClassTypeInformation.from(DocumentWithImplicitLanguageProperty.class)); + + MongoPersistentProperty property = getPropertyFor(persistentEntity, "language"); + assertThat(property.isLanguageProperty(), is(true)); + } + private MongoPersistentProperty getPropertyFor(Field field) { - return new BasicMongoPersistentProperty(field, null, entity, new SimpleTypeHolder(), + return getPropertyFor(entity, field); + } + + private MongoPersistentProperty getPropertyFor(MongoPersistentEntity persistentEntity, String fieldname) { + return getPropertyFor(persistentEntity, ReflectionUtils.findField(persistentEntity.getType(), fieldname)); + } + + private MongoPersistentProperty getPropertyFor(MongoPersistentEntity persistentEntity, Field field) { + return new BasicMongoPersistentProperty(field, null, persistentEntity, new SimpleTypeHolder(), PropertyNameFieldNamingStrategy.INSTANCE); } @@ -155,4 +190,14 @@ public class BasicMongoPersistentPropertyUnitTests { return null; } } + + static class DocumentWithLanguageProperty { + + @Language String lang; + } + + static class DocumentWithImplicitLanguageProperty { + + String language; + } } 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 8512a6edd..7577edd13 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 @@ -209,4 +209,9 @@ public class MongoPersistentEntityTestDummy implements MongoPersistentEntity< } } + + @Override + public String getLanguage() { + return null; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java index 9eba54c61..8a8d06cd3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-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. @@ -20,10 +20,13 @@ import static org.junit.Assert.*; import javax.validation.ConstraintViolationException; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; +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; @@ -33,13 +36,15 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @see DATAMONGO-36 * @author Maciej Walkowiak * @author Oliver Gierke + * @author Christoph Strobl */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class ValidatingMongoEventListenerTests { - @Autowired - MongoTemplate mongoTemplate; + public static @ClassRule MongoVersionRule version = MongoVersionRule.atLeast(new Version(2, 6)); + + @Autowired MongoTemplate mongoTemplate; @Test public void shouldThrowConstraintViolationException() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoVersionRule.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoVersionRule.java new file mode 100644 index 000000000..8d4feffa1 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoVersionRule.java @@ -0,0 +1,109 @@ +/* + * 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.test.util; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.internal.AssumptionViolatedException; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.springframework.data.util.Version; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.CommandResult; +import com.mongodb.DB; +import com.mongodb.MongoClient; + +/** + * {@link TestRule} verifying server tests are executed against match a given version. This one can be used as + * {@link ClassRule} eg. in context depending tests run with {@link SpringJUnit4ClassRunner} when the context would fail + * to start in case of invalid version, or as simple {@link Rule} on specific tests. + * + * @author Christoph Strobl + * @since 1.6 + */ +public class MongoVersionRule implements TestRule { + + private String host = "localhost"; + private int port = 27017; + + private final Version minVersion; + private final Version maxVersion; + + private Version currentVersion; + + public MongoVersionRule(Version min, Version max) { + this.minVersion = min; + this.maxVersion = max; + } + + public static MongoVersionRule any() { + return new MongoVersionRule(new Version(0, 0, 0), new Version(9999, 9999, 9999)); + } + + public static MongoVersionRule atLeast(Version minVersion) { + return new MongoVersionRule(minVersion, new Version(9999, 9999, 9999)); + } + + public static MongoVersionRule atMost(Version maxVersion) { + return new MongoVersionRule(new Version(0, 0, 0), maxVersion); + } + + public MongoVersionRule withServerRunningAt(String host, int port) { + this.host = host; + this.port = port; + + return this; + } + + @Override + public Statement apply(final Statement base, Description description) { + + initCurrentVersion(); + return new Statement() { + + @Override + public void evaluate() throws Throwable { + if (currentVersion != null) { + if (currentVersion.isLessThan(minVersion) || currentVersion.isGreaterThan(maxVersion)) { + throw new AssumptionViolatedException(String.format( + "Expected mongodb server to be in range %s to %s but found %s", minVersion, maxVersion, currentVersion)); + } + } + base.evaluate(); + } + }; + } + + private void initCurrentVersion() { + + if (currentVersion == null) { + try { + MongoClient client; + client = new MongoClient(host, port); + DB db = client.getDB("test"); + CommandResult result = db.command(new BasicDBObjectBuilder().add("buildInfo", 1).get()); + this.currentVersion = Version.parse(result.get("version").toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + +} diff --git a/src/docbkx/reference/mapping.xml b/src/docbkx/reference/mapping.xml index 91675e4d6..41fb77e39 100644 --- a/src/docbkx/reference/mapping.xml +++ b/src/docbkx/reference/mapping.xml @@ -357,6 +357,16 @@ public class Person { level to describe how to geoindex the field. + + @TextIndexed - applied at the field level + to mark the field to be included in the text index. + + + + @Language - applied at the field level to + set the language override property for text index. + + @Transient - by default all private fields are mapped to the document, this annotation excludes the field where @@ -526,7 +536,7 @@ OrderItem item = converter.read(OrderItem.class, input); test suite. -
+
Compound Indexes Compound indexes are also supported. They are defined at the class @@ -561,6 +571,51 @@ public class Person {
+
+ Text Indexes + + + The text index feature is disabled by default for mongodb + v.2.4. + + + Creating a text index allows to accumulate several fields into a + searchable full text index. It is only possible to have one text index + per collection so all fields marked with + @TextIndexed are combined into this + index. Properties can be weighted to influence document score for + ranking results. The default language for the text index is english, to + change the default language set + @Document(language="spanish") to any + language you want. Using a property called language + or @Language allows to define a language + override on a per document base. + + + Example Text Index Usage + + @Document(language = "spanish") +class SomeEntity { + + @TextIndexed String foo; + + @Language String lang; + + Nested nested; + + +} + +class Nested { + + @TextIndexed(weight=5) String bar; + + String roo; +} + + +
+
Using DBRefs diff --git a/src/docbkx/reference/mongodb.xml b/src/docbkx/reference/mongodb.xml index 4f24eff44..d85b28ef0 100644 --- a/src/docbkx/reference/mongodb.xml +++ b/src/docbkx/reference/mongodb.xml @@ -3046,11 +3046,12 @@ class MyConverter implements Converter<String, Person> { … } - You can create both standard indexes and geospatial indexes using - the classes IndexDefinition and - GeoSpatialIndex respectfully. For example, given - the Venue class defined in a previous section, you would declare a - geospatial query as shown below + You can create standard, geospatial and text indexes using the + classes IndexDefinition, + GeoSpatialIndex and + TextIndexDefinition. For example, given the Venue + class defined in a previous section, you would declare a geospatial + query as shown below. mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location"));