Skip to content

Commit 1839f55

Browse files
committed
Polishing.
Introduce HintFunction to encapsulate how hints are applied and to remove code duplications. See #4238 Original pull request: #4243
1 parent 4220df5 commit 1839f55

File tree

6 files changed

+133
-97
lines changed

6 files changed

+133
-97
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core;
17+
18+
import java.util.function.Function;
19+
20+
import org.bson.conversions.Bson;
21+
import org.springframework.data.mongodb.CodecRegistryProvider;
22+
import org.springframework.data.mongodb.util.BsonUtils;
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.util.StringUtils;
25+
26+
/**
27+
* Function object to apply a query hint. Can be an index name or a BSON document.
28+
*
29+
* @author Mark Paluch
30+
* @since 4.1
31+
*/
32+
class HintFunction {
33+
34+
private static final HintFunction EMPTY = new HintFunction(null);
35+
36+
private final @Nullable Object hint;
37+
38+
private HintFunction(@Nullable Object hint) {
39+
this.hint = hint;
40+
}
41+
42+
/**
43+
* Return an empty hint function.
44+
*
45+
* @return
46+
*/
47+
static HintFunction empty() {
48+
return EMPTY;
49+
}
50+
51+
/**
52+
* Create a {@link HintFunction} from a {@link Bson document} or {@link String index name}.
53+
*
54+
* @param hint
55+
* @return
56+
*/
57+
static HintFunction from(@Nullable Object hint) {
58+
return new HintFunction(hint);
59+
}
60+
61+
/**
62+
* Return whether a hint is present.
63+
*
64+
* @return
65+
*/
66+
public boolean isPresent() {
67+
return (hint instanceof String hintString && StringUtils.hasText(hintString)) || hint instanceof Bson;
68+
}
69+
70+
/**
71+
* Apply the hint to consumers depending on the hint format.
72+
*
73+
* @param registryProvider
74+
* @param stringConsumer
75+
* @param bsonConsumer
76+
* @return
77+
* @param <R>
78+
*/
79+
public <R> R apply(@Nullable CodecRegistryProvider registryProvider, Function<String, R> stringConsumer,
80+
Function<Bson, R> bsonConsumer) {
81+
82+
if (!isPresent()) {
83+
throw new IllegalStateException("No hint present");
84+
}
85+
86+
if (hint instanceof Bson bson) {
87+
return bsonConsumer.apply(bson);
88+
}
89+
90+
if (hint instanceof String hintString) {
91+
92+
if (BsonUtils.isJsonDocument(hintString)) {
93+
return bsonConsumer.apply(BsonUtils.parse(hintString, registryProvider));
94+
}
95+
return stringConsumer.apply(hintString);
96+
}
97+
98+
throw new IllegalStateException(
99+
"Unable to read hint of type %s".formatted(hint != null ? hint.getClass() : "null"));
100+
}
101+
102+
}

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

