DATAES-621 - Add missing fieldtypes and parameters to mapping.

Original PR: #311
This commit is contained in:
Peter-Josef Meisch 2019-08-29 07:27:49 +02:00
parent 490347f286
commit f0117e7b6a
18 changed files with 787 additions and 148 deletions

View File

@ -50,8 +50,8 @@ public @interface Field {
/**
* The <em>name</em> to be used to store the field inside the document.
* <p>5
* If not set, the name of the annotated property is used.
* <p>
* 5 If not set, the name of the annotated property is used.
*
* @since 3.2
*/
@ -86,4 +86,66 @@ public @interface Field {
* @since 4.0
*/
int ignoreAbove() default -1;
/**
* @since 4.0
*/
boolean coerce() default true;
/**
* @since 4.0
*/
boolean docValues() default true;
/**
* @since 4.0
*/
boolean ignoreMalformed() default false;
/**
* @since 4.0
*/
IndexOptions indexOptions() default IndexOptions.none;
/**
* @since 4.0
*/
boolean indexPhrases() default false;
/**
* implemented as array to enable the empty default value
*
* @since 4.0
*/
IndexPrefixes[] indexPrefixes() default {};
/**
* @since 4.0
*/
boolean norms() default true;
/**
* @since 4.0
*/
String nullValue() default "";
/**
* @since 4.0
*/
int positionIncrementGap() default -1;
/**
* @since 4.0
*/
Similarity similarity() default Similarity.Default;
/**
* @since 4.0
*/
TermVector termVector() default TermVector.none;
/**
* @since 4.0
*/
double scalingFactor() default 1;
}

View File

@ -20,22 +20,34 @@ package org.springframework.data.elasticsearch.annotations;
* @author Mohsin Husen
* @author Artur Konczak
* @author Zeng Zetang
* @author Peter-Josef Meisch
*/
public enum FieldType {
Text,
Byte,
Short,
Integer,
Long,
Date,
Half_Float,
Float,
Double,
Boolean,
Object,
Auto,
Nested,
Ip,
Attachment,
Keyword
Auto,
Text,
Keyword,
Long,
Integer,
Short,
Byte,
Double,
Float,
Half_Float,
Scaled_Float,
Date,
Date_Nanos,
Boolean,
Binary,
Integer_Range,
Float_Range,
Long_Range,
Double_Range,
Date_Range,
Ip_Range,
Object,
Nested,
Ip,
TokenCount,
Percolator,
Flattened
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2019 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;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public enum IndexOptions {
none, docs, freqs, positions, offsets
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2019 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;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public @interface IndexPrefixes {
static final int MIN_DEFAULT = 2;
static final int MAX_DEFAULT = 2;
int minChars() default MIN_DEFAULT;
int maxChars() default MAX_DEFAULT;
}

View File

@ -25,6 +25,7 @@ import java.lang.annotation.Target;
* @author Mohsin Husen
* @author Sascha Woo
* @author Xiao Yu
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@ -54,4 +55,66 @@ public @interface InnerField {
* @since 4.0
*/
int ignoreAbove() default -1;
/**
* @since 4.0
*/
boolean coerce() default true;
/**
* @since 4.0
*/
boolean docValues() default true;
/**
* @since 4.0
*/
boolean ignoreMalformed() default false;
/**
* @since 4.0
*/
IndexOptions indexOptions() default IndexOptions.none;
/**
* @since 4.0
*/
boolean indexPhrases() default false;
/**
* implemented as array to enable the empty default value
*
* @since 4.0
*/
IndexPrefixes[] indexPrefixes() default {};
/**
* @since 4.0
*/
boolean norms() default true;
/**
* @since 4.0
*/
String nullValue() default "";
/**
* @since 4.0
*/
int positionIncrementGap() default -1;
/**
* @since 4.0
*/
Similarity similarity() default Similarity.Default;
/**
* @since 4.0
*/
TermVector termVector() default TermVector.none;
/**
* @since 4.0
*/
double scalingFactor() default 1;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2019 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;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public enum Similarity {
Default("default"), BM25("BM25"), classic("classic"), Boolean("boolean");
// need to use a custom name because 'boolean' can't be used as enum name
private final String toStringName;
Similarity(String name) {
this.toStringName = name;
}
@Override
public String toString() {
return toStringName;
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2019 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;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public enum TermVector {
none, no, yes, with_positions, with_offsets, woth_positions_offsets, with_positions_payloads, with_positions_offets_payloads
}

View File

@ -101,6 +101,7 @@ import org.springframework.data.elasticsearch.core.client.support.AliasData;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
import org.springframework.data.elasticsearch.core.indexmapping.MappingBuilder;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;

View File

@ -88,6 +88,7 @@ import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
import org.springframework.data.elasticsearch.core.indexmapping.MappingBuilder;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;

View File

@ -20,7 +20,6 @@ import java.nio.charset.Charset;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;
@ -28,9 +27,10 @@ import org.springframework.util.StreamUtils;
* Utility to read {@link org.springframework.core.io.Resource}s.
*
* @author Mark Paluch
* @author Peter-Josef Meisch
* @since 3.2
*/
abstract class ResourceUtil {
public abstract class ResourceUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtil.class);

View File

@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
package org.springframework.data.elasticsearch.core.indexmapping;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.springframework.data.elasticsearch.core.indexmapping.MappingParameters.*;
import static org.springframework.util.StringUtils.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Iterator;
@ -31,7 +33,6 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.CompletionContext;
import org.springframework.data.elasticsearch.annotations.CompletionField;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@ -39,6 +40,8 @@ import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.MultiField;
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;
@ -47,7 +50,6 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
@ -69,24 +71,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Peter-Josef Meisch
* @author Xiao Yu
*/
class MappingBuilder {
public class MappingBuilder {
private static final String FIELD_DATA = "fielddata";
private static final String FIELD_STORE = "store";
private static final String FIELD_TYPE = "type";
private static final String FIELD_INDEX = "index";
private static final String FIELD_FORMAT = "format";
private static final String FIELD_SEARCH_ANALYZER = "search_analyzer";
private static final String FIELD_INDEX_ANALYZER = "analyzer";
private static final String FIELD_NORMALIZER = "normalizer";
private static final String FIELD_PROPERTIES = "properties";
private static final String FIELD_PARENT = "_parent";
private static final String FIELD_COPY_TO = "copy_to";
private static final String FIELD_CONTEXT_NAME = "name";
private static final String FIELD_CONTEXT_TYPE = "type";
private static final String FIELD_CONTEXT_PRECISION = "precision";
private static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates";
private static final String FIELD_IGNORE_ABOVE = "ignore_above";
private static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators";
private static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments";
@ -101,7 +94,7 @@ class MappingBuilder {
private final ElasticsearchConverter elasticsearchConverter;
MappingBuilder(ElasticsearchConverter elasticsearchConverter) {
public MappingBuilder(ElasticsearchConverter elasticsearchConverter) {
this.elasticsearchConverter = elasticsearchConverter;
}
@ -111,7 +104,7 @@ class MappingBuilder {
* @return JSON string
* @throws IOException
*/
String buildPropertyMapping(Class<?> clazz) throws IOException {
public String buildPropertyMapping(Class<?> clazz) throws IOException {
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
.getRequiredPersistentEntity(clazz);
@ -124,7 +117,7 @@ class MappingBuilder {
// Parent
String parentType = entity.getParentType();
if (hasText(parentType)) {
builder.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject();
builder.startObject(FIELD_PARENT).field(FIELD_PARAM_TYPE, parentType).endObject();
}
// Properties
@ -149,7 +142,7 @@ class MappingBuilder {
String type = nestedOrObjectField ? fieldType.toString().toLowerCase()
: FieldType.Object.toString().toLowerCase();
builder.startObject(nestedObjectFieldName).field(FIELD_TYPE, type);
builder.startObject(nestedObjectFieldName).field(FIELD_PARAM_TYPE, type);
if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null
&& parentFieldAnnotation.includeInParent()) {
@ -250,14 +243,14 @@ class MappingBuilder {
private void applyGeoPointFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_GEO_POINT).endObject();
builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT).endObject();
}
private void applyCompletionFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
@Nullable CompletionField annotation) throws IOException {
builder.startObject(property.getFieldName());
builder.field(FIELD_TYPE, TYPE_VALUE_COMPLETION);
builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION);
if (annotation != null) {
@ -265,10 +258,10 @@ class MappingBuilder {
builder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
builder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
if (!StringUtils.isEmpty(annotation.searchAnalyzer())) {
builder.field(FIELD_SEARCH_ANALYZER, annotation.searchAnalyzer());
builder.field(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer());
}
if (!StringUtils.isEmpty(annotation.analyzer())) {
builder.field(FIELD_INDEX_ANALYZER, annotation.analyzer());
builder.field(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer());
}
if (annotation.contexts().length > 0) {
@ -294,7 +287,7 @@ class MappingBuilder {
private void applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_KEYWORD).field(FIELD_INDEX, true)
builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD).field(FIELD_INDEX, true)
.endObject();
}
@ -335,92 +328,15 @@ class MappingBuilder {
builder.endObject();
}
private void addFieldMappingParameters(XContentBuilder builder, Object annotation, boolean nestedOrObjectField)
private void addFieldMappingParameters(XContentBuilder builder, Annotation annotation, boolean nestedOrObjectField)
throws IOException {
boolean index = true;
boolean store = false;
boolean fielddata = false;
FieldType type = null;
DateFormat dateFormat = null;
String datePattern = null;
String analyzer = null;
String searchAnalyzer = null;
String normalizer = null;
String[] copyTo = null;
Integer ignoreAbove = null;
MappingParameters mappingParameters = MappingParameters.from(annotation);
if (annotation instanceof Field) {
// @Field
Field fieldAnnotation = (Field) annotation;
index = fieldAnnotation.index();
store = fieldAnnotation.store();
fielddata = fieldAnnotation.fielddata();
type = fieldAnnotation.type();
dateFormat = fieldAnnotation.format();
datePattern = fieldAnnotation.pattern();
analyzer = fieldAnnotation.analyzer();
searchAnalyzer = fieldAnnotation.searchAnalyzer();
normalizer = fieldAnnotation.normalizer();
copyTo = fieldAnnotation.copyTo();
ignoreAbove = fieldAnnotation.ignoreAbove() >= 0 ? fieldAnnotation.ignoreAbove() : null;
} else if (annotation instanceof InnerField) {
// @InnerField
InnerField fieldAnnotation = (InnerField) annotation;
index = fieldAnnotation.index();
store = fieldAnnotation.store();
fielddata = fieldAnnotation.fielddata();
type = fieldAnnotation.type();
dateFormat = fieldAnnotation.format();
datePattern = fieldAnnotation.pattern();
analyzer = fieldAnnotation.analyzer();
searchAnalyzer = fieldAnnotation.searchAnalyzer();
normalizer = fieldAnnotation.normalizer();
ignoreAbove = fieldAnnotation.ignoreAbove() >= 0 ? fieldAnnotation.ignoreAbove() : null;
} else {
throw new IllegalArgumentException("annotation must be an instance of @Field or @InnerField");
}
if (!nestedOrObjectField) {
builder.field(FIELD_STORE, store);
}
if (fielddata) {
builder.field(FIELD_DATA, fielddata);
}
if (type != FieldType.Auto) {
builder.field(FIELD_TYPE, type.name().toLowerCase());
if (type == FieldType.Date && dateFormat != DateFormat.none) {
builder.field(FIELD_FORMAT, dateFormat == DateFormat.custom ? datePattern : dateFormat.toString());
}
}
if (!index) {
builder.field(FIELD_INDEX, index);
}
if (!StringUtils.isEmpty(analyzer)) {
builder.field(FIELD_INDEX_ANALYZER, analyzer);
}
if (!StringUtils.isEmpty(searchAnalyzer)) {
builder.field(FIELD_SEARCH_ANALYZER, searchAnalyzer);
}
if (!StringUtils.isEmpty(normalizer)) {
builder.field(FIELD_NORMALIZER, normalizer);
}
if (copyTo != null && copyTo.length > 0) {
builder.field(FIELD_COPY_TO, copyTo);
}
if (ignoreAbove != null) {
Assert.isTrue(ignoreAbove >= 0, "ignore_above must be a positive value");
builder.field(FIELD_IGNORE_ABOVE, ignoreAbove);
if (!nestedOrObjectField && mappingParameters.isStore()) {
builder.field(FIELD_PARAM_STORE, mappingParameters.isStore());
}
mappingParameters.writeTypeAndParametersTo(builder);
}
/**

View File

@ -0,0 +1,272 @@
/*
* Copyright 2019 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.indexmapping;
import java.io.IOException;
import java.lang.annotation.Annotation;
import org.elasticsearch.common.xcontent.XContentBuilder;
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.IndexOptions;
import org.springframework.data.elasticsearch.annotations.IndexPrefixes;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.Similarity;
import org.springframework.data.elasticsearch.annotations.TermVector;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A class to hold the mapping parameters that might be set on
* {@link org.springframework.data.elasticsearch.annotations.Field } or
* {@link org.springframework.data.elasticsearch.annotations.InnerField} annotation.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
public final class MappingParameters {
static final String FIELD_PARAM_COERCE = "coerce";
static final String FIELD_PARAM_COPY_TO = "copy_to";
static final String FIELD_PARAM_DOC_VALUES = "doc_values";
static final String FIELD_PARAM_DATA = "fielddata";
static final String FIELD_PARAM_FORMAT = "format";
static final String FIELD_PARAM_IGNORE_ABOVE = "ignore_above";
static final String FIELD_PARAM_IGNORE_MALFORMED = "ignore_malformed";
static final String FIELD_PARAM_INDEX = "index";
static final String FIELD_PARAM_INDEX_OPTIONS = "index_options";
static final String FIELD_PARAM_INDEX_PHRASES = "index_phrases";
static final String FIELD_PARAM_INDEX_PREFIXES = "index_prefixes";
static final String FIELD_PARAM_INDEX_PREFIXES_MIN_CHARS = "min_chars";
static final String FIELD_PARAM_INDEX_PREFIXES_MAX_CHARS = "max_chars";
static final String FIELD_PARAM_INDEX_ANALYZER = "analyzer";
static final String FIELD_PARAM_NORMALIZER = "normalizer";
static final String FIELD_PARAM_NORMS = "norms";
static final String FIELD_PARAM_NULL_VALUE = "null_value";
static final String FIELD_PARAMETER_NAME_POSITION_INCREMENT_GAP = "position_increment_gap";
static final String FIELD_PARAM_SCALING_FACTOR = "scaling_factor";
static final String FIELD_PARAM_SEARCH_ANALYZER = "search_analyzer";
static final String FIELD_PARAM_STORE = "store";
static final String FIELD_PARAM_SIMILARITY = "similarity";
static final String FIELD_PARAM_TERM_VECTOR = "term_vector";
static final String FIELD_PARAM_TYPE = "type";
private boolean index = true;
private boolean store = false;
private boolean fielddata = false;
private FieldType type = null;
private DateFormat dateFormat = null;
private String datePattern = null;
private String analyzer = null;
private String searchAnalyzer = null;
private String normalizer = null;
private String[] copyTo = null;
private Integer ignoreAbove = null;
private boolean coerce = true;
private boolean docValues = true;
private boolean ignoreMalformed = false;
private IndexOptions indexOptions = null;
boolean indexPhrases = false;
private IndexPrefixes indexPrefixes = null;
private boolean norms = true;
private String nullValue = null;
private Integer positionIncrementGap = null;
private Similarity similarity = Similarity.Default;
private TermVector termVector = TermVector.none;
private double scalingFactor = 1.0;
/**
* extracts the mapping parameters from the relevant annotations.
*
* @param annotation must not be {@literal null}.
* @return empty Optional if the annotation does not have a conformant type.
*/
public static MappingParameters from(Annotation annotation) {
Assert.notNull(annotation, "annotation must not be null!");
if (annotation instanceof Field) {
return new MappingParameters((Field) annotation);
} else if (annotation instanceof InnerField) {
return new MappingParameters((InnerField) annotation);
} else {
throw new IllegalArgumentException("annotation must be an instance of @Field or @InnerField");
}
}
private MappingParameters(Field field) {
index = field.index();
store = field.store();
fielddata = field.fielddata();
type = field.type();
dateFormat = field.format();
datePattern = field.pattern();
analyzer = field.analyzer();
searchAnalyzer = field.searchAnalyzer();
normalizer = field.normalizer();
copyTo = field.copyTo();
ignoreAbove = field.ignoreAbove() >= 0 ? field.ignoreAbove() : null;
coerce = field.coerce();
docValues = field.docValues();
Assert.isTrue(!((type == FieldType.Text || type == FieldType.Nested) && !docValues),
"docValues false is not allowed for field type text");
ignoreMalformed = field.ignoreMalformed();
indexOptions = field.indexOptions();
indexPhrases = field.indexPhrases();
if (field.indexPrefixes().length > 0) {
indexPrefixes = field.indexPrefixes()[0];
}
norms = field.norms();
nullValue = field.nullValue();
positionIncrementGap = field.positionIncrementGap();
similarity = field.similarity();
termVector = field.termVector();
scalingFactor = field.scalingFactor();
}
private MappingParameters(InnerField field) {
index = field.index();
store = field.store();
fielddata = field.fielddata();
type = field.type();
dateFormat = field.format();
datePattern = field.pattern();
analyzer = field.analyzer();
searchAnalyzer = field.searchAnalyzer();
normalizer = field.normalizer();
ignoreAbove = field.ignoreAbove() >= 0 ? field.ignoreAbove() : null;
coerce = field.coerce();
docValues = field.docValues();
Assert.isTrue(!((type == FieldType.Text || type == FieldType.Nested) && !docValues),
"docValues false is not allowed for field type text");
ignoreMalformed = field.ignoreMalformed();
indexOptions = field.indexOptions();
indexPhrases = field.indexPhrases();
if (field.indexPrefixes().length > 0) {
indexPrefixes = field.indexPrefixes()[0];
}
norms = field.norms();
nullValue = field.nullValue();
positionIncrementGap = field.positionIncrementGap();
similarity = field.similarity();
termVector = field.termVector();
scalingFactor = field.scalingFactor();
}
public boolean isStore() {
return store;
}
/**
* writes the different fields to the builder.
*
* @param builder must not be {@literal null}.
*/
public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException {
Assert.notNull(builder, "builder must ot be null");
if (fielddata) {
builder.field(FIELD_PARAM_DATA, fielddata);
}
if (type != FieldType.Auto) {
builder.field(FIELD_PARAM_TYPE, type.name().toLowerCase());
if (type == FieldType.Date && dateFormat != DateFormat.none) {
builder.field(FIELD_PARAM_FORMAT, dateFormat == DateFormat.custom ? datePattern : dateFormat.toString());
}
}
if (!index) {
builder.field(FIELD_PARAM_INDEX, index);
}
if (!StringUtils.isEmpty(analyzer)) {
builder.field(FIELD_PARAM_INDEX_ANALYZER, analyzer);
}
if (!StringUtils.isEmpty(searchAnalyzer)) {
builder.field(FIELD_PARAM_SEARCH_ANALYZER, searchAnalyzer);
}
if (!StringUtils.isEmpty(normalizer)) {
builder.field(FIELD_PARAM_NORMALIZER, normalizer);
}
if (copyTo != null && copyTo.length > 0) {
builder.field(FIELD_PARAM_COPY_TO, copyTo);
}
if (ignoreAbove != null) {
Assert.isTrue(ignoreAbove >= 0, "ignore_above must be a positive value");
builder.field(FIELD_PARAM_IGNORE_ABOVE, ignoreAbove);
}
if (!coerce) {
builder.field(FIELD_PARAM_COERCE, coerce);
}
if (!docValues) {
builder.field(FIELD_PARAM_DOC_VALUES, docValues);
}
if (ignoreMalformed) {
builder.field(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed);
}
if (indexOptions != IndexOptions.none) {
builder.field(FIELD_PARAM_INDEX_OPTIONS, indexOptions);
}
if (indexPhrases) {
builder.field(FIELD_PARAM_INDEX_PHRASES, indexPhrases);
}
if (indexPrefixes != null) {
builder.startObject(FIELD_PARAM_INDEX_PREFIXES);
if (indexPrefixes.minChars() != IndexPrefixes.MIN_DEFAULT) {
builder.field(FIELD_PARAM_INDEX_PREFIXES_MIN_CHARS, indexPrefixes.minChars());
}
if (indexPrefixes.maxChars() != IndexPrefixes.MAX_DEFAULT) {
builder.field(FIELD_PARAM_INDEX_PREFIXES_MAX_CHARS, indexPrefixes.maxChars());
}
builder.endObject();
}
if (!norms) {
builder.field(FIELD_PARAM_NORMS, norms);
}
if (!StringUtils.isEmpty(nullValue)) {
builder.field(FIELD_PARAM_NULL_VALUE, nullValue);
}
if (positionIncrementGap != null && positionIncrementGap >= 0) {
builder.field(FIELD_PARAMETER_NAME_POSITION_INCREMENT_GAP, positionIncrementGap);
}
if (similarity != Similarity.Default) {
builder.field(FIELD_PARAM_SIMILARITY, similarity);
}
if (termVector != TermVector.none) {
builder.field(FIELD_PARAM_TERM_VECTOR, termVector);
}
if (type == FieldType.Scaled_Float) {
builder.field(FIELD_PARAM_SCALING_FACTOR, scalingFactor);
}
}
}

View File

@ -0,0 +1,5 @@
/**
* infrastructure to define the Elasticsearch mapping for an index.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.elasticsearch.core.indexmapping;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
package org.springframework.data.elasticsearch.core.indexmapping;
import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
@ -30,6 +30,7 @@ import lombok.Setter;
import java.io.IOException;
import java.lang.Boolean;
import java.lang.Double;
import java.lang.Integer;
import java.math.BigDecimal;
import java.util.Collection;
@ -49,16 +50,8 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.CompletionField;
import org.springframework.data.elasticsearch.annotations.Document;
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.InnerField;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.Parent;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.completion.Completion;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
@ -125,7 +118,7 @@ public class MappingBuilderTests extends MappingContextBaseTests {
public void shouldUseValueFromAnnotationType() throws IOException, JSONException {
// Given
String expected = "{\"price\":{\"properties\":{\"price\":{\"store\":false,\"type\":\"double\"}}}}";
String expected = "{\"price\":{\"properties\":{\"price\":{\"type\":\"double\"}}}}";
// When
String mapping = getMappingBuilder().buildPropertyMapping(StockPrice.class);
@ -174,7 +167,7 @@ public class MappingBuilderTests extends MappingContextBaseTests {
public void shouldBuildMappingWithSuperclass() throws IOException, JSONException {
String expected = "{\"mapping\":{\"properties\":{\"message\":{\"store\":true,\""
+ "type\":\"text\",\"index\":false,\"analyzer\":\"standard\"}" + ",\"createdDate\":{\"store\":false,"
+ "type\":\"text\",\"index\":false,\"analyzer\":\"standard\"}" + ",\"createdDate\":{"
+ "\"type\":\"date\",\"index\":false}}}}";
String mapping = getMappingBuilder().buildPropertyMapping(SampleInheritedEntity.class);
@ -329,7 +322,7 @@ public class MappingBuilderTests extends MappingContextBaseTests {
// given
String expected = "{\"fieldname-type\":{\"properties\":{" + "\"id-property\":{\"type\":\"keyword\",\"index\":true},"
+ "\"text-property\":{\"store\":false,\"type\":\"text\"}" + "}}}";
+ "\"text-property\":{\"type\":\"text\"}" + "}}}";
// when
String mapping = getMappingBuilder().buildPropertyMapping(FieldNameEntity.TextEntity.class);
@ -371,7 +364,7 @@ public class MappingBuilderTests extends MappingContextBaseTests {
// given
String expected = "{\"fieldname-type\":{\"properties\":{" + "\"id-property\":{\"type\":\"keyword\",\"index\":true},"
+ "\"circular-property\":{\"type\":\"object\",\"properties\":{\"id-property\":{\"store\":false}}}" + "}}}";
+ "\"circular-property\":{\"type\":\"object\",\"properties\":{\"id-property\":{}}}" + "}}}";
// when
String mapping = getMappingBuilder().buildPropertyMapping(FieldNameEntity.CircularEntity.class);
@ -385,7 +378,7 @@ public class MappingBuilderTests extends MappingContextBaseTests {
// given
String expected = "{\"fieldname-type\":{\"properties\":{" + "\"id-property\":{\"type\":\"keyword\",\"index\":true},"
+ "\"completion-property\":{\"type\":\"completion\",\"max_input_length\":100,\"preserve_position_increments\":true,\"preserve_separators\":true,\"search_analyzer\":\"simple\",\"analyzer\":\"simple\"},\"completion-property\":{\"store\":false}"
+ "\"completion-property\":{\"type\":\"completion\",\"max_input_length\":100,\"preserve_position_increments\":true,\"preserve_separators\":true,\"search_analyzer\":\"simple\",\"analyzer\":\"simple\"},\"completion-property\":{}"
+ "}}}";
// when
@ -400,7 +393,7 @@ public class MappingBuilderTests extends MappingContextBaseTests {
// given
String expected = "{\"fieldname-type\":{\"properties\":{" + "\"id-property\":{\"type\":\"keyword\",\"index\":true},"
+ "\"multifield-property\":{\"store\":false,\"type\":\"text\",\"analyzer\":\"whitespace\",\"fields\":{\"prefix\":{\"store\":false,\"type\":\"text\",\"analyzer\":\"stop\",\"search_analyzer\":\"standard\"}}}"
+ "\"multifield-property\":{\"type\":\"text\",\"analyzer\":\"whitespace\",\"fields\":{\"prefix\":{\"type\":\"text\",\"analyzer\":\"stop\",\"search_analyzer\":\"standard\"}}}"
+ "}}}";
// when
@ -414,7 +407,7 @@ public class MappingBuilderTests extends MappingContextBaseTests {
public void shouldUseIgnoreAbove() throws IOException, JSONException {
// given
String expected = "{\"ignore-above-type\":{\"properties\":{\"message\":{\"store\":false,\"type\":\"keyword\",\"ignore_above\":10}}}}";
String expected = "{\"ignore-above-type\":{\"properties\":{\"message\":{\"type\":\"keyword\",\"ignore_above\":10}}}}";
// when
String mapping = getMappingBuilder().buildPropertyMapping(IgnoreAboveEntity.class);
@ -423,6 +416,104 @@ public class MappingBuilderTests extends MappingContextBaseTests {
assertEquals(expected, mapping, false);
}
@Test
public void shouldSetFieldMappingProperties() throws JSONException, IOException {
String expected = "{\n" + //
" \"fmp\": {\n" + //
" \"properties\": {\n" + //
" \"storeTrue\": {\n" + //
" \"store\": true\n" + //
" },\n" + //
" \"storeFalse\": {},\n" + //
" \"indexTrue\": {},\n" + //
" \"indexFalse\": {\n" + //
" \"index\": false\n" + //
" },\n" + //
" \"coerceTrue\": {},\n" + //
" \"coerceFalse\": {\n" + //
" \"coerce\": false\n" + //
" },\n" + //
" \"fielddataTrue\": {\n" + //
" \"fielddata\": true\n" + //
" },\n" + //
" \"fielddataFalse\": {},\n" + //
" \"type\": {\n" + //
" \"type\": \"integer\"\n" + //
" },\n" + //
" \"ignoreAbove\": {\n" + //
" \"ignore_above\": 42\n" + //
" },\n" + //
" \"copyTo\": {\n" + //
" \"copy_to\": [\"foo\", \"bar\"]\n" + //
" },\n" + //
" \"date\": {\n" + //
" \"type\": \"date\",\n" + //
" \"format\": \"YYYYMMDD\"\n" + //
" },\n" + //
" \"analyzers\": {\n" + //
" \"analyzer\": \"ana\",\n" + //
" \"search_analyzer\": \"sana\",\n" + //
" \"normalizer\": \"norma\"\n" + //
" },\n" + //
" \"docValuesTrue\": {\n" + //
" \"type\": \"keyword\"\n" + //
" },\n" + //
" \"docValuesFalse\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"doc_values\": false\n" + //
" },\n" + //
" \"ignoreMalformedFalse\": {},\n" + //
" \"ignoreMalformedTrue\": {\n" + //
" \"ignore_malformed\": true\n" + //
" },\n" + //
" \"indexPhrasesTrue\": {\n" + //
" \"index_phrases\": true\n" + //
" },\n" + //
" \"indexPhrasesFalse\": {},\n" + //
" \"indexOptionsNone\": {},\n" + //
" \"indexOptionsPositions\": {\n" + //
" \"index_options\": \"positions\"\n" + //
" },\n" + //
" \"defaultIndexPrefixes\": {\n" + //
" \"index_prefixes\":{}" + //
" },\n" + //
" \"customIndexPrefixes\": {\n" + //
" \"index_prefixes\":{\"min_chars\":1,\"max_chars\":10}" + //
" },\n" + //
" \"normsTrue\": {},\n" + //
" \"normsFalse\": {\n" + //
" \"norms\": false\n" + //
" },\n" + //
" \"nullValueNotSet\": {},\n" + //
" \"nullValueSet\": {\n" + //
" \"null_value\": \"NULLNULL\"\n" + //
" },\n" + //
" \"positionIncrementGap\": {\n" + //
" \"position_increment_gap\": 42\n" + //
" },\n" + //
" \"similarityDefault\": {},\n" + //
" \"similarityBoolean\": {\n" + //
" \"similarity\": \"boolean\"\n" + //
" },\n" + //
" \"termVectorDefault\": {},\n" + //
" \"termVectorWithOffsets\": {\n" + //
" \"term_vector\": \"with_offsets\"\n" + //
" },\n" + //
" \"scaledFloat\": {\n" + //
" \"type\": \"scaled_float\",\n" + //
" \"scaling_factor\": 100.0\n" + //
" }\n" + //
" }\n" + //
" }\n" + //
"}\n"; //
// when
String mapping = getMappingBuilder().buildPropertyMapping(FieldMappingParameters.class);
// then
assertEquals(expected, mapping, true);
}
/**
* @author Xiao Yu
*/
@ -821,4 +912,40 @@ public class MappingBuilderTests extends MappingContextBaseTests {
@Field(type = FieldType.Nested, ignoreFields = { "groups" }) private Set<User> users = new HashSet<>();
}
@Document(indexName = "test-index-field-mapping-parameters", type = "fmp")
static class FieldMappingParameters {
@Field private String indexTrue;
@Field(index = false) private String indexFalse;
@Field(store = true) private String storeTrue;
@Field private String storeFalse;
@Field private String coerceTrue;
@Field(coerce = false) private String coerceFalse;
@Field(fielddata = true) private String fielddataTrue;
@Field private String fielddataFalse;
@Field(copyTo = { "foo", "bar" }) private String copyTo;
@Field(ignoreAbove = 42) private String ignoreAbove;
@Field(type = FieldType.Integer) private String type;
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "YYYYMMDD") private String date;
@Field(analyzer = "ana", searchAnalyzer = "sana", normalizer = "norma") private String analyzers;
@Field(type = Keyword, docValues = true) private String docValuesTrue;
@Field(type = Keyword, docValues = false) private String docValuesFalse;
@Field(ignoreMalformed = true) private String ignoreMalformedTrue;
@Field(ignoreMalformed = false) private String ignoreMalformedFalse;
@Field(indexOptions = IndexOptions.none) private String indexOptionsNone;
@Field(indexOptions = IndexOptions.positions) private String indexOptionsPositions;
@Field(indexPhrases = true) private String indexPhrasesTrue;
@Field(indexPhrases = false) private String indexPhrasesFalse;
@Field(indexPrefixes = @IndexPrefixes) private String defaultIndexPrefixes;
@Field(indexPrefixes = @IndexPrefixes(minChars = 1, maxChars = 10)) private String customIndexPrefixes;
@Field private String normsTrue;
@Field(norms = false) private String normsFalse;
@Field private String nullValueNotSet;
@Field(nullValue = "NULLNULL") private String nullValueSet;
@Field(positionIncrementGap = 42) private String positionIncrementGap;
@Field private String similarityDefault;
@Field(similarity = Similarity.Boolean) private String similarityBoolean;
@Field private String termVectorDefault;
@Field(termVector = TermVector.with_offsets) private String termVectorWithOffsets;
@Field(type = FieldType.Scaled_Float, scalingFactor = 100.0) Double scaledFloat;
}
}

View File

@ -13,7 +13,7 @@
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
package org.springframework.data.elasticsearch.core.indexmapping;
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;

View File

@ -0,0 +1,68 @@
package org.springframework.data.elasticsearch.core.indexmapping;
import static org.assertj.core.api.Assertions.*;
import java.lang.annotation.Annotation;
import org.junit.Test;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.Score;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
/**
* @author Peter-Josef Meisch
*/
public class MappingParametersTest extends MappingContextBaseTests {
private ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.get().getMappingContext()
.getRequiredPersistentEntity(AnnotatedClass.class);
@Test // DATAES-621
public void shouldCreateParametersForFieldAnnotation() {
Annotation annotation = entity.getRequiredPersistentProperty("field").findAnnotation(Field.class);
MappingParameters mappingParameters = MappingParameters.from(annotation);
assertThat(mappingParameters).isNotNull();
}
@Test // DATAES-621
public void shouldCreateParametersForInnerFieldAnnotation() {
Annotation annotation = entity.getRequiredPersistentProperty("innerField").findAnnotation(InnerField.class);
MappingParameters mappingParameters = MappingParameters.from(annotation);
assertThat(mappingParameters).isNotNull();
}
@Test // DATAES-621
public void shouldNotCreateParametersForUnknownAnnotation() {
Annotation annotation = entity.getRequiredPersistentProperty("score").findAnnotation(Score.class);
assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class);
}
@Test // DATAES-621
public void shouldNotAllowDocValueFalseOnFieldTypeText() {
Annotation annotation = entity.getRequiredPersistentProperty("docValuesText").findAnnotation(Field.class);
assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class);
}
@Test // DATAES-621
public void shouldNotAllowDocValuesFalseOnFieldTypeNested() {
Annotation annotation = entity.getRequiredPersistentProperty("docValuesNested").findAnnotation(Field.class);
assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class);
}
static class AnnotatedClass {
@Field private String field;
@InnerField(suffix = "test", type = FieldType.Text) private String innerField;
@Score private float score;
@Field(type = FieldType.Text, docValues = false) private String docValuesText;
@Field(type = FieldType.Nested, docValues = false) private String docValuesNested;
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
package org.springframework.data.elasticsearch.core.indexmapping;
import static org.assertj.core.api.Assertions.*;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
package org.springframework.data.elasticsearch.core.indexmapping;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
@ -24,7 +24,6 @@ import java.io.IOException;
import java.util.Date;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
@ -40,8 +39,8 @@ import org.springframework.data.elasticsearch.annotations.FieldType;
public class SimpleElasticsearchDateMappingTests extends MappingContextBaseTests {
private static final String EXPECTED_MAPPING = "{\"mapping\":{\"properties\":{\"message\":{\"store\":true,"
+ "\"type\":\"text\",\"index\":false,\"analyzer\":\"standard\"},\"customFormatDate\":{\"store\":false,\"type\":\"date\",\"format\":\"dd.MM.yyyy hh:mm\"},"
+ "\"defaultFormatDate\":{\"store\":false,\"type\":\"date\"},\"basicFormatDate\":{\"store\":false,\""
+ "\"type\":\"text\",\"index\":false,\"analyzer\":\"standard\"},\"customFormatDate\":{\"type\":\"date\",\"format\":\"dd.MM.yyyy hh:mm\"},"
+ "\"defaultFormatDate\":{\"type\":\"date\"},\"basicFormatDate\":{\""
+ "type\":\"date\",\"format\":\"basic_date\"}}}}";
@Test // DATAES-568