Introduce dedicated Collation annotation.
The Collation annotation mainly serves as a meta annotation that allows common access to retrieving collation values for annotated queries, aggregations, etc. Original Pull Request: #4131
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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
|
||||||
|
*
|
||||||
|
* https://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.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Collation} allows to define the rules used for language-specific string comparison.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.mongodb.com/docs/manual/reference/collation/">https://www.mongodb.com/docs/manual/reference/collation/</a>
|
||||||
|
* @author Christoph Strobl
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
|
public @interface Collation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual collation definition in JSON format or a
|
||||||
|
* {@link org.springframework.expression.spel.standard.SpelExpression template expression} resolving to either a JSON
|
||||||
|
* String or a {@link org.bson.Document}. The keys of the JSON document are configuration options for the collation.
|
||||||
|
*
|
||||||
|
* @return an empty {@link String} by default.
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Core Spring Data MongoDB annotations not limited to a special use case (like Query,...).
|
||||||
|
*/
|
||||||
|
@org.springframework.lang.NonNullApi
|
||||||
|
package org.springframework.data.mongodb.core.annotation;
|
||||||
|
|
||||||
@@ -22,7 +22,10 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a class to use compound indexes. <br />
|
* Mark a class to use compound indexes. <br />
|
||||||
* <p>
|
* <p>
|
||||||
@@ -49,6 +52,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
|
|||||||
* @author Dave Perryman
|
* @author Dave Perryman
|
||||||
* @author Stefan Tirea
|
* @author Stefan Tirea
|
||||||
*/
|
*/
|
||||||
|
@Collation
|
||||||
@Target({ ElementType.TYPE })
|
@Target({ ElementType.TYPE })
|
||||||
@Documented
|
@Documented
|
||||||
@Repeatable(CompoundIndexes.class)
|
@Repeatable(CompoundIndexes.class)
|
||||||
@@ -181,5 +185,6 @@ public @interface CompoundIndex {
|
|||||||
* "https://www.mongodb.com/docs/manual/reference/collation/">https://www.mongodb.com/docs/manual/reference/collation/</a>
|
* "https://www.mongodb.com/docs/manual/reference/collation/">https://www.mongodb.com/docs/manual/reference/collation/</a>
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(annotation = Collation.class, attribute = "value")
|
||||||
String collation() default "";
|
String collation() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a field to be indexed using MongoDB's indexing feature.
|
* Mark a field to be indexed using MongoDB's indexing feature.
|
||||||
*
|
*
|
||||||
@@ -34,6 +37,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
|
|||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
* @author Stefan Tirea
|
* @author Stefan Tirea
|
||||||
*/
|
*/
|
||||||
|
@Collation
|
||||||
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD })
|
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface Indexed {
|
public @interface Indexed {
|
||||||
@@ -188,5 +192,6 @@ public @interface Indexed {
|
|||||||
* @see <a href="https://www.mongodb.com/docs/manual/reference/collation/">https://www.mongodb.com/docs/manual/reference/collation/</a>
|
* @see <a href="https://www.mongodb.com/docs/manual/reference/collation/">https://www.mongodb.com/docs/manual/reference/collation/</a>
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(annotation = Collation.class, attribute = "value")
|
||||||
String collation() default "";
|
String collation() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.mongodb.core.index;
|
package org.springframework.data.mongodb.core.index;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -23,13 +24,15 @@ import java.util.Collections;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.core.annotation.MergedAnnotation;
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.mapping.Association;
|
import org.springframework.data.mapping.Association;
|
||||||
@@ -50,12 +53,10 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
|||||||
import org.springframework.data.mongodb.core.query.Collation;
|
import org.springframework.data.mongodb.core.query.Collation;
|
||||||
import org.springframework.data.mongodb.util.BsonUtils;
|
import org.springframework.data.mongodb.util.BsonUtils;
|
||||||
import org.springframework.data.mongodb.util.DotPath;
|
import org.springframework.data.mongodb.util.DotPath;
|
||||||
|
import org.springframework.data.mongodb.util.spel.ExpressionUtils;
|
||||||
import org.springframework.data.spel.EvaluationContextProvider;
|
import org.springframework.data.spel.EvaluationContextProvider;
|
||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.Expression;
|
|
||||||
import org.springframework.expression.ParserContext;
|
|
||||||
import org.springframework.expression.common.LiteralExpression;
|
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -454,10 +455,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), entity));
|
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.hasText(index.collation())) {
|
indexDefinition.collation(resolveCollation(index, entity));
|
||||||
indexDefinition.collation(evaluateCollation(index.collation(), entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,12 +476,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), entity));
|
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.hasText(index.collation())) {
|
indexDefinition.collation(resolveCollation(index, entity));
|
||||||
indexDefinition.collation(evaluateCollation(index.collation(), entity));
|
|
||||||
} else if (entity != null && entity.hasCollation()) {
|
|
||||||
indexDefinition.collation(entity.getCollation());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,7 +491,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
return new org.bson.Document(dotPath, 1);
|
return new org.bson.Document(dotPath, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object keyDefToUse = evaluate(keyDefinitionString, getEvaluationContextForProperty(entity));
|
Object keyDefToUse = ExpressionUtils.evaluate(keyDefinitionString, () -> getEvaluationContextForProperty(entity));
|
||||||
|
|
||||||
org.bson.Document dbo = (keyDefToUse instanceof org.bson.Document) ? (org.bson.Document) keyDefToUse
|
org.bson.Document dbo = (keyDefToUse instanceof org.bson.Document) ? (org.bson.Document) keyDefToUse
|
||||||
: org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse));
|
: org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse));
|
||||||
@@ -567,7 +560,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Duration timeout = computeIndexTimeout(index.expireAfter(),
|
Duration timeout = computeIndexTimeout(index.expireAfter(),
|
||||||
getEvaluationContextForProperty(persistentProperty.getOwner()));
|
() -> getEvaluationContextForProperty(persistentProperty.getOwner()));
|
||||||
if (!timeout.isZero() && !timeout.isNegative()) {
|
if (!timeout.isZero() && !timeout.isNegative()) {
|
||||||
indexDefinition.expire(timeout);
|
indexDefinition.expire(timeout);
|
||||||
}
|
}
|
||||||
@@ -577,16 +570,13 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), persistentProperty.getOwner()));
|
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), persistentProperty.getOwner()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.hasText(index.collation())) {
|
indexDefinition.collation(resolveCollation(index, persistentProperty.getOwner()));
|
||||||
indexDefinition.collation(evaluateCollation(index.collation(), persistentProperty.getOwner()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PartialIndexFilter evaluatePartialFilter(String filterExpression, PersistentEntity<?, ?> entity) {
|
private PartialIndexFilter evaluatePartialFilter(String filterExpression, PersistentEntity<?, ?> entity) {
|
||||||
|
|
||||||
Object result = evaluate(filterExpression, getEvaluationContextForProperty(entity));
|
Object result = ExpressionUtils.evaluate(filterExpression, () -> getEvaluationContextForProperty(entity));
|
||||||
|
|
||||||
if (result instanceof org.bson.Document) {
|
if (result instanceof org.bson.Document) {
|
||||||
return PartialIndexFilter.of((org.bson.Document) result);
|
return PartialIndexFilter.of((org.bson.Document) result);
|
||||||
@@ -597,7 +587,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
|
|
||||||
private org.bson.Document evaluateWildcardProjection(String projectionExpression, PersistentEntity<?, ?> entity) {
|
private org.bson.Document evaluateWildcardProjection(String projectionExpression, PersistentEntity<?, ?> entity) {
|
||||||
|
|
||||||
Object result = evaluate(projectionExpression, getEvaluationContextForProperty(entity));
|
Object result = ExpressionUtils.evaluate(projectionExpression, () -> getEvaluationContextForProperty(entity));
|
||||||
|
|
||||||
if (result instanceof org.bson.Document) {
|
if (result instanceof org.bson.Document) {
|
||||||
return (org.bson.Document) result;
|
return (org.bson.Document) result;
|
||||||
@@ -608,7 +598,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
|
|
||||||
private Collation evaluateCollation(String collationExpression, PersistentEntity<?, ?> entity) {
|
private Collation evaluateCollation(String collationExpression, PersistentEntity<?, ?> entity) {
|
||||||
|
|
||||||
Object result = evaluate(collationExpression, getEvaluationContextForProperty(entity));
|
Object result = ExpressionUtils.evaluate(collationExpression, () -> getEvaluationContextForProperty(entity));
|
||||||
if (result instanceof org.bson.Document) {
|
if (result instanceof org.bson.Document) {
|
||||||
return Collation.from((org.bson.Document) result);
|
return Collation.from((org.bson.Document) result);
|
||||||
}
|
}
|
||||||
@@ -618,6 +608,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
if (result instanceof String) {
|
if (result instanceof String) {
|
||||||
return Collation.parse(result.toString());
|
return Collation.parse(result.toString());
|
||||||
}
|
}
|
||||||
|
if (result instanceof Map) {
|
||||||
|
return Collation.from(new org.bson.Document((Map<String, ?>) result));
|
||||||
|
}
|
||||||
throw new IllegalStateException("Cannot parse collation " + result);
|
throw new IllegalStateException("Cannot parse collation " + result);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -726,7 +719,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
String nameToUse = "";
|
String nameToUse = "";
|
||||||
if (StringUtils.hasText(indexName)) {
|
if (StringUtils.hasText(indexName)) {
|
||||||
|
|
||||||
Object result = evaluate(indexName, getEvaluationContextForProperty(entity));
|
Object result = ExpressionUtils.evaluate(indexName, () -> getEvaluationContextForProperty(entity));
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
nameToUse = ObjectUtils.nullSafeToString(result);
|
nameToUse = ObjectUtils.nullSafeToString(result);
|
||||||
@@ -787,9 +780,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
* @since 2.2
|
* @since 2.2
|
||||||
* @throws IllegalArgumentException for invalid duration values.
|
* @throws IllegalArgumentException for invalid duration values.
|
||||||
*/
|
*/
|
||||||
private static Duration computeIndexTimeout(String timeoutValue, EvaluationContext evaluationContext) {
|
private static Duration computeIndexTimeout(String timeoutValue, Supplier<EvaluationContext> evaluationContext) {
|
||||||
|
|
||||||
Object evaluatedTimeout = evaluate(timeoutValue, evaluationContext);
|
Object evaluatedTimeout = ExpressionUtils.evaluate(timeoutValue, evaluationContext);
|
||||||
|
|
||||||
if (evaluatedTimeout == null) {
|
if (evaluatedTimeout == null) {
|
||||||
return Duration.ZERO;
|
return Duration.ZERO;
|
||||||
@@ -808,15 +801,25 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
|||||||
return DurationStyle.detectAndParse(val);
|
return DurationStyle.detectAndParse(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the "collation" attribute from a given {@link Annotation} if present.
|
||||||
|
*
|
||||||
|
* @param annotation
|
||||||
|
* @param entity
|
||||||
|
* @return the collation present on either the annotation or the entity as a fallback. Might be {@literal null}.
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Object evaluate(String value, EvaluationContext evaluationContext) {
|
private Collation resolveCollation(Annotation annotation, @Nullable PersistentEntity<?, ?> entity) {
|
||||||
|
return MergedAnnotation.from(annotation).getValue("collation", String.class).filter(StringUtils::hasText)
|
||||||
|
.map(it -> evaluateCollation(it, entity)).orElseGet(() -> {
|
||||||
|
|
||||||
Expression expression = PARSER.parseExpression(value, ParserContext.TEMPLATE_EXPRESSION);
|
if (entity instanceof MongoPersistentEntity<?> mongoPersistentEntity
|
||||||
if (expression instanceof LiteralExpression) {
|
&& mongoPersistentEntity.hasCollation()) {
|
||||||
return value;
|
return mongoPersistentEntity.getCollation();
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
return expression.getValue(evaluationContext, Object.class);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isMapWithoutWildcardIndex(MongoPersistentProperty property) {
|
private static boolean isMapWithoutWildcardIndex(MongoPersistentProperty property) {
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for an entity or property that should be used as key for a
|
* Annotation for an entity or property that should be used as key for a
|
||||||
* <a href="https://docs.mongodb.com/manual/core/index-wildcard/">Wildcard Index</a>. <br />
|
* <a href="https://docs.mongodb.com/manual/core/index-wildcard/">Wildcard Index</a>. <br />
|
||||||
@@ -79,6 +82,7 @@ import java.lang.annotation.Target;
|
|||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @since 3.3
|
* @since 3.3
|
||||||
*/
|
*/
|
||||||
|
@Collation
|
||||||
@Documented
|
@Documented
|
||||||
@Target({ ElementType.TYPE, ElementType.FIELD })
|
@Target({ ElementType.TYPE, ElementType.FIELD })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@@ -126,5 +130,6 @@ public @interface WildcardIndexed {
|
|||||||
*
|
*
|
||||||
* @return an empty {@link String} by default.
|
* @return an empty {@link String} by default.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(annotation = Collation.class, attribute = "value")
|
||||||
String collation() default "";
|
String collation() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.lang.annotation.Target;
|
|||||||
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.data.annotation.Persistent;
|
import org.springframework.data.annotation.Persistent;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies a domain object to be persisted to MongoDB.
|
* Identifies a domain object to be persisted to MongoDB.
|
||||||
@@ -32,6 +33,7 @@ import org.springframework.data.annotation.Persistent;
|
|||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
*/
|
*/
|
||||||
@Persistent
|
@Persistent
|
||||||
|
@Collation
|
||||||
@Inherited
|
@Inherited
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ ElementType.TYPE })
|
@Target({ ElementType.TYPE })
|
||||||
@@ -71,6 +73,7 @@ public @interface Document {
|
|||||||
* @return an empty {@link String} by default.
|
* @return an empty {@link String} by default.
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(annotation = Collation.class, attribute = "value")
|
||||||
String collation() default "";
|
String collation() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.lang.annotation.Target;
|
|||||||
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.data.annotation.QueryAnnotation;
|
import org.springframework.data.annotation.QueryAnnotation;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Aggregation} annotation can be used to annotate a {@link org.springframework.data.repository.Repository}
|
* The {@link Aggregation} annotation can be used to annotate a {@link org.springframework.data.repository.Repository}
|
||||||
@@ -38,6 +39,7 @@ import org.springframework.data.annotation.QueryAnnotation;
|
|||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
|
@Collation
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
@Documented
|
@Documented
|
||||||
@@ -123,5 +125,6 @@ public @interface Aggregation {
|
|||||||
*
|
*
|
||||||
* @return an empty {@link String} by default.
|
* @return an empty {@link String} by default.
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(annotation = Collation.class, attribute = "value")
|
||||||
String collation() default "";
|
String collation() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.data.annotation.QueryAnnotation;
|
import org.springframework.data.annotation.QueryAnnotation;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation to declare finder queries directly on repository methods. Both attributes allow using a placeholder
|
* Annotation to declare finder queries directly on repository methods. Both attributes allow using a placeholder
|
||||||
@@ -32,6 +34,7 @@ import org.springframework.data.annotation.QueryAnnotation;
|
|||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
*/
|
*/
|
||||||
|
@Collation
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
@Documented
|
@Documented
|
||||||
@@ -124,5 +127,6 @@ public @interface Query {
|
|||||||
* @return an empty {@link String} by default.
|
* @return an empty {@link String} by default.
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
|
@AliasFor(annotation = Collation.class, attribute = "value")
|
||||||
String collation() default "";
|
String collation() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.springframework.data.geo.GeoPage;
|
|||||||
import org.springframework.data.geo.GeoResult;
|
import org.springframework.data.geo.GeoResult;
|
||||||
import org.springframework.data.geo.GeoResults;
|
import org.springframework.data.geo.GeoResults;
|
||||||
import org.springframework.data.mapping.context.MappingContext;
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||||
@@ -321,14 +322,7 @@ public class MongoQueryMethod extends QueryMethod {
|
|||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
public boolean hasAnnotatedCollation() {
|
public boolean hasAnnotatedCollation() {
|
||||||
|
return doFindAnnotation(Collation.class).map(Collation::value).filter(StringUtils::hasText).isPresent();
|
||||||
Optional<String> optionalCollation = lookupQueryAnnotation().map(Query::collation);
|
|
||||||
|
|
||||||
if (!optionalCollation.isPresent()) {
|
|
||||||
optionalCollation = lookupAggregationAnnotation().map(Aggregation::collation);
|
|
||||||
}
|
|
||||||
|
|
||||||
return optionalCollation.filter(StringUtils::hasText).isPresent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -341,10 +335,9 @@ public class MongoQueryMethod extends QueryMethod {
|
|||||||
*/
|
*/
|
||||||
public String getAnnotatedCollation() {
|
public String getAnnotatedCollation() {
|
||||||
|
|
||||||
return lookupQueryAnnotation().map(Query::collation)
|
return doFindAnnotation(Collation.class).map(Collation::value) //
|
||||||
.orElseGet(() -> lookupAggregationAnnotation().map(Aggregation::collation) //
|
|
||||||
.orElseThrow(() -> new IllegalStateException(
|
.orElseThrow(() -> new IllegalStateException(
|
||||||
"Expected to find @Query annotation but did not; Make sure to check hasAnnotatedCollation() before.")));
|
"Expected to find @Collation annotation but did not; Make sure to check hasAnnotatedCollation() before."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -447,7 +440,7 @@ public class MongoQueryMethod extends QueryMethod {
|
|||||||
private boolean isNumericOrVoidReturnValue() {
|
private boolean isNumericOrVoidReturnValue() {
|
||||||
|
|
||||||
Class<?> resultType = getReturnedObjectType();
|
Class<?> resultType = getReturnedObjectType();
|
||||||
if(ReactiveWrappers.usesReactiveType(resultType)) {
|
if (ReactiveWrappers.usesReactiveType(resultType)) {
|
||||||
resultType = getReturnType().getComponentType().getType();
|
resultType = getReturnType().getComponentType().getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.mongodb.util.spel;
|
package org.springframework.data.mongodb.util.spel;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.ParserContext;
|
import org.springframework.expression.ParserContext;
|
||||||
import org.springframework.expression.common.LiteralExpression;
|
import org.springframework.expression.common.LiteralExpression;
|
||||||
@@ -49,4 +52,15 @@ public final class ExpressionUtils {
|
|||||||
Expression expression = PARSER.parseExpression(potentialExpression, ParserContext.TEMPLATE_EXPRESSION);
|
Expression expression = PARSER.parseExpression(potentialExpression, ParserContext.TEMPLATE_EXPRESSION);
|
||||||
return expression instanceof LiteralExpression ? null : expression;
|
return expression instanceof LiteralExpression ? null : expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Object evaluate(String value, Supplier<EvaluationContext> evaluationContext) {
|
||||||
|
|
||||||
|
Expression expression = detectExpression(value);
|
||||||
|
if (expression == null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression.getValue(evaluationContext.get(), Object.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -713,6 +713,32 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
assertThat(indexDefinition.getIndexKeys()).isEqualTo(new org.bson.Document().append("foo", 1));
|
assertThat(indexDefinition.getIndexKeys()).isEqualTo(new org.bson.Document().append("foo", 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
public void compoundIndexWithCollationFromDocumentAnnotation() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
||||||
|
WithCompoundCollationFromDocument.class);
|
||||||
|
|
||||||
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
|
assertThat(indexDefinition.getIndexOptions())
|
||||||
|
.isEqualTo(new org.bson.Document().append("name", "compound_index_with_collation").append("collation",
|
||||||
|
new org.bson.Document().append("locale", "en_US").append("strength", 2)));
|
||||||
|
assertThat(indexDefinition.getIndexKeys()).isEqualTo(new org.bson.Document().append("foo", 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
public void compoundIndexWithEvaluatedCollationFromAnnotation() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
||||||
|
WithEvaluatedCollationFromCompoundIndex.class);
|
||||||
|
|
||||||
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
|
assertThat(indexDefinition.getIndexOptions())
|
||||||
|
.isEqualTo(new org.bson.Document().append("name", "compound_index_with_collation").append("collation",
|
||||||
|
new org.bson.Document().append("locale", "de_AT")));
|
||||||
|
assertThat(indexDefinition.getIndexKeys()).isEqualTo(new org.bson.Document().append("foo", 1));
|
||||||
|
}
|
||||||
|
|
||||||
@Document("CompoundIndexOnLevelOne")
|
@Document("CompoundIndexOnLevelOne")
|
||||||
class CompoundIndexOnLevelOne {
|
class CompoundIndexOnLevelOne {
|
||||||
|
|
||||||
@@ -793,6 +819,14 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
@CompoundIndex(name = "compound_index_with_collation", def = "{'foo': 1}",
|
@CompoundIndex(name = "compound_index_with_collation", def = "{'foo': 1}",
|
||||||
collation = "{'locale': 'en_US', 'strength': 2}")
|
collation = "{'locale': 'en_US', 'strength': 2}")
|
||||||
class CompoundIndexWithCollation {}
|
class CompoundIndexWithCollation {}
|
||||||
|
|
||||||
|
@Document(collation = "{'locale': 'en_US', 'strength': 2}")
|
||||||
|
@CompoundIndex(name = "compound_index_with_collation", def = "{'foo': 1}")
|
||||||
|
class WithCompoundCollationFromDocument {}
|
||||||
|
|
||||||
|
@Document(collation = "{'locale': 'en_US', 'strength': 2}")
|
||||||
|
@CompoundIndex(name = "compound_index_with_collation", def = "{'foo': 1}", collation = "#{{ 'locale' : 'de' + '_' + 'AT' }}")
|
||||||
|
class WithEvaluatedCollationFromCompoundIndex {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TextIndexedResolutionTests {
|
public static class TextIndexedResolutionTests {
|
||||||
@@ -1423,7 +1457,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
public void indexedWithCollation() {
|
public void indexedWithCollation() {
|
||||||
|
|
||||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
||||||
IndexedWithCollation.class);
|
WithCollationFromIndexedAnnotation.class);
|
||||||
|
|
||||||
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "value")
|
assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "value")
|
||||||
@@ -1431,6 +1465,29 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
.append("collation", new org.bson.Document().append("locale", "en_US").append("strength", 2)));
|
.append("collation", new org.bson.Document().append("locale", "en_US").append("strength", 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
public void indexedWithCollationFromDocumentAnnotation() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
||||||
|
WithCollationFromDocumentAnnotation.class);
|
||||||
|
|
||||||
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
|
assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "value")
|
||||||
|
.append("unique", true)
|
||||||
|
.append("collation", new org.bson.Document().append("locale", "en_US").append("strength", 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
public void indexedWithEvaluatedCollation() {
|
||||||
|
|
||||||
|
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
||||||
|
WithEvaluatedCollationFromIndexedAnnotation.class);
|
||||||
|
|
||||||
|
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
|
||||||
|
assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "value")
|
||||||
|
.append("collation", new org.bson.Document().append("locale", "de_AT")));
|
||||||
|
}
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
class MixedIndexRoot {
|
class MixedIndexRoot {
|
||||||
|
|
||||||
@@ -1749,11 +1806,26 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
class IndexedWithCollation {
|
class WithCollationFromIndexedAnnotation {
|
||||||
|
|
||||||
@Indexed(collation = "{'locale': 'en_US', 'strength': 2}", unique = true) //
|
@Indexed(collation = "{'locale': 'en_US', 'strength': 2}", unique = true) //
|
||||||
private String value;
|
private String value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Document(collation = "{'locale': 'en_US', 'strength': 2}")
|
||||||
|
class WithCollationFromDocumentAnnotation {
|
||||||
|
|
||||||
|
@Indexed(unique = true) //
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Document(collation = "en_US")
|
||||||
|
class WithEvaluatedCollationFromIndexedAnnotation {
|
||||||
|
|
||||||
|
@Indexed(collation = "#{{'locale' : 'de' + '_' + 'AT'}}") //
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
|
|
||||||
@HashIndexed
|
@HashIndexed
|
||||||
@Indexed
|
@Indexed
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.springframework.data.geo.GeoResults;
|
|||||||
import org.springframework.data.geo.Point;
|
import org.springframework.data.geo.Point;
|
||||||
import org.springframework.data.mongodb.core.User;
|
import org.springframework.data.mongodb.core.User;
|
||||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||||
import org.springframework.data.mongodb.core.query.Update;
|
import org.springframework.data.mongodb.core.query.Update;
|
||||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||||
@@ -39,6 +40,7 @@ import org.springframework.data.mongodb.repository.Aggregation;
|
|||||||
import org.springframework.data.mongodb.repository.Contact;
|
import org.springframework.data.mongodb.repository.Contact;
|
||||||
import org.springframework.data.mongodb.repository.Meta;
|
import org.springframework.data.mongodb.repository.Meta;
|
||||||
import org.springframework.data.mongodb.repository.Person;
|
import org.springframework.data.mongodb.repository.Person;
|
||||||
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
import org.springframework.data.projection.ProjectionFactory;
|
import org.springframework.data.projection.ProjectionFactory;
|
||||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
@@ -278,6 +280,33 @@ public class MongoQueryMethodUnitTests {
|
|||||||
.withMessageContaining("findAndIncrementVisitsByFirstname");
|
.withMessageContaining("findAndIncrementVisitsByFirstname");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
void readsCollationFromAtCollationAnnotation() throws Exception {
|
||||||
|
|
||||||
|
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithCollationFromAtCollationByFirstname", String.class);
|
||||||
|
|
||||||
|
assertThat(method.hasAnnotatedCollation()).isTrue();
|
||||||
|
assertThat(method.getAnnotatedCollation()).isEqualTo("en_US");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
void readsCollationFromAtQueryAnnotation() throws Exception {
|
||||||
|
|
||||||
|
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithCollationFromAtQueryByFirstname", String.class);
|
||||||
|
|
||||||
|
assertThat(method.hasAnnotatedCollation()).isTrue();
|
||||||
|
assertThat(method.getAnnotatedCollation()).isEqualTo("en_US");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
void annotatedCollationClashSelectsAtCollationAnnotationValue() throws Exception {
|
||||||
|
|
||||||
|
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname", String.class);
|
||||||
|
|
||||||
|
assertThat(method.hasAnnotatedCollation()).isTrue();
|
||||||
|
assertThat(method.getAnnotatedCollation()).isEqualTo("de_AT");
|
||||||
|
}
|
||||||
|
|
||||||
private MongoQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters) throws Exception {
|
private MongoQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters) throws Exception {
|
||||||
|
|
||||||
Method method = repository.getMethod(name, parameters);
|
Method method = repository.getMethod(name, parameters);
|
||||||
@@ -338,6 +367,16 @@ public class MongoQueryMethodUnitTests {
|
|||||||
void findAndUpdateBy(String firstname, UpdateDefinition update);
|
void findAndUpdateBy(String firstname, UpdateDefinition update);
|
||||||
|
|
||||||
void findAndUpdateBy(String firstname, AggregationUpdate update);
|
void findAndUpdateBy(String firstname, AggregationUpdate update);
|
||||||
|
|
||||||
|
@Collation("en_US")
|
||||||
|
List<User> findWithCollationFromAtCollationByFirstname(String firstname);
|
||||||
|
|
||||||
|
@Query(collation = "en_US")
|
||||||
|
List<User> findWithCollationFromAtQueryByFirstname(String firstname);
|
||||||
|
|
||||||
|
@Collation("de_AT")
|
||||||
|
@Query(collation = "en_US")
|
||||||
|
List<User> findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname(String firstname);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SampleRepository extends Repository<Contact, Long> {
|
interface SampleRepository extends Repository<Contact, Long> {
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ package org.springframework.data.mongodb.repository.query;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.mongodb.core.annotation.Collation;
|
||||||
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@@ -24,8 +28,6 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -56,7 +58,7 @@ public class ReactiveMongoQueryMethodUnitTests {
|
|||||||
|
|
||||||
MongoMappingContext context;
|
MongoMappingContext context;
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
context = new MongoMappingContext();
|
context = new MongoMappingContext();
|
||||||
}
|
}
|
||||||
@@ -102,13 +104,13 @@ public class ReactiveMongoQueryMethodUnitTests {
|
|||||||
.isTrue();
|
.isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1444
|
@Test // DATAMONGO-1444
|
||||||
public void rejectsNullMappingContext() throws Exception {
|
public void rejectsNullMappingContext() throws Exception {
|
||||||
|
|
||||||
Method method = PersonRepository.class.getMethod("findByFirstname", String.class, Point.class);
|
Method method = PersonRepository.class.getMethod("findByFirstname", String.class, Point.class);
|
||||||
|
|
||||||
new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class),
|
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class),
|
||||||
new SpelAwareProxyProjectionFactory(), null);
|
new SpelAwareProxyProjectionFactory(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAMONGO-1444
|
@Test // DATAMONGO-1444
|
||||||
@@ -197,6 +199,33 @@ public class ReactiveMongoQueryMethodUnitTests {
|
|||||||
.withMessageContaining("findAndIncrementVisitsByFirstname");
|
.withMessageContaining("findAndIncrementVisitsByFirstname");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
void readsCollationFromAtCollationAnnotation() throws Exception {
|
||||||
|
|
||||||
|
ReactiveMongoQueryMethod method = queryMethod(MongoQueryMethodUnitTests.PersonRepository.class, "findWithCollationFromAtCollationByFirstname", String.class);
|
||||||
|
|
||||||
|
assertThat(method.hasAnnotatedCollation()).isTrue();
|
||||||
|
assertThat(method.getAnnotatedCollation()).isEqualTo("en_US");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
void readsCollationFromAtQueryAnnotation() throws Exception {
|
||||||
|
|
||||||
|
ReactiveMongoQueryMethod method = queryMethod(MongoQueryMethodUnitTests.PersonRepository.class, "findWithCollationFromAtQueryByFirstname", String.class);
|
||||||
|
|
||||||
|
assertThat(method.hasAnnotatedCollation()).isTrue();
|
||||||
|
assertThat(method.getAnnotatedCollation()).isEqualTo("en_US");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-3002
|
||||||
|
void annotatedCollationClashSelectsAtCollationAnnotationValue() throws Exception {
|
||||||
|
|
||||||
|
ReactiveMongoQueryMethod method = queryMethod(MongoQueryMethodUnitTests.PersonRepository.class, "findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname", String.class);
|
||||||
|
|
||||||
|
assertThat(method.hasAnnotatedCollation()).isTrue();
|
||||||
|
assertThat(method.getAnnotatedCollation()).isEqualTo("de_AT");
|
||||||
|
}
|
||||||
|
|
||||||
private ReactiveMongoQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters)
|
private ReactiveMongoQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
@@ -238,6 +267,16 @@ public class ReactiveMongoQueryMethodUnitTests {
|
|||||||
@Aggregation(pipeline = "{'$group': { _id: '$templateId', maxVersion : { $max : '$version'} } }",
|
@Aggregation(pipeline = "{'$group': { _id: '$templateId', maxVersion : { $max : '$version'} } }",
|
||||||
collation = "de_AT")
|
collation = "de_AT")
|
||||||
Flux<User> findByAggregationWithCollation();
|
Flux<User> findByAggregationWithCollation();
|
||||||
|
|
||||||
|
@Collation("en_US")
|
||||||
|
List<User> findWithCollationFromAtCollationByFirstname(String firstname);
|
||||||
|
|
||||||
|
@Query(collation = "en_US")
|
||||||
|
List<User> findWithCollationFromAtQueryByFirstname(String firstname);
|
||||||
|
|
||||||
|
@Collation("de_AT")
|
||||||
|
@Query(collation = "en_US")
|
||||||
|
List<User> findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname(String firstname);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SampleRepository extends Repository<Contact, Long> {
|
interface SampleRepository extends Repository<Contact, Long> {
|
||||||
|
|||||||
@@ -2011,7 +2011,35 @@ and `Document` (eg. new Document("locale", "en_US"))
|
|||||||
NOTE: In case you enabled the automatic index creation for repository finder methods a potential static collation definition,
|
NOTE: In case you enabled the automatic index creation for repository finder methods a potential static collation definition,
|
||||||
as shown in (1) and (2), will be included when creating the index.
|
as shown in (1) and (2), will be included when creating the index.
|
||||||
|
|
||||||
TIP: The most specifc `Collation` outroules potentially defined others. Which means Method argument over query method annotation over doamin type annotation.
|
TIP: The most specifc `Collation` outrules potentially defined others. Which means Method argument over query method annotation over domain type annotation.
|
||||||
|
====
|
||||||
|
|
||||||
|
To streamline usage of collation attributes throughout the codebase it is also possible to use the `@Collation` annotation, which serves as a meta annotation for the ones mentioned above.
|
||||||
|
The same rules and locations apply, plus, direct usage of `@Collation` supersedes any collation values defined on `@Query` and other annotations.
|
||||||
|
Which means, if a collation is declared via `@Query` and additionally via `@Collation`, then the one from `@Collation` is picked.
|
||||||
|
|
||||||
|
.Using `@Collation`
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Collation("en_US") <1>
|
||||||
|
class Game {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GameRepository extends Repository<Game, String> {
|
||||||
|
|
||||||
|
@Collation("en_GB") <2>
|
||||||
|
List<Game> findByTitle(String title);
|
||||||
|
|
||||||
|
@Collation("de_AT") <3>
|
||||||
|
@Query(collation="en_GB")
|
||||||
|
List<Game> findByDescriptionContaining(String keyword);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Instead of `@Document(collation=...)`.
|
||||||
|
<2> Instead of `@Query(collation=...)`.
|
||||||
|
<3> Favors `@Collation` over meta usage.
|
||||||
====
|
====
|
||||||
|
|
||||||
include::./mongo-json-schema.adoc[leveloffset=+1]
|
include::./mongo-json-schema.adoc[leveloffset=+1]
|
||||||
|
|||||||
Reference in New Issue
Block a user