+11-48
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,7 @@
6969
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
7070
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
7171
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
72-
import org.springframework.data.mongodb.core.convert.DbRefResolver;
73-
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
74-
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
75-
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
76-
import org.springframework.data.mongodb.core.convert.MongoConverter;
77-
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
78-
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
79-
import org.springframework.data.mongodb.core.convert.MongoWriter;
80-
import org.springframework.data.mongodb.core.convert.QueryMapper;
81-
import org.springframework.data.mongodb.core.convert.UpdateMapper;
72+
import org.springframework.data.mongodb.core.convert.*;
8273
import org.springframework.data.mongodb.core.index.IndexOperations;
8374
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
8475
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
@@ -99,7 +90,6 @@
9990
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
10091
import org.springframework.data.mongodb.core.timeseries.Granularity;
10192
import org.springframework.data.mongodb.core.validation.Validator;
102-
import org.springframework.data.mongodb.util.BsonUtils;
10393
import org.springframework.data.projection.EntityProjection;
10494
import org.springframework.data.util.CloseableIterator;
10595
import org.springframework.data.util.Optionals;
@@ -116,16 +106,7 @@
116106
import com.mongodb.MongoException;
117107
import com.mongodb.ReadPreference;
118108
import com.mongodb.WriteConcern;
119-
import com.mongodb.client.AggregateIterable;
120-
import com.mongodb.client.ClientSession;
121-
import com.mongodb.client.DistinctIterable;
122-
import com.mongodb.client.FindIterable;
123-
import com.mongodb.client.MapReduceIterable;
124-
import com.mongodb.client.MongoClient;
125-
import com.mongodb.client.MongoCollection;
126-
import com.mongodb.client.MongoCursor;
127-
import com.mongodb.client.MongoDatabase;
128-
import com.mongodb.client.MongoIterable;
109+
import com.mongodb.client.*;
129110
import com.mongodb.client.model.*;
130111
import com.mongodb.client.result.DeleteResult;
131112
import com.mongodb.client.result.UpdateResult;
@@ -2067,15 +2048,9 @@ protected <O> AggregationResults<O> doAggregate(Aggregation aggregation, String
20672048
}
20682049

