diff --git a/server/src/main/java/org/elasticsearch/common/geo/GeometryFormat.java b/server/src/main/java/org/elasticsearch/common/geo/GeometryFormat.java new file mode 100644 index 00000000000..5e14e20050b --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/geo/GeometryFormat.java @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you 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.elasticsearch.common.geo; + +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.geo.geometry.Geometry; + +import java.io.IOException; +import java.text.ParseException; + +/** + * Geometry serializer/deserializer + */ +public interface GeometryFormat { + + /** + * Parser JSON representation of a geometry + */ + Geometry fromXContent(XContentParser parser) throws IOException, ParseException; + + /** + * Serializes the geometry into its JSON representation + */ + XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException; + +} diff --git a/server/src/main/java/org/elasticsearch/common/geo/GeometryParser.java b/server/src/main/java/org/elasticsearch/common/geo/GeometryParser.java index e58372d8255..b96e41df5e4 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/GeometryParser.java +++ b/server/src/main/java/org/elasticsearch/common/geo/GeometryParser.java @@ -20,6 +20,8 @@ package org.elasticsearch.common.geo; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.geo.geometry.Geometry; import org.elasticsearch.geo.utils.GeographyValidator; @@ -47,14 +49,64 @@ public final class GeometryParser { /** * Parses supplied XContent into Geometry */ - public Geometry parse(XContentParser parser) throws IOException, - ParseException { + public Geometry parse(XContentParser parser) throws IOException, ParseException { + return geometryFormat(parser).fromXContent(parser); + } + + /** + * Returns a geometry format object that can parse and then serialize the object back to the same format. + */ + public GeometryFormat geometryFormat(XContentParser parser) { if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { - return null; + return new GeometryFormat() { + @Override + public Geometry fromXContent(XContentParser parser) throws IOException { + return null; + } + + @Override + public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException { + if (geometry != null) { + // We don't know the format of the original geometry - so going with default + return GeoJson.toXContent(geometry, builder, params); + } else { + return builder.nullValue(); + } + } + }; } else if (parser.currentToken() == XContentParser.Token.START_OBJECT) { - return geoJsonParser.fromXContent(parser); + return new GeometryFormat() { + @Override + public Geometry fromXContent(XContentParser parser) throws IOException { + return geoJsonParser.fromXContent(parser); + } + + @Override + public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException { + if (geometry != null) { + return GeoJson.toXContent(geometry, builder, params); + } else { + return builder.nullValue(); + } + } + }; } else if (parser.currentToken() == XContentParser.Token.VALUE_STRING) { - return wellKnownTextParser.fromWKT(parser.text()); + return new GeometryFormat() { + @Override + public Geometry fromXContent(XContentParser parser) throws IOException, ParseException { + return wellKnownTextParser.fromWKT(parser.text()); + } + + @Override + public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException { + if (geometry != null) { + return builder.value(wellKnownTextParser.toWKT(geometry)); + } else { + return builder.nullValue(); + } + } + }; + } throw new ElasticsearchParseException("shape must be an object consisting of type and coordinates"); } diff --git a/server/src/test/java/org/elasticsearch/common/geo/GeometryParserTests.java b/server/src/test/java/org/elasticsearch/common/geo/GeometryParserTests.java index 13b3f8f67b3..e3db70fc24e 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/GeometryParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/geo/GeometryParserTests.java @@ -20,6 +20,8 @@ package org.elasticsearch.common.geo; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParseException; @@ -44,7 +46,11 @@ public class GeometryParserTests extends ESTestCase { try (XContentParser parser = createParser(pointGeoJson)) { parser.nextToken(); - assertEquals(new Point(0, 100), new GeometryParser(true, randomBoolean(), randomBoolean()).parse(parser)); + GeometryFormat format = new GeometryParser(true, randomBoolean(), randomBoolean()).geometryFormat(parser); + assertEquals(new Point(0, 100), format.fromXContent(parser)); + XContentBuilder newGeoJson = XContentFactory.jsonBuilder(); + format.toXContent(new Point(10, 100), newGeoJson, ToXContent.EMPTY_PARAMS); + assertEquals("{\"type\":\"Point\",\"coordinates\":[100.0,10.0]}", Strings.toString(newGeoJson)); } XContentBuilder pointGeoJsonWithZ = XContentFactory.jsonBuilder() @@ -77,7 +83,7 @@ public class GeometryParserTests extends ESTestCase { .endArray() .endObject(); - Polygon p = new Polygon(new LinearRing(new double[] {1d, 1d, 0d, 0d, 1d}, new double[] {100d, 101d, 101d, 100d, 100d})); + Polygon p = new Polygon(new LinearRing(new double[]{1d, 1d, 0d, 0d, 1d}, new double[]{100d, 101d, 101d, 100d, 100d})); try (XContentParser parser = createParser(polygonGeoJson)) { parser.nextToken(); // Coerce should automatically close the polygon @@ -101,7 +107,12 @@ public class GeometryParserTests extends ESTestCase { parser.nextToken(); // Start object parser.nextToken(); // Field Name parser.nextToken(); // Field Value - assertEquals(new Point(0, 100), new GeometryParser(true, randomBoolean(), randomBoolean()).parse(parser)); + GeometryFormat format = new GeometryParser(true, randomBoolean(), randomBoolean()).geometryFormat(parser); + assertEquals(new Point(0, 100), format.fromXContent(parser)); + XContentBuilder newGeoJson = XContentFactory.jsonBuilder().startObject().field("val"); + format.toXContent(new Point(10, 100), newGeoJson, ToXContent.EMPTY_PARAMS); + newGeoJson.endObject(); + assertEquals("{\"val\":\"point (100.0 10.0)\"}", Strings.toString(newGeoJson)); } } @@ -115,7 +126,20 @@ public class GeometryParserTests extends ESTestCase { parser.nextToken(); // Start object parser.nextToken(); // Field Name parser.nextToken(); // Field Value - assertNull(new GeometryParser(true, randomBoolean(), randomBoolean()).parse(parser)); + GeometryFormat format = new GeometryParser(true, randomBoolean(), randomBoolean()).geometryFormat(parser); + assertNull(format.fromXContent(parser)); + + XContentBuilder newGeoJson = XContentFactory.jsonBuilder().startObject().field("val"); + // if we serialize non-null value - it should be serialized as geojson + format.toXContent(new Point(10, 100), newGeoJson, ToXContent.EMPTY_PARAMS); + newGeoJson.endObject(); + assertEquals("{\"val\":{\"type\":\"Point\",\"coordinates\":[100.0,10.0]}}", Strings.toString(newGeoJson)); + + newGeoJson = XContentFactory.jsonBuilder().startObject().field("val"); + format.toXContent(null, newGeoJson, ToXContent.EMPTY_PARAMS); + newGeoJson.endObject(); + assertEquals("{\"val\":null}", Strings.toString(newGeoJson)); + } }