diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java index 0e8fbf983..d18e3476a 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java @@ -70,6 +70,7 @@ import org.apache.olingo.commons.api.edm.geo.MultiPoint; import org.apache.olingo.commons.api.edm.geo.MultiPolygon; import org.apache.olingo.commons.api.edm.geo.Point; import org.apache.olingo.commons.api.edm.geo.Polygon; +import org.apache.olingo.commons.api.edm.geo.SRID; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.DeserializerException; @@ -769,9 +770,13 @@ public class ODataJsonDeserializer implements ODataDeserializer { final JsonNode topNode = jsonNode.remove( geoDataType.equals(GeospatialCollection.class) ? Constants.JSON_GEOMETRIES : Constants.JSON_COORDINATES); - // The "crs" member mentioned in some versions of the OData specification is not part of GeoJSON. - // It used to be used to specify the coordinate reference system. - // TODO: Is it OK to follow RFC 7946 strictly and not allow this element from its obsolete predecessor? + SRID srid = null; + if (jsonNode.has(Constants.JSON_CRS)) { + srid = SRID.valueOf( + jsonNode.remove(Constants.JSON_CRS).get(Constants.PROPERTIES). + get(Constants.JSON_NAME).asText().split(":")[1]); + } + assertJsonNodeIsEmpty(jsonNode); if (topNode != null && topNode.isArray()) { @@ -779,29 +784,29 @@ public class ODataJsonDeserializer implements ODataDeserializer { Geospatial.Dimension.GEOMETRY : Geospatial.Dimension.GEOGRAPHY; if (geoDataType.equals(Point.class)) { - return readGeoPointValue(name, dimension, topNode); + return readGeoPointValue(name, dimension, topNode, srid); } else if (geoDataType.equals(MultiPoint.class)) { - return new MultiPoint(dimension, null, readGeoPointValues(name, dimension, 0, false, topNode)); + return new MultiPoint(dimension, srid, readGeoPointValues(name, dimension, 0, false, topNode)); } else if (geoDataType.equals(LineString.class)) { // Although a line string with less than two points is not really one, the OData specification says: // "The coordinates member of a LineString can have zero or more positions". // Therefore the required minimal size of the points array currently is zero. - return new LineString(dimension, null, readGeoPointValues(name, dimension, 0, false, topNode)); + return new LineString(dimension, srid, readGeoPointValues(name, dimension, 0, false, topNode)); } else if (geoDataType.equals(MultiLineString.class)) { List lines = new ArrayList(); for (final JsonNode element : topNode) { // Line strings can be empty (see above). - lines.add(new LineString(dimension, null, readGeoPointValues(name, dimension, 0, false, element))); + lines.add(new LineString(dimension, srid, readGeoPointValues(name, dimension, 0, false, element))); } - return new MultiLineString(dimension, null, lines); + return new MultiLineString(dimension, srid, lines); } else if (geoDataType.equals(Polygon.class)) { - return readGeoPolygon(name, dimension, topNode); + return readGeoPolygon(name, dimension, topNode, srid); } else if (geoDataType.equals(MultiPolygon.class)) { List polygons = new ArrayList(); for (final JsonNode element : topNode) { - polygons.add(readGeoPolygon(name, dimension, element)); + polygons.add(readGeoPolygon(name, dimension, element, null)); } - return new MultiPolygon(dimension, null, polygons); + return new MultiPolygon(dimension, srid, polygons); } else if (geoDataType.equals(GeospatialCollection.class)) { List elements = new ArrayList(); for (final JsonNode element : topNode) { @@ -812,7 +817,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { DeserializerException.MessageKeys.INVALID_VALUE_FOR_PROPERTY, name); } } - return new GeospatialCollection(dimension, null, elements); + return new GeospatialCollection(dimension, srid, elements); } } } @@ -821,11 +826,11 @@ public class ODataJsonDeserializer implements ODataDeserializer { DeserializerException.MessageKeys.INVALID_VALUE_FOR_PROPERTY, name); } - private Point readGeoPointValue(final String name, final Geospatial.Dimension dimension, JsonNode node) + private Point readGeoPointValue(final String name, final Geospatial.Dimension dimension, JsonNode node, SRID srid) throws DeserializerException, EdmPrimitiveTypeException { if (node.isArray() && (node.size() ==2 || node.size() == 3) && node.get(0).isNumber() && node.get(1).isNumber() && (node.get(2) == null || node.get(2).isNumber())) { - Point point = new Point(dimension, null); + Point point = new Point(dimension, srid); point.setX(getDoubleValue(node.get(0).asText())); point.setY(getDoubleValue(node.get(1).asText())); if (node.get(2) != null) { @@ -855,7 +860,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { if (node.isArray()) { List points = new ArrayList(); for (final JsonNode element : node) { - points.add(readGeoPointValue(name, dimension, element)); + points.add(readGeoPointValue(name, dimension, element, null)); } if (points.size() >= minimalSize && (!closed || points.get(points.size() - 1).equals(points.get(0)))) { @@ -866,13 +871,13 @@ public class ODataJsonDeserializer implements ODataDeserializer { DeserializerException.MessageKeys.INVALID_VALUE_FOR_PROPERTY, name); } - private Polygon readGeoPolygon(final String name, final Geospatial.Dimension dimension, JsonNode node) + private Polygon readGeoPolygon(final String name, final Geospatial.Dimension dimension, JsonNode node, SRID srid) throws DeserializerException, EdmPrimitiveTypeException { // GeoJSON would allow for more than one interior polygon (hole). // But there is no place in the data object to store this information so for now we throw an error. // There could be a more strict verification that the lines describe boundaries and have the correct winding order. if (node.isArray() && (node.size() == 1 || node.size() == 2)) { - return new Polygon(dimension, null, + return new Polygon(dimension, srid, node.size() > 1 ? readGeoPointValues(name, dimension, 4, true, node.get(1)) : null, readGeoPointValues(name, dimension, 4, true, node.get(0))); } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java index 43222106d..c331ed623 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java @@ -63,6 +63,7 @@ import org.apache.olingo.commons.api.edm.geo.MultiPoint; import org.apache.olingo.commons.api.edm.geo.MultiPolygon; import org.apache.olingo.commons.api.edm.geo.Point; import org.apache.olingo.commons.api.edm.geo.Polygon; +import org.apache.olingo.commons.api.edm.geo.SRID; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; import org.apache.olingo.server.api.ODataServerError; @@ -920,7 +921,7 @@ public class ODataJsonSerializer extends AbstractODataSerializer { writePrimitiveValue(property.getName(), type, property.asPrimitive(), isNullable, maxLength, precision, scale, isUnicode, json); } else if (property.isGeospatial()) { - writeGeoValue(property.getName(), type, property.asGeospatial(), isNullable, json); + writeGeoValue(property.getName(), type, property.asGeospatial(), isNullable, json, null); } else if (property.isEnum()) { writePrimitiveValue(property.getName(), type, property.asEnum(), isNullable, maxLength, precision, scale, isUnicode, json); @@ -976,7 +977,7 @@ public class ODataJsonSerializer extends AbstractODataSerializer { /** Writes a geospatial value following the GeoJSON specification defined in RFC 7946. */ protected void writeGeoValue(final String name, final EdmPrimitiveType type, final Geospatial geoValue, - final Boolean isNullable, JsonGenerator json) + final Boolean isNullable, JsonGenerator json, SRID parentSrid) throws EdmPrimitiveTypeException, IOException, SerializerException { if (geoValue == null) { if (isNullable == null || isNullable) { @@ -988,10 +989,6 @@ public class ODataJsonSerializer extends AbstractODataSerializer { if (!type.getDefaultType().isAssignableFrom(geoValue.getClass())) { throw new EdmPrimitiveTypeException("The value type " + geoValue.getClass() + " is not supported."); } - if (geoValue.getSrid() != null && geoValue.getSrid().isNotDefault()) { - throw new SerializerException("Non-standard SRID not supported!", - SerializerException.MessageKeys.WRONG_PROPERTY_VALUE, name, geoValue.toString()); - } json.writeStartObject(); json.writeStringField(Constants.ATTR_TYPE, geoValueTypeToJsonName.get(geoValue.getGeoType())); json.writeFieldName(geoValue.getGeoType() == Geospatial.Type.GEOSPATIALCOLLECTION ? @@ -1028,14 +1025,28 @@ public class ODataJsonSerializer extends AbstractODataSerializer { case GEOSPATIALCOLLECTION: for (final Geospatial element : (GeospatialCollection) geoValue) { writeGeoValue(name, EdmPrimitiveTypeFactory.getInstance(element.getEdmPrimitiveTypeKind()), - element, isNullable, json); + element, isNullable, json, geoValue.getSrid()); } break; } json.writeEndArray(); + + if (geoValue.getSrid() != null && geoValue.getSrid().isNotDefault() + && (parentSrid == null || !parentSrid.equals(geoValue.getSrid()))) { + srid(json, geoValue.getSrid()); + } json.writeEndObject(); } } + + private void srid(final JsonGenerator jgen, final SRID srid) throws IOException { + jgen.writeObjectFieldStart(Constants.JSON_CRS); + jgen.writeStringField(Constants.ATTR_TYPE, Constants.JSON_NAME); + jgen.writeObjectFieldStart(Constants.PROPERTIES); + jgen.writeStringField(Constants.JSON_NAME, "EPSG:" + srid.toString()); + jgen.writeEndObject(); + jgen.writeEndObject(); + } private void writeGeoPoint(JsonGenerator json, final Point point) throws IOException { json.writeNumber(point.getX()); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java index 1eb96a93b..417bfe3b3 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java @@ -896,12 +896,14 @@ public class ODataJsonDeserializerEntityTest extends AbstractODataDeserializerTe public void geoPoint() throws Exception { final EdmEntityType entityType = mockEntityType(EdmPrimitiveTypeKind.GeometryPoint); final String preamble = "{\"" + entityType.getPropertyNames().get(0) + "\":{"; - final Entity entity = deserialize(preamble + "\"type\":\"Point\",\"coordinates\":[1.25,2.75]}}", + final Entity entity = deserialize(preamble + "\"type\":\"Point\",\"coordinates\":[1.25,2.75]," + + "\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:42\"}}}}", entityType); assertEquals(1, entity.getProperties().size()); assertTrue(entity.getProperties().get(0).getValue() instanceof Point); final Point point = (Point) entity.getProperties().get(0).getValue(); assertEquals(Geospatial.Dimension.GEOMETRY, point.getDimension()); + assertEquals("42", point.getSrid().toString()); assertEquals(1.25, point.getX(), 0); assertEquals(2.75, point.getY(), 0); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java index 13a7ed52a..ec9482bbb 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java @@ -18,9 +18,7 @@ */ package org.apache.olingo.server.core.serializer.json; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.io.IOException; import java.io.InputStream; @@ -2306,12 +2304,9 @@ public class ODataJsonSerializerTest { final Entity entity = new Entity() .addProperty(new Property(null, entityType.getPropertyNames().get(0), ValueType.GEOSPATIAL, new Point(Dimension.GEOMETRY, SRID.valueOf("42")))); - try { - serializerNoMetadata.entity(metadata, entityType, entity, null); - fail("Expected exception not thrown."); - } catch (final SerializerException e) { - assertNotNull(e); - } + Assert.assertEquals("{\"PropertyGeometryPoint\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]," + + "\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:42\"}}}}", + IOUtils.toString(serializerNoMetadata.entity(metadata, entityType, entity, null).getContent())); } private Point createPoint(final double x, final double y) { diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerv01Test.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerv01Test.java index 1b8d60fb7..17532c0f0 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerv01Test.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerv01Test.java @@ -2312,12 +2312,9 @@ public class ODataJsonSerializerv01Test { final Entity entity = new Entity() .addProperty(new Property(null, entityType.getPropertyNames().get(0), ValueType.GEOSPATIAL, new Point(Dimension.GEOMETRY, SRID.valueOf("42")))); - try { - serializerNoMetadata.entity(metadata, entityType, entity, null); - fail("Expected exception not thrown."); - } catch (final SerializerException e) { - assertNotNull(e); - } + Assert.assertEquals("{\"PropertyGeometryPoint\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]," + + "\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:42\"}}}}", + IOUtils.toString(serializerNoMetadata.entity(metadata, entityType, entity, null).getContent())); } private Point createPoint(final double x, final double y) {