20692050
options.getComment().ifPresent(aggregateIterable::comment);
2070-
if (options.getHintObject().isPresent()) {
2071-
Object hintObject = options.getHintObject().get();
2072-
if (hintObject instanceof String hintString) {
2073-
aggregateIterable = aggregateIterable.hintString(hintString);
2074-
} else if (hintObject instanceof Document hintDocument) {
2075-
aggregateIterable = aggregateIterable.hint(hintDocument);
2076-
} else {
2077-
throw new IllegalStateException("Unable to read hint of type %s".formatted(hintObject.getClass()));
2078-
}
2051+
HintFunction hintFunction = options.getHintObject().map(HintFunction::from).orElseGet(HintFunction::empty);
2052+
if (hintFunction.isPresent()) {
2053+
aggregateIterable = hintFunction.apply(mongoDbFactory, aggregateIterable::hintString, aggregateIterable::hint);
20792054
}
20802055

20812056
if (options.hasExecutionTimeLimit()) {
@@ -2135,15 +2110,9 @@ protected <O> Stream<O> aggregateStream(Aggregation aggregation, String collecti
21352110
}
21362111

21372112
options.getComment().ifPresent(cursor::comment);
2113+
HintFunction hintFunction = options.getHintObject().map(HintFunction::from).orElseGet(HintFunction::empty);
21382114
if (options.getHintObject().isPresent()) {
2139-
Object hintObject = options.getHintObject().get();
2140-
if (hintObject instanceof String hintString) {
2141-
cursor = cursor.hintString(hintString);
2142-
} else if (hintObject instanceof Document hintDocument) {
2143-
cursor = cursor.hint(hintDocument);
2144-
} else {
2145-
throw new IllegalStateException("Unable to read hint of type %s".formatted(hintObject.getClass()));
2146-
}
2115+
cursor = hintFunction.apply(mongoDbFactory, cursor::hintString, cursor::hint);
21472116
}
21482117

21492118
Class<?> domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType()
@@ -3172,8 +3141,9 @@ public FindIterable<Document> prepare(FindIterable<Document> iterable) {
31723141
.ifPresent(cursorToUse::collation);
31733142

31743143
Meta meta = query.getMeta();
3144+
HintFunction hintFunction = HintFunction.from(query.getHint());
31753145
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
3176-
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues() && !query.getCollation().isPresent()) {
3146+
&& !hintFunction.isPresent() && !meta.hasValues() && !query.getCollation().isPresent()) {
31773147
return cursorToUse;
31783148
}
31793149

@@ -3189,15 +3159,8 @@ public FindIterable<Document> prepare(FindIterable<Document> iterable) {
31893159
cursorToUse = cursorToUse.sort(sort);
31903160
}
31913161

3192-
if (StringUtils.hasText(query.getHint())) {
3193-
3194-
String hint = query.getHint();
3195-
3196-
if (BsonUtils.isJsonDocument(hint)) {
3197-
cursorToUse = cursorToUse.hint(BsonUtils.parse(hint, mongoDbFactory));
3198-
} else {
3199-
cursorToUse = cursorToUse.hintString(hint);
3200-
}
3162+
if (hintFunction.isPresent()) {
3163+
cursorToUse = hintFunction.apply(mongoDbFactory, cursorToUse::hintString, cursorToUse::hint);
32013164
}
32023165

32033166
if (meta.hasValues()) {

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

+4-8
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
import org.springframework.data.util.Lazy;
6161
import org.springframework.lang.Nullable;
6262
import org.springframework.util.ClassUtils;
63-
import org.springframework.util.StringUtils;
6463

6564
import com.mongodb.client.model.CountOptions;
6665
import com.mongodb.client.model.DeleteOptions;
@@ -567,14 +566,11 @@ CountOptions getCountOptions(@Nullable Class<?> domainType, @Nullable Consumer<C
567566
if (query.getSkip() > 0) {
568567
options.skip((int) query.getSkip());
569568
}
570-
if (StringUtils.hasText(query.getHint())) {
571569

572-
String hint = query.getHint();
573-
if (BsonUtils.isJsonDocument(hint)) {
574-
options.hint(BsonUtils.parse(hint, codecRegistryProvider));
575-
} else {
576-
options.hintString(hint);
577-
}
570+
HintFunction hintFunction = HintFunction.from(query.getHint());
571+
572+
if (hintFunction.isPresent()) {
573+
options = hintFunction.apply(codecRegistryProvider, options::hintString, options::hint);
578574
}
579575

580576
if (callback != null) {

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

+11-21
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@
110110
import org.springframework.data.mongodb.core.query.Query;
111111
import org.springframework.data.mongodb.core.query.UpdateDefinition;
112112
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
113-
import org.springframework.data.mongodb.util.BsonUtils;
114113
import org.springframework.data.projection.EntityProjection;
115114
import org.springframework.data.util.Optionals;
116115
import org.springframework.lang.Nullable;
@@ -938,15 +937,10 @@ private <O> Flux<O> aggregateAndMap(MongoCollection<Document> collection, List<D
938937
}
939938

940939
options.getComment().ifPresent(cursor::comment);
941-
if (options.getHintObject().isPresent()) {
942-
Object hintObject = options.getHintObject().get();
943-
if (hintObject instanceof String hintString) {
944-
cursor = cursor.hintString(hintString);
945-
} else if (hintObject instanceof Document hintDocument) {
946-
cursor = cursor.hint(hintDocument);
947-
} else {
948-
throw new IllegalStateException("Unable to read hint of type %s".formatted(hintObject.getClass()));
949-
}
940+
941+
HintFunction hintFunction = options.getHintObject().map(HintFunction::from).orElseGet(HintFunction::empty);
942+
if (hintFunction.isPresent()) {
943+
cursor = hintFunction.apply(mongoDatabaseFactory, cursor::hintString, cursor::hint);
950944
}
951945

952946
Optionals.firstNonEmpty(options::getCollation, () -> operations.forType(inputType).getCollation()) //
@@ -1535,7 +1529,8 @@ protected Mono<Object> saveDocument(String collectionName, Document document, Cl
15351529

15361530
Publisher<?> publisher;
15371531
if (!mapped.hasId()) {
1538-
publisher = collectionToUse.insertOne(queryOperations.createInsertContext(mapped).prepareId(entityClass).getDocument());
1532+
publisher = collectionToUse
1533+
.insertOne(queryOperations.createInsertContext(mapped).prepareId(entityClass).getDocument());
15391534
} else {
15401535

15411536
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
@@ -3044,9 +3039,10 @@ public FindPublisher<Document> prepare(FindPublisher<Document> findPublisher) {
30443039
.map(findPublisher::collation) //
30453040
.orElse(findPublisher);
30463041

3042+
HintFunction hintFunction = HintFunction.from(query.getHint());
30473043
Meta meta = query.getMeta();
30483044
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
3049-
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues()) {
3045+
&& !hintFunction.isPresent() && !meta.hasValues()) {
30503046
return findPublisherToUse;
30513047
}
30523048

@@ -3065,15 +3061,9 @@ public FindPublisher<Document> prepare(FindPublisher<Document> findPublisher) {
30653061
findPublisherToUse = findPublisherToUse.sort(sort);
30663062
}
30673063

3068-
if (StringUtils.hasText(query.getHint())) {
3069-
3070-
String hint = query.getHint();
3071-
3072-
if (BsonUtils.isJsonDocument(hint)) {
3073-
findPublisherToUse = findPublisherToUse.hint(BsonUtils.parse(hint, mongoDatabaseFactory));
3074-
} else {
3075-
findPublisherToUse = findPublisherToUse.hintString(hint);
3076-
}
3064+
if (hintFunction.isPresent()) {
3065+
findPublisherToUse = hintFunction.apply(mongoDatabaseFactory, findPublisherToUse::hintString,
3066+
findPublisherToUse::hint);
30773067
}
30783068

30793069
if (meta.hasValues()) {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java

+3-20
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,11 @@ public Optional<String> getComment() {
237237
}
238238

239239
/**
240-
* Get the hint used to to fulfill the aggregation.
240+
* Get the hint used to fulfill the aggregation.
241241
*
242242
* @return never {@literal null}.
243243
* @since 3.1
244+
* @deprecated since 4.1, use {@link #getHintObject()} instead.
244245
*/
245246
public Optional<Document> getHint() {
246247
return hint.map(it -> {
@@ -257,25 +258,7 @@ public Optional<Document> getHint() {
257258
}
258259

259260
/**
260-
* Get the hint (indexName) used to to fulfill the aggregation.
261-
*
262-
* @return never {@literal null}.
263-
* @since 4.1
264-
*/
265-
public Optional<String> getHintAsString() {
266-
return hint.map(it -> {
267-
if (it instanceof String hintString) {
268-
return hintString;
269-
}
270-
if (it instanceof Document doc) {
271-
return BsonUtils.toJson(doc);
272-
}
273-
throw new IllegalStateException("Unable to read hint of type %s".formatted(it.getClass()));
274-
});
275-
}
276-
277-
/**
278-
* Get the hint used to to fulfill the aggregation.
261+
* Get the hint used to fulfill the aggregation.
279262
*
280263
* @return never {@literal null}.
281264
* @since 4.1

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void aggregationOptionsBuilderShouldSetOptionsAccordingly() {
5353
assertThat(aggregationOptions.isExplain()).isTrue();
5454
assertThat(aggregationOptions.getCursor()).contains(new Document("batchSize", 1));
5555
assertThat(aggregationOptions.getHint()).contains(dummyHint);
56+
assertThat(aggregationOptions.getHintObject()).contains(dummyHint);
5657
}
5758

5859
@Test // DATAMONGO-1637, DATAMONGO-2153, DATAMONGO-1836
@@ -73,6 +74,7 @@ void shouldInitializeFromDocument() {
7374
assertThat(aggregationOptions.getCursorBatchSize()).isEqualTo(1);
7475
assertThat(aggregationOptions.getComment()).contains("hola");
7576
assertThat(aggregationOptions.getHint()).contains(dummyHint);
77+
assertThat(aggregationOptions.getHintObject()).contains(dummyHint);
7678
}
7779

7880
@Test // DATAMONGO-960, DATAMONGO-2153, DATAMONGO-1836

0 commit comments

Comments
 (0)