diff --git a/core/src/main/java/org/elasticsearch/percolator/PercolateContext.java b/core/src/main/java/org/elasticsearch/percolator/PercolateContext.java index e7d5c3fcb07..bf3b060db12 100644 --- a/core/src/main/java/org/elasticsearch/percolator/PercolateContext.java +++ b/core/src/main/java/org/elasticsearch/percolator/PercolateContext.java @@ -32,6 +32,7 @@ import org.apache.lucene.util.Counter; import org.elasticsearch.action.percolate.PercolateShardRequest; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.cache.recycler.PageCacheRecycler; +import org.elasticsearch.client.Client; import org.elasticsearch.common.*; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.lease.Releasables; @@ -58,7 +59,7 @@ import org.elasticsearch.search.aggregations.SearchContextAggregations; import org.elasticsearch.search.dfs.DfsSearchResult; import org.elasticsearch.search.fetch.FetchSearchResult; import org.elasticsearch.search.fetch.FetchSubPhase; -import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; import org.elasticsearch.search.fetch.script.ScriptFieldsContext; import org.elasticsearch.search.fetch.source.FetchSourceContext; @@ -116,6 +117,7 @@ public class PercolateContext extends SearchContext { private SearchContextAggregations aggregations; private QuerySearchResult querySearchResult; private Sort sort; + private final Map subPhaseContexts = new HashMap<>(); public PercolateContext(PercolateShardRequest request, SearchShardTarget searchShardTarget, IndexShard indexShard, IndexService indexService, PageCacheRecycler pageCacheRecycler, @@ -282,6 +284,15 @@ public class PercolateContext extends SearchContext { return this; } + @Override + public SubPhaseContext getFetchSubPhaseContext(FetchSubPhase.ContextFactory contextFactory) { + String subPhaseName = contextFactory.getName(); + if (subPhaseContexts.get(subPhaseName) == null) { + subPhaseContexts.put(subPhaseName, contextFactory.newContextInstance()); + } + return (SubPhaseContext) subPhaseContexts.get(subPhaseName); + } + // Unused: @Override public void preProcess() { @@ -378,16 +389,6 @@ public class PercolateContext extends SearchContext { throw new UnsupportedOperationException(); } - @Override - public boolean hasFieldDataFields() { - throw new UnsupportedOperationException(); - } - - @Override - public FieldDataFieldsContext fieldDataFields() { - throw new UnsupportedOperationException(); - } - @Override public boolean hasScriptFields() { throw new UnsupportedOperationException(); diff --git a/core/src/main/java/org/elasticsearch/search/SearchModule.java b/core/src/main/java/org/elasticsearch/search/SearchModule.java index cf1cf71afef..eba17c8afd7 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/core/src/main/java/org/elasticsearch/search/SearchModule.java @@ -32,6 +32,7 @@ import org.elasticsearch.search.aggregations.AggregationModule; import org.elasticsearch.search.controller.SearchPhaseController; import org.elasticsearch.search.dfs.DfsPhase; import org.elasticsearch.search.fetch.FetchPhase; +import org.elasticsearch.search.fetch.FetchSubPhaseModule; import org.elasticsearch.search.fetch.explain.ExplainFetchSubPhase; import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsFetchSubPhase; import org.elasticsearch.search.fetch.innerhits.InnerHitsFetchSubPhase; @@ -63,7 +64,8 @@ public class SearchModule extends AbstractModule implements SpawnModules { new HighlightModule(), new SuggestModule(), new FunctionScoreModule(), - new AggregationModule()); + new AggregationModule(), + new FetchSubPhaseModule()); } @Override @@ -73,14 +75,6 @@ public class SearchModule extends AbstractModule implements SpawnModules { bind(SearchPhaseController.class).asEagerSingleton(); bind(FetchPhase.class).asEagerSingleton(); - bind(ExplainFetchSubPhase.class).asEagerSingleton(); - bind(FieldDataFieldsFetchSubPhase.class).asEagerSingleton(); - bind(ScriptFieldsFetchSubPhase.class).asEagerSingleton(); - bind(FetchSourceSubPhase.class).asEagerSingleton(); - bind(VersionFetchSubPhase.class).asEagerSingleton(); - bind(MatchedQueriesFetchSubPhase.class).asEagerSingleton(); - bind(HighlightPhase.class).asEagerSingleton(); - bind(InnerHitsFetchSubPhase.class).asEagerSingleton(); bind(SearchServiceTransportAction.class).asEagerSingleton(); bind(MoreLikeThisFetchService.class).asEagerSingleton(); diff --git a/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java index 8e9782c3547..81f13c32727 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java @@ -45,6 +45,7 @@ import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.internal.SourceFieldMapper; import org.elasticsearch.index.mapper.object.ObjectMapper; +import org.elasticsearch.index.query.functionscore.ScoreFunctionParser; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchParseElement; @@ -83,13 +84,10 @@ public class FetchPhase implements SearchPhase { private final FetchSubPhase[] fetchSubPhases; @Inject - public FetchPhase(HighlightPhase highlightPhase, ScriptFieldsFetchSubPhase scriptFieldsPhase, - MatchedQueriesFetchSubPhase matchedQueriesPhase, ExplainFetchSubPhase explainPhase, VersionFetchSubPhase versionPhase, - FetchSourceSubPhase fetchSourceSubPhase, FieldDataFieldsFetchSubPhase fieldDataFieldsFetchSubPhase, - InnerHitsFetchSubPhase innerHitsFetchSubPhase) { + public FetchPhase(Set fetchSubPhases, InnerHitsFetchSubPhase innerHitsFetchSubPhase) { innerHitsFetchSubPhase.setFetchPhase(this); - this.fetchSubPhases = new FetchSubPhase[]{scriptFieldsPhase, matchedQueriesPhase, explainPhase, highlightPhase, - fetchSourceSubPhase, versionPhase, fieldDataFieldsFetchSubPhase, innerHitsFetchSubPhase}; + this.fetchSubPhases = fetchSubPhases.toArray(new FetchSubPhase[fetchSubPhases.size() + 1]); + this.fetchSubPhases[fetchSubPhases.size()] = innerHitsFetchSubPhase; } @Override diff --git a/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java index a6dc424683c..91cc7034401 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhase.java @@ -114,4 +114,23 @@ public interface FetchSubPhase { boolean hitsExecutionNeeded(SearchContext context); void hitsExecute(SearchContext context, InternalSearchHit[] hits); + + /** + * This interface is in the fetch phase plugin mechanism. + * Whenever a new search is executed we create a new {@link SearchContext} that holds individual contexts for each {@link org.elasticsearch.search.fetch.FetchSubPhase}. + * Fetch phases that use the plugin mechanism must provide a ContextFactory to the SearchContext that creates the fetch phase context and also associates them with a name. + * See {@link SearchContext#getFetchSubPhaseContext(FetchSubPhase.ContextFactory)} + */ + public interface ContextFactory { + + /** + * The name of the context. + */ + public String getName(); + + /** + * Creates a new instance of a FetchSubPhaseContext that holds all information a FetchSubPhase needs to execute on hits. + */ + public SubPhaseContext newContextInstance(); + } } diff --git a/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseContext.java b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseContext.java new file mode 100644 index 00000000000..237f43519fa --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseContext.java @@ -0,0 +1,47 @@ +/* + * 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.fetch; + +/** + * All configuration and context needed by the FetchSubPhase to execute on hits. + * The only required information in this base class is whether or not the sub phase needs to be run at all. + * It can be extended by FetchSubPhases to hold information the phase needs to execute on hits. + * See {@link org.elasticsearch.search.fetch.FetchSubPhase.ContextFactory} and also {@link org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext} for an example. + */ +public class FetchSubPhaseContext { + + // This is to store if the FetchSubPhase should be executed at all. + private boolean hitExecutionNeeded = false; + + /** + * Set if this phase should be executed at all. + */ + void setHitExecutionNeeded(boolean hitExecutionNeeded) { + this.hitExecutionNeeded = hitExecutionNeeded; + } + + /** + * Returns if this phase be executed at all. + */ + public boolean hitExecutionNeeded() { + return hitExecutionNeeded; + } + +} diff --git a/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseModule.java b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseModule.java new file mode 100644 index 00000000000..e01de72b232 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseModule.java @@ -0,0 +1,74 @@ +/* + * 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.fetch; + +import com.google.common.collect.Lists; +import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.common.inject.multibindings.Multibinder; +import org.elasticsearch.search.fetch.explain.ExplainFetchSubPhase; +import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsFetchSubPhase; +import org.elasticsearch.search.fetch.innerhits.InnerHitsFetchSubPhase; +import org.elasticsearch.search.fetch.matchedqueries.MatchedQueriesFetchSubPhase; +import org.elasticsearch.search.fetch.script.ScriptFieldsFetchSubPhase; +import org.elasticsearch.search.fetch.source.FetchSourceSubPhase; +import org.elasticsearch.search.fetch.version.VersionFetchSubPhase; +import org.elasticsearch.search.highlight.HighlightPhase; + +import java.util.List; + +/** + * Module for registering fetch sub phases. Fetch phases are executed when the document is finally + * retrieved from the shard. To implement a new fetch phase one needs to implement the following classes and interfaces + *

+ *

    + *
  • {@link FetchSubPhaseParseElement}
  • + *
  • {@link FetchSubPhase}
  • + *
  • {@link FetchSubPhaseContext}
  • + *
+ *

+ * The FetchSubPhase must then be registered with this module with {@link FetchSubPhaseModule#registerFetchSubPhase(Class)}. + * See {@link FieldDataFieldsFetchSubPhase} for an example. + */ +public class FetchSubPhaseModule extends AbstractModule { + + private List> fetchSubPhases = Lists.newArrayList(); + + public FetchSubPhaseModule() { + registerFetchSubPhase(ExplainFetchSubPhase.class); + registerFetchSubPhase(FieldDataFieldsFetchSubPhase.class); + registerFetchSubPhase(ScriptFieldsFetchSubPhase.class); + registerFetchSubPhase(FetchSourceSubPhase.class); + registerFetchSubPhase(VersionFetchSubPhase.class); + registerFetchSubPhase(MatchedQueriesFetchSubPhase.class); + registerFetchSubPhase(HighlightPhase.class); + } + + public void registerFetchSubPhase(Class subPhase) { + fetchSubPhases.add(subPhase); + } + + @Override + protected void configure() { + Multibinder parserMapBinder = Multibinder.newSetBinder(binder(), FetchSubPhase.class); + for (Class clazz : fetchSubPhases) { + parserMapBinder.addBinding().to(clazz); + } + bind(InnerHitsFetchSubPhase.class).asEagerSingleton(); + } +} diff --git a/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseParseElement.java b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseParseElement.java new file mode 100644 index 00000000000..f43ed68d769 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/fetch/FetchSubPhaseParseElement.java @@ -0,0 +1,48 @@ +/* + * 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.fetch; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.internal.SearchContext; + +/** + * A parse element for a {@link org.elasticsearch.search.fetch.FetchSubPhase} that is used when parsing a search request. + */ +public abstract class FetchSubPhaseParseElement implements SearchParseElement { + + @Override + final public void parse(XContentParser parser, SearchContext context) throws Exception { + SubPhaseContext fetchSubPhaseContext = context.getFetchSubPhaseContext(getContextFactory()); + // this is to make sure that the SubFetchPhase knows it should execute + fetchSubPhaseContext.setHitExecutionNeeded(true); + innerParse(parser, fetchSubPhaseContext, context); + } + + /** + * Implement the actual parsing here. + */ + protected abstract void innerParse(XContentParser parser, SubPhaseContext fetchSubPhaseContext, SearchContext searchContext) throws Exception; + + /** + * Return the ContextFactory for this FetchSubPhase. + */ + protected abstract FetchSubPhase.ContextFactory getContextFactory(); +} diff --git a/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsContext.java b/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsContext.java index aebed3956a0..9f1bfc08ed7 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsContext.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsContext.java @@ -19,13 +19,14 @@ package org.elasticsearch.search.fetch.fielddata; import com.google.common.collect.Lists; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; import java.util.List; /** * All the required context to pull a field from the field data cache. */ -public class FieldDataFieldsContext { +public class FieldDataFieldsContext extends FetchSubPhaseContext { public static class FieldDataField { private final String name; diff --git a/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsFetchSubPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsFetchSubPhase.java index 7d8bb51e94b..1ec0a98b1fa 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsFetchSubPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsFetchSubPhase.java @@ -27,6 +27,7 @@ import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchParseElement; import org.elasticsearch.search.fetch.FetchSubPhase; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; import org.elasticsearch.search.internal.InternalSearchHit; import org.elasticsearch.search.internal.InternalSearchHitField; import org.elasticsearch.search.internal.SearchContext; @@ -43,6 +44,20 @@ import java.util.Map; */ public class FieldDataFieldsFetchSubPhase implements FetchSubPhase { + public static final String[] NAMES = {"fielddata_fields", "fielddataFields"}; + public static final ContextFactory CONTEXT_FACTORY = new ContextFactory() { + + @Override + public String getName() { + return NAMES[0]; + } + + @Override + public FieldDataFieldsContext newContextInstance() { + return new FieldDataFieldsContext(); + } + }; + @Inject public FieldDataFieldsFetchSubPhase() { } @@ -66,12 +81,12 @@ public class FieldDataFieldsFetchSubPhase implements FetchSubPhase { @Override public boolean hitExecutionNeeded(SearchContext context) { - return context.hasFieldDataFields(); + return context.getFetchSubPhaseContext(CONTEXT_FACTORY).hitExecutionNeeded(); } @Override public void hitExecute(SearchContext context, HitContext hitContext) { - for (FieldDataFieldsContext.FieldDataField field : context.fieldDataFields().fields()) { + for (FieldDataFieldsContext.FieldDataField field : context.getFetchSubPhaseContext(CONTEXT_FACTORY).fields()) { if (hitContext.hit().fieldsOrNull() == null) { hitContext.hit().fields(new HashMap(2)); } diff --git a/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsParseElement.java b/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsParseElement.java index 3c45148cdfa..cd5cb7d323d 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsParseElement.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/fielddata/FieldDataFieldsParseElement.java @@ -20,12 +20,15 @@ package org.elasticsearch.search.fetch.fielddata; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.fetch.FetchSubPhase; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; +import org.elasticsearch.search.fetch.FetchSubPhaseParseElement; import org.elasticsearch.search.internal.SearchContext; /** * Parses field name values from the {@code fielddata_fields} parameter in a * search request. - * + *

*

  * {
  *   "query": {...},
@@ -33,20 +36,26 @@ import org.elasticsearch.search.internal.SearchContext;
  * }
  * 
*/ -public class FieldDataFieldsParseElement implements SearchParseElement { +public class FieldDataFieldsParseElement extends FetchSubPhaseParseElement { + @Override - public void parse(XContentParser parser, SearchContext context) throws Exception { + protected void innerParse(XContentParser parser, FieldDataFieldsContext fieldDataFieldsContext, SearchContext searchContext) throws Exception { XContentParser.Token token = parser.currentToken(); if (token == XContentParser.Token.START_ARRAY) { while (parser.nextToken() != XContentParser.Token.END_ARRAY) { String fieldName = parser.text(); - context.fieldDataFields().add(new FieldDataFieldsContext.FieldDataField(fieldName)); + fieldDataFieldsContext.add(new FieldDataFieldsContext.FieldDataField(fieldName)); } } else if (token == XContentParser.Token.VALUE_STRING) { String fieldName = parser.text(); - context.fieldDataFields().add(new FieldDataFieldsContext.FieldDataField(fieldName)); - } else { + fieldDataFieldsContext.add(new FieldDataFieldsContext.FieldDataField(fieldName)); + } else { throw new IllegalStateException("Expected either a VALUE_STRING or an START_ARRAY but got " + token); } } + + @Override + protected FetchSubPhase.ContextFactory getContextFactory() { + return FieldDataFieldsFetchSubPhase.CONTEXT_FACTORY; + } } diff --git a/core/src/main/java/org/elasticsearch/search/internal/DefaultSearchContext.java b/core/src/main/java/org/elasticsearch/search/internal/DefaultSearchContext.java index d564a6c06c1..f420986272d 100644 --- a/core/src/main/java/org/elasticsearch/search/internal/DefaultSearchContext.java +++ b/core/src/main/java/org/elasticsearch/search/internal/DefaultSearchContext.java @@ -53,7 +53,8 @@ import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.aggregations.SearchContextAggregations; import org.elasticsearch.search.dfs.DfsSearchResult; import org.elasticsearch.search.fetch.FetchSearchResult; -import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext; +import org.elasticsearch.search.fetch.FetchSubPhase; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; import org.elasticsearch.search.fetch.script.ScriptFieldsContext; import org.elasticsearch.search.fetch.source.FetchSourceContext; @@ -99,7 +100,6 @@ public class DefaultSearchContext extends SearchContext { private boolean explain; private boolean version = false; // by default, we don't return versions private List fieldNames; - private FieldDataFieldsContext fieldDataFields; private ScriptFieldsContext scriptFields; private FetchSourceContext fetchSourceContext; private int from = -1; @@ -126,11 +126,13 @@ public class DefaultSearchContext extends SearchContext { private volatile long lastAccessTime = -1; private InnerHitsContext innerHitsContext; + private final Map subPhaseContexts = new HashMap<>(); + public DefaultSearchContext(long id, ShardSearchRequest request, SearchShardTarget shardTarget, - Engine.Searcher engineSearcher, IndexService indexService, IndexShard indexShard, - ScriptService scriptService, PageCacheRecycler pageCacheRecycler, - BigArrays bigArrays, Counter timeEstimateCounter, ParseFieldMatcher parseFieldMatcher, - TimeValue timeout + Engine.Searcher engineSearcher, IndexService indexService, IndexShard indexShard, + ScriptService scriptService, PageCacheRecycler pageCacheRecycler, + BigArrays bigArrays, Counter timeEstimateCounter, ParseFieldMatcher parseFieldMatcher, + TimeValue timeout ) { super(parseFieldMatcher); this.id = id; @@ -302,6 +304,16 @@ public class DefaultSearchContext extends SearchContext { return this; } + @Override + public SubPhaseContext getFetchSubPhaseContext(FetchSubPhase.ContextFactory contextFactory) { + String subPhaseName = contextFactory.getName(); + if (subPhaseContexts.get(subPhaseName) == null) { + subPhaseContexts.put(subPhaseName, contextFactory.newContextInstance()); + } + return (SubPhaseContext) subPhaseContexts.get(subPhaseName); + } + + @Override public SearchContextHighlight highlight() { return highlight; @@ -338,19 +350,6 @@ public class DefaultSearchContext extends SearchContext { this.rescore.add(rescore); } - @Override - public boolean hasFieldDataFields() { - return fieldDataFields != null; - } - - @Override - public FieldDataFieldsContext fieldDataFields() { - if (fieldDataFields == null) { - fieldDataFields = new FieldDataFieldsContext(); - } - return this.fieldDataFields; - } - @Override public boolean hasScriptFields() { return scriptFields != null; diff --git a/core/src/main/java/org/elasticsearch/search/internal/FilteredSearchContext.java b/core/src/main/java/org/elasticsearch/search/internal/FilteredSearchContext.java index b4308c93215..c22842e7093 100644 --- a/core/src/main/java/org/elasticsearch/search/internal/FilteredSearchContext.java +++ b/core/src/main/java/org/elasticsearch/search/internal/FilteredSearchContext.java @@ -45,7 +45,8 @@ import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.aggregations.SearchContextAggregations; import org.elasticsearch.search.dfs.DfsSearchResult; import org.elasticsearch.search.fetch.FetchSearchResult; -import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext; +import org.elasticsearch.search.fetch.FetchSubPhase; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; import org.elasticsearch.search.fetch.script.ScriptFieldsContext; import org.elasticsearch.search.fetch.source.FetchSourceContext; @@ -209,16 +210,6 @@ public abstract class FilteredSearchContext extends SearchContext { in.addRescore(rescore); } - @Override - public boolean hasFieldDataFields() { - return in.hasFieldDataFields(); - } - - @Override - public FieldDataFieldsContext fieldDataFields() { - return in.fieldDataFields(); - } - @Override public boolean hasScriptFields() { return in.hasScriptFields(); @@ -628,4 +619,9 @@ public abstract class FilteredSearchContext extends SearchContext { public void copyContextAndHeadersFrom(HasContextAndHeaders other) { in.copyContextAndHeadersFrom(other); } + + @Override + public SubPhaseContext getFetchSubPhaseContext(FetchSubPhase.ContextFactory contextFactory) { + return in.getFetchSubPhaseContext(contextFactory); + } } diff --git a/core/src/main/java/org/elasticsearch/search/internal/SearchContext.java b/core/src/main/java/org/elasticsearch/search/internal/SearchContext.java index b7030954930..901b721c3b5 100644 --- a/core/src/main/java/org/elasticsearch/search/internal/SearchContext.java +++ b/core/src/main/java/org/elasticsearch/search/internal/SearchContext.java @@ -50,7 +50,8 @@ import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.aggregations.SearchContextAggregations; import org.elasticsearch.search.dfs.DfsSearchResult; import org.elasticsearch.search.fetch.FetchSearchResult; -import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext; +import org.elasticsearch.search.fetch.FetchSubPhase; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; import org.elasticsearch.search.fetch.script.ScriptFieldsContext; import org.elasticsearch.search.fetch.source.FetchSourceContext; @@ -163,6 +164,8 @@ public abstract class SearchContext implements Releasable, HasContextAndHeaders public abstract SearchContext aggregations(SearchContextAggregations aggregations); + public abstract SubPhaseContext getFetchSubPhaseContext(FetchSubPhase.ContextFactory contextFactory); + public abstract SearchContextHighlight highlight(); public abstract void highlight(SearchContextHighlight highlight); @@ -182,10 +185,6 @@ public abstract class SearchContext implements Releasable, HasContextAndHeaders public abstract void addRescore(RescoreSearchContext rescore); - public abstract boolean hasFieldDataFields(); - - public abstract FieldDataFieldsContext fieldDataFields(); - public abstract boolean hasScriptFields(); public abstract ScriptFieldsContext scriptFields(); diff --git a/core/src/main/java/org/elasticsearch/search/internal/SubSearchContext.java b/core/src/main/java/org/elasticsearch/search/internal/SubSearchContext.java index f1162a98e86..be445c99e88 100644 --- a/core/src/main/java/org/elasticsearch/search/internal/SubSearchContext.java +++ b/core/src/main/java/org/elasticsearch/search/internal/SubSearchContext.java @@ -30,7 +30,6 @@ import org.elasticsearch.index.query.ParsedQuery; import org.elasticsearch.search.Scroll; import org.elasticsearch.search.aggregations.SearchContextAggregations; import org.elasticsearch.search.fetch.FetchSearchResult; -import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; import org.elasticsearch.search.fetch.script.ScriptFieldsContext; import org.elasticsearch.search.fetch.source.FetchSourceContext; @@ -62,7 +61,6 @@ public class SubSearchContext extends FilteredSearchContext { private int docsIdsToLoadSize; private List fieldNames; - private FieldDataFieldsContext fieldDataFields; private ScriptFieldsContext scriptFields; private FetchSourceContext fetchSourceContext; private SearchContextHighlight highlight; @@ -132,19 +130,6 @@ public class SubSearchContext extends FilteredSearchContext { throw new UnsupportedOperationException("Not supported"); } - @Override - public boolean hasFieldDataFields() { - return fieldDataFields != null; - } - - @Override - public FieldDataFieldsContext fieldDataFields() { - if (fieldDataFields == null) { - fieldDataFields = new FieldDataFieldsContext(); - } - return this.fieldDataFields; - } - @Override public boolean hasScriptFields() { return scriptFields != null; diff --git a/core/src/test/java/org/elasticsearch/search/fetch/FetchSubPhasePluginTests.java b/core/src/test/java/org/elasticsearch/search/fetch/FetchSubPhasePluginTests.java new file mode 100644 index 00000000000..a4263b20f9c --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/fetch/FetchSubPhasePluginTests.java @@ -0,0 +1,218 @@ +/* + * 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.fetch; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.index.PostingsEnum; +import org.apache.lucene.index.TermsEnum; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.termvectors.TermVectorsRequest; +import org.elasticsearch.action.termvectors.TermVectorsResponse; +import org.elasticsearch.common.Priority; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.plugins.AbstractPlugin; +import org.elasticsearch.search.SearchHitField; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.internal.InternalSearchHit; +import org.elasticsearch.search.internal.InternalSearchHitField; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; +import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.elasticsearch.client.Requests.indexRequest; +import static org.elasticsearch.common.settings.Settings.settingsBuilder; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; +import static org.hamcrest.Matchers.equalTo; + +/** + * + */ +@ClusterScope(scope = Scope.SUITE, numDataNodes = 1) +public class FetchSubPhasePluginTests extends ElasticsearchIntegrationTest { + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return settingsBuilder() + .put(super.nodeSettings(nodeOrdinal)) + .put("plugin.types", FetchTermVectorsPlugin.class.getName()) + .build(); + } + + @Test + public void testPlugin() throws Exception { + client().admin() + .indices() + .prepareCreate("test") + .addMapping( + "type1", + jsonBuilder() + .startObject().startObject("type1") + .startObject("properties") + .startObject("test") + .field("type", "string").field("term_vector", "yes") + .endObject() + .endObject() + .endObject().endObject()).execute().actionGet(); + client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForYellowStatus().execute().actionGet(); + + client().index( + indexRequest("test").type("type1").id("1") + .source(jsonBuilder().startObject().field("test", "I am sam i am").endObject())).actionGet(); + + client().admin().indices().prepareRefresh().execute().actionGet(); + + String searchSource = jsonBuilder().startObject() + .field("term_vectors_fetch", "test") + .endObject().string(); + SearchResponse response = client().prepareSearch().setSource(searchSource).get(); + assertSearchResponse(response); + assertThat(((Map) response.getHits().getAt(0).field("term_vectors_fetch").getValues().get(0)).get("i"), equalTo(2)); + assertThat(((Map) response.getHits().getAt(0).field("term_vectors_fetch").getValues().get(0)).get("am"), equalTo(2)); + assertThat(((Map) response.getHits().getAt(0).field("term_vectors_fetch").getValues().get(0)).get("sam"), equalTo(1)); + } + + public static class FetchTermVectorsPlugin extends AbstractPlugin { + + @Override + public String name() { + return "fetch-term-vectors"; + } + + @Override + public String description() { + return "fetch plugin to test if the plugin mechanism works"; + } + + public void onModule(FetchSubPhaseModule fetchSubPhaseModule) { + fetchSubPhaseModule.registerFetchSubPhase(TermVectorsFetchSubPhase.class); + } + } + + public static class TermVectorsFetchSubPhase implements FetchSubPhase { + + public static final ContextFactory CONTEXT_FACTORY = new ContextFactory() { + + @Override + public String getName() { + return NAMES[0]; + } + + @Override + public TermVectorsFetchContext newContextInstance() { + return new TermVectorsFetchContext(); + } + }; + + public TermVectorsFetchSubPhase() { + } + + public static final String[] NAMES = {"term_vectors_fetch"}; + + @Override + public Map parseElements() { + return ImmutableMap.of("term_vectors_fetch", new TermVectorsFetchParseElement()); + } + + @Override + public boolean hitsExecutionNeeded(SearchContext context) { + return false; + } + + @Override + public void hitsExecute(SearchContext context, InternalSearchHit[] hits) { + } + + @Override + public boolean hitExecutionNeeded(SearchContext context) { + return context.getFetchSubPhaseContext(CONTEXT_FACTORY).hitExecutionNeeded(); + } + + @Override + public void hitExecute(SearchContext context, HitContext hitContext) { + String field = context.getFetchSubPhaseContext(CONTEXT_FACTORY).getField(); + + if (hitContext.hit().fieldsOrNull() == null) { + hitContext.hit().fields(new HashMap()); + } + SearchHitField hitField = hitContext.hit().fields().get(NAMES[0]); + if (hitField == null) { + hitField = new InternalSearchHitField(NAMES[0], new ArrayList<>(1)); + hitContext.hit().fields().put(NAMES[0], hitField); + } + TermVectorsResponse termVector = context.indexShard().termVectorsService().getTermVectors(new TermVectorsRequest(context.indexShard().indexService().index().getName(), hitContext.hit().type(), hitContext.hit().id()), context.indexShard().indexService().index().getName()); + try { + Map tv = new HashMap<>(); + TermsEnum terms = termVector.getFields().terms(field).iterator(); + BytesRef term; + while ((term = terms.next()) != null) { + tv.put(term.utf8ToString(), terms.postings(null, null, PostingsEnum.ALL).freq()); + } + hitField.values().add(tv); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static class TermVectorsFetchParseElement extends FetchSubPhaseParseElement { + + @Override + protected void innerParse(XContentParser parser, TermVectorsFetchContext termVectorsFetchContext, SearchContext searchContext) throws Exception { + XContentParser.Token token = parser.currentToken(); + if (token == XContentParser.Token.VALUE_STRING) { + String fieldName = parser.text(); + termVectorsFetchContext.setField(fieldName); + } else { + throw new IllegalStateException("Expected a VALUE_STRING but got " + token); + } + } + + @Override + protected FetchSubPhase.ContextFactory getContextFactory() { + return TermVectorsFetchSubPhase.CONTEXT_FACTORY; + } + } + + public static class TermVectorsFetchContext extends FetchSubPhaseContext { + + private String field = null; + + public TermVectorsFetchContext() { + } + + public void setField(String field) { + this.field = field; + } + + public String getField() { + return field; + } + } +} diff --git a/core/src/test/java/org/elasticsearch/test/TestSearchContext.java b/core/src/test/java/org/elasticsearch/test/TestSearchContext.java index a186bf43edf..44004fe6f48 100644 --- a/core/src/test/java/org/elasticsearch/test/TestSearchContext.java +++ b/core/src/test/java/org/elasticsearch/test/TestSearchContext.java @@ -48,7 +48,8 @@ import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.aggregations.SearchContextAggregations; import org.elasticsearch.search.dfs.DfsSearchResult; import org.elasticsearch.search.fetch.FetchSearchResult; -import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext; +import org.elasticsearch.search.fetch.FetchSubPhase; +import org.elasticsearch.search.fetch.FetchSubPhaseContext; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; import org.elasticsearch.search.fetch.script.ScriptFieldsContext; import org.elasticsearch.search.fetch.source.FetchSourceContext; @@ -63,9 +64,7 @@ import org.elasticsearch.search.scan.ScanContext; import org.elasticsearch.search.suggest.SuggestionSearchContext; import org.elasticsearch.threadpool.ThreadPool; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; public class TestSearchContext extends SearchContext { @@ -83,6 +82,7 @@ public class TestSearchContext extends SearchContext { private SearchContextAggregations aggregations; private final long originNanoTime = System.nanoTime(); + private final Map subPhaseContexts = new HashMap<>(); public TestSearchContext(ThreadPool threadPool,PageCacheRecycler pageCacheRecycler, BigArrays bigArrays, IndexService indexService, QueryCache filterCache, IndexFieldDataService indexFieldDataService) { super(ParseFieldMatcher.STRICT); @@ -203,6 +203,15 @@ public class TestSearchContext extends SearchContext { return this; } + @Override + public SubPhaseContext getFetchSubPhaseContext(FetchSubPhase.ContextFactory contextFactory) { + String subPhaseName = contextFactory.getName(); + if (subPhaseContexts.get(subPhaseName) == null) { + subPhaseContexts.put(subPhaseName, contextFactory.newContextInstance()); + } + return (SubPhaseContext) subPhaseContexts.get(subPhaseName); + } + @Override public SearchContextHighlight highlight() { return null; @@ -230,16 +239,6 @@ public class TestSearchContext extends SearchContext { public void addRescore(RescoreSearchContext rescore) { } - @Override - public boolean hasFieldDataFields() { - return false; - } - - @Override - public FieldDataFieldsContext fieldDataFields() { - return null; - } - @Override public boolean hasScriptFields() { return false;