/* * 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; import com.carrotsearch.hppc.ObjectObjectHashMap; import com.carrotsearch.hppc.ObjectObjectMap; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexableField; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.lucene.all.AllEntries; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public abstract class ParseContext { /** Fork of {@link org.apache.lucene.document.Document} with additional functionality. */ public static class Document implements Iterable<IndexableField> { private final Document parent; private final String path; private final String prefix; private final List<IndexableField> fields; private ObjectObjectMap<Object, IndexableField> keyedFields; private Document(String path, Document parent) { fields = new ArrayList<>(); this.path = path; this.prefix = path.isEmpty() ? "" : path + "."; this.parent = parent; } public Document() { this("", null); } /** * Return the path associated with this document. */ public String getPath() { return path; } /** * Return a prefix that all fields in this document should have. */ public String getPrefix() { return prefix; } /** * Return the parent document, or null if this is the root document. */ public Document getParent() { return parent; } @Override public Iterator<IndexableField> iterator() { return fields.iterator(); } public List<IndexableField> getFields() { return fields; } public void add(IndexableField field) { // either a meta fields or starts with the prefix assert field.name().startsWith("_") || field.name().startsWith(prefix) : field.name() + " " + prefix; fields.add(field); } /** Add fields so that they can later be fetched using {@link #getByKey(Object)}. */ public void addWithKey(Object key, IndexableField field) { if (keyedFields == null) { keyedFields = new ObjectObjectHashMap<>(); } else if (keyedFields.containsKey(key)) { throw new IllegalStateException("Only one field can be stored per key"); } keyedFields.put(key, field); add(field); } /** Get back fields that have been previously added with {@link #addWithKey(Object, IndexableField)}. */ public IndexableField getByKey(Object key) { return keyedFields == null ? null : keyedFields.get(key); } public IndexableField[] getFields(String name) { List<IndexableField> f = new ArrayList<>(); for (IndexableField field : fields) { if (field.name().equals(name)) { f.add(field); } } return f.toArray(new IndexableField[f.size()]); } /** * Returns an array of values of the field specified as the method parameter. * This method returns an empty array when there are no * matching fields. It never returns null. * If you want the actual numeric field instances back, use {@link #getFields}. * @param name the name of the field * @return a <code>String[]</code> of field values */ public final String[] getValues(String name) { List<String> result = new ArrayList<>(); for (IndexableField field : fields) { if (field.name().equals(name) && field.stringValue() != null) { result.add(field.stringValue()); } } return result.toArray(new String[result.size()]); } public IndexableField getField(String name) { for (IndexableField field : fields) { if (field.name().equals(name)) { return field; } } return null; } public String get(String name) { for (IndexableField f : fields) { if (f.name().equals(name) && f.stringValue() != null) { return f.stringValue(); } } return null; } public BytesRef getBinaryValue(String name) { for (IndexableField f : fields) { if (f.name().equals(name) && f.binaryValue() != null) { return f.binaryValue(); } } return null; } } private static class FilterParseContext extends ParseContext { private final ParseContext in; private FilterParseContext(ParseContext in) { this.in = in; } @Override public DocumentMapperParser docMapperParser() { return in.docMapperParser(); } @Override public boolean isWithinCopyTo() { return in.isWithinCopyTo(); } @Override public boolean isWithinMultiFields() { return in.isWithinMultiFields(); } @Override public Settings indexSettings() { return in.indexSettings(); } @Override public SourceToParse sourceToParse() { return in.sourceToParse(); } @Override public ContentPath path() { return in.path(); } @Override public XContentParser parser() { return in.parser(); } @Override public Document rootDoc() { return in.rootDoc(); } @Override public List<Document> docs() { return in.docs(); } @Override public Document doc() { return in.doc(); } @Override protected void addDoc(Document doc) { in.addDoc(doc); } @Override public RootObjectMapper root() { return in.root(); } @Override public DocumentMapper docMapper() { return in.docMapper(); } @Override public MapperService mapperService() { return in.mapperService(); } @Override public Field version() { return in.version(); } @Override public void version(Field version) { in.version(version); } @Override public SeqNoFieldMapper.SequenceIDFields seqID() { return in.seqID(); } @Override public void seqID(SeqNoFieldMapper.SequenceIDFields seqID) { in.seqID(seqID); } @Override public AllEntries allEntries() { return in.allEntries(); } @Override public boolean externalValueSet() { return in.externalValueSet(); } @Override public Object externalValue() { return in.externalValue(); } @Override public void addDynamicMapper(Mapper update) { in.addDynamicMapper(update); } @Override public List<Mapper> getDynamicMappers() { return in.getDynamicMappers(); } } public static class InternalParseContext extends ParseContext { private final DocumentMapper docMapper; private final DocumentMapperParser docMapperParser; private final ContentPath path; private final XContentParser parser; private Document document; private final List<Document> documents; @Nullable private final Settings indexSettings; private final SourceToParse sourceToParse; private Field version; private SeqNoFieldMapper.SequenceIDFields seqID; private final AllEntries allEntries; private final List<Mapper> dynamicMappers; public InternalParseContext(@Nullable Settings indexSettings, DocumentMapperParser docMapperParser, DocumentMapper docMapper, SourceToParse source, XContentParser parser) { this.indexSettings = indexSettings; this.docMapper = docMapper; this.docMapperParser = docMapperParser; this.path = new ContentPath(0); this.parser = parser; this.document = new Document(); this.documents = new ArrayList<>(); this.documents.add(document); this.version = null; this.sourceToParse = source; this.allEntries = new AllEntries(); this.dynamicMappers = new ArrayList<>(); } @Override public DocumentMapperParser docMapperParser() { return this.docMapperParser; } @Override @Nullable public Settings indexSettings() { return this.indexSettings; } @Override public SourceToParse sourceToParse() { return this.sourceToParse; } @Override public ContentPath path() { return this.path; } @Override public XContentParser parser() { return this.parser; } @Override public Document rootDoc() { return documents.get(0); } @Override public List<Document> docs() { return this.documents; } @Override public Document doc() { return this.document; } @Override protected void addDoc(Document doc) { this.documents.add(doc); } @Override public RootObjectMapper root() { return docMapper.root(); } @Override public DocumentMapper docMapper() { return this.docMapper; } @Override public MapperService mapperService() { return docMapperParser.mapperService; } @Override public Field version() { return this.version; } @Override public void version(Field version) { this.version = version; } @Override public SeqNoFieldMapper.SequenceIDFields seqID() { return this.seqID; } @Override public void seqID(SeqNoFieldMapper.SequenceIDFields seqID) { this.seqID = seqID; } @Override public AllEntries allEntries() { return this.allEntries; } @Override public void addDynamicMapper(Mapper mapper) { dynamicMappers.add(mapper); } @Override public List<Mapper> getDynamicMappers() { return dynamicMappers; } } public abstract DocumentMapperParser docMapperParser(); /** Return a view of this {@link ParseContext} that changes the return * value of {@link #getIncludeInAllDefault()}. */ public final ParseContext setIncludeInAllDefault(boolean includeInAll) { return new FilterParseContext(this) { @Override public Boolean getIncludeInAllDefault() { return includeInAll; } }; } /** Whether field values should be added to the _all field by default. */ public Boolean getIncludeInAllDefault() { return null; } /** * Return a new context that will be within a copy-to operation. */ public final ParseContext createCopyToContext() { return new FilterParseContext(this) { @Override public boolean isWithinCopyTo() { return true; } }; } public boolean isWithinCopyTo() { return false; } /** * Return a new context that will be within multi-fields. */ public final ParseContext createMultiFieldContext() { return new FilterParseContext(this) { @Override public boolean isWithinMultiFields() { return true; } }; } /** * Return a new context that will be used within a nested document. */ public final ParseContext createNestedContext(String fullPath) { final Document doc = new Document(fullPath, doc()); addDoc(doc); return switchDoc(doc); } /** * Return a new context that has the provided document as the current document. */ public final ParseContext switchDoc(final Document document) { return new FilterParseContext(this) { @Override public Document doc() { return document; } }; } /** * Return a new context that will have the provided path. */ public final ParseContext overridePath(final ContentPath path) { return new FilterParseContext(this) { @Override public ContentPath path() { return path; } }; } public boolean isWithinMultiFields() { return false; } @Nullable public abstract Settings indexSettings(); public abstract SourceToParse sourceToParse(); public abstract ContentPath path(); public abstract XContentParser parser(); public abstract Document rootDoc(); public abstract List<Document> docs(); public abstract Document doc(); protected abstract void addDoc(Document doc); public abstract RootObjectMapper root(); public abstract DocumentMapper docMapper(); public abstract MapperService mapperService(); public abstract Field version(); public abstract void version(Field version); public abstract SeqNoFieldMapper.SequenceIDFields seqID(); public abstract void seqID(SeqNoFieldMapper.SequenceIDFields seqID); public final boolean includeInAll(Boolean includeInAll, FieldMapper mapper) { return includeInAll(includeInAll, mapper.fieldType().indexOptions() != IndexOptions.NONE); } /** * Is all included or not. Will always disable it if {@link org.elasticsearch.index.mapper.AllFieldMapper#enabled()} * is <tt>false</tt>. If its enabled, then will return <tt>true</tt> only if the specific flag is <tt>null</tt> or * its actual value (so, if not set, defaults to "true") and the field is indexed. */ private boolean includeInAll(Boolean includeInAll, boolean indexed) { if (isWithinCopyTo()) { return false; } if (isWithinMultiFields()) { return false; } if (!docMapper().allFieldMapper().enabled()) { return false; } if (includeInAll == null) { includeInAll = getIncludeInAllDefault(); } // not explicitly set if (includeInAll == null) { return indexed; } return includeInAll; } public abstract AllEntries allEntries(); /** * Return a new context that will have the external value set. */ public final ParseContext createExternalValueContext(final Object externalValue) { return new FilterParseContext(this) { @Override public boolean externalValueSet() { return true; } @Override public Object externalValue() { return externalValue; } }; } public boolean externalValueSet() { return false; } public Object externalValue() { throw new IllegalStateException("External value is not set"); } /** * Try to parse an externalValue if any * @param clazz Expected class for external value * @return null if no external value has been set or the value */ public final <T> T parseExternalValue(Class<T> clazz) { if (!externalValueSet() || externalValue() == null) { return null; } if (!clazz.isInstance(externalValue())) { throw new IllegalArgumentException("illegal external value class [" + externalValue().getClass().getName() + "]. Should be " + clazz.getName()); } return clazz.cast(externalValue()); } /** * Add a new mapper dynamically created while parsing. */ public abstract void addDynamicMapper(Mapper update); /** * Get dynamic mappers created while parsing. */ public abstract List<Mapper> getDynamicMappers(); }