Skip to content

Commit b5818eb

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-1854 - Add collation option to @query annotation.
We now allow to specify the collation via the @query annotation. public interface PersonRepository extends MongoRepository<Person, String> { @query(collation = "en_US") List<Person> findByFirstname(String firstname); @query(collation = "{ 'locale' : 'en_US' }") List<Person> findPersonByFirstname(String firstname); @query(collation = "?1") List<Person> findByFirstname(String firstname, Object collation); @query(collation = "{ 'locale' : '?1' }") List<Person> findByFirstname(String firstname, String collation); List<Person> findByFirstname(String firstname, Collation collation); }
1 parent c62171b commit b5818eb

15 files changed

+915
-16
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,36 @@
9595
* @since 2.1
9696
*/
9797
String sort() default "";
98+
99+
/**
100+
* Defines the collation to apply when executing the query. <br />
101+
*
102+
* <pre>
103+
* <code>
104+
*
105+
* // Fixed value
106+
* &#64;Query(collation = "en_US")
107+
* List<Entry> findAllByFixedCollation();
108+
*
109+
* // Fixed value as Document
110+
* &#64;Query(collation = "{ 'locale' : 'en_US' }")
111+
* List<Entry> findAllByFixedJsonCollation();
112+
*
113+
* // Dynamic value as String
114+
* &#64;Query(collation = "?0")
115+
* List<Entry> findAllByDynamicCollation(String collation);
116+
*
117+
* // Dynamic value as Document
118+
* &#64;Query(collation = "{ 'locale' : ?0 }")
119+
* List<Entry> findAllByDynamicJsonCollation(String collation);
120+
*
121+
* // SpEL expression
122+
* &#64;Query(collation = "?#{[0]}")
123+
* List<Entry> findAllByDynamicSpElCollation(String collation);
124+
* </code>
125+
* </pre>
126+
*
127+
* @since 2.2
128+
*/
129+
String collation() default "";
98130
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public Object execute(Object[] parameters) {
8686

8787
applyQueryMetaAttributesWhenPresent(query);
8888
query = applyAnnotatedDefaultSortIfPresent(query);
89+
query = applyAnnotatedCollationIfPresent(query, accessor);
8990

9091
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
9192
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
@@ -154,6 +155,21 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
154155
return QueryUtils.decorateSort(query, Document.parse(method.getAnnotatedSort()));
155156
}
156157

