Browse Source

DATAMONGO-1518 - Polishing.

Rename ICULocale to CollationLocale. Introduce interface for ComparisonLevel construction and let ICUComparisonLevel types implement that interface. Make value types immutable where possible. Provide static instances for default comparison level instances.

Replace collation conversion IndexConverters with Collation.from(…).toMongoCollation() converter. Introduce missing generic types. Replace Optional.get() with Optional.map(…).orElse(…).

Update reference documentation.

Original pull request: #459.
pull/460/head
Mark Paluch 9 years ago
parent
commit
42672a6df9
  1. 419
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/Collation.java
  2. 51
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java
  3. 11
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java
  4. 36
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java
  5. 63
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  6. 64
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  7. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java
  8. 10
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java
  9. 31
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CollationUnitTests.java
  10. 5
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java
  11. 5
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java
  12. 9
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateCollationTests.java
  13. 14
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
  14. 1
      src/main/asciidoc/new-features.adoc
  15. 83
      src/main/asciidoc/reference/mongodb.adoc

419
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/Collation.java

@ -15,12 +15,18 @@ @@ -15,12 +15,18 @@
*/
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;
@ -37,45 +43,36 @@ import com.mongodb.client.model.CollationStrength; @@ -37,45 +43,36 @@ import com.mongodb.client.model.CollationStrength;
* query itself specifies the same collation.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
* @see <a href="https://docs.mongodb.com/manual/reference/collation/">MongoDB Reference - Collation</a>
*/
public class Collation {
private static final Collation DEFAULT = of("simple");
private static final Collation SIMPLE = of("simple");
private final ICULocale locale;
private final CollationLocale locale;
private Optional<ICUComparisonLevel> strength = Optional.empty();
private Optional<ComparisonLevel> strength = Optional.empty();
private Optional<Boolean> numericOrdering = Optional.empty();
private Optional<Alternate> alternate = Optional.empty();
private Optional<Boolean> backwards = Optional.empty();
private Optional<Boolean> normalization = Optional.empty();
private Optional<String> version = Optional.empty();
private Collation(ICULocale locale) {
private Collation(CollationLocale locale) {
Assert.notNull(locale, "ICULocale must not be null!");
this.locale = locale;
}
/**
* Create new {@link Collation} using simple binary comparison.
* Create a {@link Collation} using {@literal simple} binary comparison.
*
* @return
* @see #binary()
* @return a {@link Collation} for {@literal simple} binary comparison.
*/
public static Collation simple() {
return binary();
}
/**
* Create new {@link Collation} using simple binary comparison.
*
* @return
*/
public static Collation binary() {
return DEFAULT;
return SIMPLE;
}
/**
@ -88,7 +85,16 @@ public class Collation { @@ -88,7 +85,16 @@ public class Collation {
public static Collation of(Locale locale) {
Assert.notNull(locale, "Locale must not be null!");
return of(ICULocale.of(locale.getLanguage()).variant(locale.getVariant()));
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()));
}
/**
@ -98,16 +104,16 @@ public class Collation { @@ -98,16 +104,16 @@ public class Collation {
* @return
*/
public static Collation of(String language) {
return of(ICULocale.of(language));
return of(CollationLocale.of(language));
}
/**
* Create new {@link Collation} with locale set to the given {@link ICULocale}.
* Create new {@link Collation} with locale set to the given {@link CollationLocale}.
*
* @param locale must not be {@literal null}.
* @return
*/
public static Collation of(ICULocale locale) {
public static Collation of(CollationLocale locale) {
return new Collation(locale);
}
@ -157,13 +163,13 @@ public class Collation { @@ -157,13 +163,13 @@ public class Collation {
/**
* Set the level of comparison to perform.
*
* @param strength must not be {@literal null}.
* @param strength
* @return new {@link Collation}.
*/
public Collation strength(Integer strength) {
public Collation strength(int strength) {
ICUComparisonLevel current = this.strength.orElseGet(() -> new ICUComparisonLevel(strength, null, null));
return strength(new ICUComparisonLevel(strength, current.caseFirst.orElse(null), current.caseLevel.orElse(null)));
ComparisonLevel current = this.strength.orElseGet(() -> new ICUComparisonLevel(strength));
return strength(new ICUComparisonLevel(strength, current.getCaseFirst(), current.getCaseLevel()));
}
/**
@ -172,23 +178,24 @@ public class Collation { @@ -172,23 +178,24 @@ public class Collation {
* @param comparisonLevel must not be {@literal null}.
* @return new {@link Collation}
*/
public Collation strength(ICUComparisonLevel comparisonLevel) {
public Collation strength(ComparisonLevel comparisonLevel) {
Collation newInstance = copy();
newInstance.strength = Optional.ofNullable(comparisonLevel);
newInstance.strength = Optional.of(comparisonLevel);
return newInstance;
}
/**
* Set {@code caseLevel} comarison. <br />
* Set whether to include {@code caseLevel} comparison. <br />
*
* @param caseLevel must not be {@literal null}.
* @param caseLevel
* @return new {@link Collation}.
*/
public Collation caseLevel(Boolean caseLevel) {
public Collation caseLevel(boolean caseLevel) {
ICUComparisonLevel strengthValue = strength.orElseGet(() -> ICUComparisonLevel.primary());
return strength(new ICUComparisonLevel(strengthValue.level, strengthValue.caseFirst.orElse(null), caseLevel));
ComparisonLevel strengthValue = strength.orElseGet(ComparisonLevel::primary);
return strength(
new ICUComparisonLevel(strengthValue.getLevel(), strengthValue.getCaseFirst(), Optional.of(caseLevel)));
}
/**
@ -198,7 +205,7 @@ public class Collation { @@ -198,7 +205,7 @@ public class Collation {
* @return
*/
public Collation caseFirst(String caseFirst) {
return caseFirst(new ICUCaseFirst(caseFirst));
return caseFirst(new CaseFirst(caseFirst));
}
/**
@ -207,10 +214,10 @@ public class Collation { @@ -207,10 +214,10 @@ public class Collation {
* @param caseFirst must not be {@literal null}.
* @return
*/
public Collation caseFirst(ICUCaseFirst sort) {
public Collation caseFirst(CaseFirst sort) {
ICUComparisonLevel strengthValue = strength.orElseGet(() -> ICUComparisonLevel.tertiary());
return strength(new ICUComparisonLevel(strengthValue.level, sort, strengthValue.caseLevel.orElse(null)));
ComparisonLevel strengthValue = strength.orElseGet(ComparisonLevel::tertiary);
return strength(new ICUComparisonLevel(strengthValue.getLevel(), Optional.of(sort), strengthValue.getCaseLevel()));
}
/**
@ -236,10 +243,10 @@ public class Collation { @@ -236,10 +243,10 @@ public class Collation {
*
* @return new {@link Collation}.
*/
public Collation numericOrdering(Boolean flag) {
public Collation numericOrdering(boolean flag) {
Collation newInstance = copy();
newInstance.numericOrdering = Optional.ofNullable(flag);
newInstance.numericOrdering = Optional.of(flag);
return newInstance;
}
@ -252,8 +259,8 @@ public class Collation { @@ -252,8 +259,8 @@ public class Collation {
*/
public Collation alternate(String alternate) {
Alternate instance = this.alternate.orElseGet(() -> new Alternate(alternate, null));
return alternate(new Alternate(alternate, instance.maxVariable.orElse(null)));
Alternate instance = this.alternate.orElseGet(() -> new Alternate(alternate, Optional.empty()));
return alternate(new Alternate(alternate, instance.maxVariable));
}
/**
@ -340,7 +347,7 @@ public class Collation { @@ -340,7 +347,7 @@ public class Collation {
*/
public Collation maxVariable(String maxVariable) {
Alternate alternateValue = alternate.orElseGet(() -> Alternate.shifted());
Alternate alternateValue = alternate.orElseGet(Alternate::shifted);
return alternate(new AlternateWithMaxVariable(alternateValue.alternate, maxVariable));
}
@ -362,6 +369,13 @@ public class Collation { @@ -362,6 +369,13 @@ public class Collation {
return map(toMongoCollationConverter());
}
/**
* Transform {@code this} {@link Collation} by applying a {@link Converter}.
*
* @param mapper
* @param <R>
* @return
*/
public <R> R map(Converter<? super Collation, ? extends R> mapper) {
return mapper.convert(this);
}
@ -387,39 +401,30 @@ public class Collation { @@ -387,39 +401,30 @@ public class Collation {
*
* @since 2.0
*/
public static class ICUComparisonLevel {
protected final Integer level;
private final Optional<ICUCaseFirst> caseFirst;
private final Optional<Boolean> caseLevel;
private ICUComparisonLevel(Integer level, ICUCaseFirst caseFirst, Boolean caseLevel) {
this.level = level;
this.caseFirst = Optional.ofNullable(caseFirst);
this.caseLevel = Optional.ofNullable(caseLevel);
}
public interface ComparisonLevel {
/**
* Primary level of comparison. Collation performs comparisons of the base characters only, ignoring other
* differences such as diacritics and case. <br />
* The {@code caseLevel} can be set via {@link ComparisonLevelWithCase#caseLevel(Boolean)}.
* The {@code caseLevel} can be set via {@link PrimaryICUComparisonLevel#includeCase()} and
* {@link PrimaryICUComparisonLevel#excludeCase()}.
*
* @return new {@link ComparisonLevelWithCase}.
* @return new {@link SecondaryICUComparisonLevel}.
*/
public static PrimaryICUComparisonLevel primary() {
return new PrimaryICUComparisonLevel(1, null);
static PrimaryICUComparisonLevel primary() {
return PrimaryICUComparisonLevel.DEFAULT;
}
/**
* Scondary level of comparison. Collation performs comparisons up to secondary differences, such as
* Secondary level of comparison. Collation performs comparisons up to secondary differences, such as
* diacritics.<br />
* The {@code caseLevel} can be set via {@link ComparisonLevelWithCase#caseLevel(Boolean)}.
* The {@code caseLevel} can be set via {@link SecondaryICUComparisonLevel#includeCase()} and
* {@link SecondaryICUComparisonLevel#excludeCase()}.
*
* @return new {@link ComparisonLevelWithCase}.
* @return new {@link SecondaryICUComparisonLevel}.
*/
public static SecondaryICUComparisonLevel secondary() {
return new SecondaryICUComparisonLevel(2, null);
static SecondaryICUComparisonLevel secondary() {
return SecondaryICUComparisonLevel.DEFAULT;
}
/**
@ -429,159 +434,231 @@ public class Collation { @@ -429,159 +434,231 @@ public class Collation {
*
* @return new {@link ICUComparisonLevel}.
*/
public static TertiaryICUComparisonLevel tertiary() {
return new TertiaryICUComparisonLevel(3, null);
static TertiaryICUComparisonLevel tertiary() {
return TertiaryICUComparisonLevel.DEFAULT;
}
/**
* Quaternary Level. Limited for specific use case to consider punctuation. <br />
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
*
* @return new {@link ICUComparisonLevel}.
* @return new {@link ComparisonLevel}.
*/
public static ICUComparisonLevel quaternary() {
return new ICUComparisonLevel(4, null, null);
static ComparisonLevel quaternary() {
return ComparisonLevels.QUATERNARY;
}
/**
* Identical Level. Limited for specific use case of tie breaker. <br />
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
*
* @return new {@link ICUComparisonLevel}.
* @return new {@link ComparisonLevel}.
*/
public static ICUComparisonLevel identical() {
return new ICUComparisonLevel(5, null, null);
static ComparisonLevel identical() {
return ComparisonLevels.IDENTICAL;
}
/**
* @return collation strength, {@literal 1} for primary, {@literal 2} for secondary and so on.
*/
int getLevel();
default Optional<CaseFirst> getCaseFirst() {
return Optional.empty();
}
default Optional<Boolean> getCaseLevel() {
return Optional.empty();
}
}
public static class TertiaryICUComparisonLevel extends ICUComparisonLevel {
/**
* 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> caseFirst;
private final Optional<Boolean> caseLevel;
private TertiaryICUComparisonLevel(Integer level, ICUCaseFirst caseFirst) {
super(level, caseFirst, null);
ICUComparisonLevel(int level) {
this(level, Optional.empty(), Optional.empty());
}
}
/**
* Set the flag that determines sort order of case differences.
*
* @param caseFirstSort must not be {@literal null}.
* @return
*/
public TertiaryICUComparisonLevel caseFirst(ICUCaseFirst caseFirst) {
/**
* Simple comparison levels.
*/
enum ComparisonLevels implements ComparisonLevel {
Assert.notNull(caseFirst, "CaseFirst must not be null!");
return new TertiaryICUComparisonLevel(level, caseFirst);
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 {
private PrimaryICUComparisonLevel(Integer level, Boolean caseLevel) {
super(level, null, caseLevel);
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 ComparisonLevelWithCase}
* @return new {@link ICUComparisonLevel}
*/
public PrimaryICUComparisonLevel includeCase() {
return caseLevel(Boolean.TRUE);
public ComparisonLevel includeCase() {
return WITH_CASE_LEVEL;
}
/**
* Exclude case comparison.
*
* @return new {@link ComparisonLevelWithCase}
* @return new {@link ICUComparisonLevel}
*/
public PrimaryICUComparisonLevel excludeCase() {
return caseLevel(Boolean.FALSE);
}
PrimaryICUComparisonLevel caseLevel(Boolean caseLevel) {
return new PrimaryICUComparisonLevel(level, caseLevel);
public ComparisonLevel excludeCase() {
return WITHOUT_CASE_LEVEL;
}
}
/**
* Secondary-strength {@link ICUComparisonLevel}.
*/
public static class SecondaryICUComparisonLevel extends ICUComparisonLevel {
private SecondaryICUComparisonLevel(Integer level, Boolean caseLevel) {
super(level, null, caseLevel);
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 ComparisonLevelWithCase}
* @return new {@link SecondaryICUComparisonLevel}
*/
public SecondaryICUComparisonLevel includeCase() {
return caseLevel(Boolean.TRUE);
public ComparisonLevel includeCase() {
return WITH_CASE_LEVEL;
}
/**
* Exclude case comparison.
*
* @return new {@link ComparisonLevelWithCase}
* @return new {@link SecondaryICUComparisonLevel}
*/
public SecondaryICUComparisonLevel excludeCase() {
return caseLevel(Boolean.FALSE);
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);
}
SecondaryICUComparisonLevel caseLevel(Boolean caseLevel) {
return new SecondaryICUComparisonLevel(level, caseLevel);
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
*/
public static class ICUCaseFirst {
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class CaseFirst {
private final String state;
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 ICUCaseFirst(String state) {
this.state = state;
}
private final String state;
/**
* Sort uppercase before lowercase.
*
* @return new {@link ICUCaseFirst}.
* @return new {@link CaseFirst}.
*/
public static ICUCaseFirst upper() {
return new ICUCaseFirst("upper");
public static CaseFirst upper() {
return UPPER;
}
/**
* Sort lowercase before uppercase.
*
* @return new {@link ICUCaseFirst}.
* @return new {@link CaseFirst}.
*/
public static ICUCaseFirst lower() {
return new ICUCaseFirst("lower");
public static CaseFirst lower() {
return LOWER;
}
/**
* Use the default.
*
* @return new {@link ICUCaseFirst}.
* @return new {@link CaseFirst}.
*/
public static ICUCaseFirst off() {
return new ICUCaseFirst("off");
public static CaseFirst off() {
return OFF;
}
}
/**
* @since 2.0
*/
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public static class Alternate {
protected final String alternate;
protected Optional<String> maxVariable;
private static final Alternate NON_IGNORABLE = new Alternate("non-ignorable", Optional.empty());
private Alternate(String alternate, String maxVariable) {
this.alternate = alternate;
this.maxVariable = Optional.ofNullable(maxVariable);
}
final String alternate;
final Optional<String> maxVariable;
/**
* Consider Whitespace and punctuation as base characters.
@ -589,18 +666,18 @@ public class Collation { @@ -589,18 +666,18 @@ public class Collation {
* @return new {@link Alternate}.
*/
public static Alternate nonIgnorable() {
return new Alternate("non-ignorable", null);
return NON_IGNORABLE;
}
/**
* Whitespace and punctuation are <strong>not</strong> considered base characters and are only distinguished at
* strength. <br />
* <strong>NOTE:</strong> Only works for {@link ICUComparisonLevel} above {@link ICUComparisonLevel#tertiary()}.
* <strong>NOTE:</strong> Only works for {@link ICUComparisonLevel} above {@link ComparisonLevel#tertiary()}.
*
* @return new {@link AlternateWithMaxVariable}.
*/
public static AlternateWithMaxVariable shifted() {
return new AlternateWithMaxVariable("shifted", null);
return AlternateWithMaxVariable.DEFAULT;
}
}
@ -609,8 +686,16 @@ public class Collation { @@ -609,8 +686,16 @@ public class Collation {
*/
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, maxVariable);
super(alternate, Optional.of(maxVariable));
}
/**
@ -618,8 +703,8 @@ public class Collation { @@ -618,8 +703,8 @@ public class Collation {
*
* @return new {@link AlternateWithMaxVariable}.
*/
public AlternateWithMaxVariable punct() {
return new AlternateWithMaxVariable(alternate, "punct");
public Alternate punct() {
return SHIFTED_PUNCT;
}
/**
@ -627,10 +712,9 @@ public class Collation { @@ -627,10 +712,9 @@ public class Collation {
*
* @return new {@link AlternateWithMaxVariable}.
*/
public AlternateWithMaxVariable space() {
return new AlternateWithMaxVariable(alternate, "space");
public Alternate space() {
return SHIFTED_SPACE;
}
}
/**
@ -639,38 +723,34 @@ public class Collation { @@ -639,38 +723,34 @@ public class Collation {
* @since 2.0
* @see <a href="http://site.icu-project.org">ICU - International Components for Unicode</a>
*/
public static class ICULocale {
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class CollationLocale {
private final String language;
private final Optional<String> variant;
private ICULocale(String language, String variant) {
this.language = language;
this.variant = Optional.ofNullable(variant);
}
/**
* Create new {@link ICULocale} for given language.
* Create new {@link CollationLocale} for given language.
*
* @param language must not be {@literal null}.
* @return
*/
public static ICULocale of(String language) {
public static CollationLocale of(String language) {
Assert.notNull(language, "Code must not be null!");
return new ICULocale(language, null);
return new CollationLocale(language, Optional.empty());
}
/**
* Define language variant.
*
* @param variant must not be {@literal null}.
* @return new {@link ICULocale}.
* @return new {@link CollationLocale}.
*/
public ICULocale variant(String variant) {
public CollationLocale variant(String variant) {
Assert.notNull(variant, "Variant must not be null!");
return new ICULocale(language, variant);
return new CollationLocale(language, Optional.of(variant));
}
/**
@ -681,12 +761,13 @@ public class Collation { @@ -681,12 +761,13 @@ public class Collation {
public String asString() {
StringBuilder sb = new StringBuilder(language);
variant.ifPresent(val -> {
if (!val.isEmpty()) {
sb.append("@collation=").append(val);
}
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();
}
}
@ -698,24 +779,24 @@ public class Collation { @@ -698,24 +779,24 @@ public class Collation {
Document document = new Document();
document.append("locale", source.locale.asString());
source.strength.ifPresent(val -> {
source.strength.ifPresent(strength -> {
document.append("strength", val.level);
document.append("strength", strength.getLevel());
val.caseLevel.ifPresent(cl -> document.append("caseLevel", cl));
val.caseFirst.ifPresent(cl -> document.append("caseFirst", cl.state));
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(val -> {
source.alternate.ifPresent(it -> {
document.append("alternate", val.alternate);
val.maxVariable.ifPresent(maxVariable -> document.append("maxVariable", maxVariable));
document.append("alternate", it.alternate);
it.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));
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;
};
@ -729,24 +810,24 @@ public class Collation { @@ -729,24 +810,24 @@ public class Collation {
builder.locale(source.locale.asString());
source.strength.ifPresent(val -> {
source.strength.ifPresent(strength -> {
builder.collationStrength(CollationStrength.fromInt(val.level));
builder.collationStrength(CollationStrength.fromInt(strength.getLevel()));
val.caseLevel.ifPresent(cl -> builder.caseLevel(cl));
val.caseFirst.ifPresent(cl -> builder.collationCaseFirst(CollationCaseFirst.fromString(cl.state)));
strength.getCaseLevel().ifPresent(builder::caseLevel);
strength.getCaseFirst().ifPresent(it -> builder.collationCaseFirst(CollationCaseFirst.fromString(it.state)));
});
source.numericOrdering.ifPresent(val -> builder.numericOrdering(val));
source.alternate.ifPresent(val -> {
source.numericOrdering.ifPresent(builder::numericOrdering);
source.alternate.ifPresent(it -> {
builder.collationAlternate(CollationAlternate.fromString(val.alternate));
val.maxVariable
builder.collationAlternate(CollationAlternate.fromString(it.alternate));
it.maxVariable
.ifPresent(maxVariable -> builder.collationMaxVariable(CollationMaxVariable.fromString(maxVariable)));
});
source.backwards.ifPresent(val -> builder.backwards(val));
source.normalization.ifPresent(val -> builder.normalization(val));
source.backwards.ifPresent(builder::backwards);
source.normalization.ifPresent(builder::normalization);
return builder.build();
};

51
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java

@ -24,27 +24,33 @@ import org.springframework.util.Assert; @@ -24,27 +24,33 @@ import org.springframework.util.Assert;
*
* @author Thomas Risberg
* @author Christoph Strobl
* @author Mark Paluch
*/
public class CollectionOptions {
private Integer maxDocuments;
private Integer size;
private Boolean capped;
private Collation collation;
private Optional<Collation> collation;
/**
* Constructs a new <code>CollectionOptions</code> 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) {
this(size, maxDocuments, capped, Optional.empty());
}
private CollectionOptions(Integer size, Integer maxDocuments, Boolean capped, Optional<Collation> collation) {
this.maxDocuments = maxDocuments;
this.size = size;
this.capped = capped;
this.collation = collation;
}
private CollectionOptions() {}
@ -66,16 +72,24 @@ public class CollectionOptions { @@ -66,16 +72,24 @@ public class CollectionOptions {
}
/**
* Create new {@link CollectionOptions} with already given settings and capped set to {@literal true}.
* Create new empty {@link CollectionOptions}.
*
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions capped() {
public static CollectionOptions empty() {
return new CollectionOptions();
}
CollectionOptions options = new CollectionOptions(size, maxDocuments, true);
options.setCollation(collation);
return options;
/**
* 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);
}
/**
@ -86,10 +100,7 @@ public class CollectionOptions { @@ -86,10 +100,7 @@ public class CollectionOptions {
* @since 2.0
*/
public CollectionOptions maxDocuments(Integer maxDocuments) {
CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
options.setCollation(collation);
return options;
return new CollectionOptions(size, maxDocuments, capped, collation);
}
/**
@ -99,11 +110,8 @@ public class CollectionOptions { @@ -99,11 +110,8 @@ public class CollectionOptions {
* @return new {@link CollectionOptions}.
* @since 2.0
*/
public CollectionOptions size(Integer size) {
CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
options.setCollation(collation);
return options;
public CollectionOptions size(int size) {
return new CollectionOptions(size, maxDocuments, capped, collation);
}
/**
@ -114,10 +122,7 @@ public class CollectionOptions { @@ -114,10 +122,7 @@ public class CollectionOptions {
* @since 2.0
*/
public CollectionOptions collation(Collation collation) {
CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
options.setCollation(collation);
return options;
return new CollectionOptions(size, maxDocuments, capped, Optional.ofNullable(collation));
}
public Integer getMaxDocuments() {
@ -151,7 +156,7 @@ public class CollectionOptions { @@ -151,7 +156,7 @@ public class CollectionOptions {
* @since 2.0
*/
public void setCollation(Collation collation) {
this.collation = collation;
this.collation = Optional.ofNullable(collation);
}
/**
@ -161,6 +166,6 @@ public class CollectionOptions { @@ -161,6 +166,6 @@ public class CollectionOptions {
* @since 2.0
*/
public Optional<Collation> getCollation() {
return Optional.ofNullable(collation);
return collation;
}
}

11
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java

@ -24,15 +24,15 @@ import java.util.Optional; @@ -24,15 +24,15 @@ import java.util.Optional;
*/
public class FindAndModifyOptions {
boolean returnNew;
boolean upsert;
boolean remove;
private boolean returnNew;
private boolean upsert;
private boolean remove;
private Collation collation;
/**
* Static factory method to create a FindAndModifyOptions instance
*
*
* @return a new instance
*/
public static FindAndModifyOptions options() {
@ -46,9 +46,8 @@ public class FindAndModifyOptions { @@ -46,9 +46,8 @@ public class FindAndModifyOptions {
*/
public static FindAndModifyOptions of(FindAndModifyOptions source) {
FindAndModifyOptions options = new FindAndModifyOptions();
if(source == null) {
if (source == null) {
return options;
}

36
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java

@ -25,10 +25,6 @@ import org.springframework.data.mongodb.core.index.IndexInfo; @@ -25,10 +25,6 @@ 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;
/**
@ -129,39 +125,11 @@ abstract class IndexConverters { @@ -129,39 +125,11 @@ abstract class IndexConverters {
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();
return org.springframework.data.mongodb.core.Collation.from(source).toMongoCollation();
}
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
return ix -> IndexInfo.indexInfoOf(ix);
return IndexInfo::indexInfoOf;
}
}

63
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -20,19 +20,8 @@ import static org.springframework.data.mongodb.core.query.SerializationUtils.*; @@ -20,19 +20,8 @@ import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import static org.springframework.data.util.Optionals.*;
import java.io.IOException;
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.*;
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;
@ -60,6 +49,7 @@ import org.springframework.data.geo.Distance; @@ -60,6 +49,7 @@ import org.springframework.data.geo.Distance;
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;
@ -722,7 +712,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -722,7 +712,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
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.");
"Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
});
query.getCollation().ifPresent(optionsToUse::collation);
@ -885,7 +875,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -885,7 +875,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
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));
@ -972,7 +962,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -972,7 +962,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.hasText(collectionName, "Collection name must not be null or empty!");
Optional<? extends MongoPersistentEntity<?>> entity = getPersistentEntity(objectToSave.getClass());
Optional<MongoPersistentProperty> versionProperty = entity.flatMap(it -> it.getVersionProperty());
Optional<MongoPersistentProperty> versionProperty = entity.flatMap(PersistentEntity::getVersionProperty);
mapIfAllPresent(entity, versionProperty, //
(l, r) -> doSaveVersioned(objectToSave, l, collectionName))//
@ -1225,18 +1215,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1225,18 +1215,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
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);
}
@ -1458,7 +1449,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1458,7 +1449,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
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.");
"Both Query and MapReduceOptions define a collation. Please provide the collation only via one of the two.");
});
if (mapReduceOptions.getCollation().isPresent()) {
@ -1482,9 +1473,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1482,9 +1473,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
}
if (collation.isPresent()) {
result = result.collation(collation.map(Collation::toMongoCollation).get());
}
result = collation.map(Collation::toMongoCollation).map(result::collation).orElse(result);
List<T> mappedResults = new ArrayList<T>();
DocumentCallback<T> callback = new ReadDocumentCallback<T>(mongoConverter, entityClass, inputCollectionName);
@ -2297,7 +2286,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2297,7 +2286,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
serializeToJsonSafely(fields.orElseGet(() -> new Document())), collection.getNamespace().getFullName());
serializeToJsonSafely(fields.orElseGet(Document::new)), collection.getNamespace().getFullName());
}
if (fields.isPresent()) {
@ -2336,11 +2325,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2336,11 +2325,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
FindIterable<Document> iterable = collection.find(query);
if (fields.filter(val -> !val.isEmpty()).isPresent()) {
iterable = iterable.projection(fields.get());
}
return iterable;
return fields.filter(val -> !val.isEmpty()).map(iterable::projection).orElse(iterable);
}
}
@ -2399,7 +2384,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2399,7 +2384,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
opts.upsert(true);
}
opts.projection(fields);
if (options.returnNew) {
if (options.isReturnNew()) {
opts.returnDocument(ReturnDocument.AFTER);
}
@ -2506,16 +2491,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2506,16 +2491,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return cursor;
}
if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {
if (query.getSkip() <= 0 && query.getLimit() <= 0
&& (query.getSortObject() == null || query.getSortObject().isEmpty()) && !StringUtils.hasText(query.getHint())
&& !query.getMeta().hasValues() && !query.getCollation().isPresent()) {
return cursor;
}
FindIterable<Document> cursorToUse = cursor;
FindIterable<Document> cursorToUse;
cursorToUse = query.getCollation().map(Collation::toMongoCollation).map(cursor::collation).orElse(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());

64
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -59,6 +59,7 @@ import org.springframework.data.convert.EntityReader; @@ -59,6 +59,7 @@ import org.springframework.data.convert.EntityReader;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
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;
@ -573,11 +574,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -573,11 +574,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return createFlux(collectionName, collection -> {
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
FindPublisher findPublisher = collection.find(mappedQuery).projection(new Document("_id", 1));
FindPublisher<Document> findPublisher = collection.find(mappedQuery).projection(new Document("_id", 1));
if (query.getCollation().isPresent()) {
findPublisher = findPublisher.collation(query.getCollation().map(Collation::toMongoCollation).get());
}
findPublisher = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
.orElse(findPublisher);
return findPublisher.limit(1);
}).hasElements();
@ -616,8 +616,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -616,8 +616,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public <T> Mono<T> findById(Object id, Class<T> entityClass, String collectionName) {
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext.getPersistentEntity(entityClass);
MongoPersistentProperty idProperty = persistentEntity.isPresent()
? persistentEntity.get().getIdProperty().orElse(null) : null;
MongoPersistentProperty idProperty = persistentEntity.flatMap(PersistentEntity::getIdProperty).orElse(null);
String idKey = idProperty == null ? ID_FIELD : idProperty.getName();
@ -712,7 +711,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -712,7 +711,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
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.");
"Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
});
query.getCollation().ifPresent(optionsToUse::collation);
@ -1091,34 +1090,35 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1091,34 +1090,35 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return collectionToUse;
}
protected Mono<Object> saveDocument(final String collectionName, final Document dbDoc, final Class<?> entityClass) {
protected Mono<Object> saveDocument(final String collectionName, final Document document,
final Class<?> entityClass) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Saving Document containing fields: " + dbDoc.keySet());
LOGGER.debug("Saving Document containing fields: " + document.keySet());
}
return createMono(collectionName, collection -> {
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass,
dbDoc, null);
document, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
Publisher<?> publisher;
if (!dbDoc.containsKey(ID_FIELD)) {
if (!document.containsKey(ID_FIELD)) {
if (writeConcernToUse == null) {
publisher = collection.insertOne(dbDoc);
publisher = collection.insertOne(document);
} else {
publisher = collection.withWriteConcern(writeConcernToUse).insertOne(dbDoc);
publisher = collection.withWriteConcern(writeConcernToUse).insertOne(document);
}
} else if (writeConcernToUse == null) {
publisher = collection.replaceOne(Filters.eq(ID_FIELD, dbDoc.get(ID_FIELD)), dbDoc,
publisher = collection.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document,
new UpdateOptions().upsert(true));
} else {
publisher = collection.withWriteConcern(writeConcernToUse).replaceOne(Filters.eq(ID_FIELD, dbDoc.get(ID_FIELD)),
dbDoc, new UpdateOptions().upsert(true));
publisher = collection.withWriteConcern(writeConcernToUse)
.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document, new UpdateOptions().upsert(true));
}
return Mono.from(publisher).map(o -> dbDoc.get(ID_FIELD));
return Mono.from(publisher).map(o -> document.get(ID_FIELD));
});
}
@ -1317,7 +1317,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1317,7 +1317,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(objectType);
MongoPersistentProperty idProp = entity.isPresent() ? entity.get().getIdProperty().orElse(null) : null;
MongoPersistentProperty idProp = entity.flatMap(PersistentEntity::getIdProperty).orElse(null);
if (idProp == null) {
throw new MappingException("No id property found for object of type " + objectType);
@ -1907,8 +1907,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1907,8 +1907,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
private MongoPersistentProperty getIdPropertyFor(Class<?> type) {
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext.getPersistentEntity(type);
return persistentEntity.isPresent() ? persistentEntity.get().getIdProperty().orElse(null) : null;
return persistentEntity.flatMap(PersistentEntity::getIdProperty).orElse(null);
}
private <T> String determineEntityCollectionName(T obj) {
@ -2011,21 +2012,19 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2011,21 +2012,19 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException {
FindPublisher publisher = collection.find(query);
FindPublisher<Document> publisher = 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());
serializeToJsonSafely(fields.orElseGet(Document::new)), collection.getNamespace().getFullName());
}
if (fields.isPresent()) {
publisher = publisher.projection(fields.get());
}
if (collation.isPresent()) {
publisher = publisher.collation(collation.map(Collation::toMongoCollation).get());
}
publisher = collation.map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
return publisher.limit(1).first();
}
@ -2129,10 +2128,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2129,10 +2128,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (options.isRemove()) {
FindOneAndDeleteOptions findOneAndDeleteOptions = convertToFindOneAndDeleteOptions(fields, sort);
if (options.getCollation().isPresent()) {
findOneAndDeleteOptions = findOneAndDeleteOptions
.collation(options.getCollation().map(Collation::toMongoCollation).get());
}
findOneAndDeleteOptions = options.getCollation().map(Collation::toMongoCollation)
.map(findOneAndDeleteOptions::collation).orElse(findOneAndDeleteOptions);
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
}
@ -2154,9 +2151,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2154,9 +2151,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
result = result.returnDocument(ReturnDocument.BEFORE);
}
if (options.getCollation().isPresent()) {
result = result.collation(options.getCollation().map(Collation::toMongoCollation).get());
}
result = options.getCollation().map(Collation::toMongoCollation).map(result::collation).orElse(result);
return result;
}
@ -2293,11 +2288,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2293,11 +2288,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return findPublisher;
}
FindPublisher<T> findPublisherToUse = findPublisher;
FindPublisher<T> findPublisherToUse;
if (query.getCollation().isPresent()) {
findPublisherToUse = findPublisherToUse.collation(query.getCollation().map(Collation::toMongoCollation).get());
}
findPublisherToUse = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
.orElse(findPublisher);
if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {

15
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java

@ -30,6 +30,7 @@ import org.springframework.util.StringUtils; @@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
/**
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
@SuppressWarnings("deprecation")
public class Index implements IndexDefinition {
@ -39,21 +40,13 @@ public class Index implements IndexDefinition { @@ -39,21 +40,13 @@ public class Index implements IndexDefinition {
}
private final Map<String, Direction> fieldSpec = new LinkedHashMap<String, Direction>();
private String name;
private boolean unique = false;
private boolean dropDuplicates = false;
private boolean sparse = false;
private boolean background = false;
private long expire = -1;
private Optional<IndexFilter> filter = Optional.empty();
private Optional<Collation> collation = Optional.empty();
public Index() {}
@ -98,7 +91,7 @@ public class Index implements IndexDefinition { @@ -98,7 +91,7 @@ public class Index implements IndexDefinition {
/**
* Build the index in background (non blocking).
*
*
* @return
* @since 1.5
*/
@ -110,7 +103,7 @@ public class Index implements IndexDefinition { @@ -110,7 +103,7 @@ public class Index implements IndexDefinition {
/**
* Specifies TTL in seconds.
*
*
* @param value
* @return
* @since 1.5
@ -121,7 +114,7 @@ public class Index implements IndexDefinition { @@ -121,7 +114,7 @@ public class Index implements IndexDefinition {
/**
* Specifies TTL with given {@link TimeUnit}.
*
*
* @param value
* @param unit
* @return

10
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java

@ -24,7 +24,7 @@ import org.springframework.data.mongodb.core.Collation; @@ -24,7 +24,7 @@ import org.springframework.data.mongodb.core.Collation;
* Collects the parameters required to perform a group operation on a collection. The query condition and the input
* collection are specified on the group method as method arguments to be consistent with other operations, e.g.
* map-reduce.
*
*
* @author Mark Pollack
* @author Christoph Strobl
*/
@ -33,7 +33,7 @@ public class GroupBy { @@ -33,7 +33,7 @@ public class GroupBy {
private Document initialDocument;
private String reduce;
private Optional<Document> dboKeys = Optional.empty();
private Optional<Document> keys = Optional.empty();
private Optional<String> keyFunction = Optional.empty();
private Optional<String> initial = Optional.empty();
private Optional<String> finalize = Optional.empty();
@ -46,7 +46,7 @@ public class GroupBy { @@ -46,7 +46,7 @@ public class GroupBy {
document.put(key, 1);
}
dboKeys = Optional.of(document);
this.keys = Optional.of(document);
}
// NOTE GroupByCommand does not handle keyfunction.
@ -58,7 +58,7 @@ public class GroupBy { @@ -58,7 +58,7 @@ public class GroupBy {
keyFunction = Optional.ofNullable(key);
} else {
document.put(key, 1);
dboKeys = Optional.of(document);
keys = Optional.of(document);
}
}
@ -152,7 +152,7 @@ public class GroupBy { @@ -152,7 +152,7 @@ public class GroupBy {
Document document = new Document();
dboKeys.ifPresent(val -> document.append("key", val));
keys.ifPresent(val -> document.append("key", val));
keyFunction.ifPresent(val -> document.append("$keyf", val));
document.put("$reduce", reduce);

31
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CollationUnitTests.java

@ -17,15 +17,18 @@ package org.springframework.data.mongodb.core; @@ -17,15 +17,18 @@ package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import java.util.Locale;
import org.bson.Document;
import org.junit.Test;
import org.springframework.data.mongodb.core.Collation.Alternate;
import org.springframework.data.mongodb.core.Collation.ICUCaseFirst;
import org.springframework.data.mongodb.core.Collation.ICUComparisonLevel;
import org.springframework.data.mongodb.core.Collation.ICULocale;
import org.springframework.data.mongodb.core.Collation.CaseFirst;
import org.springframework.data.mongodb.core.Collation.CollationLocale;
import org.springframework.data.mongodb.core.Collation.ComparisonLevel;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
public class CollationUnitTests {
@ -59,7 +62,8 @@ public class CollationUnitTests { @@ -59,7 +62,8 @@ public class CollationUnitTests {
@Test // DATAMONGO-1518
public void localeWithVariant() {
assertThat(Collation.of(ICULocale.of("de_AT").variant("phonebook")).toDocument()).isEqualTo(LOCALE_WITH_VARIANT);
assertThat(Collation.of(CollationLocale.of("de_AT").variant("phonebook")).toDocument())
.isEqualTo(LOCALE_WITH_VARIANT);
}
@Test // DATAMONGO-1518
@ -68,14 +72,15 @@ public class CollationUnitTests { @@ -68,14 +72,15 @@ public class CollationUnitTests {
}
@Test // DATAMONGO-1518
public void lcaleFromJavaUtilLocale() {
assertThat(Collation.of(java.util.Locale.US).toDocument()).isEqualTo(new Document().append("locale", "en"));
public void localeFromJavaUtilLocale() {
assertThat(Collation.of(java.util.Locale.US).toDocument()).isEqualTo(new Document().append("locale", "en_US"));
assertThat(Collation.of(Locale.ENGLISH).toDocument()).isEqualTo(new Document().append("locale", "en"));
}
@Test // DATAMONGO-1518
public void withStrenghPrimary() {
assertThat(Collation.of("en_US").strength(ICUComparisonLevel.primary()).toDocument())
.isEqualTo(WITH_STRENGTH_PRIMARY);
assertThat(Collation.of("en_US").strength(ComparisonLevel.primary()).toDocument()).isEqualTo(WITH_STRENGTH_PRIMARY);
}
@Test // DATAMONGO-1518
@ -86,7 +91,7 @@ public class CollationUnitTests { @@ -86,7 +91,7 @@ public class CollationUnitTests {
@Test // DATAMONGO-1518
public void withStrenghPrimaryAndIncludeCase() {
assertThat(Collation.of("en_US").strength(ICUComparisonLevel.primary().includeCase()).toDocument())
assertThat(Collation.of("en_US").strength(ComparisonLevel.primary().includeCase()).toDocument())
.isEqualTo(WITH_STRENGTH_PRIMARY_INCLUDE_CASE);
}
@ -129,7 +134,7 @@ public class CollationUnitTests { @@ -129,7 +134,7 @@ public class CollationUnitTests {
@Test // DATAMONGO-1518
public void withCaseFirst() {
assertThat(Collation.of("en_US").caseFirst(ICUCaseFirst.upper()).toDocument()).isEqualTo(WITH_CASE_FIRST_UPPER);
assertThat(Collation.of("en_US").caseFirst(CaseFirst.upper()).toDocument()).isEqualTo(WITH_CASE_FIRST_UPPER);
}
@Test // DATAMONGO-1518
@ -164,8 +169,8 @@ public class CollationUnitTests { @@ -164,8 +169,8 @@ public class CollationUnitTests {
@Test // DATAMONGO-1518
public void allTheThings() {
assertThat(Collation.of(ICULocale.of("de_AT").variant("phonebook"))
.strength(ICUComparisonLevel.primary().includeCase()).normalizationEnabled().backwardDiacriticSort()
assertThat(Collation.of(CollationLocale.of("de_AT").variant("phonebook"))
.strength(ComparisonLevel.primary().includeCase()).normalizationEnabled().backwardDiacriticSort()
.numericOrderingEnabled().alternate(Alternate.shifted().punct()).toDocument()).isEqualTo(ALL_THE_THINGS);
}
@ -176,7 +181,7 @@ public class CollationUnitTests { @@ -176,7 +181,7 @@ public class CollationUnitTests {
@Test // DATAMONGO-1518
public void justTheDefault() {
assertThat(Collation.binary().toDocument()).isEqualTo(BINARY_COMPARISON);
assertThat(Collation.simple().toDocument()).isEqualTo(BINARY_COMPARISON);
}
}

5
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java

@ -27,7 +27,7 @@ import org.junit.Test; @@ -27,7 +27,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.Collation.ICUCaseFirst;
import org.springframework.data.mongodb.core.Collation.CaseFirst;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexDefinition;
@ -45,6 +45,7 @@ import com.mongodb.client.MongoCollection; @@ -45,6 +45,7 @@ import com.mongodb.client.MongoCollection;
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
@ -154,7 +155,7 @@ public class DefaultIndexOperationsIntegrationTests { @@ -154,7 +155,7 @@ public class DefaultIndexOperationsIntegrationTests {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR), is(true));
IndexDefinition id = new Index().named("with-collation").on("xyz", Direction.ASC)
.collation(Collation.of("de_AT").caseFirst(ICUCaseFirst.off()));
.collation(Collation.of("de_AT").caseFirst(CaseFirst.off()));
new DefaultIndexOperations(template.getMongoDbFactory(),
this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class),

5
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java

@ -29,7 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -29,7 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.Collation.ICUCaseFirst;
import org.springframework.data.mongodb.core.Collation.CaseFirst;
import org.springframework.data.mongodb.core.DefaultIndexOperationsIntegrationTests.DefaultIndexOperationsIntegrationTestsSample;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexDefinition;
@ -43,6 +43,7 @@ import com.mongodb.reactivestreams.client.MongoCollection; @@ -43,6 +43,7 @@ import com.mongodb.reactivestreams.client.MongoCollection;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@ -95,7 +96,7 @@ public class DefaultReactiveIndexOperationsTests { @@ -95,7 +96,7 @@ public class DefaultReactiveIndexOperationsTests {
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR), is(true));
IndexDefinition id = new Index().named("with-collation").on("xyz", Direction.ASC)
.collation(Collation.of("de_AT").caseFirst(ICUCaseFirst.off()));
.collation(Collation.of("de_AT").caseFirst(CaseFirst.off()));
indexOps.ensureIndex(id).subscribe();

9
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateCollationTests.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import java.util.Locale;
import org.bson.Document;
import org.junit.Before;
@ -28,8 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -28,8 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.Collation.Alternate;
import org.springframework.data.mongodb.core.Collation.ICUComparisonLevel;
import org.springframework.data.mongodb.core.Collation.ICULocale;
import org.springframework.data.mongodb.core.Collation.ComparisonLevel;
import org.springframework.data.mongodb.test.util.MongoVersionRule;
import org.springframework.data.util.Version;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ -38,6 +38,7 @@ import com.mongodb.MongoClient; @@ -38,6 +38,7 @@ import com.mongodb.MongoClient;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
public class MongoTemplateCollationTests {
@ -79,7 +80,7 @@ public class MongoTemplateCollationTests { @@ -79,7 +80,7 @@ public class MongoTemplateCollationTests {
public void createCollectionWithCollationHavingLocaleVariant() {
template.createCollection(COLLECTION_NAME,
CollectionOptions.just(Collation.of(ICULocale.of("de_AT").variant("phonebook"))));
CollectionOptions.just(Collation.of(new Locale("de", "AT", "phonebook"))));
Document collation = getCollationInfo(COLLECTION_NAME);
assertThat(collation.get("locale")).isEqualTo("de_AT@collation=phonebook");
@ -89,7 +90,7 @@ public class MongoTemplateCollationTests { @@ -89,7 +90,7 @@ public class MongoTemplateCollationTests {
public void createCollectionWithCollationHavingStrength() {
template.createCollection(COLLECTION_NAME,
CollectionOptions.just(Collation.of("en_US").strength(ICUComparisonLevel.primary().includeCase())));
CollectionOptions.just(Collation.of("en_US").strength(ComparisonLevel.primary().includeCase())));
Document collation = getCollationInfo(COLLECTION_NAME);
assertThat(collation.get("strength")).isEqualTo(1);

14
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java

@ -244,18 +244,4 @@ public class ReactiveMongoTemplateUnitTests { @@ -244,18 +244,4 @@ public class ReactiveMongoTemplateUnitTests {
assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr")));
}
@Ignore("currently no groupBy")
@Test // DATAMONGO-1518
public void groupShouldUseCollationWhenPresent() {
// template.group("collection-1", GroupBy.key("id").reduceFunction("bar").collation(Collation.of("fr")),
// AutogenerateableId.class).subscribe();
//
// ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
// verify(db).runCommand(cmd.capture(), Mockito.any(Class.class));
//
// assertThat(cmd.getValue().get("group", Document.class).get("collation", Document.class),
// equalTo(new Document("locale", "fr")));
}
}

1
src/main/asciidoc/new-features.adoc

@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
* Usage of the `Document` API instead of `DBObject`.
* <<mongo.reactive>>.
* Support for aggregation result streaming via Java 8 `Stream`.
* Integration of collations for collection and index creation and query operations.
[[new-features.1-10-0]]
== What's new in Spring Data MongoDB 1.10

83
src/main/asciidoc/reference/mongodb.adoc

@ -1344,6 +1344,85 @@ TextQuery.searching(new TextCriteria().phrase("coffee cake")); @@ -1344,6 +1344,85 @@ TextQuery.searching(new TextCriteria().phrase("coffee cake"));
The flags for `$caseSensitive` and `$diacriticSensitive` can be set via the according methods on `TextCriteria`. Please note that these two optional flags have been introduced in MongoDB 3.2 and will not be included in the query unless explicitly set.
[[mongo.collation]]
=== Collations
MongoDB supports since 3.4 collations for collection and index creation and various query operations. Collations define string comparison rules based on the http://userguide.icu-project.org/collation/concepts[ICU collations]. A collation document consists of various properties that are encapsulated in `Collation`:
====
[source,java]
----
Collation collation = Collation.of("fr") <1>
.strength(ComparisonLevel.secondary() <2>
.includeCase())
.numericOrderingEnabled() <3>
.alternate(Alternate.shifted().punct()) <4>
.forwardDiacriticSort() <5>
.normalizationEnabled(); <6>
----
<1> `Collation` requires a locale for creation. This can be either a string representation of the locale, a `Locale` (considering language, country and variant) or a `CollationLocale`. The locale is mandatory for creation.
<2> Collation strength defines comparison levels denoting differences between characters. You can configure various options (case-sensitivity, case-ordering) depending on the selected strength.
<3> Specify whether to compare numeric strings as numbers or as strings.
<4> Specify whether the collation should consider whitespace and punctuation as base characters for purposes of comparison.
<5> Specify whether strings with diacritics sort from back of the string, such as with some French dictionary ordering.
<6> Specify whether to check if text requires normalization and to perform normalization.
====
Collations can be used to create collections and indexes. If you create a collection specifying a collation, the collation is applied to index creation and queries unless you specify a different collation. A collation is valid for a whole operation and cannot be specified on a per-field basis.
[source,java]
----
Collation french = Collation.of("fr");
Collation german = Collation.of("de");
template.createCollection(Person.class, CollectionOptions.just(collation));
template.indexOps(Person.class).ensureIndex(new Index("name", Direction.ASC).collation(german));
----
NOTE: MongoDB uses simple binary comparison if no collation is specified (`Collation.simple()`).
Using collations with collection operations is a matter of specifying a `Collation` instance in your query or operation options.
.Using collation with `find`
====
[source,java]
----
Collation collation = Collation.of("de");
Query query = new Query(Criteria.where("firstName").is("Amél")).collation(collation);
List<Person> results = template.find(query, Person.class);
----
====
.Using collation with `aggregate`
====
[source,java]
----
Collation collation = Collation.of("de");
AggregationOptions options = new AggregationOptions.Builder().collation(collation).build();
Aggregation aggregation = newAggregation(
project("tags"),
unwind("tags"),
group("tags")
.count().as("count")
).withOptions(options);
AggregationResults<TagCount> results = template.aggregate(aggregation, "tags", TagCount.class);
----
====
WARNING: Indexes are only used if the collation used for the operation and the index collation matches.
include::../{spring-data-commons-docs}/query-by-example.adoc[leveloffset=+1]
include::query-by-example.adoc[leveloffset=+1]
@ -2241,6 +2320,8 @@ You can create standard, geospatial and text indexes using the classes `IndexDef @@ -2241,6 +2320,8 @@ You can create standard, geospatial and text indexes using the classes `IndexDef
mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location"));
----
NOTE: `Index` and `GeospatialIndex` support configuration of <<mongo.collation,collations>>.
[[mongo-template.index-and-collections.access]]
=== Accessing index information
@ -2281,6 +2362,8 @@ mongoTemplate.dropCollection("MyNewCollection"); @@ -2281,6 +2362,8 @@ mongoTemplate.dropCollection("MyNewCollection");
* *dropCollection* Drop the collection
* *getCollection* Get a collection by name, creating it if it doesn't exist.
NOTE: Collection creation allows customization via `CollectionOptions` and supports <<mongo.collation,collations>>.
[[mongo-template.commands]]
== Executing Commands

Loading…
Cancel
Save