diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/Collation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/Collation.java
new file mode 100644
index 000000000..3dae36ee8
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/Collation.java
@@ -0,0 +1,754 @@
+/*
+ * Copyright 2017 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;
+
+import java.util.Locale;
+import java.util.Optional;
+
+import org.bson.Document;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.Assert;
+
+import com.mongodb.client.model.Collation.Builder;
+import com.mongodb.client.model.CollationAlternate;
+import com.mongodb.client.model.CollationCaseFirst;
+import com.mongodb.client.model.CollationMaxVariable;
+import com.mongodb.client.model.CollationStrength;
+
+/**
+ * Central abstraction for MongoDB collation support.
+ * Allows fluent creation of a collation {@link Document} that can be used for creating collections & indexes as well as
+ * querying data.
+ *
+ * NOTE: Please keep in mind that queries will only make use of an index with collation settings if the
+ * query itself specifies the same collation.
+ *
+ * @author Christoph Strobl
+ * @since 2.0
+ * @see MongoDB Reference - Collation
+ */
+public class Collation {
+
+ private static final Collation DEFAULT = of("simple");
+
+ private final ICULocale locale;
+
+ private Optional strength = Optional.empty();
+ private Optional numericOrdering = Optional.empty();
+ private Optional alternate = Optional.empty();
+ private Optional backwards = Optional.empty();
+ private Optional normalization = Optional.empty();
+ private Optional version = Optional.empty();
+
+ private Collation(ICULocale locale) {
+
+ Assert.notNull(locale, "ICULocale must not be null!");
+ this.locale = locale;
+ }
+
+ /**
+ * Create new {@link Collation} using simple binary comparison.
+ *
+ * @return
+ * @see #binary()
+ */
+ public static Collation simple() {
+ return binary();
+ }
+
+ /**
+ * Create new {@link Collation} using simple binary comparison.
+ *
+ * @return
+ */
+ public static Collation binary() {
+ return DEFAULT;
+ }
+
+ /**
+ * Create new {@link Collation} with locale set to {{@link java.util.Locale#getLanguage()}} and
+ * {@link java.util.Locale#getVariant()}.
+ *
+ * @param locale must not be {@literal null}.
+ * @return
+ */
+ public static Collation of(Locale locale) {
+
+ Assert.notNull(locale, "Locale must not be null!");
+ return of(ICULocale.of(locale.getLanguage()).variant(locale.getVariant()));
+ }
+
+ /**
+ * Create new {@link Collation} with locale set to the given ICU language.
+ *
+ * @param language must not be {@literal null}.
+ * @return
+ */
+ public static Collation of(String language) {
+ return of(ICULocale.of(language));
+ }
+
+ /**
+ * Create new {@link Collation} with locale set to the given {@link ICULocale}.
+ *
+ * @param locale must not be {@literal null}.
+ * @return
+ */
+ public static Collation of(ICULocale locale) {
+ return new Collation(locale);
+ }
+
+ /**
+ * Create new {@link Collation} from values in {@link Document}.
+ *
+ * @param source must not be {@literal null}.
+ * @return
+ * @see MongoDB Reference -
+ * Collation Document
+ */
+ public static Collation from(Document source) {
+
+ Assert.notNull(source, "Source must not be null!");
+
+ Collation collation = Collation.of(source.getString("locale"));
+ if (source.containsKey("strength")) {
+ collation = collation.strength(source.getInteger("strength"));
+ }
+ if (source.containsKey("caseLevel")) {
+ collation = collation.caseLevel(source.getBoolean("caseLevel"));
+ }
+ if (source.containsKey("caseFirst")) {
+ collation = collation.caseFirst(source.getString("caseFirst"));
+ }
+ if (source.containsKey("numericOrdering")) {
+ collation = collation.numericOrdering(source.getBoolean("numericOrdering"));
+ }
+ if (source.containsKey("alternate")) {
+ collation = collation.alternate(source.getString("alternate"));
+ }
+ if (source.containsKey("maxVariable")) {
+ collation = collation.maxVariable(source.getString("maxVariable"));
+ }
+ if (source.containsKey("backwards")) {
+ collation = collation.backwards(source.getBoolean("backwards"));
+ }
+ if (source.containsKey("normalization")) {
+ collation = collation.normalization(source.getBoolean("normalization"));
+ }
+ if (source.containsKey("version")) {
+ collation.version = Optional.of(source.get("version").toString());
+ }
+ return collation;
+ }
+
+ /**
+ * Set the level of comparison to perform.
+ *
+ * @param strength must not be {@literal null}.
+ * @return new {@link Collation}.
+ */
+ public Collation strength(Integer strength) {
+
+ ICUComparisonLevel current = this.strength.orElseGet(() -> new ICUComparisonLevel(strength, null, null));
+ return strength(new ICUComparisonLevel(strength, current.caseFirst.orElse(null), current.caseLevel.orElse(null)));
+ }
+
+ /**
+ * Set the level of comparison to perform.
+ *
+ * @param comparisonLevel must not be {@literal null}.
+ * @return new {@link Collation}
+ */
+ public Collation strength(ICUComparisonLevel comparisonLevel) {
+
+ Collation newInstance = copy();
+ newInstance.strength = Optional.ofNullable(comparisonLevel);
+ return newInstance;
+ }
+
+ /**
+ * Set {@code caseLevel} comarison.
+ *
+ * @param caseLevel must not be {@literal null}.
+ * @return new {@link Collation}.
+ */
+ public Collation caseLevel(Boolean caseLevel) {
+
+ ICUComparisonLevel strengthValue = strength.orElseGet(() -> ICUComparisonLevel.primary());
+ return strength(new ICUComparisonLevel(strengthValue.level, strengthValue.caseFirst.orElse(null), caseLevel));
+ }
+
+ /**
+ * Set the flag that determines sort order of case differences during tertiary level comparisons.
+ *
+ * @param caseFirst must not be {@literal null}.
+ * @return
+ */
+ public Collation caseFirst(String caseFirst) {
+ return caseFirst(new ICUCaseFirst(caseFirst));
+ }
+
+ /**
+ * Set the flag that determines sort order of case differences during tertiary level comparisons.
+ *
+ * @param caseFirst must not be {@literal null}.
+ * @return
+ */
+ public Collation caseFirst(ICUCaseFirst sort) {
+
+ ICUComparisonLevel strengthValue = strength.orElseGet(() -> ICUComparisonLevel.tertiary());
+ return strength(new ICUComparisonLevel(strengthValue.level, sort, strengthValue.caseLevel.orElse(null)));
+ }
+
+ /**
+ * Treat numeric strings as numbers for comparison.
+ *
+ * @return new {@link Collation}.
+ */
+ public Collation numericOrderingEnabled() {
+ return numericOrdering(true);
+ }
+
+ /**
+ * Treat numeric strings as string for comparison.
+ *
+ * @return new {@link Collation}.
+ */
+ public Collation numericOrderingDisabled() {
+ return numericOrdering(false);
+ }
+
+ /**
+ * Set the flag that determines whether to compare numeric strings as numbers or as strings.
+ *
+ * @return new {@link Collation}.
+ */
+ public Collation numericOrdering(Boolean flag) {
+
+ Collation newInstance = copy();
+ newInstance.numericOrdering = Optional.ofNullable(flag);
+ return newInstance;
+ }
+
+ /**
+ * Set the Field that determines whether collation should consider whitespace and punctuation as base characters for
+ * purposes of comparison.
+ *
+ * @param alternate must not be {@literal null}.
+ * @return new {@link Collation}.
+ */
+ public Collation alternate(String alternate) {
+
+ Alternate instance = this.alternate.orElseGet(() -> new Alternate(alternate, null));
+ return alternate(new Alternate(alternate, instance.maxVariable.orElse(null)));
+ }
+
+ /**
+ * Set the Field that determines whether collation should consider whitespace and punctuation as base characters for
+ * purposes of comparison.
+ *
+ * @param alternate must not be {@literal null}.
+ * @return new {@link Collation}.
+ */
+ public Collation alternate(Alternate alternate) {
+
+ Collation newInstance = copy();
+ newInstance.alternate = Optional.ofNullable(alternate);
+ return newInstance;
+ }
+
+ /**
+ * Sort string with diacritics sort from back of the string.
+ *
+ * @return new {@link Collation}.
+ */
+ public Collation backwardDiacriticSort() {
+ return backwards(true);
+ }
+
+ /**
+ * Do not sort string with diacritics sort from back of the string.
+ *
+ * @return new {@link Collation}.
+ */
+ public Collation forwardDiacriticSort() {
+ return backwards(false);
+ }
+
+ /**
+ * Set the flag that determines whether strings with diacritics sort from back of the string.
+ *
+ * @param backwards must not be {@literal null}.
+ * @return new {@link Collation}.
+ */
+ public Collation backwards(Boolean backwards) {
+
+ Collation newInstance = copy();
+ newInstance.backwards = Optional.ofNullable(backwards);
+ return newInstance;
+ }
+
+ /**
+ * Enable text normalization.
+ *
+ * @return new {@link Collation}.
+ */
+ public Collation normalizationEnabled() {
+ return normalization(true);
+ }
+
+ /**
+ * Disable text normalization.
+ *
+ * @return new {@link Collation}.
+ */
+ public Collation normalizationDisabled() {
+ return normalization(false);
+ }
+
+ /**
+ * Set the flag that determines whether to check if text require normalization and to perform normalization.
+ *
+ * @param normalization must not be {@literal null}.
+ * @return new {@link Collation}.
+ */
+ public Collation normalization(Boolean normalization) {
+
+ Collation newInstance = copy();
+ newInstance.normalization = Optional.ofNullable(normalization);
+ return newInstance;
+ }
+
+ /**
+ * Set the field that determines up to which characters are considered ignorable when alternate is {@code shifted}.
+ *
+ * @param maxVariable must not be {@literal null}.
+ * @return new {@link Collation}.
+ */
+ public Collation maxVariable(String maxVariable) {
+
+ Alternate alternateValue = alternate.orElseGet(() -> Alternate.shifted());
+ return alternate(new AlternateWithMaxVariable(alternateValue.alternate, maxVariable));
+ }
+
+ /**
+ * Get the {@link Document} representation of the {@link Collation}.
+ *
+ * @return
+ */
+ public Document toDocument() {
+ return map(toMongoDocumentConverter());
+ }
+
+ /**
+ * Get the {@link com.mongodb.client.model.Collation} representation of the {@link Collation}.
+ *
+ * @return
+ */
+ public com.mongodb.client.model.Collation toMongoCollation() {
+ return map(toMongoCollationConverter());
+ }
+
+ public R map(Converter super Collation, ? extends R> mapper) {
+ return mapper.convert(this);
+ }
+
+ @Override
+ public String toString() {
+ return toDocument().toJson();
+ }
+
+ private Collation copy() {
+
+ Collation collation = new Collation(locale);
+ collation.strength = this.strength;
+ collation.normalization = this.normalization;
+ collation.numericOrdering = this.numericOrdering;
+ collation.alternate = this.alternate;
+ collation.backwards = this.backwards;
+ return collation;
+ }
+
+ /**
+ * Abstraction for the ICU Comparison Levels.
+ *
+ * @since 2.0
+ */
+ public static class ICUComparisonLevel {
+
+ protected final Integer level;
+ private final Optional caseFirst;
+ private final Optional caseLevel;
+
+ private ICUComparisonLevel(Integer level, ICUCaseFirst caseFirst, Boolean caseLevel) {
+
+ this.level = level;
+ this.caseFirst = Optional.ofNullable(caseFirst);
+ this.caseLevel = Optional.ofNullable(caseLevel);
+ }
+
+ /**
+ * Primary level of comparison. Collation performs comparisons of the base characters only, ignoring other
+ * differences such as diacritics and case.
+ * The {@code caseLevel} can be set via {@link ComparisonLevelWithCase#caseLevel(Boolean)}.
+ *
+ * @return new {@link ComparisonLevelWithCase}.
+ */
+ public static PrimaryICUComparisonLevel primary() {
+ return new PrimaryICUComparisonLevel(1, null);
+ }
+
+ /**
+ * Scondary level of comparison. Collation performs comparisons up to secondary differences, such as
+ * diacritics.
+ * The {@code caseLevel} can be set via {@link ComparisonLevelWithCase#caseLevel(Boolean)}.
+ *
+ * @return new {@link ComparisonLevelWithCase}.
+ */
+ public static SecondaryICUComparisonLevel secondary() {
+ return new SecondaryICUComparisonLevel(2, null);
+ }
+
+ /**
+ * Tertiary level of comparison. Collation performs comparisons up to tertiary differences, such as case and letter
+ * variants.
+ * The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
+ *
+ * @return new {@link ICUComparisonLevel}.
+ */
+ public static TertiaryICUComparisonLevel tertiary() {
+ return new TertiaryICUComparisonLevel(3, null);
+ }
+
+ /**
+ * Quaternary Level. Limited for specific use case to consider punctuation.
+ * The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
+ *
+ * @return new {@link ICUComparisonLevel}.
+ */
+ public static ICUComparisonLevel quaternary() {
+ return new ICUComparisonLevel(4, null, null);
+ }
+
+ /**
+ * Identical Level. Limited for specific use case of tie breaker.
+ * The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
+ *
+ * @return new {@link ICUComparisonLevel}.
+ */
+ public static ICUComparisonLevel identical() {
+ return new ICUComparisonLevel(5, null, null);
+ }
+ }
+
+ public static class TertiaryICUComparisonLevel extends ICUComparisonLevel {
+
+ private TertiaryICUComparisonLevel(Integer level, ICUCaseFirst caseFirst) {
+ super(level, caseFirst, null);
+ }
+
+ /**
+ * Set the flag that determines sort order of case differences.
+ *
+ * @param caseFirstSort must not be {@literal null}.
+ * @return
+ */
+ public TertiaryICUComparisonLevel caseFirst(ICUCaseFirst caseFirst) {
+
+ Assert.notNull(caseFirst, "CaseFirst must not be null!");
+ return new TertiaryICUComparisonLevel(level, caseFirst);
+ }
+ }
+
+ public static class PrimaryICUComparisonLevel extends ICUComparisonLevel {
+
+ private PrimaryICUComparisonLevel(Integer level, Boolean caseLevel) {
+ super(level, null, caseLevel);
+ }
+
+ /**
+ * Include case comparison.
+ *
+ * @return new {@link ComparisonLevelWithCase}
+ */
+ public PrimaryICUComparisonLevel includeCase() {
+ return caseLevel(Boolean.TRUE);
+ }
+
+ /**
+ * Exclude case comparison.
+ *
+ * @return new {@link ComparisonLevelWithCase}
+ */
+ public PrimaryICUComparisonLevel excludeCase() {
+ return caseLevel(Boolean.FALSE);
+ }
+
+ PrimaryICUComparisonLevel caseLevel(Boolean caseLevel) {
+ return new PrimaryICUComparisonLevel(level, caseLevel);
+ }
+ }
+
+ public static class SecondaryICUComparisonLevel extends ICUComparisonLevel {
+
+ private SecondaryICUComparisonLevel(Integer level, Boolean caseLevel) {
+ super(level, null, caseLevel);
+ }
+
+ /**
+ * Include case comparison.
+ *
+ * @return new {@link ComparisonLevelWithCase}
+ */
+ public SecondaryICUComparisonLevel includeCase() {
+ return caseLevel(Boolean.TRUE);
+ }
+
+ /**
+ * Exclude case comparison.
+ *
+ * @return new {@link ComparisonLevelWithCase}
+ */
+ public SecondaryICUComparisonLevel excludeCase() {
+ return caseLevel(Boolean.FALSE);
+ }
+
+ SecondaryICUComparisonLevel caseLevel(Boolean caseLevel) {
+ return new SecondaryICUComparisonLevel(level, caseLevel);
+ }
+ }
+
+ /**
+ * @since 2.0
+ */
+ public static class ICUCaseFirst {
+
+ private final String state;
+
+ private ICUCaseFirst(String state) {
+ this.state = state;
+ }
+
+ /**
+ * Sort uppercase before lowercase.
+ *
+ * @return new {@link ICUCaseFirst}.
+ */
+ public static ICUCaseFirst upper() {
+ return new ICUCaseFirst("upper");
+ }
+
+ /**
+ * Sort lowercase before uppercase.
+ *
+ * @return new {@link ICUCaseFirst}.
+ */
+ public static ICUCaseFirst lower() {
+ return new ICUCaseFirst("lower");
+ }
+
+ /**
+ * Use the default.
+ *
+ * @return new {@link ICUCaseFirst}.
+ */
+ public static ICUCaseFirst off() {
+ return new ICUCaseFirst("off");
+ }
+ }
+
+ /**
+ * @since 2.0
+ */
+ public static class Alternate {
+
+ protected final String alternate;
+ protected Optional maxVariable;
+
+ private Alternate(String alternate, String maxVariable) {
+ this.alternate = alternate;
+ this.maxVariable = Optional.ofNullable(maxVariable);
+ }
+
+ /**
+ * Consider Whitespace and punctuation as base characters.
+ *
+ * @return new {@link Alternate}.
+ */
+ public static Alternate nonIgnorable() {
+ return new Alternate("non-ignorable", null);
+ }
+
+ /**
+ * Whitespace and punctuation are not considered base characters and are only distinguished at
+ * strength.
+ * NOTE: Only works for {@link ICUComparisonLevel} above {@link ICUComparisonLevel#tertiary()}.
+ *
+ * @return new {@link AlternateWithMaxVariable}.
+ */
+ public static AlternateWithMaxVariable shifted() {
+ return new AlternateWithMaxVariable("shifted", null);
+ }
+ }
+
+ /**
+ * @since 2.0
+ */
+ public static class AlternateWithMaxVariable extends Alternate {
+
+ private AlternateWithMaxVariable(String alternate, String maxVariable) {
+ super(alternate, maxVariable);
+ }
+
+ /**
+ * Consider both whitespaces and punctuation as ignorable.
+ *
+ * @return new {@link AlternateWithMaxVariable}.
+ */
+ public AlternateWithMaxVariable punct() {
+ return new AlternateWithMaxVariable(alternate, "punct");
+ }
+
+ /**
+ * Only consider whitespaces as ignorable.
+ *
+ * @return new {@link AlternateWithMaxVariable}.
+ */
+ public AlternateWithMaxVariable space() {
+ return new AlternateWithMaxVariable(alternate, "space");
+ }
+
+ }
+
+ /**
+ * ICU locale abstraction for usage with MongoDB {@link Collation}.
+ *
+ * @since 2.0
+ * @see ICU - International Components for Unicode
+ */
+ public static class ICULocale {
+
+ private final String language;
+ private final Optional variant;
+
+ private ICULocale(String language, String variant) {
+ this.language = language;
+ this.variant = Optional.ofNullable(variant);
+ }
+
+ /**
+ * Create new {@link ICULocale} for given language.
+ *
+ * @param language must not be {@literal null}.
+ * @return
+ */
+ public static ICULocale of(String language) {
+
+ Assert.notNull(language, "Code must not be null!");
+ return new ICULocale(language, null);
+ }
+
+ /**
+ * Define language variant.
+ *
+ * @param variant must not be {@literal null}.
+ * @return new {@link ICULocale}.
+ */
+ public ICULocale variant(String variant) {
+
+ Assert.notNull(variant, "Variant must not be null!");
+ return new ICULocale(language, variant);
+ }
+
+ /**
+ * Get the string representation.
+ *
+ * @return
+ */
+ public String asString() {
+
+ StringBuilder sb = new StringBuilder(language);
+ variant.ifPresent(val -> {
+
+ if (!val.isEmpty()) {
+ sb.append("@collation=").append(val);
+ }
+ });
+ return sb.toString();
+ }
+ }
+
+ private static Converter toMongoDocumentConverter() {
+
+ return source -> {
+
+ Document document = new Document();
+ document.append("locale", source.locale.asString());
+
+ source.strength.ifPresent(val -> {
+
+ document.append("strength", val.level);
+
+ val.caseLevel.ifPresent(cl -> document.append("caseLevel", cl));
+ val.caseFirst.ifPresent(cl -> document.append("caseFirst", cl.state));
+ });
+
+ source.numericOrdering.ifPresent(val -> document.append("numericOrdering", val));
+ source.alternate.ifPresent(val -> {
+
+ document.append("alternate", val.alternate);
+ val.maxVariable.ifPresent(maxVariable -> document.append("maxVariable", maxVariable));
+ });
+
+ source.backwards.ifPresent(val -> document.append("backwards", val));
+ source.normalization.ifPresent(val -> document.append("normalization", val));
+ source.version.ifPresent(val -> document.append("version", val));
+
+ return document;
+ };
+ }
+
+ private static Converter toMongoCollationConverter() {
+
+ return source -> {
+
+ Builder builder = com.mongodb.client.model.Collation.builder();
+
+ builder.locale(source.locale.asString());
+
+ source.strength.ifPresent(val -> {
+
+ builder.collationStrength(CollationStrength.fromInt(val.level));
+
+ val.caseLevel.ifPresent(cl -> builder.caseLevel(cl));
+ val.caseFirst.ifPresent(cl -> builder.collationCaseFirst(CollationCaseFirst.fromString(cl.state)));
+ });
+
+ source.numericOrdering.ifPresent(val -> builder.numericOrdering(val));
+ source.alternate.ifPresent(val -> {
+
+ builder.collationAlternate(CollationAlternate.fromString(val.alternate));
+ val.maxVariable
+ .ifPresent(maxVariable -> builder.collationMaxVariable(CollationMaxVariable.fromString(maxVariable)));
+ });
+
+ source.backwards.ifPresent(val -> builder.backwards(val));
+ source.normalization.ifPresent(val -> builder.normalization(val));
+
+ return builder.build();
+ };
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java
index 756e2863e..e694110f0 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 the original author or authors.
+ * Copyright 2010-2017 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.
@@ -15,18 +15,22 @@
*/
package org.springframework.data.mongodb.core;
+import java.util.Optional;
+
+import org.springframework.util.Assert;
+
/**
* Provides a simple wrapper to encapsulate the variety of settings you can use when creating a collection.
- *
+ *
* @author Thomas Risberg
+ * @author Christoph Strobl
*/
public class CollectionOptions {
private Integer maxDocuments;
-
private Integer size;
-
private Boolean capped;
+ private Collation collation;
/**
* Constructs a new CollectionOptions instance.
@@ -37,12 +41,85 @@ public class CollectionOptions {
* false otherwise.
*/
public CollectionOptions(Integer size, Integer maxDocuments, Boolean capped) {
- super();
+
this.maxDocuments = maxDocuments;
this.size = size;
this.capped = capped;
}
+ private CollectionOptions() {}
+
+ /**
+ * Create new {@link CollectionOptions} by just providing the {@link Collation} to use.
+ *
+ * @param collation must not be {@literal null}.
+ * @return new {@link CollectionOptions}.
+ * @since 2.0
+ */
+ public static CollectionOptions just(Collation collation) {
+
+ Assert.notNull(collation, "Collation must not be null!");
+
+ CollectionOptions options = new CollectionOptions();
+ options.setCollation(collation);
+ return options;
+ }
+
+ /**
+ * Create new {@link CollectionOptions} with already given settings and capped set to {@literal true}.
+ *
+ * @return new {@link CollectionOptions}.
+ * @since 2.0
+ */
+ public CollectionOptions capped() {
+
+ CollectionOptions options = new CollectionOptions(size, maxDocuments, true);
+ options.setCollation(collation);
+ return options;
+ }
+
+ /**
+ * Create new {@link CollectionOptions} with already given settings and {@code maxDocuments} set to given value.
+ *
+ * @param maxDocuments can be {@literal null}.
+ * @return new {@link CollectionOptions}.
+ * @since 2.0
+ */
+ public CollectionOptions maxDocuments(Integer maxDocuments) {
+
+ CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
+ options.setCollation(collation);
+ return options;
+ }
+
+ /**
+ * Create new {@link CollectionOptions} with already given settings and {@code size} set to given value.
+ *
+ * @param size can be {@literal null}.
+ * @return new {@link CollectionOptions}.
+ * @since 2.0
+ */
+ public CollectionOptions size(Integer size) {
+
+ CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
+ options.setCollation(collation);
+ return options;
+ }
+
+ /**
+ * Create new {@link CollectionOptions} with already given settings and {@code collation} set to given value.
+ *
+ * @param collation can be {@literal null}.
+ * @return new {@link CollectionOptions}.
+ * @since 2.0
+ */
+ public CollectionOptions collation(Collation collation) {
+
+ CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
+ options.setCollation(collation);
+ return options;
+ }
+
public Integer getMaxDocuments() {
return maxDocuments;
}
@@ -67,4 +144,23 @@ public class CollectionOptions {
this.capped = capped;
}
+ /**
+ * Set {@link Collation} options.
+ *
+ * @param collation
+ * @since 2.0
+ */
+ public void setCollation(Collation collation) {
+ this.collation = collation;
+ }
+
+ /**
+ * Get the {@link Collation} settings.
+ *
+ * @return
+ * @since 2.0
+ */
+ public Optional getCollation() {
+ return Optional.ofNullable(collation);
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java
index d06a4bff2..fb4411b47 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2016 the original author or authors.
+ * Copyright 2015-2017 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.
@@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import com.mongodb.client.model.DeleteOptions;
import org.bson.Document;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
@@ -58,12 +59,12 @@ class DefaultBulkOperations implements BulkOperations {
private BulkWriteOptions bulkOptions;
- List> models = new ArrayList>();
+ List> models = new ArrayList<>();
/**
* Creates a new {@link DefaultBulkOperations} for the given {@link MongoOperations}, {@link BulkMode}, collection
* name and {@link WriteConcern}.
- *
+ *
* @param mongoOperations The underlying {@link MongoOperations}, must not be {@literal null}.
* @param bulkMode must not be {@literal null}.
* @param collectionName Name of the collection to work on, must not be {@literal null} or empty.
@@ -88,7 +89,7 @@ class DefaultBulkOperations implements BulkOperations {
/**
* Configures the {@link PersistenceExceptionTranslator} to be used. Defaults to {@link MongoExceptionTranslator}.
- *
+ *
* @param exceptionTranslator can be {@literal null}.
*/
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
@@ -97,7 +98,7 @@ class DefaultBulkOperations implements BulkOperations {
/**
* Configures the {@link WriteConcernResolver} to be used. Defaults to {@link DefaultWriteConcernResolver}.
- *
+ *
* @param writeConcernResolver can be {@literal null}.
*/
public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) {
@@ -107,7 +108,7 @@ class DefaultBulkOperations implements BulkOperations {
/**
* Configures the default {@link WriteConcern} to be used. Defaults to {@literal null}.
- *
+ *
* @param defaultWriteConcern can be {@literal null}.
*/
public void setDefaultWriteConcern(WriteConcern defaultWriteConcern) {
@@ -244,7 +245,10 @@ class DefaultBulkOperations implements BulkOperations {
Assert.notNull(query, "Query must not be null!");
- models.add(new DeleteManyModel(query.getQueryObject()));
+ DeleteOptions deleteOptions = new DeleteOptions();
+ query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation);
+
+ models.add(new DeleteManyModel(query.getQueryObject(), deleteOptions));
return this;
}
@@ -306,11 +310,12 @@ class DefaultBulkOperations implements BulkOperations {
UpdateOptions options = new UpdateOptions();
options.upsert(upsert);
+ query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
if (multi) {
- models.add(new UpdateManyModel(query.getQueryObject(), update.getUpdateObject(), options));
+ models.add(new UpdateManyModel<>(query.getQueryObject(), update.getUpdateObject(), options));
} else {
- models.add(new UpdateOneModel(query.getQueryObject(), update.getUpdateObject(), options));
+ models.add(new UpdateOneModel<>(query.getQueryObject(), update.getUpdateObject(), options));
}
return this;
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java
index c5e88c7fd..1142f9491 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 the original author or authors.
+ * Copyright 2010-2017 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.
@@ -15,14 +15,21 @@
*/
package org.springframework.data.mongodb.core;
+import java.util.Optional;
+
+/**
+ * @author Mark Pollak
+ * @author Oliver Gierke
+ * @author Christoph Strobl
+ */
public class FindAndModifyOptions {
boolean returnNew;
-
boolean upsert;
-
boolean remove;
+ private Collation collation;
+
/**
* Static factory method to create a FindAndModifyOptions instance
*
@@ -32,6 +39,27 @@ public class FindAndModifyOptions {
return new FindAndModifyOptions();
}
+ /**
+ * @param options
+ * @return
+ * @since 2.0
+ */
+ public static FindAndModifyOptions of(FindAndModifyOptions source) {
+
+
+ FindAndModifyOptions options = new FindAndModifyOptions();
+ if(source == null) {
+ return options;
+ }
+
+ options.returnNew = source.returnNew;
+ options.upsert = source.upsert;
+ options.remove = source.remove;
+ options.collation = source.collation;
+
+ return options;
+ }
+
public FindAndModifyOptions returnNew(boolean returnNew) {
this.returnNew = returnNew;
return this;
@@ -47,6 +75,19 @@ public class FindAndModifyOptions {
return this;
}
+ /**
+ * Define the {@link Collation} specifying language-specific rules for string comparison.
+ *
+ * @param collation
+ * @return
+ * @since 2.0
+ */
+ public FindAndModifyOptions collation(Collation collation) {
+
+ this.collation = collation;
+ return this;
+ }
+
public boolean isReturnNew() {
return returnNew;
}
@@ -59,4 +100,14 @@ public class FindAndModifyOptions {
return remove;
}
+ /**
+ * Get the {@link Collation} specifying language-specific rules for string comparison.
+ *
+ * @return
+ * @since 2.0
+ */
+ public Optional getCollation() {
+ return Optional.ofNullable(collation);
+ }
+
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java
index 488546404..2dbaa3fc6 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java
@@ -16,21 +16,19 @@
package org.springframework.data.mongodb.core;
-import static org.springframework.data.domain.Sort.Direction.*;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
import java.util.concurrent.TimeUnit;
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.core.index.IndexDefinition;
-import org.springframework.data.mongodb.core.index.IndexField;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.util.ObjectUtils;
+import com.mongodb.client.model.Collation;
+import com.mongodb.client.model.CollationAlternate;
+import com.mongodb.client.model.CollationCaseFirst;
+import com.mongodb.client.model.CollationMaxVariable;
+import com.mongodb.client.model.CollationStrength;
import com.mongodb.client.model.IndexOptions;
/**
@@ -45,10 +43,6 @@ abstract class IndexConverters {
private static final Converter DEFINITION_TO_MONGO_INDEX_OPTIONS;
private static final Converter DOCUMENT_INDEX_INFO;
- private static final Double ONE = Double.valueOf(1);
- private static final Double MINUS_ONE = Double.valueOf(-1);
- private static final Collection TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere");
-
static {
DEFINITION_TO_MONGO_INDEX_OPTIONS = getIndexDefinitionIndexOptionsConverter();
@@ -117,18 +111,57 @@ abstract class IndexConverters {
}
}
- if(indexOptions.containsKey("partialFilterExpression")) {
- ops = ops.partialFilterExpression((org.bson.Document)indexOptions.get("partialFilterExpression"));
+ if (indexOptions.containsKey("partialFilterExpression")) {
+ ops = ops.partialFilterExpression((org.bson.Document) indexOptions.get("partialFilterExpression"));
+ }
+
+ if (indexOptions.containsKey("collation")) {
+ ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class)));
}
return ops;
};
}
- private static Converter getDocumentIndexInfoConverter() {
+ public static Collation fromDocument(Document source) {
- return ix -> {
- return IndexInfo.indexInfoOf(ix);
- };
+ if (source == null) {
+ return null;
+ }
+
+ com.mongodb.client.model.Collation.Builder collationBuilder = Collation.builder();
+
+ collationBuilder.locale(source.getString("locale"));
+ if (source.containsKey("caseLevel")) {
+ collationBuilder.caseLevel(source.getBoolean("caseLevel"));
+ }
+ if (source.containsKey("caseFirst")) {
+ collationBuilder.collationCaseFirst(CollationCaseFirst.fromString(source.getString("caseFirst")));
+ }
+ if (source.containsKey("strength")) {
+ collationBuilder.collationStrength(CollationStrength.fromInt(source.getInteger("strength")));
+ }
+ if (source.containsKey("numericOrdering")) {
+ collationBuilder.numericOrdering(source.getBoolean("numericOrdering"));
+ }
+ if (source.containsKey("alternate")) {
+ collationBuilder.collationAlternate(CollationAlternate.fromString(source.getString("alternate")));
+ }
+ if (source.containsKey("maxVariable")) {
+ collationBuilder.collationMaxVariable(CollationMaxVariable.fromString(source.getString("maxVariable")));
+ }
+ if (source.containsKey("backwards")) {
+ collationBuilder.backwards(source.getBoolean("backwards"));
+ }
+ if (source.containsKey("normalization")) {
+ collationBuilder.normalization(source.getBoolean("normalization"));
+ }
+
+ return collationBuilder.build();
}
+
+ private static Converter getDocumentIndexInfoConverter() {
+ return ix -> IndexInfo.indexInfoOf(ix);
+ }
+
}
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 5e36f8658..d039a3286 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
@@ -20,8 +20,19 @@ import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import static org.springframework.data.util.Optionals.*;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bson.Document;
@@ -118,6 +129,7 @@ import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CreateCollectionOptions;
+import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
@@ -347,12 +359,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity);
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
- FindIterable cursor = collection.find(mappedQuery).projection(mappedFields);
- QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType);
+ FindIterable cursor = new QueryCursorPreparer(query, entityType)
+ .prepare(collection.find(mappedQuery).projection(mappedFields));
- ReadDocumentCallback readCallback = new ReadDocumentCallback(mongoConverter, entityType, collectionName);
-
- return new CloseableIterableCursorAdapter(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
+ return new CloseableIterableCursorAdapter(cursor, exceptionTranslator,
+ new ReadDocumentCallback(mongoConverter, entityType, collectionName));
}
});
}
@@ -563,6 +574,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
public T findOne(Query query, Class entityClass, String collectionName) {
+
if (query.getSortObject() == null) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
} else {
@@ -587,7 +599,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
- return execute(collectionName, new FindCallback(mappedQuery)).iterator().hasNext();
+ FindIterable iterable = execute(collectionName, new FindCallback(mappedQuery));
+
+ if (query.getCollation().isPresent()) {
+ iterable = iterable
+ .collation(query.getCollation().map(org.springframework.data.mongodb.core.Collation::toMongoCollation).get());
+ }
+
+ return iterable.iterator().hasNext();
}
// Find methods that take a Query to express the query and that return a List of objects.
@@ -698,8 +717,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
String collectionName) {
+
+ FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
+
+ Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
+ throw new IllegalArgumentException(
+ "Both Query and FindAndModifyOptions define the collation. Please provide the collation only via one of the two.");
+ });
+
+ query.getCollation().ifPresent(optionsToUse::collation);
+
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
- getMappedSortObject(query, entityClass), entityClass, update, options);
+ getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
}
// Find methods that take a Query to express the query and that return a single object that is also removed from the
@@ -712,7 +741,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public T findAndRemove(Query query, Class entityClass, String collectionName) {
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
- getMappedSortObject(query, entityClass), entityClass);
+ getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
}
public long count(Query query, Class> entityClass) {
@@ -1151,8 +1180,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
increaseVersionForUpdateIfNecessary(entity, update);
- Document queryObj = query == null ? new Document()
- : queryMapper.getMappedObject(query.getQueryObject(), entity);
+ UpdateOptions opts = new UpdateOptions();
+ opts.upsert(upsert);
+
+ Document queryObj = new Document();
+
+ if (query != null) {
+
+ queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
+ query.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
+ }
+
Document updateObj = update == null ? new Document()
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
@@ -1169,9 +1207,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
entityClass, updateObj, queryObj);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
- UpdateOptions opts = new UpdateOptions();
- opts.upsert(upsert);
-
collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
if (!UpdateMapper.isUpdateObject(updateObj)) {
@@ -1331,6 +1366,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
final Optional extends MongoPersistentEntity>> entity = getPersistentEntity(entityClass);
return execute(collectionName, new CollectionCallback() {
+
public DeleteResult doInCollection(MongoCollection collection)
throws MongoException, DataAccessException {
@@ -1338,8 +1374,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document mappedQuery = queryMapper.getMappedObject(queryObject, entity);
+ DeleteOptions options = new DeleteOptions();
+ query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
+
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName,
entityClass, null, queryObject);
+
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
DeleteResult dr = null;
@@ -1349,9 +1389,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
if (writeConcernToUse == null) {
- dr = collection.deleteMany(mappedQuery);
+
+ dr = collection.deleteMany(mappedQuery, options);
} else {
- dr = collection.withWriteConcern(writeConcernToUse).deleteMany(mappedQuery);
+ dr = collection.withWriteConcern(writeConcernToUse).deleteMany(mappedQuery, options);
}
maybeEmitEvent(new AfterDeleteEvent(queryObject, entityClass, collectionName));
@@ -1366,7 +1407,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
public List findAll(Class entityClass, String collectionName) {
- return executeFindMultiInternal(new FindCallback(null), null,
+ return executeFindMultiInternal(new FindCallback(null, null), null,
new ReadDocumentCallback(mongoConverter, entityClass, collectionName), collectionName);
}
@@ -1411,26 +1452,40 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
result = result.filter(queryMapper.getMappedObject(query.getQueryObject(), Optional.empty()));
}
+ Optional collation = query != null ? query.getCollation() : Optional.empty();
+
if (mapReduceOptions != null) {
+ Optionals.ifAllPresent(collation, mapReduceOptions.getCollation(), (l, r) -> {
+ throw new IllegalArgumentException(
+ "Both Query and MapReduceOptions define the collation. Please provide the collation only via one of the two.");
+ });
+
+ if (mapReduceOptions.getCollation().isPresent()) {
+ collation = mapReduceOptions.getCollation();
+ }
+
if (!CollectionUtils.isEmpty(mapReduceOptions.getScopeVariables())) {
- Document vars = new Document();
- vars.putAll(mapReduceOptions.getScopeVariables());
- result = result.scope(vars);
+ result = result.scope(new Document(mapReduceOptions.getScopeVariables()));
}
if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) {
result = result.limit(mapReduceOptions.getLimit());
}
- if (StringUtils.hasText(mapReduceOptions.getFinalizeFunction())) {
- result = result.finalizeFunction(mapReduceOptions.getFinalizeFunction());
+ if (mapReduceOptions.getFinalizeFunction().filter(StringUtils::hasText).isPresent()) {
+ result = result.finalizeFunction(mapReduceOptions.getFinalizeFunction().get());
}
if (mapReduceOptions.getJavaScriptMode() != null) {
result = result.jsMode(mapReduceOptions.getJavaScriptMode());
}
- if (mapReduceOptions.getOutputSharded() != null) {
- result = result.sharded(mapReduceOptions.getOutputSharded());
+ if (mapReduceOptions.getOutputSharded().isPresent()) {
+ result = result.sharded(mapReduceOptions.getOutputSharded().get());
}
}
+
+ if (collation.isPresent()) {
+ result = result.collation(collation.map(Collation::toMongoCollation).get());
+ }
+
List mappedResults = new ArrayList();
DocumentCallback callback = new ReadDocumentCallback(mongoConverter, entityClass, inputCollectionName);
@@ -1709,7 +1764,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Integer cursorBatchSize = options.getCursorBatchSize();
if (cursorBatchSize != null) {
- cursor.batchSize(cursorBatchSize);
+ cursor = cursor.batchSize(cursorBatchSize);
+ }
+
+ if (options.getCollation().isPresent()) {
+ cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
}
return new CloseableIterableCursorAdapter(cursor.iterator(), exceptionTranslator, readCallback);
@@ -1806,9 +1865,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
co.maxDocuments(((Number) collectionOptions.get("max")).longValue());
}
+ if (collectionOptions.containsKey("collation")) {
+ co.collation(IndexConverters.fromDocument(collectionOptions.get("collation", Document.class)));
+ }
+
db.createCollection(collectionName, co);
MongoCollection coll = db.getCollection(collectionName, Document.class);
+
// TODO: Emit a collection created event
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created collection [{}]", coll.getNamespace().getCollectionName());
@@ -1895,6 +1959,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
protected Document convertToDocument(CollectionOptions collectionOptions) {
+
Document document = new Document();
if (collectionOptions != null) {
if (collectionOptions.getCapped() != null) {
@@ -1906,6 +1971,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (collectionOptions.getMaxDocuments() != null) {
document.put("max", collectionOptions.getMaxDocuments().intValue());
}
+
+ collectionOptions.getCollation().ifPresent(val -> document.append("collation", val.toDocument()));
}
return document;
}
@@ -1922,7 +1989,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @return the List of converted objects.
*/
protected T doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
- Class entityClass) {
+ Collation collation, Class entityClass) {
EntityReader super T, Bson> readerToUse = this.mongoConverter;
@@ -1933,7 +2000,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Optional extends MongoPersistentEntity>> entity = mappingContext.getPersistentEntity(entityClass);
- return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort),
+ return executeFindOneInternal(
+ new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
new ReadDocumentCallback(readerToUse, entityClass, collectionName), collectionName);
}
@@ -2210,31 +2278,33 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*
* @author Oliver Gierke
* @author Thomas Risberg
+ * @author Christoph Strobl
*/
private static class FindOneCallback implements CollectionCallback {
private final Document query;
- private final Document fields;
+ private final Optional fields;
public FindOneCallback(Document query, Document fields) {
this.query = query;
- this.fields = fields;
+ this.fields = Optional.ofNullable(fields);
}
public Document doInCollection(MongoCollection collection) throws MongoException, DataAccessException {
- if (fields == null) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("findOne using query: {} in db.collection: {}", serializeToJsonSafely(query),
- collection.getNamespace().getFullName());
- }
- return collection.find(query).first();
- } else {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query), fields,
- collection.getNamespace().getFullName());
- }
- return collection.find(query).projection(fields).first();
+
+ FindIterable iterable = collection.find(query);
+
+ if (LOGGER.isDebugEnabled()) {
+
+ LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
+ serializeToJsonSafely(fields.orElseGet(() -> new Document())), collection.getNamespace().getFullName());
}
+
+ if (fields.isPresent()) {
+ iterable = iterable.projection(fields.get());
+ }
+
+ return iterable.first();
}
}
@@ -2244,29 +2314,33 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*
* @author Oliver Gierke
* @author Thomas Risberg
+ * @author Christoph Strobl
*/
private static class FindCallback implements CollectionCallback> {
private final Document query;
- private final Document fields;
+ private final Optional fields;
public FindCallback(Document query) {
this(query, null);
}
public FindCallback(Document query, Document fields) {
- this.query = query == null ? new Document() : query;
- this.fields = fields;
+
+ this.query = query != null ? query : new Document();
+ this.fields = Optional.ofNullable(fields);
}
public FindIterable doInCollection(MongoCollection collection)
throws MongoException, DataAccessException {
- if (fields == null || fields.isEmpty()) {
- return collection.find(query);
- } else {
- return collection.find(query).projection(fields);
+ FindIterable iterable = collection.find(query);
+
+ if (fields.filter(val -> !val.isEmpty()).isPresent()) {
+ iterable = iterable.projection(fields.get());
}
+
+ return iterable;
}
}
@@ -2281,18 +2355,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final Document query;
private final Document fields;
private final Document sort;
+ private final Optional collation;
+
+ public FindAndRemoveCallback(Document query, Document fields, Document sort, Collation collation) {
- public FindAndRemoveCallback(Document query, Document fields, Document sort) {
this.query = query;
this.fields = fields;
this.sort = sort;
+ this.collation = Optional.ofNullable(collation);
}
public Document doInCollection(MongoCollection collection) throws MongoException, DataAccessException {
- FindOneAndDeleteOptions opts = new FindOneAndDeleteOptions();
- opts.sort(sort);
- opts.projection(fields);
+ FindOneAndDeleteOptions opts = new FindOneAndDeleteOptions().sort(sort).projection(fields);
+ collation.map(Collation::toMongoCollation).ifPresent(opts::collation);
return collection.findOneAndDelete(query, opts);
}
@@ -2326,9 +2402,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (options.returnNew) {
opts.returnDocument(ReturnDocument.AFTER);
}
+
+ options.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
+
return collection.findOneAndUpdate(query, update, opts);
}
-
}
/**
@@ -2435,6 +2513,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
FindIterable cursorToUse = cursor;
+ if (query.getCollation().isPresent()) {
+ cursorToUse = cursorToUse.collation(query.getCollation().map(val -> val.toMongoCollation()).get());
+ }
try {
if (query.getSkip() > 0) {
cursorToUse = cursorToUse.skip((int) query.getSkip());
@@ -2442,7 +2523,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (query.getLimit() > 0) {
cursorToUse = cursorToUse.limit(query.getLimit());
}
- if (query.getSortObject() != null) {
+ if (query.getSortObject() != null && !query.getSortObject().isEmpty()) {
Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
cursorToUse = cursorToUse.sort(sort);
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
index 4aaec24b6..0e21292c5 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
@@ -18,6 +18,10 @@ package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.util.function.Tuple2;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -88,6 +92,7 @@ import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.util.MongoClientVersion;
+import org.springframework.data.util.Optionals;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -116,10 +121,6 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
import com.mongodb.reactivestreams.client.Success;
import com.mongodb.util.JSONParseException;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-import reactor.util.function.Tuple2;
-
/**
* Primary implementation of {@link ReactiveMongoOperations}. It simplifies the use of Reactive MongoDB usage and helps
* to avoid common errors. It executes core MongoDB workflow, leaving application code to provide {@link Document} and
@@ -336,11 +337,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#executeCommand(org.bson.Document)
*/
public Mono executeCommand(final Document command) {
-
- Assert.notNull(command, "Command must not be null!");
-
- return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference) : db.runCommand(command))
- .next();
+ return executeCommand(command, null);
}
/* (non-Javadoc)
@@ -350,8 +347,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Assert.notNull(command, "Command must not be null!");
- return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference) : db.runCommand(command))
- .next();
+ return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference, Document.class)
+ : db.runCommand(command, Document.class)).next();
}
/* (non-Javadoc)
@@ -541,8 +538,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public Mono findOne(Query query, Class entityClass, String collectionName) {
- if (query.getSortObject() == null) {
- return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
+ if (ObjectUtils.isEmpty(query.getSortObject())) {
+ return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
+ query.getCollation().orElse(null));
}
query.limit(1);
@@ -575,7 +573,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return createFlux(collectionName, collection -> {
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
- return collection.find(mappedQuery).limit(1);
+ FindPublisher findPublisher = collection.find(mappedQuery).projection(new Document("_id", 1));
+
+ if (query.getCollation().isPresent()) {
+ findPublisher = findPublisher.collation(query.getCollation().map(Collation::toMongoCollation).get());
+ }
+
+ return findPublisher.limit(1);
}).hasElements();
}
@@ -612,11 +616,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public Mono findById(Object id, Class entityClass, String collectionName) {
Optional extends MongoPersistentEntity>> persistentEntity = mappingContext.getPersistentEntity(entityClass);
- MongoPersistentProperty idProperty = persistentEntity.isPresent() ? persistentEntity.get().getIdProperty().orElse(null) : null;
+ MongoPersistentProperty idProperty = persistentEntity.isPresent()
+ ? persistentEntity.get().getIdProperty().orElse(null) : null;
String idKey = idProperty == null ? ID_FIELD : idProperty.getName();
- return doFindOne(collectionName, new Document(idKey, id), null, entityClass);
+ return doFindOne(collectionName, new Document(idKey, id), null, entityClass, null);
}
/*
@@ -672,12 +677,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return Flux.empty();
}
return Flux.fromIterable(l);
- }).skip(near.getSkip() != null ? near.getSkip() : 0).map(new Function>() {
- @Override
- public GeoResult apply(Document object) {
- return callback.doWith(object);
- }
- });
+ }).skip(near.getSkip() != null ? near.getSkip() : 0).map(callback::doWith);
});
}
@@ -707,8 +707,18 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public Mono findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
String collectionName) {
+
+ FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
+
+ Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
+ throw new IllegalArgumentException(
+ "Both Query and FindAndModifyOptions define the collation. Please provide the collation only via one of the two.");
+ });
+
+ query.getCollation().ifPresent(optionsToUse::collation);
+
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
- getMappedSortObject(query, entityClass), entityClass, update, options);
+ getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
}
/* (non-Javadoc)
@@ -724,7 +734,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public Mono findAndRemove(Query query, Class entityClass, String collectionName) {
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
- getMappedSortObject(query, entityClass), entityClass);
+ getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
}
/* (non-Javadoc)
@@ -962,8 +972,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor(
entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService());
- MongoPersistentProperty idProperty = entity.getIdProperty().orElseThrow(() -> new IllegalArgumentException("No id property present!"));
- MongoPersistentProperty versionProperty = entity.getVersionProperty().orElseThrow(() -> new IllegalArgumentException("No version property present!"));;
+ MongoPersistentProperty idProperty = entity.getIdProperty()
+ .orElseThrow(() -> new IllegalArgumentException("No id property present!"));
+ MongoPersistentProperty versionProperty = entity.getVersionProperty()
+ .orElseThrow(() -> new IllegalArgumentException("No version property present!"));
+ ;
Optional