/*
* 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.index.mapper.murmur3;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.hash.MurmurHash3;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.analysis.NumericLongAnalyzer;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.core.LongFieldMapper;
import org.elasticsearch.index.mapper.core.NumberFieldMapper;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseNumberField;
public class Murmur3FieldMapper extends LongFieldMapper {
public static final String CONTENT_TYPE = "murmur3";
public static class Defaults extends LongFieldMapper.Defaults {
public static final MappedFieldType FIELD_TYPE = new Murmur3FieldType();
static {
FIELD_TYPE.freeze();
}
}
public static class Builder extends NumberFieldMapper.Builder<Builder, Murmur3FieldMapper> {
public Builder(String name) {
super(name, Defaults.FIELD_TYPE, Integer.MAX_VALUE);
builder = this;
builder.precisionStep(Integer.MAX_VALUE);
}
@Override
public Murmur3FieldMapper build(BuilderContext context) {
setupFieldType(context);
Murmur3FieldMapper fieldMapper = new Murmur3FieldMapper(name, fieldType, defaultFieldType,
ignoreMalformed(context), coerce(context),
context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
return (Murmur3FieldMapper) fieldMapper.includeInAll(includeInAll);
}
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
if (context.indexCreatedVersion().onOrAfter(Version.V_2_0_0_beta1)) {
fieldType.setIndexOptions(IndexOptions.NONE);
defaultFieldType.setIndexOptions(IndexOptions.NONE);
fieldType.setHasDocValues(true);
defaultFieldType.setHasDocValues(true);
}
}
@Override
protected NamedAnalyzer makeNumberAnalyzer(int precisionStep) {
return NumericLongAnalyzer.buildNamedAnalyzer(precisionStep);
}
@Override
protected int maxPrecisionStep() {
return 64;
}
}
public static class TypeParser implements Mapper.TypeParser {
@Override
@SuppressWarnings("unchecked")
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
Builder builder = new Builder(name);
// tweaking these settings is no longer allowed, the entire purpose of murmur3 fields is to store a hash
if (parserContext.indexVersionCreated().onOrAfter(Version.V_2_0_0_beta1)) {
if (node.get("doc_values") != null) {
throw new MapperParsingException("Setting [doc_values] cannot be modified for field [" + name + "]");
}
if (node.get("index") != null) {
throw new MapperParsingException("Setting [index] cannot be modified for field [" + name + "]");
}
}
if (parserContext.indexVersionCreated().before(Version.V_2_0_0_beta1)) {
builder.indexOptions(IndexOptions.DOCS);
}
parseNumberField(builder, name, node, parserContext);
// Because this mapper extends LongFieldMapper the null_value field will be added to the JSON when transferring cluster state
// between nodes so we have to remove the entry here so that the validation doesn't fail
// TODO should murmur3 support null_value? at the moment if a user sets null_value it has to be silently ignored since we can't
// determine whether the JSON is the original JSON from the user or if its the serialised cluster state being passed between nodes.
// node.remove("null_value");
return builder;
}
}
// this only exists so a check can be done to match the field type to using murmur3 hashing...
public static class Murmur3FieldType extends LongFieldMapper.LongFieldType {
public Murmur3FieldType() {
}
protected Murmur3FieldType(Murmur3FieldType ref) {
super(ref);
}
@Override
public Murmur3FieldType clone() {
return new Murmur3FieldType(this);
}
}
protected Murmur3FieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
Settings indexSettings, MultiFields multiFields, CopyTo copyTo) {
super(simpleName, fieldType, defaultFieldType, ignoreMalformed, coerce, indexSettings, multiFields, copyTo);
}
@Override
protected String contentType() {
return CONTENT_TYPE;
}
@Override
protected void innerParseCreateField(ParseContext context, List<Field> fields) throws IOException {
final Object value;
if (context.externalValueSet()) {
value = context.externalValue();
} else {
value = context.parser().textOrNull();
}
if (value != null) {
final BytesRef bytes = new BytesRef(value.toString());
final long hash = MurmurHash3.hash128(bytes.bytes, bytes.offset, bytes.length, 0, new MurmurHash3.Hash128()).h1;
super.innerParseCreateField(context.createExternalValueContext(hash), fields);
}
}
@Override
public boolean isGenerated() {
return true;
}
}