158+
/**
159+
* If present apply a {@link org.springframework.data.mongodb.core.query.Collation} derived from the
160+
* {@link org.springframework.data.repository.query.QueryMethod} the given {@link Query}.
161+
*
162+
* @param query must not be {@literal null}.
163+
* @param accessor the {@link ParameterAccessor} used to obtain parameter placeholder replacement values.
164+
* @return
165+
* @since 2.2
166+
*/
167+
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
168+
169+
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
170+
accessor);
171+
}
172+
157173
/**
158174
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
159175
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,14 @@ private Object executeDeferred(Object[] parameters) {
105105

106106
private Object execute(MongoParameterAccessor parameterAccessor) {
107107

108-
Query query = createQuery(new ConvertingParameterAccessor(operations.getConverter(), parameterAccessor));
108+
ConvertingParameterAccessor convertingParamterAccessor = new ConvertingParameterAccessor(operations.getConverter(), parameterAccessor);
109+
Query query = createQuery(convertingParamterAccessor);
109110

110111
applyQueryMetaAttributesWhenPresent(query);
111112
query = applyAnnotatedDefaultSortIfPresent(query);
113+
query = applyAnnotatedCollationIfPresent(query, convertingParamterAccessor);
112114

113-
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
115+
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(convertingParamterAccessor);
114116
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
115117

116118
FindWithQuery<?> find = typeToRead == null //
@@ -119,7 +121,7 @@ private Object execute(MongoParameterAccessor parameterAccessor) {
119121

120122
String collection = method.getEntityInformation().getCollectionName();
121123

122-
ReactiveMongoQueryExecution execution = getExecution(parameterAccessor,
124+
ReactiveMongoQueryExecution execution = getExecution(convertingParamterAccessor,
123125
new ResultProcessingConverter(processor, operations, instantiators), find);
124126

125127
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
@@ -195,6 +197,21 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
195197
return QueryUtils.decorateSort(query, Document.parse(method.getAnnotatedSort()));
196198
}
197199

200+
/**
201+
* If present apply a {@link org.springframework.data.mongodb.core.query.Collation} derived from the
202+
* {@link org.springframework.data.repository.query.QueryMethod} the given {@link Query}.
203+
*
204+
* @param query must not be {@literal null}.
205+
* @param accessor the {@link ParameterAccessor} used to obtain parameter placeholder replacement values.
206+
* @return
207+
* @since 2.2
208+
*/
209+
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
210+
211+
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
212+
accessor);
213+
}
214+
198215
/**
199216
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
200217
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.data.geo.Point;
3030
import org.springframework.data.mongodb.core.convert.MongoWriter;
3131
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
32+
import org.springframework.data.mongodb.core.query.Collation;
3233
import org.springframework.data.mongodb.core.query.TextCriteria;
3334
import org.springframework.data.repository.query.ParameterAccessor;
3435
import org.springframework.data.util.TypeInformation;
@@ -135,6 +136,15 @@ public TextCriteria getFullText() {
135136
return delegate.getFullText();
136137
}
137138

139+
/*
140+
* (non-Javadoc)
141+
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getCollation()
142+
*/
143+
@Override
144+
public Collation getCollation() {
145+
return delegate.getCollation();
146+
}
147+
138148
/**
139149
* Converts the given value with the underlying {@link MongoWriter}.
140150
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.data.domain.Range;
1919
import org.springframework.data.geo.Distance;
2020
import org.springframework.data.geo.Point;
21+
import org.springframework.data.mongodb.core.query.Collation;
2122
import org.springframework.data.mongodb.core.query.TextCriteria;
2223
import org.springframework.data.repository.query.ParameterAccessor;
2324
import org.springframework.lang.Nullable;
@@ -57,6 +58,15 @@ public interface MongoParameterAccessor extends ParameterAccessor {
5758
@Nullable
5859
TextCriteria getFullText();
5960

61+
/**
62+
* Returns the {@link Collation} to be used for the query.
63+
*
64+
* @return {@literal null} if not set.
65+
* @since 2.2
66+
*/
67+
@Nullable
68+
Collation getCollation();
69+
6070
/**
6171
* Returns the raw parameter values of the underlying query method.
6272
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.data.domain.Range;
2424
import org.springframework.data.geo.Distance;
2525
import org.springframework.data.geo.Point;
26+
import org.springframework.data.mongodb.core.query.Collation;
2627
import org.springframework.data.mongodb.core.query.TextCriteria;
2728
import org.springframework.data.mongodb.repository.Near;
2829
import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter;
@@ -45,12 +46,13 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
4546
private final int maxDistanceIndex;
4647
private final @Nullable Integer fullTextIndex;
4748
private final @Nullable Integer nearIndex;
49+
private final @Nullable Integer collationIndex;
4850

4951
/**
5052
* Creates a new {@link MongoParameters} instance from the given {@link Method} and {@link MongoQueryMethod}.
5153
*
5254
* @param method must not be {@literal null}.
53-
* @param queryMethod must not be {@literal null}.
55+
* @param isGeoNearMethod indicate if this is a geo spatial query method
5456
*/
5557
public MongoParameters(Method method, boolean isGeoNearMethod) {
5658

@@ -64,6 +66,7 @@ public MongoParameters(Method method, boolean isGeoNearMethod) {
6466

6567
this.rangeIndex = getTypeIndex(parameterTypeInfo, Range.class, Distance.class);
6668
this.maxDistanceIndex = this.rangeIndex == -1 ? getTypeIndex(parameterTypeInfo, Distance.class, null) : -1;
69+
this.collationIndex = getTypeIndex(parameterTypeInfo, Collation.class, null);
6770

6871
int index = findNearIndexInParameters(method);
6972
if (index == -1 && isGeoNearMethod) {
@@ -74,14 +77,15 @@ public MongoParameters(Method method, boolean isGeoNearMethod) {
7477
}
7578

7679
private MongoParameters(List<MongoParameter> parameters, int maxDistanceIndex, @Nullable Integer nearIndex,
77-
@Nullable Integer fullTextIndex, int rangeIndex) {
80+
@Nullable Integer fullTextIndex, int rangeIndex, @Nullable Integer collationIndex) {
7881

7982
super(parameters);
8083

8184
this.nearIndex = nearIndex;
8285
this.fullTextIndex = fullTextIndex;
8386
this.maxDistanceIndex = maxDistanceIndex;
8487
this.rangeIndex = rangeIndex;
88+
this.collationIndex = collationIndex;
8589
}
8690

8791
private final int getNearIndex(List<Class<?>> parameterTypes) {
@@ -111,11 +115,11 @@ private int findNearIndexInParameters(Method method) {
111115

112116
MongoParameter param = createParameter(MethodParameter.forParameter(p));
113117
if (param.isManuallyAnnotatedNearParameter()) {
114-
if(index == -1) {
118+
if (index == -1) {
115119
index = param.getIndex();
116120
} else {
117-
throw new IllegalStateException(String.format("Found multiple @Near annotations ond method %s! Only one allowed!",
118-
method.toString()));
121+
throw new IllegalStateException(
122+
String.format("Found multiple @Near annotations ond method %s! Only one allowed!", method.toString()));
119123
}
120124

121125
}
@@ -132,8 +136,6 @@ protected MongoParameter createParameter(MethodParameter parameter) {
132136
return new MongoParameter(parameter);
133137
}
134138

135-
136-
137139
public int getDistanceRangeIndex() {
138140
return -1;
139141
}
@@ -158,7 +160,7 @@ public int getNearIndex() {
158160
}
159161

160162
/**
161-
* Returns ths inde of the parameter to be used as a textquery param
163+
* Returns the index of the parameter to be used as a textquery param
162164
*
163165
* @return
164166
* @since 1.6
@@ -183,13 +185,24 @@ public int getRangeIndex() {
183185
return rangeIndex;
184186
}
185187

188+
/**
189+
* Returns the index of the {@link Collation} parameter or -1 if not present.
190+
*
191+
* @return -1 if not set.
192+
* @since 2.2
193+
*/
194+
public int getCollationParameterIndex() {
195+
return collationIndex != null ? collationIndex.intValue() : -1;
196+
}
197+
186198
/*
187199
* (non-Javadoc)
188200
* @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List)
189201
*/
190202
@Override
191203
protected MongoParameters createFrom(List<MongoParameter> parameters) {
192-
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex);
204+
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex,
205+
this.collationIndex);
193206
}
194207

195208
private int getTypeIndex(List<TypeInformation<?>> parameterTypes, Class<?> type, @Nullable Class<?> componentType) {
@@ -241,7 +254,7 @@ class MongoParameter extends Parameter {
241254
@Override
242255
public boolean isSpecialParameter() {
243256
return super.isSpecialParameter() || Distance.class.isAssignableFrom(getType()) || isNearParameter()
244-
|| TextCriteria.class.isAssignableFrom(getType());
257+
|| TextCriteria.class.isAssignableFrom(getType()) || Collation.class.isAssignableFrom(getType());
245258
}
246259

247260
private boolean isNearParameter() {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.domain.Range.Bound;
2323
import org.springframework.data.geo.Distance;
2424
import org.springframework.data.geo.Point;
25+
import org.springframework.data.mongodb.core.query.Collation;
2526
import org.springframework.data.mongodb.core.query.Term;
2627
import org.springframework.data.mongodb.core.query.TextCriteria;
2728
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -67,7 +68,8 @@ public Range<Distance> getDistanceRange() {
6768
}
6869

6970
int maxDistanceIndex = mongoParameters.getMaxDistanceIndex();
70-
Bound<Distance> maxDistance = maxDistanceIndex == -1 ? Bound.unbounded() : Bound.inclusive((Distance) getValue(maxDistanceIndex));
71+
Bound<Distance> maxDistance = maxDistanceIndex == -1 ? Bound.unbounded()
72+
: Bound.inclusive((Distance) getValue(maxDistanceIndex));
7173

7274
return Range.of(Bound.unbounded(), maxDistance);
7375
}
@@ -134,6 +136,20 @@ protected TextCriteria potentiallyConvertFullText(Object fullText) {
134136
ClassUtils.getShortName(fullText.getClass())));
135137
}
136138

139+
/*
140+
* (non-Javadoc)
141+
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getCollation()
142+
*/
143+
@Override
144+
public Collation getCollation() {
145+
146+
if (method.getParameters().getCollationParameterIndex() == -1) {
147+
return null;
148+
}
149+
150+
return getValue(method.getParameters().getCollationParameterIndex());
151+
}
152+
137153
/*
138154
* (non-Javadoc)
139155
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getValues()

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,30 @@ public String getAnnotatedSort() {
322322
"Expected to find @Query annotation but did not. Make sure to check hasAnnotatedSort() before."));
323323
}
324324

325+
/**
326+
* Check if the query method is decorated with an non empty {@link Query#collation()}.
327+
*
328+
* @return true if method annotated with {@link Query} having an non empty collation attribute.
329+
* @since 2.2
330+
*/
331+
public boolean hasAnnotatedCollation() {
332+
return lookupQueryAnnotation().map(it -> !it.collation().isEmpty()).orElse(false);
333+
}
334+
335+
/**
336+
* Get the collation value extracted from the {@link Query} annotation.
337+
*
338+
* @return the {@link Query#sort()} value.
339+
* @throws IllegalStateException if method not annotated with {@link Query}. Make sure to check
340+
* {@link #hasAnnotatedQuery()} first.
341+
* @since 2.2
342+
*/
343+
public String getAnnotatedCollation() {
344+
345+
return lookupQueryAnnotation().map(Query::collation).orElseThrow(() -> new IllegalStateException(
346+
"Expected to find @Query annotation but did not. Make sure to check hasAnnotatedCollation() before."));
347+
}
348+
325349
@SuppressWarnings("unchecked")
326350
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
327351

0 commit comments

Comments
 (0)