diff --git a/pom.xml b/pom.xml index 5cda32baa9..08197d2743 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-1181-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index c7610beee4..cc44cb5c1d 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-1181-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-1181-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 13110137b6..3d853461c8 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-1181-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index 6ff09e4577..868ed6352f 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-1181-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 7c2d818c42..7cbf4f45b8 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATAMONGO-1181-SNAPSHOT ../pom.xml @@ -144,6 +144,13 @@ ${threetenbp} true + + + com.fasterxml.jackson.core + jackson-databind + ${jackson} + true + org.slf4j diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonModule.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonModule.java new file mode 100644 index 0000000000..e7e3879d4c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonModule.java @@ -0,0 +1,336 @@ +/* + * Copyright 2015 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 + * + * http://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.geo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.geo.GeoModule; +import org.springframework.data.geo.Point; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * @author Christoph Strobl + * @since 1.7 + */ +public class GeoJsonModule extends GeoModule { + + public GeoJsonModule() { + super(); + + addDeserializer(GeoJsonPoint.class, new GeoJsonPointDeserializer()); + addDeserializer(GeoJsonMultiPoint.class, new GeoJsonMultiPointDeserializer()); + addDeserializer(GeoJsonLineString.class, new GeoJsonLineStringDeserializer()); + addDeserializer(GeoJsonMultiLineString.class, new GeoJsonMultiLineStringDeserializer()); + addDeserializer(GeoJsonPolygon.class, new GeoJsonPolygonDeserializer()); + addDeserializer(GeoJsonMultiPolygon.class, new GeoJsonMultiPolygonDeserializer()); + } + + /** + * @author Christoph Strobl + * @since 1.7 + */ + static abstract class GeoJsonDeserializer> extends JsonDeserializer { + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext) + */ + @Override + public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + + JsonNode node = jp.readValueAsTree(); + JsonNode coordinates = node.get("coordinates"); + + if (coordinates != null && coordinates.isArray()) { + return doDeserialize((ArrayNode) coordinates); + } + return null; + } + + /** + * Perform the actual deserialization given the {@literal coordinates} as {@link ArrayNode}. + * + * @param coordinates + * @return + */ + protected abstract T doDeserialize(ArrayNode coordinates); + + /** + * Get the {@link GeoJsonPoint} representation of given {@link ArrayNode} assuming {@code node.[0]} represents + * {@literal x - coordinate} and {@code node.[1]} is {@literal y}. + * + * @param node can be {@literal null}. + * @return {@literal null} when given a {@code null} value. + */ + protected GeoJsonPoint toGeoJsonPoint(ArrayNode node) { + + if (node == null) { + return null; + } + + return new GeoJsonPoint(node.get(0).asDouble(), node.get(1).asDouble()); + } + + /** + * Get the {@link Point} representation of given {@link ArrayNode} assuming {@code node.[0]} represents + * {@literal x - coordinate} and {@code node.[1]} is {@literal y}. + * + * @param node can be {@literal null}. + * @return {@literal null} when given a {@code null} value. + */ + protected Point toPoint(ArrayNode node) { + + if (node == null) { + return null; + } + + return new Point(node.get(0).asDouble(), node.get(1).asDouble()); + } + + /** + * Get the points nested within given {@link ArrayNode}. + * + * @param node can be {@literal null}. + * @return {@literal empty list} when given a {@code null} value. + */ + protected List toPoints(ArrayNode node) { + + if (node == null) { + return Collections.emptyList(); + } + + List points = new ArrayList(node.size()); + + for (JsonNode coordinatePair : node) { + if (coordinatePair.isArray()) { + points.add(toPoint((ArrayNode) coordinatePair)); + } + } + return points; + } + + protected GeoJsonLineString toLineString(ArrayNode node) { + return new GeoJsonLineString(toPoints((ArrayNode) node)); + } + } + + /** + * {@link JsonDeserializer} converting GeoJSON representation of {@literal Point}. + * + *
+	 * 
+	 * { "type": "Point", "coordinates": [10.0, 20.0] }
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.7 + */ + static class GeoJsonPointDeserializer extends GeoJsonDeserializer { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode) + */ + @Override + protected GeoJsonPoint doDeserialize(ArrayNode coordinates) { + return toGeoJsonPoint(coordinates); + } + } + + /** + * {@link JsonDeserializer} converting GeoJSON representation of {@literal LineString}. + * + *
+	 * 
+	 * { 
+	 *   "type": "LineString", 
+	 *   "coordinates": [ 
+	 *     [10.0, 20.0], [30.0, 40.0], [50.0, 60.0]
+	 *   ]
+	 * }
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.7 + */ + static class GeoJsonLineStringDeserializer extends GeoJsonDeserializer { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode) + */ + @Override + protected GeoJsonLineString doDeserialize(ArrayNode coordinates) { + return new GeoJsonLineString(toPoints(coordinates)); + } + } + + /** + * {@link JsonDeserializer} converting GeoJSON representation of {@literal MultiPoint}. + * + *
+	 * 
+	 * { 
+	 *   "type": "MultiPoint", 
+	 *   "coordinates": [ 
+	 *     [10.0, 20.0], [30.0, 40.0], [50.0, 60.0]
+	 *   ]
+	 * }
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.7 + */ + static class GeoJsonMultiPointDeserializer extends GeoJsonDeserializer { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode) + */ + @Override + protected GeoJsonMultiPoint doDeserialize(ArrayNode coordinates) { + return new GeoJsonMultiPoint(toPoints(coordinates)); + } + } + + /** + * {@link JsonDeserializer} converting GeoJSON representation of {@literal MultiLineString}. + * + *
+	 * 
+	 * { 
+	 *   "type": "MultiLineString", 
+	 *   "coordinates": [
+	 *     [ [10.0, 20.0], [30.0, 40.0] ], 
+	 *     [ [50.0, 60.0] , [70.0, 80.0] ]
+	 *   ]
+	 * }
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.7 + */ + static class GeoJsonMultiLineStringDeserializer extends GeoJsonDeserializer { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode) + */ + @Override + protected GeoJsonMultiLineString doDeserialize(ArrayNode coordinates) { + + List lines = new ArrayList(coordinates.size()); + + for (JsonNode lineString : coordinates) { + + if (lineString.isArray()) { + lines.add(toLineString((ArrayNode) lineString)); + } + } + + return new GeoJsonMultiLineString(lines); + } + } + + /** + * {@link JsonDeserializer} converting GeoJSON representation of {@literal Polygon}. + * + *
+	 * 
+	 * { 
+	 *   "type": "Polygon", 
+	 *   "coordinates": [ 
+	 *     [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] 
+	 *   ]
+	 * }
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.7 + */ + static class GeoJsonPolygonDeserializer extends GeoJsonDeserializer { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode) + */ + @Override + protected GeoJsonPolygon doDeserialize(ArrayNode coordinates) { + + for (JsonNode ring : coordinates) { + + // currently we do not support holes in polygons. + GeoJsonPolygon polygon = new GeoJsonPolygon(toPoints((ArrayNode) ring)); + return polygon; + } + return null; + } + } + + /** + * {@link JsonDeserializer} converting GeoJSON representation of {@literal MultiPolygon}. + * + *
+	 * 
+	 * { 
+	 *   "type": "MultiPolygon", 
+	 *   "coordinates": [
+	 *     [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],
+	 *     [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
+	 *     [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]
+	 *   ]
+	 * }
+	 * 
+	 * 
+ * + * @author Christoph Strobl + * @since 1.7 + */ + static class GeoJsonMultiPolygonDeserializer extends GeoJsonDeserializer { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.geo.GeoJsonModule.GeoJsonDeserializer#doDeserialize(com.fasterxml.jackson.databind.node.ArrayNode) + */ + @Override + protected GeoJsonMultiPolygon doDeserialize(ArrayNode coordinates) { + + List polygones = new ArrayList(coordinates.size()); + + for (JsonNode polygon : coordinates) { + for (JsonNode ring : (ArrayNode) polygon) { + polygones.add(new GeoJsonPolygon(toPoints((ArrayNode) ring))); + } + } + + return new GeoJsonMultiPolygon(polygones); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java new file mode 100644 index 0000000000..97b282317d --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2015 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 + * + * http://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.geo; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.geo.Point; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Christoph Strobl + */ +public class GeoJsonModuleUnitTests { + + ObjectMapper mapper; + + @Before + public void setUp() { + + mapper = new ObjectMapper(); + mapper.registerModule(new GeoJsonModule()); + } + + /** + * @see DATAMONGO-1181 + */ + @Test + public void shouldDeserializeJsonPointCorrectly() throws JsonParseException, JsonMappingException, IOException { + + String json = "{ \"type\": \"Point\", \"coordinates\": [10.0, 20.0] }"; + + assertThat(mapper.readValue(json, GeoJsonPoint.class), is(new GeoJsonPoint(10D, 20D))); + } + + /** + * @see DATAMONGO-1181 + */ + @Test + public void shouldDeserializeGeoJsonLineStringCorrectly() throws JsonParseException, JsonMappingException, + IOException { + + String json = "{ \"type\": \"LineString\", \"coordinates\": [ [10.0, 20.0], [30.0, 40.0], [50.0, 60.0] ]}"; + + assertThat(mapper.readValue(json, GeoJsonLineString.class), + is(new GeoJsonLineString(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))))); + } + + /** + * @see DATAMONGO-1181 + */ + @Test + public void shouldDeserializeGeoJsonMultiPointCorrectly() throws JsonParseException, JsonMappingException, + IOException { + + String json = "{ \"type\": \"MultiPoint\", \"coordinates\": [ [10.0, 20.0], [30.0, 40.0], [50.0, 60.0] ]}"; + + assertThat(mapper.readValue(json, GeoJsonLineString.class), + is(new GeoJsonMultiPoint(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))))); + } + + /** + * @see DATAMONGO-1181 + */ + @Test + @SuppressWarnings("unchecked") + public void shouldDeserializeGeoJsonMultiLineStringCorrectly() throws JsonParseException, JsonMappingException, + IOException { + + String json = "{ \"type\": \"MultiLineString\", \"coordinates\": [ [ [10.0, 20.0], [30.0, 40.0] ], [ [50.0, 60.0] , [70.0, 80.0] ] ]}"; + + assertThat( + mapper.readValue(json, GeoJsonMultiLineString.class), + is(new GeoJsonMultiLineString(Arrays.asList(new Point(10, 20), new Point(30, 40)), Arrays.asList(new Point(50, + 60), new Point(70, 80))))); + } + + /** + * @see DATAMONGO-1181 + */ + @Test + public void shouldDeserializeGeoJsonPolygonCorrectly() throws JsonParseException, JsonMappingException, IOException { + + String json = "{ \"type\": \"Polygon\", \"coordinates\": [ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ]}"; + + assertThat( + mapper.readValue(json, GeoJsonPolygon.class), + is(new GeoJsonPolygon(Arrays.asList(new Point(100, 0), new Point(101, 0), new Point(101, 1), new Point(100, 1), + new Point(100, 0))))); + } + + /** + * @see DATAMONGO-1181 + */ + @Test + public void shouldDeserializeGeoJsonMultiPolygonCorrectly() throws JsonParseException, JsonMappingException, + IOException { + + String json = "{ \"type\": \"Polygon\", \"coordinates\": [" + + "[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]]," + + "[[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]," + + "[[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]"// + + "]}"; + + assertThat( + mapper.readValue(json, GeoJsonMultiPolygon.class), + is(new GeoJsonMultiPolygon(Arrays.asList( + new GeoJsonPolygon(Arrays.asList(new Point(102, 2), new Point(103, 2), new Point(103, 3), + new Point(102, 3), new Point(102, 2))), + new GeoJsonPolygon(Arrays.asList(new Point(100, 0), new Point(101, 0), new Point(101, 1), + new Point(100, 1), new Point(100, 0))), + new GeoJsonPolygon(Arrays.asList(new Point(100.2, 0.2), new Point(100.8, 0.2), new Point(100.8, 0.8), + new Point(100.2, 0.8), new Point(100.2, 0.2))))))); + + } +} diff --git a/spring-data-mongodb/template.mf b/spring-data-mongodb/template.mf index 8071550e3d..79482f3fa2 100644 --- a/spring-data-mongodb/template.mf +++ b/spring-data-mongodb/template.mf @@ -22,4 +22,5 @@ Import-Template: org.springframework.*;version="${spring:[=.=.=.=,+1.0.0)}", org.springframework.data.*;version="${springdata.commons:[=.=.=.=,+1.0.0)}", org.springframework.data.mongodb.*;version="${project.version:[=.=.=.=,+1.0.0)}", - org.w3c.dom.*;version="0" + org.w3c.dom.*;version="0", + com.fasterxml.jackson.*;version="${jackson:[=.=.=,+1.0.0)}";resolution:=optional