Skip to content

Commit f603f06

Browse files
authored
Geo: refactor geo mapper and query builder (#44884)
Refactors out the indexing and query generation logic out of the mapper and query builder into a separate unit-testable classes.
1 parent a76242d commit f603f06

20 files changed

+648
-441
lines changed

server/src/main/java/org/elasticsearch/common/geo/parsers/GeoJsonParser.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.elasticsearch.common.unit.DistanceUnit;
3030
import org.elasticsearch.common.xcontent.XContentParser;
3131
import org.elasticsearch.common.xcontent.XContentSubParser;
32-
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
32+
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
3333
import org.locationtech.jts.geom.Coordinate;
3434

3535
import java.io.IOException;
@@ -42,21 +42,21 @@
4242
* complies with geojson specification: https://tools.ietf.org/html/rfc7946
4343
*/
4444
abstract class GeoJsonParser {
45-
protected static ShapeBuilder parse(XContentParser parser, BaseGeoShapeFieldMapper shapeMapper)
45+
protected static ShapeBuilder parse(XContentParser parser, AbstractGeometryFieldMapper shapeMapper)
4646
throws IOException {
4747
GeoShapeType shapeType = null;
4848
DistanceUnit.Distance radius = null;
4949
CoordinateNode coordinateNode = null;
5050
GeometryCollectionBuilder geometryCollections = null;
5151

5252
Orientation orientation = (shapeMapper == null)
53-
? BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value()
53+
? AbstractGeometryFieldMapper.Defaults.ORIENTATION.value()
5454
: shapeMapper.orientation();
5555
Explicit<Boolean> coerce = (shapeMapper == null)
56-
? BaseGeoShapeFieldMapper.Defaults.COERCE
56+
? AbstractGeometryFieldMapper.Defaults.COERCE
5757
: shapeMapper.coerce();
5858
Explicit<Boolean> ignoreZValue = (shapeMapper == null)
59-
? BaseGeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE
59+
? AbstractGeometryFieldMapper.Defaults.IGNORE_Z_VALUE
6060
: shapeMapper.ignoreZValue();
6161

6262
String malformedException = null;
@@ -208,7 +208,7 @@ private static Coordinate parseCoordinate(XContentParser parser, boolean ignoreZ
208208
* @return Geometry[] geometries of the GeometryCollection
209209
* @throws IOException Thrown if an error occurs while reading from the XContentParser
210210
*/
211-
static GeometryCollectionBuilder parseGeometries(XContentParser parser, BaseGeoShapeFieldMapper mapper) throws
211+
static GeometryCollectionBuilder parseGeometries(XContentParser parser, AbstractGeometryFieldMapper mapper) throws
212212
IOException {
213213
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
214214
throw new ElasticsearchParseException("geometries must be an array of geojson objects");

server/src/main/java/org/elasticsearch/common/geo/parsers/GeoWKTParser.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import org.elasticsearch.common.geo.builders.ShapeBuilder;
3535
import org.elasticsearch.common.logging.Loggers;
3636
import org.elasticsearch.common.xcontent.XContentParser;
37-
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
37+
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
3838
import org.locationtech.jts.geom.Coordinate;
3939

4040
import java.io.IOException;
@@ -63,7 +63,7 @@ public class GeoWKTParser {
6363
// no instance
6464
private GeoWKTParser() {}
6565

66-
public static ShapeBuilder parse(XContentParser parser, final BaseGeoShapeFieldMapper shapeMapper)
66+
public static ShapeBuilder parse(XContentParser parser, final AbstractGeometryFieldMapper shapeMapper)
6767
throws IOException, ElasticsearchParseException {
6868
return parseExpectedType(parser, null, shapeMapper);
6969
}
@@ -75,12 +75,12 @@ public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoSha
7575

7676
/** throws an exception if the parsed geometry type does not match the expected shape type */
7777
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType,
78-
final BaseGeoShapeFieldMapper shapeMapper)
78+
final AbstractGeometryFieldMapper shapeMapper)
7979
throws IOException, ElasticsearchParseException {
8080
try (StringReader reader = new StringReader(parser.text())) {
81-
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? BaseGeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE :
81+
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? AbstractGeometryFieldMapper.Defaults.IGNORE_Z_VALUE :
8282
shapeMapper.ignoreZValue();
83-
Explicit<Boolean> coerce = (shapeMapper == null) ? BaseGeoShapeFieldMapper.Defaults.COERCE : shapeMapper.coerce();
83+
Explicit<Boolean> coerce = (shapeMapper == null) ? AbstractGeometryFieldMapper.Defaults.COERCE : shapeMapper.coerce();
8484
// setup the tokenizer; configured to read words w/o numbers
8585
StreamTokenizer tokenizer = new StreamTokenizer(reader);
8686
tokenizer.resetSyntax();
@@ -258,7 +258,7 @@ private static PolygonBuilder parsePolygon(StreamTokenizer stream, final boolean
258258
return null;
259259
}
260260
PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce),
261-
BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value());
261+
AbstractGeometryFieldMapper.Defaults.ORIENTATION.value());
262262
while (nextCloserOrComma(stream).equals(COMMA)) {
263263
builder.hole(parseLinearRing(stream, ignoreZValue, coerce));
264264
}

server/src/main/java/org/elasticsearch/common/geo/parsers/ShapeParser.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.elasticsearch.common.xcontent.XContent;
2727
import org.elasticsearch.common.xcontent.XContentParser;
2828
import org.elasticsearch.common.xcontent.support.MapXContentParser;
29-
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
29+
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
3030

3131
import java.io.IOException;
3232
import java.util.Collections;
@@ -50,7 +50,7 @@ public interface ShapeParser {
5050
* if the parsers current token has been <code>null</code>
5151
* @throws IOException if the input could not be read
5252
*/
53-
static ShapeBuilder parse(XContentParser parser, BaseGeoShapeFieldMapper shapeMapper) throws IOException {
53+
static ShapeBuilder parse(XContentParser parser, AbstractGeometryFieldMapper shapeMapper) throws IOException {
5454
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
5555
return null;
5656
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {

server/src/main/java/org/elasticsearch/index/mapper/BaseGeoShapeFieldMapper.java renamed to server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java

+105-27
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,22 @@
2626
import org.elasticsearch.Version;
2727
import org.elasticsearch.common.Explicit;
2828
import org.elasticsearch.common.ParseField;
29+
import org.elasticsearch.common.geo.ShapeRelation;
30+
import org.elasticsearch.common.geo.SpatialStrategy;
2931
import org.elasticsearch.common.geo.builders.ShapeBuilder;
3032
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
3133
import org.elasticsearch.common.settings.Settings;
3234
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
3335
import org.elasticsearch.common.xcontent.XContentBuilder;
36+
import org.elasticsearch.common.xcontent.XContentParser;
3437
import org.elasticsearch.common.xcontent.support.XContentMapValues;
38+
import org.elasticsearch.geo.geometry.Geometry;
3539
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
3640
import org.elasticsearch.index.query.QueryShardContext;
3741
import org.elasticsearch.index.query.QueryShardException;
3842

3943
import java.io.IOException;
44+
import java.text.ParseException;
4045
import java.util.Iterator;
4146
import java.util.List;
4247
import java.util.Map;
@@ -47,8 +52,8 @@
4752
/**
4853
* Base class for {@link GeoShapeFieldMapper} and {@link LegacyGeoShapeFieldMapper}
4954
*/
50-
public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
51-
public static final String CONTENT_TYPE = "geo_shape";
55+
public abstract class AbstractGeometryFieldMapper<Parsed, Processed> extends FieldMapper {
56+
5257

5358
public static class Names {
5459
public static final ParseField ORIENTATION = new ParseField("orientation");
@@ -62,7 +67,36 @@ public static class Defaults {
6267
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
6368
}
6469

65-
public abstract static class Builder<T extends Builder, Y extends BaseGeoShapeFieldMapper>
70+
71+
/**
72+
* Interface representing an preprocessor in geo-shape indexing pipeline
73+
*/
74+
public interface Indexer<Parsed, Processed> {
75+
76+
Processed prepareForIndexing(Parsed geometry);
77+
78+
Class<Processed> processedClass();
79+
80+
}
81+
82+
/**
83+
* interface representing parser in geo shape indexing pipeline
84+
*/
85+
public interface Parser<Parsed> {
86+
87+
Parsed parse(XContentParser parser, AbstractGeometryFieldMapper mapper) throws IOException, ParseException;
88+
89+
}
90+
91+
/**
92+
* interface representing a query builder that generates a query from the given shape
93+
*/
94+
public interface QueryProcessor {
95+
96+
Query process(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation, QueryShardContext context);
97+
}
98+
99+
public abstract static class Builder<T extends Builder, Y extends AbstractGeometryFieldMapper>
66100
extends FieldMapper.Builder<T, Y> {
67101
protected Boolean coerce;
68102
protected Boolean ignoreMalformed;
@@ -152,7 +186,7 @@ protected void setupFieldType(BuilderContext context) {
152186
throw new IllegalArgumentException("name cannot be empty string");
153187
}
154188

155-
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
189+
AbstractGeometryFieldType ft = (AbstractGeometryFieldType)fieldType();
156190
ft.setOrientation(orientation().value());
157191
}
158192
}
@@ -218,26 +252,32 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
218252
}
219253
}
220254

221-
public abstract static class BaseGeoShapeFieldType extends MappedFieldType {
255+
public abstract static class AbstractGeometryFieldType<Parsed, Processed> extends MappedFieldType {
222256
protected Orientation orientation = Defaults.ORIENTATION.value();
223257

224-
protected BaseGeoShapeFieldType() {
258+
protected Indexer<Parsed, Processed> geometryIndexer;
259+
260+
protected Parser<Parsed> geometryParser;
261+
262+
protected QueryProcessor geometryQueryBuilder;
263+
264+
protected AbstractGeometryFieldType() {
225265
setIndexOptions(IndexOptions.DOCS);
226266
setTokenized(false);
227267
setStored(false);
228268
setStoreTermVectors(false);
229269
setOmitNorms(true);
230270
}
231271

232-
protected BaseGeoShapeFieldType(BaseGeoShapeFieldType ref) {
272+
protected AbstractGeometryFieldType(AbstractGeometryFieldType ref) {
233273
super(ref);
234274
this.orientation = ref.orientation;
235275
}
236276

237277
@Override
238278
public boolean equals(Object o) {
239279
if (!super.equals(o)) return false;
240-
BaseGeoShapeFieldType that = (BaseGeoShapeFieldType) o;
280+
AbstractGeometryFieldType that = (AbstractGeometryFieldType) o;
241281
return orientation == that.orientation;
242282
}
243283

@@ -246,16 +286,6 @@ public int hashCode() {
246286
return Objects.hash(super.hashCode(), orientation);
247287
}
248288

249-
@Override
250-
public String typeName() {
251-
return CONTENT_TYPE;
252-
}
253-
254-
@Override
255-
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
256-
super.checkCompatibility(fieldType, conflicts);
257-
}
258-
259289
public Orientation orientation() { return this.orientation; }
260290

261291
public void setOrientation(Orientation orientation) {
@@ -272,16 +302,40 @@ public Query existsQuery(QueryShardContext context) {
272302
public Query termQuery(Object value, QueryShardContext context) {
273303
throw new QueryShardException(context, "Geo fields do not support exact searching, use dedicated geo queries instead");
274304
}
305+
306+
public void setGeometryIndexer(Indexer<Parsed, Processed> geometryIndexer) {
307+
this.geometryIndexer = geometryIndexer;
308+
}
309+
310+
protected Indexer<Parsed, Processed> geometryIndexer() {
311+
return geometryIndexer;
312+
}
313+
314+
public void setGeometryParser(Parser<Parsed> geometryParser) {
315+
this.geometryParser = geometryParser;
316+
}
317+
318+
protected Parser<Parsed> geometryParser() {
319+
return geometryParser;
320+
}
321+
322+
public void setGeometryQueryBuilder(QueryProcessor geometryQueryBuilder) {
323+
this.geometryQueryBuilder = geometryQueryBuilder;
324+
}
325+
326+
public QueryProcessor geometryQueryBuilder() {
327+
return geometryQueryBuilder;
328+
}
275329
}
276330

277331
protected Explicit<Boolean> coerce;
278332
protected Explicit<Boolean> ignoreMalformed;
279333
protected Explicit<Boolean> ignoreZValue;
280334

281-
protected BaseGeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
282-
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
283-
Explicit<Boolean> ignoreZValue, Settings indexSettings,
284-
MultiFields multiFields, CopyTo copyTo) {
335+
protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
336+
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
337+
Explicit<Boolean> ignoreZValue, Settings indexSettings,
338+
MultiFields multiFields, CopyTo copyTo) {
285339
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
286340
this.coerce = coerce;
287341
this.ignoreMalformed = ignoreMalformed;
@@ -291,7 +345,7 @@ protected BaseGeoShapeFieldMapper(String simpleName, MappedFieldType fieldType,
291345
@Override
292346
protected void doMerge(Mapper mergeWith) {
293347
super.doMerge(mergeWith);
294-
BaseGeoShapeFieldMapper gsfm = (BaseGeoShapeFieldMapper)mergeWith;
348+
AbstractGeometryFieldMapper gsfm = (AbstractGeometryFieldMapper)mergeWith;
295349
if (gsfm.coerce.explicit()) {
296350
this.coerce = gsfm.coerce;
297351
}
@@ -310,7 +364,7 @@ protected void parseCreateField(ParseContext context, List<IndexableField> field
310364
@Override
311365
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
312366
builder.field("type", contentType());
313-
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
367+
AbstractGeometryFieldType ft = (AbstractGeometryFieldType)fieldType();
314368
if (includeDefaults || ft.orientation() != Defaults.ORIENTATION.value()) {
315369
builder.field(Names.ORIENTATION.getPreferredName(), ft.orientation());
316370
}
@@ -338,11 +392,35 @@ public Explicit<Boolean> ignoreZValue() {
338392
}
339393

340394
public Orientation orientation() {
341-
return ((BaseGeoShapeFieldType)fieldType).orientation();
395+
return ((AbstractGeometryFieldType)fieldType).orientation();
342396
}
343397

398+
protected abstract void indexShape(ParseContext context, Processed shape);
399+
400+
/** parsing logic for geometry indexing */
344401
@Override
345-
protected String contentType() {
346-
return CONTENT_TYPE;
402+
public void parse(ParseContext context) throws IOException {
403+
AbstractGeometryFieldType fieldType = (AbstractGeometryFieldType)fieldType();
404+
405+
@SuppressWarnings("unchecked") Indexer<Parsed, Processed> geometryIndexer = fieldType.geometryIndexer();
406+
@SuppressWarnings("unchecked") Parser<Parsed> geometryParser = fieldType.geometryParser();
407+
try {
408+
Processed shape = context.parseExternalValue(geometryIndexer.processedClass());
409+
if (shape == null) {
410+
Parsed geometry = geometryParser.parse(context.parser(), this);
411+
if (geometry == null) {
412+
return;
413+
}
414+
shape = geometryIndexer.prepareForIndexing(geometry);
415+
}
416+
indexShape(context, shape);
417+
} catch (Exception e) {
418+
if (ignoreMalformed.value() == false) {
419+
throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
420+
fieldType().typeName());
421+
}
422+
context.addIgnoredField(fieldType().name());
423+
}
347424
}
425+
348426
}

0 commit comments

Comments
 (0)