Skip to content

Commit 6fe3e67

Browse files
committed
DATAMONGO-517 - Fixed complex keyword handling.
Introduced intermediate getMappedKeyword(Keyword keyword, MongoPersistentProperty property) to correctly return a DBObject for keyword plus converted value. A few refactorings and improvements in the implementation of QueryMapper (Keyword value object etc.).
1 parent 3b78034 commit 6fe3e67

File tree

2 files changed

+86
-44
lines changed

2 files changed

+86
-44
lines changed

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

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright (c) 2011 by the original author(s).
2+
* Copyright 2011-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -19,7 +19,6 @@
1919
import java.util.Arrays;
2020
import java.util.List;
2121

22-
import org.bson.types.BasicBSONList;
2322
import org.bson.types.ObjectId;
2423
import org.springframework.core.convert.ConversionException;
2524
import org.springframework.core.convert.ConversionService;
@@ -35,11 +34,12 @@
3534
import com.mongodb.BasicDBList;
3635
import com.mongodb.BasicDBObject;
3736
import com.mongodb.DBObject;
37+
import com.mongodb.DBRef;
3838

3939
/**
4040
* A helper class to encapsulate any modifications of a Query object before it gets submitted to the database.
4141
*
42-
* @author Jon Brisbin <[email protected]>
42+
* @author Jon Brisbin
4343
* @author Oliver Gierke
4444
*/
4545
public class QueryMapper {
@@ -75,8 +75,8 @@ public QueryMapper(MongoConverter converter) {
7575
*/
7676
public DBObject getMappedObject(DBObject query, MongoPersistentEntity<?> entity) {
7777

78-
if (isKeyWord(query)) {
79-
return getMappedKeyword(query, entity);
78+
if (Keyword.isKeyword(query)) {
79+
return getMappedKeyword(new Keyword(query), entity);
8080
}
8181

8282
DBObject result = new BasicDBObject();
@@ -100,25 +100,38 @@ public DBObject getMappedObject(DBObject query, MongoPersistentEntity<?> entity)
100100
* @param entity
101101
* @return
102102
*/
103-
private DBObject getMappedKeyword(DBObject query, MongoPersistentEntity<?> entity) {
104-
105-
String newKey = query.keySet().iterator().next();
106-
Object value = query.get(newKey);
103+
private DBObject getMappedKeyword(Keyword query, MongoPersistentEntity<?> entity) {
107104

108105
// $or/$nor
109-
if (newKey.matches(N_OR_PATTERN)) {
106+
if (query.key.matches(N_OR_PATTERN)) {
110107

111-
Iterable<?> conditions = (Iterable<?>) value;
108+
Iterable<?> conditions = (Iterable<?>) query.value;
112109
BasicDBList newConditions = new BasicDBList();
113110

114111
for (Object condition : conditions) {
115112
newConditions.add(getMappedObject((DBObject) condition, entity));
116113
}
117114

118-
return new BasicDBObject(newKey, newConditions);
115+
return new BasicDBObject(query.key, newConditions);
119116
}
120117

121-
return new BasicDBObject(newKey, convertSimpleOrDBObject(value, entity));
118+
return new BasicDBObject(query.key, convertSimpleOrDBObject(query.value, entity));
119+
}
120+
121+
/**
122+
* Returns the mapped keyword considered defining a criteria for the given property.
123+
*
124+
* @param keyword
125+
* @param property
126+
* @return
127+
*/
128+
public DBObject getMappedKeyword(Keyword keyword, MongoPersistentProperty property) {
129+
130+
if (property.isAssociation()) {
131+
convertAssociation(keyword.value, property);
132+
}
133+
134+
return new BasicDBObject(keyword.key, getMappedValue(keyword.value, property, keyword.key));
122135
}
123136

124137
/**
@@ -161,7 +174,7 @@ private Object getMappedValue(Object source, MongoPersistentProperty property, S
161174
}
162175

163176
if (property.isAssociation()) {
164-
return isKeyWord(source) ? getMappedValue(getKeywordValue(source), property, newKey) : convertAssociation(source,
177+
return Keyword.isKeyword(source) ? getMappedKeyword(new Keyword(source), property) : convertAssociation(source,
165178
property);
166179
}
167180

@@ -243,14 +256,14 @@ private Object convertAssociation(Object source, MongoPersistentProperty propert
243256
}
244257

245258
if (source instanceof Iterable) {
246-
BasicBSONList result = new BasicBSONList();
259+
BasicDBList result = new BasicDBList();
247260
for (Object element : (Iterable<?>) source) {
248-
result.add(converter.toDBRef(element, property));
261+
result.add(element instanceof DBRef ? element : converter.toDBRef(element, property));
249262
}
250263
return result;
251264
}
252265

253-
return converter.toDBRef(source, property);
266+
return source instanceof DBRef ? source : converter.toDBRef(source, property);
254267
}
255268

256269
/**
@@ -276,47 +289,59 @@ private boolean isIdKey(String key, MongoPersistentEntity<?> entity) {
276289
}
277290

278291
/**
279-
* Returns whether the given value is representing a query keyword.
292+
* Converts the given raw id value into either {@link ObjectId} or {@link String}.
280293
*
281-
* @param value
294+
* @param id
282295
* @return
283296
*/
284-
private static boolean isKeyWord(Object value) {
297+
public Object convertId(Object id) {
285298

286-
if (!(value instanceof DBObject) || value instanceof BasicDBList) {
287-
return false;
299+
try {
300+
return conversionService.convert(id, ObjectId.class);
301+
} catch (ConversionException e) {
302+
// Ignore
288303
}
289304

290-
DBObject dbObject = (DBObject) value;
291-
return dbObject.keySet().size() == 1 && dbObject.keySet().iterator().next().startsWith("$");
305+
return converter.convertToMongoType(id);
292306
}
293307

294308
/**
295-
* Returns the value of the given source assuming it's a query keyword.
309+
* Value object to capture a query keyword representation.
296310
*
297-
* @param source
298-
* @return
311+
* @author Oliver Gierke
299312
*/
300-
private static Object getKeywordValue(Object source) {
313+
private static class Keyword {
301314

302-
DBObject dbObject = (DBObject) source;
303-
return dbObject.get(dbObject.keySet().iterator().next());
304-
}
315+
String key;
316+
Object value;
305317

306-
/**
307-
* Converts the given raw id value into either {@link ObjectId} or {@link String}.
308-
*
309-
* @param id
310-
* @return
311-
*/
312-
public Object convertId(Object id) {
318+
Keyword(Object source) {
313319

314-
try {
315-
return conversionService.convert(id, ObjectId.class);
316-
} catch (ConversionException e) {
317-
// Ignore
320+
Assert.isInstanceOf(DBObject.class, source);
321+
322+
DBObject value = (DBObject) source;
323+
324+
Assert.isTrue(value.keySet().size() == 1, "Keyword must have a single key only!");
325+
326+
this.key = value.keySet().iterator().next();
327+
this.value = value.get(key);
318328
}
319329

320-
return converter.convertToMongoType(id);
330+
/**
331+
* Returns whether the given value actually represents a keyword. If this returns {@literal true} it's safe to call
332+
* the constructor.
333+
*
334+
* @param value
335+
* @return
336+
*/
337+
static boolean isKeyword(Object value) {
338+
339+
if (!(value instanceof DBObject)) {
340+
return false;
341+
}
342+
343+
DBObject dbObject = (DBObject) value;
344+
return dbObject.keySet().size() == 1 && dbObject.keySet().iterator().next().startsWith("$");
345+
}
321346
}
322347
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.mockito.runners.MockitoJUnitRunner;
3535
import org.springframework.data.annotation.Id;
3636
import org.springframework.data.mongodb.MongoDbFactory;
37+
import org.springframework.data.mongodb.core.DBObjectUtils;
3738
import org.springframework.data.mongodb.core.Person;
3839
import org.springframework.data.mongodb.core.mapping.DBRef;
3940
import org.springframework.data.mongodb.core.mapping.Field;
@@ -318,6 +319,22 @@ public void convertsNestedAssociationCorrectly() {
318319
assertThat(referenceObject, is(instanceOf(com.mongodb.DBRef.class)));
319320
}
320321

322+
@Test
323+
public void convertsInKeywordCorrectly() {
324+
325+
Reference first = new Reference();
326+
first.id = 5L;
327+
328+
Reference second = new Reference();
329+
second.id = 6L;
330+
331+
Query query = query(where("reference").in(first, second));
332+
DBObject result = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(WithDBRef.class));
333+
334+
DBObject reference = DBObjectUtils.getAsDBObject(result, "reference");
335+
assertThat(reference.containsField("$in"), is(true));
336+
}
337+
321338
class IdWrapper {
322339
Object id;
323340
}

0 commit comments

Comments
 (0)