/*
* 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 java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version;
import org.elasticsearch.common.hash.MurmurHash3;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexNumericFieldData.NumericType;
import org.elasticsearch.index.fielddata.plain.DocValuesIndexFieldData;
import org.elasticsearch.index.mapper.FieldMapper;
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.TypeParsers;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
public class Murmur3FieldMapper extends FieldMapper {
public static final String CONTENT_TYPE = "murmur3";
public static class Defaults {
public static final MappedFieldType FIELD_TYPE = new Murmur3FieldType();
static {
FIELD_TYPE.freeze();
}
}
public static class Builder extends FieldMapper.Builder<Builder, Murmur3FieldMapper> {
public Builder(String name) {
super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
builder = this;
}
@Override
public Murmur3FieldMapper build(BuilderContext context) {
setupFieldType(context);
return new Murmur3FieldMapper(name, fieldType, defaultFieldType,
context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
}
@Override
protected void setupFieldType(BuilderContext context) {
super.setupFieldType(context);
fieldType.setIndexOptions(IndexOptions.NONE);
defaultFieldType.setIndexOptions(IndexOptions.NONE);
fieldType.setHasDocValues(true);
defaultFieldType.setHasDocValues(true);
}
}
public static class TypeParser implements Mapper.TypeParser {
@Override
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 (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_5_0_0_alpha2)) {
node.remove("precision_step");
}
TypeParsers.parseField(builder, name, node, parserContext);
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 MappedFieldType {
public Murmur3FieldType() {
}
protected Murmur3FieldType(Murmur3FieldType ref) {
super(ref);
}
@Override
public String typeName() {
return CONTENT_TYPE;
}
@Override
public Murmur3FieldType clone() {
return new Murmur3FieldType(this);
}
@Override
public IndexFieldData.Builder fielddataBuilder() {
failIfNoDocValues();
return new DocValuesIndexFieldData.Builder().numericType(NumericType.LONG);
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
throw new QueryShardException(context, "Murmur3 fields are not searchable: [" + name() + "]");
}
}
protected Murmur3FieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
Settings indexSettings, MultiFields multiFields, CopyTo copyTo) {
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
}
@Override
protected String contentType() {
return CONTENT_TYPE;
}
@Override
protected void parseCreateField(ParseContext context, List<IndexableField> 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;
fields.add(new SortedNumericDocValuesField(fieldType().name(), hash));
if (fieldType().stored()) {
fields.add(new StoredField(name(), hash));
}
}
}
}