/* * Licensed to ElasticSearch and Shay Banon 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.internal; import org.apache.lucene.document.Field; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.Term; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.uid.UidField; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.mapper.*; import org.elasticsearch.index.mapper.core.AbstractFieldMapper; import java.io.IOException; import java.util.Map; import static org.elasticsearch.index.mapper.MapperBuilders.uid; /** * */ public class UidFieldMapper extends AbstractFieldMapper<Uid> implements InternalMapper, RootMapper { public static final String NAME = "_uid".intern(); public static final Term TERM_FACTORY = new Term(NAME, ""); public static final String CONTENT_TYPE = "_uid"; public static class Defaults extends AbstractFieldMapper.Defaults { public static final String NAME = UidFieldMapper.NAME; public static final Field.Index INDEX = Field.Index.NOT_ANALYZED; public static final boolean OMIT_NORMS = true; public static final FieldInfo.IndexOptions INDEX_OPTIONS = FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS; // we store payload (otherwise, we really need just docs) } public static class Builder extends Mapper.Builder<Builder, UidFieldMapper> { protected String indexName; public Builder() { super(Defaults.NAME); this.indexName = name; } @Override public UidFieldMapper build(BuilderContext context) { return new UidFieldMapper(name, indexName); } } public static class TypeParser implements Mapper.TypeParser { @Override public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException { return uid(); } } private ThreadLocal<UidField> fieldCache = new ThreadLocal<UidField>() { @Override protected UidField initialValue() { return new UidField(names().indexName(), "", 0); } }; public UidFieldMapper() { this(Defaults.NAME); } protected UidFieldMapper(String name) { this(name, name); } protected UidFieldMapper(String name, String indexName) { super(new Names(name, indexName, indexName, name), Defaults.INDEX, Field.Store.YES, Defaults.TERM_VECTOR, Defaults.BOOST, Defaults.OMIT_NORMS, Defaults.INDEX_OPTIONS, Lucene.KEYWORD_ANALYZER, Lucene.KEYWORD_ANALYZER); } @Override public void preParse(ParseContext context) throws IOException { // if we have the id provided, fill it, and parse now if (context.sourceToParse().id() != null) { context.id(context.sourceToParse().id()); super.parse(context); } } @Override public void postParse(ParseContext context) throws IOException { if (context.id() == null && !context.sourceToParse().flyweight()) { throw new MapperParsingException("No id found while parsing the content source"); } // if we did not have the id as part of the sourceToParse, then we need to parse it here // it would have been filled in the _id parse phase if (context.sourceToParse().id() == null) { super.parse(context); // since we did not have the uid in the pre phase, we did not add it automatically to the nested docs // as they were created we need to make sure we add it to all the nested docs... if (context.docs().size() > 1) { UidField uidField = (UidField) context.rootDoc().getFieldable(UidFieldMapper.NAME); assert uidField != null; // we need to go over the docs and add it... for (int i = 1; i < context.docs().size(); i++) { // we don't need to add it as a full uid field in nested docs, since we don't need versioning context.docs().get(i).add(new Field(UidFieldMapper.NAME, uidField.uid(), Field.Store.NO, Field.Index.NOT_ANALYZED)); } } } } @Override public void parse(ParseContext context) throws IOException { // nothing to do here, we either do it in post parse, or in pre parse. } @Override public void validate(ParseContext context) throws MapperParsingException { } @Override public boolean includeInObject() { return false; } @Override protected Fieldable parseCreateField(ParseContext context) throws IOException { context.uid(Uid.createUid(context.stringBuilder(), context.type(), context.id())); // so, caching uid stream and field is fine // since we don't do any mapping parsing without immediate indexing // and, when percolating, we don't index the uid UidField field = fieldCache.get(); field.setUid(context.uid()); return field; // version get updated by the engine } @Override public Uid value(Fieldable field) { return Uid.createUid(field.stringValue()); } @Override public Uid valueFromString(String value) { return Uid.createUid(value); } @Override public String valueAsString(Fieldable field) { return field.stringValue(); } @Override public String indexedValue(String value) { return value; } public Term term(String type, String id) { return term(Uid.createUid(type, id)); } public Term term(String uid) { return names().createIndexNameTerm(uid); } @Override public void close() { fieldCache.remove(); } @Override protected String contentType() { return CONTENT_TYPE; } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { // for now, don't output it at all return builder; } @Override public void merge(Mapper mergeWith, MergeContext mergeContext) throws MergeMappingException { // do nothing here, no merging, but also no exception } }