org.elasticsearch.search.suggest.context.ContextMapping Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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.search.suggest.context;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.search.suggest.analyzing.XAnalyzingSuggester;
import org.apache.lucene.util.automaton.Automata;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations;
import org.apache.lucene.util.fst.FST;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.ParseContext.Document;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
/**
* A {@link ContextMapping} is used t define a context that may used
* in conjunction with a suggester. To define a suggester that depends on a
* specific context derived class of {@link ContextMapping} will be
* used to specify the kind of additional information required in order to make
* suggestions.
*/
public abstract class ContextMapping implements ToXContent {
/** Character used to separate several contexts */
public static final char SEPARATOR = '\u001D';
/** Dummy Context Mapping that should be used if no context is used*/
public static final SortedMap EMPTY_MAPPING = Maps.newTreeMap();
/** Dummy Context Config matching the Dummy Mapping by providing an empty context*/
public static final SortedMap EMPTY_CONFIG = Maps.newTreeMap();
/** Dummy Context matching the Dummy Mapping by not wrapping a {@link TokenStream} */
public static final Context EMPTY_CONTEXT = new Context(EMPTY_CONFIG, null);
public static final String FIELD_VALUE = "value";
public static final String FIELD_MISSING = "default";
public static final String FIELD_TYPE = "type";
protected final String type; // Type of the Contextmapping
protected final String name;
/**
* Define a new context mapping of a specific type
*
* @param type
* name of the new context mapping
*/
protected ContextMapping(String type, String name) {
super();
this.type = type;
this.name = name;
}
/**
* @return the type name of the context
*/
protected String type() {
return type;
}
/**
* @return the name/id of the context
*/
public String name() {
return name;
}
@Override
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(name);
builder.field(FIELD_TYPE, type);
toInnerXContent(builder, params);
builder.endObject();
return builder;
}
/**
* A {@link ContextMapping} combined with the information provided by a document
* form a {@link ContextConfig} which is used to build the underlying FST.
*
* @param parseContext context of parsing phase
* @param parser {@link XContentParser} used to read and setup the configuration
* @return A {@link ContextConfig} related to this mapping
*
* @throws IOException
* @throws ElasticsearchParseException
*/
public abstract ContextConfig parseContext(ParseContext parseContext, XContentParser parser) throws IOException, ElasticsearchParseException;
public abstract ContextConfig defaultConfig();
/**
* Parse a query according to the context. Parsing starts at parsers current position
*
* @param name name of the context
* @param parser {@link XContentParser} providing the data of the query
*
* @return {@link ContextQuery} according to this mapping
*
* @throws IOException
* @throws ElasticsearchParseException
*/
public abstract ContextQuery parseQuery(String name, XContentParser parser) throws IOException, ElasticsearchParseException;
/**
* Since every context mapping is assumed to have a name given by the field name of an context object, this
* method is used to build the value used to serialize the mapping
*
* @param builder builder to append the mapping to
* @param params parameters passed to the builder
*
* @return the builder used
*
* @throws IOException
*/
protected abstract XContentBuilder toInnerXContent(XContentBuilder builder, Params params) throws IOException;
/**
* Test equality of two mapping
*
* @param thisMappings first mapping
* @param otherMappings second mapping
*
* @return true if both arguments are equal
*/
public static boolean mappingsAreEqual(SortedMap thisMappings, SortedMap otherMappings) {
return Iterables.elementsEqual(thisMappings.entrySet(), otherMappings.entrySet());
}
@Override
public String toString() {
try {
return toXContent(JsonXContent.contentBuilder(), ToXContent.EMPTY_PARAMS).string();
} catch (IOException e) {
return super.toString();
}
}
/**
* A collection of {@link ContextMapping}s, their {@link ContextConfig}uration and a
* Document form a complete {@link Context}. Since this Object provides all information used
* to setup a suggestion, it can be used to wrap the entire {@link TokenStream} used to build a
* path within the {@link FST}.
*/
public static class Context {
final SortedMap contexts;
final Document doc;
public Context(SortedMap contexts, Document doc) {
super();
this.contexts = contexts;
this.doc = doc;
}
/**
* Wrap the {@link TokenStream} according to the provided informations of {@link ContextConfig}
* and a related {@link Document}.
*
* @param tokenStream {@link TokenStream} to wrap
*
* @return wrapped token stream
*/
public TokenStream wrapTokenStream(TokenStream tokenStream) {
for (ContextConfig context : contexts.values()) {
tokenStream = context.wrapTokenStream(doc, tokenStream);
}
return tokenStream;
}
}
/**
* A {@link ContextMapping} combined with the information provided by a document
* form a {@link ContextConfig} which is used to build the underlying {@link FST}. This class hold
* a simple method wrapping a {@link TokenStream} by provided document informations.
*/
public static abstract class ContextConfig {
/**
* Wrap a {@link TokenStream} for building suggestions to use context informations
* provided by a document or a {@link ContextMapping}
*
* @param doc document related to the stream
* @param stream original stream used to build the underlying {@link FST}
*
* @return A new {@link TokenStream} providing additional context information
*/
protected abstract TokenStream wrapTokenStream(Document doc, TokenStream stream);
}
/**
* A {@link ContextQuery} defines the context information for a specific {@link ContextMapping}
* defined within a suggestion request. According to the parameters set in the request and the
* {@link ContextMapping} such a query is used to wrap the {@link TokenStream} of the actual
* suggestion request into a {@link TokenStream} with the context settings
*/
public static abstract class ContextQuery implements ToXContent {
protected final String name;
protected ContextQuery(String name) {
this.name = name;
}
public String name() {
return name;
}
/**
* Create a automaton for a given context query this automaton will be used
* to find the matching paths with the fst
*
* @param preserveSep set an additional char (XAnalyzingSuggester.SEP_LABEL
) between each context query
* @param queries list of {@link ContextQuery} defining the lookup context
*
* @return Automaton matching the given Query
*/
public static Automaton toAutomaton(boolean preserveSep, Iterable queries) {
Automaton a = Automata.makeEmptyString();
Automaton gap = Automata.makeChar(ContextMapping.SEPARATOR);
if (preserveSep) {
// if separators are preserved the fst contains a SEP_LABEL
// behind each gap. To have a matching automaton, we need to
// include the SEP_LABEL in the query as well
gap = Operations.concatenate(gap, Automata.makeChar(XAnalyzingSuggester.SEP_LABEL));
}
for (ContextQuery query : queries) {
a = Operations.concatenate(Arrays.asList(query.toAutomaton(), gap, a));
}
// TODO: should we limit this? Do any of our ContextQuery impls really create exponential regexps? GeoQuery looks safe (union
// of strings).
return Operations.determinize(a, Integer.MAX_VALUE);
}
/**
* Build a LookUp Automaton for this context.
* @return LookUp Automaton
*/
protected abstract Automaton toAutomaton();
/**
* Parse a set of {@link ContextQuery} according to a given mapping
* @param mappings List of mapping defined y the suggest field
* @param parser parser holding the settings of the queries. The parsers
* current token is assumed hold an array. The number of elements
* in this array must match the number of elements in the mappings.
* @return List of context queries
*
* @throws IOException if something unexpected happened on the underlying stream
* @throws ElasticsearchParseException if the list of queries could not be parsed
*/
public static List parseQueries(Map mappings, XContentParser parser)
throws IOException, ElasticsearchParseException {
Map querySet = new HashMap<>();
Token token = parser.currentToken();
if(token == Token.START_OBJECT) {
while ((token = parser.nextToken()) != Token.END_OBJECT) {
String name = parser.currentName();
ContextMapping mapping = mappings.get(name);
if (mapping == null) {
throw new ElasticsearchParseException("no mapping defined for [{}]", name);
}
parser.nextToken();
querySet.put(name, mapping.parseQuery(name, parser));
}
}
List queries = new ArrayList<>(mappings.size());
for (ContextMapping mapping : mappings.values()) {
queries.add(querySet.get(mapping.name));
}
return queries;
}
@Override
public String toString() {
try {
return toXContent(JsonXContent.contentBuilder(), ToXContent.EMPTY_PARAMS).string();
} catch (IOException e) {
return super.toString();
}
}
}
}