DATAES-929 - Support geo_shape field type field type.

Original PR: #520
This commit is contained in:
Peter-Josef Meisch 2020-09-19 19:27:39 +02:00 committed by GitHub
parent b7b17180f6
commit 5dc68600f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 234 additions and 60 deletions

View File

@ -0,0 +1,44 @@
/*
* Copyright 2017-2020 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
*
* https://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.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Lukas Vorisek
* @author Peter-Josef Meisch
* @since 4.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface GeoShapeField {
Orientation orientation() default Orientation.ccw;
boolean ignoreMalformed() default false;
boolean ignoreZValue() default true;
boolean coerce() default false;
enum Orientation {
right, ccw, counterclockwise, left, cw, clockwise
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2020 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
*
* https://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.elasticsearch.core.index;
import java.io.IOException;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.data.elasticsearch.annotations.GeoShapeField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
*/
final class GeoShapeMappingParameters {
private static final String FIELD_PARAM_TYPE = "type";
private static final String FIELD_PARAM_COERCE = "coerce";
private static final String FIELD_PARAM_IGNORE_MALFORMED = "ignore_malformed";
private static final String FIELD_PARAM_IGNORE_Z_VALUE = "ignore_z_value";
private static final String FIELD_PARAM_ORIENTATION = "orientation";
private static final String TYPE_VALUE_GEO_SHAPE = "geo_shape";
private final boolean coerce;
private final boolean ignoreMalformed;
private final boolean ignoreZValue;
private final GeoShapeField.Orientation orientation;
/**
* Creates a GeoShapeMappingParameters from the given annotation.
*
* @param annotation if null, default values are set in the returned object
* @return a parameters object
*/
public static GeoShapeMappingParameters from(@Nullable GeoShapeField annotation) {
if (annotation == null) {
return new GeoShapeMappingParameters(false, false, true, GeoShapeField.Orientation.ccw);
} else {
return new GeoShapeMappingParameters(annotation.coerce(), annotation.ignoreMalformed(), annotation.ignoreZValue(),
annotation.orientation());
}
}
private GeoShapeMappingParameters(boolean coerce, boolean ignoreMalformed, boolean ignoreZValue,
GeoShapeField.Orientation orientation) {
this.coerce = coerce;
this.ignoreMalformed = ignoreMalformed;
this.ignoreZValue = ignoreZValue;
this.orientation = orientation;
}
public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException {
Assert.notNull(builder, "builder must ot be null");
if (coerce) {
builder.field(FIELD_PARAM_COERCE, coerce);
}
if (ignoreMalformed) {
builder.field(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed);
}
if (!ignoreZValue) {
builder.field(FIELD_PARAM_IGNORE_Z_VALUE, ignoreZValue);
}
if (orientation != GeoShapeField.Orientation.ccw) {
builder.field(FIELD_PARAM_ORIENTATION, orientation.name());
}
builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_SHAPE);
}
}

View File

@ -35,10 +35,7 @@ import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ResourceUtil;
import org.springframework.data.elasticsearch.core.completion.Completion;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.join.JoinField;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.MappingException;
@ -203,17 +200,21 @@ public class MappingBuilder {
}
}
if (isGeoPointProperty(property)) {
if (property.isGeoPointProperty()) {
applyGeoPointFieldMapping(builder, property);
return;
}
if (isJoinFieldProperty(property)) {
if (property.isGeoShapeProperty()) {
applyGeoShapeMapping(builder, property);
}
if (property.isJoinFieldProperty()) {
addJoinFieldMapping(builder, property);
}
Field fieldAnnotation = property.findAnnotation(Field.class);
boolean isCompletionProperty = isCompletionProperty(property);
boolean isCompletionProperty = property.isCompletionProperty();
boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property);
if (!isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) {
@ -228,8 +229,8 @@ public class MappingBuilder {
? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next())
: null;
mapEntity(builder, persistentEntity, false, property.getFieldName(), isNestedOrObjectProperty,
fieldAnnotation.type(), fieldAnnotation, property.findAnnotation(DynamicMapping.class));
mapEntity(builder, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
fieldAnnotation, property.findAnnotation(DynamicMapping.class));
return;
}
}
@ -259,10 +260,17 @@ public class MappingBuilder {
private void applyGeoPointFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT).endObject();
}
private void applyGeoShapeMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName());
GeoShapeMappingParameters.from(property.findAnnotation(GeoShapeField.class)).writeTypeAndParametersTo(builder);
builder.endObject();
}
private void applyCompletionFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
@Nullable CompletionField annotation) throws IOException {
@ -448,16 +456,4 @@ public class MappingBuilder {
return fieldAnnotation != null
&& (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type());
}
private boolean isGeoPointProperty(ElasticsearchPersistentProperty property) {
return property.getActualType() == GeoPoint.class || property.isAnnotationPresent(GeoPointField.class);
}
private boolean isJoinFieldProperty(ElasticsearchPersistentProperty property) {
return property.getActualType() == JoinField.class;
}
private boolean isCompletionProperty(ElasticsearchPersistentProperty property) {
return property.getActualType() == Completion.class;
}
}

