diff --git a/pom.xml b/pom.xml
index eea61f492c..fb54e3fb96 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.dataspring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1518-SNAPSHOTpomSpring Data MongoDB
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index 4a49168713..a56830737e 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
org.springframework.dataspring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1518-SNAPSHOT../pom.xml
@@ -48,7 +48,7 @@
org.springframework.dataspring-data-mongodb
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1518-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 750ed23aa8..47a73cef5f 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -13,7 +13,7 @@
org.springframework.dataspring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1518-SNAPSHOT../pom.xml
diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml
index 50d0a6454a..fb85f2c5f7 100644
--- a/spring-data-mongodb-log4j/pom.xml
+++ b/spring-data-mongodb-log4j/pom.xml
@@ -5,7 +5,7 @@
org.springframework.dataspring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1518-SNAPSHOT../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 886bcca7b6..049523bcd6 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.dataspring-data-mongodb-parent
- 2.0.0.BUILD-SNAPSHOT
+ 2.0.0.DATAMONGO-1518-SNAPSHOT../pom.xml
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 0000000000..a0b7d9e51d
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/Collation.java
@@ -0,0 +1,835 @@
+/*
+ * 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 lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+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 org.springframework.util.StringUtils;
+
+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
+ * @author Mark Paluch
+ * @since 2.0
+ * @see MongoDB Reference - Collation
+ */
+public class Collation {
+
+ private static final Collation SIMPLE = of("simple");
+
+ private final CollationLocale 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(CollationLocale locale) {
+
+ Assert.notNull(locale, "ICULocale must not be null!");
+ this.locale = locale;
+ }
+
+ /**
+ * Create a {@link Collation} using {@literal simple} binary comparison.
+ *
+ * @return a {@link Collation} for {@literal simple} binary comparison.
+ */
+ public static Collation simple() {
+ return SIMPLE;
+ }
+
+ /**
+ * 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!");
+
+ String format;
+
+ if (StringUtils.hasText(locale.getCountry())) {
+ format = String.format("%s_%s", locale.getLanguage(), locale.getCountry());
+ } else {
+ format = locale.getLanguage();
+ }
+
+ return of(CollationLocale.of(format).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(CollationLocale.of(language));
+ }
+
+ /**
+ * Create new {@link Collation} with locale set to the given {@link CollationLocale}.
+ *
+ * @param locale must not be {@literal null}.
+ * @return
+ */
+ public static Collation of(CollationLocale 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
+ * @return new {@link Collation}.
+ */
+ public Collation strength(int strength) {
+
+ ComparisonLevel current = this.strength.orElseGet(() -> new ICUComparisonLevel(strength));
+ return strength(new ICUComparisonLevel(strength, current.getCaseFirst(), current.getCaseLevel()));
+ }
+
+ /**
+ * Set the level of comparison to perform.
+ *
+ * @param comparisonLevel must not be {@literal null}.
+ * @return new {@link Collation}
+ */
+ public Collation strength(ComparisonLevel comparisonLevel) {
+
+ Collation newInstance = copy();
+ newInstance.strength = Optional.of(comparisonLevel);
+ return newInstance;
+ }
+
+ /**
+ * Set whether to include {@code caseLevel} comparison.
+ *
+ * @param caseLevel
+ * @return new {@link Collation}.
+ */
+ public Collation caseLevel(boolean caseLevel) {
+
+ ComparisonLevel strengthValue = strength.orElseGet(ComparisonLevel::primary);
+ return strength(
+ new ICUComparisonLevel(strengthValue.getLevel(), strengthValue.getCaseFirst(), Optional.of(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 CaseFirst(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(CaseFirst sort) {
+
+ ComparisonLevel strengthValue = strength.orElseGet(ComparisonLevel::tertiary);
+ return strength(new ICUComparisonLevel(strengthValue.getLevel(), Optional.of(sort), strengthValue.getCaseLevel()));
+ }
+
+ /**
+ * 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.of(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, Optional.empty()));
+ return alternate(new Alternate(alternate, instance.maxVariable));
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * Transform {@code this} {@link Collation} by applying a {@link Converter}.
+ *
+ * @param mapper
+ * @param
+ * @return
+ */
+ 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 interface ComparisonLevel {
+
+ /**
+ * 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 PrimaryICUComparisonLevel#includeCase()} and
+ * {@link PrimaryICUComparisonLevel#excludeCase()}.
+ *
+ * @return new {@link SecondaryICUComparisonLevel}.
+ */
+ static PrimaryICUComparisonLevel primary() {
+ return PrimaryICUComparisonLevel.DEFAULT;
+ }
+
+ /**
+ * Secondary level of comparison. Collation performs comparisons up to secondary differences, such as
+ * diacritics.
+ * The {@code caseLevel} can be set via {@link SecondaryICUComparisonLevel#includeCase()} and
+ * {@link SecondaryICUComparisonLevel#excludeCase()}.
+ *
+ * @return new {@link SecondaryICUComparisonLevel}.
+ */
+ static SecondaryICUComparisonLevel secondary() {
+ return SecondaryICUComparisonLevel.DEFAULT;
+ }
+
+ /**
+ * 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}.
+ */
+ static TertiaryICUComparisonLevel tertiary() {
+ return TertiaryICUComparisonLevel.DEFAULT;
+ }
+
+ /**
+ * 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 ComparisonLevel}.
+ */
+ static ComparisonLevel quaternary() {
+ return ComparisonLevels.QUATERNARY;
+ }
+
+ /**
+ * 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 ComparisonLevel}.
+ */
+ static ComparisonLevel identical() {
+ return ComparisonLevels.IDENTICAL;
+ }
+
+ /**
+ * @return collation strength, {@literal 1} for primary, {@literal 2} for secondary and so on.
+ */
+ int getLevel();
+
+ default Optional getCaseFirst() {
+ return Optional.empty();
+ }
+
+ default Optional getCaseLevel() {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Abstraction for the ICU Comparison Levels.
+ *
+ * @since 2.0
+ */
+ @AllArgsConstructor(access = AccessLevel.PACKAGE)
+ @Getter
+ static class ICUComparisonLevel implements ComparisonLevel {
+
+ private final int level;
+ private final Optional caseFirst;
+ private final Optional caseLevel;
+
+ ICUComparisonLevel(int level) {
+ this(level, Optional.empty(), Optional.empty());
+ }
+ }
+
+ /**
+ * Simple comparison levels.
+ */
+ enum ComparisonLevels implements ComparisonLevel {
+
+ QUATERNARY(4), IDENTICAL(5);
+
+ private final int level;
+
+ ComparisonLevels(int level) {
+ this.level = level;
+ }
+
+ @Override
+ public int getLevel() {
+ return level;
+ }
+ }
+
+ /**
+ * Primary-strength {@link ICUComparisonLevel}.
+ */
+ public static class PrimaryICUComparisonLevel extends ICUComparisonLevel {
+
+ static final PrimaryICUComparisonLevel DEFAULT = new PrimaryICUComparisonLevel();
+ static final PrimaryICUComparisonLevel WITH_CASE_LEVEL = new PrimaryICUComparisonLevel(true);
+ static final PrimaryICUComparisonLevel WITHOUT_CASE_LEVEL = new PrimaryICUComparisonLevel(false);
+
+ private PrimaryICUComparisonLevel() {
+ super(1);
+ }
+
+ private PrimaryICUComparisonLevel(boolean caseLevel) {
+ super(1, Optional.empty(), Optional.of(caseLevel));
+ }
+
+ /**
+ * Include case comparison.
+ *
+ * @return new {@link ICUComparisonLevel}
+ */
+ public ComparisonLevel includeCase() {
+ return WITH_CASE_LEVEL;
+ }
+
+ /**
+ * Exclude case comparison.
+ *
+ * @return new {@link ICUComparisonLevel}
+ */
+ public ComparisonLevel excludeCase() {
+ return WITHOUT_CASE_LEVEL;
+ }
+ }
+
+ /**
+ * Secondary-strength {@link ICUComparisonLevel}.
+ */
+ public static class SecondaryICUComparisonLevel extends ICUComparisonLevel {
+
+ static final SecondaryICUComparisonLevel DEFAULT = new SecondaryICUComparisonLevel();
+ static final SecondaryICUComparisonLevel WITH_CASE_LEVEL = new SecondaryICUComparisonLevel(true);
+ static final SecondaryICUComparisonLevel WITHOUT_CASE_LEVEL = new SecondaryICUComparisonLevel(false);
+
+ private SecondaryICUComparisonLevel() {
+ super(2);
+ }
+
+ private SecondaryICUComparisonLevel(boolean caseLevel) {
+ super(2, Optional.empty(), Optional.of(caseLevel));
+ }
+
+ /**
+ * Include case comparison.
+ *
+ * @return new {@link SecondaryICUComparisonLevel}
+ */
+ public ComparisonLevel includeCase() {
+ return WITH_CASE_LEVEL;
+ }
+
+ /**
+ * Exclude case comparison.
+ *
+ * @return new {@link SecondaryICUComparisonLevel}
+ */
+ public ComparisonLevel excludeCase() {
+ return WITHOUT_CASE_LEVEL;
+ }
+ }
+
+ /**
+ * Tertiary-strength {@link ICUComparisonLevel}.
+ */
+ public static class TertiaryICUComparisonLevel extends ICUComparisonLevel {
+
+ static final TertiaryICUComparisonLevel DEFAULT = new TertiaryICUComparisonLevel();
+
+ private TertiaryICUComparisonLevel() {
+ super(3);
+ }
+
+ private TertiaryICUComparisonLevel(CaseFirst caseFirst) {
+ super(3, Optional.of(caseFirst), Optional.empty());
+ }
+
+ /**
+ * Set the flag that determines sort order of case differences.
+ *
+ * @param caseFirst must not be {@literal null}.
+ * @return new {@link ICUComparisonLevel}
+ */
+ public ComparisonLevel caseFirst(CaseFirst caseFirst) {
+
+ Assert.notNull(caseFirst, "CaseFirst must not be null!");
+ return new TertiaryICUComparisonLevel(caseFirst);
+ }
+ }
+
+ /**
+ * @since 2.0
+ */
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+ public static class CaseFirst {
+
+ private static final CaseFirst UPPER = new CaseFirst("upper");
+ private static final CaseFirst LOWER = new CaseFirst("lower");
+ private static final CaseFirst OFF = new CaseFirst("off");
+
+ private final String state;
+
+ /**
+ * Sort uppercase before lowercase.
+ *
+ * @return new {@link CaseFirst}.
+ */
+ public static CaseFirst upper() {
+ return UPPER;
+ }
+
+ /**
+ * Sort lowercase before uppercase.
+ *
+ * @return new {@link CaseFirst}.
+ */
+ public static CaseFirst lower() {
+ return LOWER;
+ }
+
+ /**
+ * Use the default.
+ *
+ * @return new {@link CaseFirst}.
+ */
+ public static CaseFirst off() {
+ return OFF;
+ }
+ }
+
+ /**
+ * @since 2.0
+ */
+ @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+ public static class Alternate {
+
+ private static final Alternate NON_IGNORABLE = new Alternate("non-ignorable", Optional.empty());
+
+ final String alternate;
+ final Optional maxVariable;
+
+ /**
+ * Consider Whitespace and punctuation as base characters.
+ *
+ * @return new {@link Alternate}.
+ */
+ public static Alternate nonIgnorable() {
+ return NON_IGNORABLE;
+ }
+
+ /**
+ * Whitespace and punctuation are not considered base characters and are only distinguished at
+ * strength.
+ * NOTE: Only works for {@link ICUComparisonLevel} above {@link ComparisonLevel#tertiary()}.
+ *
+ * @return new {@link AlternateWithMaxVariable}.
+ */
+ public static AlternateWithMaxVariable shifted() {
+ return AlternateWithMaxVariable.DEFAULT;
+ }
+ }
+
+ /**
+ * @since 2.0
+ */
+ public static class AlternateWithMaxVariable extends Alternate {
+
+ static final AlternateWithMaxVariable DEFAULT = new AlternateWithMaxVariable("shifted");
+ static final Alternate SHIFTED_PUNCT = new AlternateWithMaxVariable("shifted", "punct");
+ static final Alternate SHIFTED_SPACE = new AlternateWithMaxVariable("shifted", "space");
+
+ private AlternateWithMaxVariable(String alternate) {
+ super(alternate, Optional.empty());
+ }
+
+ private AlternateWithMaxVariable(String alternate, String maxVariable) {
+ super(alternate, Optional.of(maxVariable));
+ }
+
+ /**
+ * Consider both whitespaces and punctuation as ignorable.
+ *
+ * @return new {@link AlternateWithMaxVariable}.
+ */
+ public Alternate punct() {
+ return SHIFTED_PUNCT;
+ }
+
+ /**
+ * Only consider whitespaces as ignorable.
+ *
+ * @return new {@link AlternateWithMaxVariable}.
+ */
+ public Alternate space() {
+ return SHIFTED_SPACE;
+ }
+ }
+
+ /**
+ * ICU locale abstraction for usage with MongoDB {@link Collation}.
+ *
+ * @since 2.0
+ * @see ICU - International Components for Unicode
+ */
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+ public static class CollationLocale {
+
+ private final String language;
+ private final Optional variant;
+
+ /**
+ * Create new {@link CollationLocale} for given language.
+ *
+ * @param language must not be {@literal null}.
+ * @return
+ */
+ public static CollationLocale of(String language) {
+
+ Assert.notNull(language, "Code must not be null!");
+ return new CollationLocale(language, Optional.empty());
+ }
+
+ /**
+ * Define language variant.
+ *
+ * @param variant must not be {@literal null}.
+ * @return new {@link CollationLocale}.
+ */
+ public CollationLocale variant(String variant) {
+
+ Assert.notNull(variant, "Variant must not be null!");
+ return new CollationLocale(language, Optional.of(variant));
+ }
+
+ /**
+ * Get the string representation.
+ *
+ * @return
+ */
+ public String asString() {
+
+ StringBuilder sb = new StringBuilder(language);
+
+ variant.filter(it -> !it.isEmpty()).ifPresent(val -> {
+
+ // Mongo requires variant rendered as ICU keyword (@key=value;key=value…)
+ 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(strength -> {
+
+ document.append("strength", strength.getLevel());
+
+ strength.getCaseLevel().ifPresent(it -> document.append("caseLevel", it));
+ strength.getCaseFirst().ifPresent(it -> document.append("caseFirst", it.state));
+ });
+
+ source.numericOrdering.ifPresent(val -> document.append("numericOrdering", val));
+ source.alternate.ifPresent(it -> {
+
+ document.append("alternate", it.alternate);
+ it.maxVariable.ifPresent(maxVariable -> document.append("maxVariable", maxVariable));
+ });
+
+ source.backwards.ifPresent(it -> document.append("backwards", it));
+ source.normalization.ifPresent(it -> document.append("normalization", it));
+ source.version.ifPresent(it -> document.append("version", it));
+
+ return document;
+ };
+ }
+
+ private static Converter toMongoCollationConverter() {
+
+ return source -> {
+
+ Builder builder = com.mongodb.client.model.Collation.builder();
+
+ builder.locale(source.locale.asString());
+
+ source.strength.ifPresent(strength -> {
+
+ builder.collationStrength(CollationStrength.fromInt(strength.getLevel()));
+
+ strength.getCaseLevel().ifPresent(builder::caseLevel);
+ strength.getCaseFirst().ifPresent(it -> builder.collationCaseFirst(CollationCaseFirst.fromString(it.state)));
+ });
+
+ source.numericOrdering.ifPresent(builder::numericOrdering);
+ source.alternate.ifPresent(it -> {
+
+ builder.collationAlternate(CollationAlternate.fromString(it.alternate));
+ it.maxVariable
+ .ifPresent(maxVariable -> builder.collationMaxVariable(CollationMaxVariable.fromString(maxVariable)));
+ });
+
+ source.backwards.ifPresent(builder::backwards);
+ source.normalization.ifPresent(builder::normalization);
+
+ 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 756e2863e4..469fc1c947 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,32 +15,114 @@
*/
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
+ * @author Mark Paluch
*/
public class CollectionOptions {
private Integer maxDocuments;
-
private Integer size;
-
private Boolean capped;
+ private Optional collation;
/**
* Constructs a new CollectionOptions instance.
- *
- * @param size the collection size in bytes, this data space is preallocated
+ *
+ * @param size the collection size in bytes, this data space is preallocated.
* @param maxDocuments the maximum number of documents in the collection.
* @param capped true to created a "capped" collection (fixed size with auto-FIFO behavior based on insertion order),
* false otherwise.
*/
public CollectionOptions(Integer size, Integer maxDocuments, Boolean capped) {
- super();
+ this(size, maxDocuments, capped, Optional.empty());
+ }
+
+ private CollectionOptions(Integer size, Integer maxDocuments, Boolean capped, Optional collation) {
+
this.maxDocuments = maxDocuments;
this.size = size;
this.capped = capped;
+ this.collation = collation;
+ }
+
+ 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 empty {@link CollectionOptions}.
+ *
+ * @return new {@link CollectionOptions}.
+ * @since 2.0
+ */
+ public static CollectionOptions empty() {
+ return new CollectionOptions();
+ }
+
+ /**
+ * Create new {@link CollectionOptions} with already given settings and capped set to {@literal true}.
+ *
+ * @param size the collection size in bytes, this data space is preallocated.
+ * @return new {@link CollectionOptions}.
+ * @since 2.0
+ */
+ public CollectionOptions capped(int size) {
+ return new CollectionOptions(size, maxDocuments, true, collation);
+ }
+
+ /**
+ * 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) {
+ return new CollectionOptions(size, maxDocuments, capped, collation);
+ }
+
+ /**
+ * 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(int size) {
+ return new CollectionOptions(size, maxDocuments, capped, collation);
+ }
+
+ /**
+ * 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) {
+ return new CollectionOptions(size, maxDocuments, capped, Optional.ofNullable(collation));
}
public Integer getMaxDocuments() {
@@ -67,4 +149,23 @@ public void setCapped(Boolean capped) {
this.capped = capped;
}
+ /**
+ * Set {@link Collation} options.
+ *
+ * @param collation
+ * @since 2.0
+ */
+ public void setCollation(Collation collation) {
+ this.collation = Optional.ofNullable(collation);
+ }
+
+ /**
+ * Get the {@link Collation} settings.
+ *
+ * @return
+ * @since 2.0
+ */
+ public Optional getCollation() {
+ return 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 d06a4bff2f..fb4411b47e 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.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 @@ public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTrans
/**
* 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 @@ public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) {
/**
* 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 @@ public BulkOperations remove(Query query) {
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 @@ private BulkOperations update(Query query, Update update, boolean upsert, boolea
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 c5e88c7fd9..6a53f18fda 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,23 +15,50 @@
*/
package org.springframework.data.mongodb.core;
-public class FindAndModifyOptions {
+import java.util.Optional;
- boolean returnNew;
+/**
+ * @author Mark Pollak
+ * @author Oliver Gierke
+ * @author Christoph Strobl
+ */
+public class FindAndModifyOptions {
- boolean upsert;
+ private boolean returnNew;
+ private boolean upsert;
+ private boolean remove;
- boolean remove;
+ private Collation collation;
/**
* Static factory method to create a FindAndModifyOptions instance
- *
+ *
* @return a new instance
*/
public static FindAndModifyOptions options() {
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 +74,19 @@ public FindAndModifyOptions remove(boolean remove) {
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 +99,14 @@ public boolean isRemove() {
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 4885464040..66ddfdb0e5 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,15 @@
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.IndexOptions;
/**
@@ -45,10 +39,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 +107,29 @@ private static Converter getIndexDefinitionIndexO
}
}
- 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;
+ }
+
+ return org.springframework.data.mongodb.core.Collation.from(source).toMongoCollation();
}
+
+ private static Converter getDocumentIndexInfoConverter() {
+ return IndexInfo::indexInfoOf;
+ }
+
}
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 5e36f86581..7b418aafaf 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
@@ -49,6 +49,7 @@
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
+import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@@ -118,6 +119,7 @@
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 +349,11 @@ public CloseableIterator doInCollection(MongoCollection collection)
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 +564,7 @@ public T findOne(Query query, Class entityClass) {
}
public T findOne(Query query, Class entityClass, String collectionName) {
+
if (query.getSortObject() == null) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
} else {
@@ -587,7 +589,14 @@ public boolean exists(Query query, Class> entityClass, String collectionName)
}
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 +707,18 @@ public T findAndModify(Query query, Update update, FindAndModifyOptions opti
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 a 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 +731,7 @@ public T findAndRemove(Query query, Class entityClass) {
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) {
@@ -856,7 +875,7 @@ private void initializeVersionProperty(Object entity) {
Optional extends MongoPersistentEntity>> persistentEntity = getPersistentEntity(entity.getClass());
- ifAllPresent(persistentEntity, persistentEntity.flatMap(it -> it.getVersionProperty()), (l, r) -> {
+ ifAllPresent(persistentEntity, persistentEntity.flatMap(PersistentEntity::getVersionProperty), (l, r) -> {
ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(l.getPropertyAccessor(entity),
mongoConverter.getConversionService());
accessor.setProperty(r, Optional.of(0));
@@ -943,7 +962,7 @@ public void save(Object objectToSave, String collectionName) {
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Optional extends MongoPersistentEntity>> entity = getPersistentEntity(objectToSave.getClass());
- Optional versionProperty = entity.flatMap(it -> it.getVersionProperty());
+ Optional versionProperty = entity.flatMap(PersistentEntity::getVersionProperty);
mapIfAllPresent(entity, versionProperty, //
(l, r) -> doSaveVersioned(objectToSave, l, collectionName))//
@@ -1151,8 +1170,17 @@ public UpdateResult doInCollection(MongoCollection collection)
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 +1197,6 @@ public UpdateResult doInCollection(MongoCollection collection)
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)) {
@@ -1190,18 +1215,19 @@ public UpdateResult doInCollection(MongoCollection collection)
private void increaseVersionForUpdateIfNecessary(Optional extends MongoPersistentEntity>> persistentEntity,
Update update) {
- ifAllPresent(persistentEntity, persistentEntity.flatMap(it -> it.getVersionProperty()), (entity, property) -> {
- String versionFieldName = property.getFieldName();
- if (!update.modifies(versionFieldName)) {
- update.inc(versionFieldName, 1L);
- }
- });
+ ifAllPresent(persistentEntity, persistentEntity.flatMap(PersistentEntity::getVersionProperty),
+ (entity, property) -> {
+ String versionFieldName = property.getFieldName();
+ if (!update.modifies(versionFieldName)) {
+ update.inc(versionFieldName, 1L);
+ }
+ });
}
private boolean documentContainsVersionProperty(Document document,
Optional extends MongoPersistentEntity>> persistentEntity) {
- return mapIfAllPresent(persistentEntity, persistentEntity.flatMap(it -> it.getVersionProperty()), //
+ return mapIfAllPresent(persistentEntity, persistentEntity.flatMap(PersistentEntity::getVersionProperty), //
(entity, property) -> document.containsKey(property.getFieldName()))//
.orElse(false);
}
@@ -1331,6 +1357,7 @@ protected DeleteResult doRemove(final String collectionName, final Query que
final Optional extends MongoPersistentEntity>> entity = getPersistentEntity(entityClass);
return execute(collectionName, new CollectionCallback() {
+
public DeleteResult doInCollection(MongoCollection collection)
throws MongoException, DataAccessException {
@@ -1338,8 +1365,12 @@ public DeleteResult doInCollection(MongoCollection collection)
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 +1380,10 @@ public DeleteResult doInCollection(MongoCollection collection)
}
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 +1398,7 @@ public List findAll(Class entityClass) {
}
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 +1443,38 @@ public MapReduceResults mapReduce(Query query, String inputCollectionName
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 a 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());
}
}
+
+ result = collation.map(Collation::toMongoCollation).map(result::collation).orElse(result);
+
List mappedResults = new ArrayList();
DocumentCallback callback = new ReadDocumentCallback(mongoConverter, entityClass, inputCollectionName);
@@ -1709,7 +1753,11 @@ public CloseableIterator doInCollection(MongoCollection collection)
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 +1854,14 @@ public MongoCollection doInDB(MongoDatabase db) throws MongoException,
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 +1948,7 @@ protected List doFind(String collectionName, Document query, Document
}
protected Document convertToDocument(CollectionOptions collectionOptions) {
+
Document document = new Document();
if (collectionOptions != null) {
if (collectionOptions.getCapped() != null) {
@@ -1906,6 +1960,8 @@ protected Document convertToDocument(CollectionOptions collectionOptions) {
if (collectionOptions.getMaxDocuments() != null) {
document.put("max", collectionOptions.getMaxDocuments().intValue());
}
+
+ collectionOptions.getCollation().ifPresent(val -> document.append("collation", val.toDocument()));
}
return document;
}
@@ -1922,7 +1978,7 @@ protected Document convertToDocument(CollectionOptions collectionOptions) {
* @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 +1989,8 @@ protected T doFindAndRemove(String collectionName, Document query, Document
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 +2267,33 @@ private static List