Skip to content

Commit 160de0a

Browse files
committed
DATAMONGO-1361 - Guard command result statistics evaluation against changes in MongoDB 3.2.
MongoDB 3.2 RC1 decided to remove fields from statistics JSON documents returned in case no result was found for a geo near query. The avgDistance field is unfortunately missing as of that version. Introduced a value object to encapsulate the mitigation behavior and make client code unaware of that.
1 parent b4753f3 commit 160de0a

File tree

3 files changed

+139
-3
lines changed

3 files changed

+139
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2016 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+
* http://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 org.springframework.util.Assert;
19+
20+
import com.mongodb.BasicDBObject;
21+
import com.mongodb.DBObject;
22+
23+
/**
24+
* Value object to mitigate different representations of geo command execution results in MongoDB.
25+
*
26+
* @author Oliver Gierke
27+
* @soundtrack Fruitcake - Jeff Coffin (The Inside of the Outside)
28+
*/
29+
class GeoCommandStatistics {
30+
31+
private static final GeoCommandStatistics NONE = new GeoCommandStatistics(new BasicDBObject());
32+
33+
private final DBObject source;
34+
35+
/**
36+
* Creates a new {@link GeoCommandStatistics} instance with the given source document.
37+
*
38+
* @param source must not be {@literal null}.
39+
*/
40+
private GeoCommandStatistics(DBObject source) {
41+
42+
Assert.notNull(source, "Source document must not be null!");
43+
this.source = source;
44+
}
45+
46+
/**
47+
* Creates a new {@link GeoCommandStatistics} from the given command result extracting the statistics.
48+
*
49+
* @param commandResult must not be {@literal null}.
50+
* @return
51+
*/
52+
public static GeoCommandStatistics from(DBObject commandResult) {
53+
54+
Assert.notNull(commandResult, "Command result must not be null!");
55+
56+
Object stats = commandResult.get("stats");
57+
return stats == null ? NONE : new GeoCommandStatistics((DBObject) stats);
58+
}
59+
60+
/**
61+
* Returns the average distance reported by the command result. Mitigating a removal of the field in case the command
62+
* didn't return any result introduced in MongoDB 3.2 RC1.
63+
*
64+
* @return
65+
* @see https://jira.mongodb.org/browse/SERVER-21024
66+
*/
67+
public double getAverageDistance() {
68+
69+
Object averageDistance = source.get("avgDistance");
70+
return averageDistance == null ? Double.NaN : (Double) averageDistance;
71+
}
72+
}

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -694,9 +694,8 @@ public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass, String co
694694
return new GeoResults<T>(result, near.getMetric());
695695
}
696696

697-
DBObject stats = (DBObject) commandResult.get("stats");
698-
double averageDistance = stats == null ? 0 : (Double) stats.get("avgDistance");
699-
return new GeoResults<T>(result, new Distance(averageDistance, near.getMetric()));
697+
GeoCommandStatistics stats = GeoCommandStatistics.from(commandResult);
698+
return new GeoResults<T>(result, new Distance(stats.getAverageDistance(), near.getMetric()));
700699
}
701700

702701
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2016 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+
* http://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 static org.hamcrest.CoreMatchers.*;
19+
import static org.junit.Assert.*;
20+
21+
import org.junit.Test;
22+
23+
import com.mongodb.BasicDBObject;
24+
25+
/**
26+
* Unit tests for {@link GeoCommandStatistics}.
27+
*
28+
* @author Oliver Gierke
29+
* @soundtrack Fruitcake - Jeff Coffin (The Inside of the Outside)
30+
*/
31+
public class GeoCommandStatisticsUnitTests {
32+
33+
/**
34+
* @see DATAMONGO-1361
35+
*/
36+
@Test(expected = IllegalArgumentException.class)
37+
public void rejectsNullCommandResult() {
38+
GeoCommandStatistics.from(null);
39+
}
40+
41+
/**
42+
* @see DATAMONGO-1361
43+
*/
44+
@Test
45+
public void fallsBackToNanIfNoAverageDistanceIsAvailable() {
46+
47+
GeoCommandStatistics statistics = GeoCommandStatistics.from(new BasicDBObject("stats", null));
48+
assertThat(statistics.getAverageDistance(), is(Double.NaN));
49+
50+
statistics = GeoCommandStatistics.from(new BasicDBObject("stats", new BasicDBObject()));
51+
assertThat(statistics.getAverageDistance(), is(Double.NaN));
52+
}
53+
54+
/**
55+
* @see DATAMONGO-1361
56+
*/
57+
@Test
58+
public void returnsAverageDistanceIfPresent() {
59+
60+
GeoCommandStatistics statistics = GeoCommandStatistics
61+
.from(new BasicDBObject("stats", new BasicDBObject("avgDistance", 1.5)));
62+
63+
assertThat(statistics.getAverageDistance(), is(1.5));
64+
}
65+
}

0 commit comments

Comments
 (0)