View File

@ -195,6 +195,7 @@ public final class MappingParameters {
* @param builder must not be {@literal null}.
*/
public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException {
Assert.notNull(builder, "builder must ot be null");
if (fielddata) {

View File

@ -102,6 +102,30 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
*/
boolean storeNullValue();
/**
* @return {@literal true} if this is a GeoPoint property
* @since 4.1
*/
boolean isGeoPointProperty();
/**
* @return {@literal true} if this is a GeoShape property
* @since 4.1
*/
boolean isGeoShapeProperty();
/**
* @return {@literal true} if this is a JoinField property
* @since 4.1
*/
boolean isJoinFieldProperty();
/**
* @return {@literal true} if this is a Completion property
* @since 4.1
*/
boolean isCompletionProperty();
enum PropertyToFieldNameConverter implements Converter<ElasticsearchPersistentProperty, String> {
INSTANCE;

View File

@ -26,10 +26,15 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.annotations.GeoShapeField;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.Parent;
import org.springframework.data.elasticsearch.annotations.Score;
import org.springframework.data.elasticsearch.core.completion.Completion;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchDateConverter;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.join.JoinField;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
@ -215,66 +220,58 @@ public class SimpleElasticsearchPersistentProperty extends
return StringUtils.hasText(name) ? name : null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty#getFieldName()
*/
@Override
public String getFieldName() {
return annotatedFieldName == null ? getProperty().getName() : annotatedFieldName;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isIdProperty()
*/
@Override
public boolean isIdProperty() {
return isId;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
*/
@Override
protected Association<ElasticsearchPersistentProperty> createAssociation() {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty#isScoreProperty()
*/
@Override
public boolean isScoreProperty() {
return isScore;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#isImmutable()
*/
@Override
public boolean isImmutable() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty#isParentProperty()
*/
@Override
public boolean isParentProperty() {
return isParent;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty#isSeqNoPrimaryTermProperty()
*/
@Override
public boolean isSeqNoPrimaryTermProperty() {
return isSeqNoPrimaryTerm;
}
@Override
public boolean isGeoPointProperty() {
return getActualType() == GeoPoint.class || isAnnotationPresent(GeoPointField.class);
}
@Override
public boolean isGeoShapeProperty() {
return isAnnotationPresent(GeoShapeField.class);
}
@Override
public boolean isJoinFieldProperty() {
return getActualType() == JoinField.class;
}
@Override
public boolean isCompletionProperty() {
return getActualType() == Completion.class;
}
}

View File

@ -210,13 +210,37 @@ public class MappingBuilderTests extends MappingContextBaseTests {
assertThat(entry.getMessage()).isEqualTo(message);
}
@Test // DATAES-568
public void shouldBuildMappingsForGeoPoint() throws JSONException {
@Test // DATAES-568, DATAES-929
@DisplayName("should build mappings for geo types")
void shouldBuildMappingsForGeoTypes() throws JSONException {
// given
String expected = "{\"properties\": {" + "\"pointA\":{\"type\":\"geo_point\"},"
+ "\"pointB\":{\"type\":\"geo_point\"}," + "\"pointC\":{\"type\":\"geo_point\"},"
+ "\"pointD\":{\"type\":\"geo_point\"}" + "}}";
String expected = "{\n" + //
" \"properties\": {\n" + //
" \"pointA\": {\n" + //
" \"type\": \"geo_point\"\n" + //
" },\n" + //
" \"pointB\": {\n" + //
" \"type\": \"geo_point\"\n" + //
" },\n" + //
" \"pointC\": {\n" + //
" \"type\": \"geo_point\"\n" + //
" },\n" + //
" \"pointD\": {\n" + //
" \"type\": \"geo_point\"\n" + //
" },\n" + //
" \"shape1\": {\n" + //
" \"type\": \"geo_shape\"\n" + //
" },\n" + //
" \"shape2\": {\n" + //
" \"type\": \"geo_shape\",\n" + //
" \"orientation\": \"clockwise\",\n" + //
" \"ignore_malformed\": true,\n" + //
" \"ignore_z_value\": false,\n" + //
" \"coerce\": true\n" + //
" }\n" + //
" }\n" + //
"}\n}"; //
// when
String mapping;
@ -961,9 +985,6 @@ public class MappingBuilderTests extends MappingContextBaseTests {
}
}
/**
* @author Artur Konczak
*/
@Setter
@Getter
@NoArgsConstructor
@ -981,12 +1002,14 @@ public class MappingBuilderTests extends MappingContextBaseTests {
// geo point - Custom implementation + Spring Data
@GeoPointField private Point pointA;
private GeoPoint pointB;
@GeoPointField private String pointC;
@GeoPointField private double[] pointD;
// geo shape, until e have the classes for this, us a strng
@GeoShapeField private String shape1;
@GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false,
orientation = GeoShapeField.Orientation.clockwise) private String shape2;
}
/**