From dddca692b83a0a6135a00475e592bcff076c46e8 Mon Sep 17 00:00:00 2001 From: kimchy Date: Sun, 12 Dec 2010 02:01:13 +0200 Subject: [PATCH] add search hit phase, which operates on a hit, and have all relevant hit phases implement and use it --- .../elasticsearch/search/SearchModule.java | 11 ++- .../search/fetch/FetchPhase.java | 82 +++++-------------- .../search/fetch/SearchHitPhase.java | 44 ++++++++++ .../{ => explain}/ExplainParseElement.java | 2 +- .../fetch/explain/ExplainSearchHitPhase.java | 56 +++++++++++++ .../MatchedFiltersSearchHitPhase.java | 69 ++++++++++++++++ .../script/ScriptFieldsSearchHitPhase.java | 75 +++++++++++++++++ .../search/highlight/HighlightPhase.java | 64 +++++++-------- 8 files changed, 303 insertions(+), 100 deletions(-) create mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java rename modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/{ => explain}/ExplainParseElement.java (96%) create mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java create mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java create mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java index 2f3af9a8a46..f1bbae8255d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java @@ -25,6 +25,9 @@ import org.elasticsearch.search.controller.SearchPhaseController; import org.elasticsearch.search.dfs.DfsPhase; import org.elasticsearch.search.facet.FacetsPhase; import org.elasticsearch.search.fetch.FetchPhase; +import org.elasticsearch.search.fetch.explain.ExplainSearchHitPhase; +import org.elasticsearch.search.fetch.matchedfilters.MatchedFiltersSearchHitPhase; +import org.elasticsearch.search.fetch.script.ScriptFieldsSearchHitPhase; import org.elasticsearch.search.highlight.HighlightPhase; import org.elasticsearch.search.query.QueryPhase; @@ -37,11 +40,15 @@ public class SearchModule extends AbstractModule { bind(DfsPhase.class).asEagerSingleton(); bind(FacetsPhase.class).asEagerSingleton(); bind(QueryPhase.class).asEagerSingleton(); - bind(FetchPhase.class).asEagerSingleton(); - bind(HighlightPhase.class).asEagerSingleton(); bind(SearchService.class).asEagerSingleton(); bind(SearchPhaseController.class).asEagerSingleton(); + bind(FetchPhase.class).asEagerSingleton(); + bind(ExplainSearchHitPhase.class).asEagerSingleton(); + bind(ScriptFieldsSearchHitPhase.class).asEagerSingleton(); + bind(MatchedFiltersSearchHitPhase.class).asEagerSingleton(); + bind(HighlightPhase.class).asEagerSingleton(); + bind(SearchServiceTransportAction.class).asEagerSingleton(); } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java index 33a603ed34c..f695e90abb3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java @@ -23,18 +23,15 @@ import org.apache.lucene.document.Document; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.IndexReader; -import org.apache.lucene.search.DocIdSet; -import org.apache.lucene.search.Filter; import org.elasticsearch.common.collect.ImmutableMap; -import org.elasticsearch.common.collect.Lists; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.lucene.docset.DocSet; import org.elasticsearch.index.mapper.*; import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchParseElement; import org.elasticsearch.search.SearchPhase; -import org.elasticsearch.search.fetch.script.ScriptFieldsContext; -import org.elasticsearch.search.fetch.script.ScriptFieldsParseElement; +import org.elasticsearch.search.fetch.explain.ExplainSearchHitPhase; +import org.elasticsearch.search.fetch.matchedfilters.MatchedFiltersSearchHitPhase; +import org.elasticsearch.search.fetch.script.ScriptFieldsSearchHitPhase; import org.elasticsearch.search.highlight.HighlightPhase; import org.elasticsearch.search.internal.InternalSearchHit; import org.elasticsearch.search.internal.InternalSearchHitField; @@ -44,7 +41,6 @@ import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -52,24 +48,26 @@ import java.util.Map; */ public class FetchPhase implements SearchPhase { + private final SearchHitPhase[] hitPhases; + private final HighlightPhase highlightPhase; - @Inject public FetchPhase(HighlightPhase highlightPhase) { + @Inject public FetchPhase(HighlightPhase highlightPhase, ScriptFieldsSearchHitPhase scriptFieldsPhase, + MatchedFiltersSearchHitPhase matchFiltersPhase, ExplainSearchHitPhase explainPhase) { this.highlightPhase = highlightPhase; + this.hitPhases = new SearchHitPhase[]{scriptFieldsPhase, matchFiltersPhase, explainPhase, highlightPhase}; } @Override public Map parseElements() { ImmutableMap.Builder parseElements = ImmutableMap.builder(); - parseElements.put("explain", new ExplainParseElement()) - .put("fields", new FieldsParseElement()) - .put("script_fields", new ScriptFieldsParseElement()) - .put("scriptFields", new ScriptFieldsParseElement()) - .putAll(highlightPhase.parseElements()); + parseElements.put("fields", new FieldsParseElement()); + for (SearchHitPhase hitPhase : hitPhases) { + parseElements.putAll(hitPhase.parseElements()); + } return parseElements.build(); } @Override public void preProcess(SearchContext context) { - highlightPhase.preProcess(context); } public void execute(SearchContext context) { @@ -131,64 +129,26 @@ public class FetchPhase implements SearchPhase { hitField.values().add(value); } - if (context.hasScriptFields()) { - int readerIndex = context.searcher().readerIndex(docId); - IndexReader subReader = context.searcher().subReaders()[readerIndex]; - int subDoc = docId - context.searcher().docStarts()[readerIndex]; - for (ScriptFieldsContext.ScriptField scriptField : context.scriptFields().fields()) { - scriptField.script().setNextReader(subReader); - - Object value = scriptField.script().execute(subDoc); - - if (searchHit.fieldsOrNull() == null) { - searchHit.fields(new HashMap(2)); - } - - SearchHitField hitField = searchHit.fields().get(scriptField.name()); - if (hitField == null) { - hitField = new InternalSearchHitField(scriptField.name(), new ArrayList(2)); - searchHit.fields().put(scriptField.name(), hitField); - } - hitField.values().add(value); + boolean hitPhaseExecutionRequired = false; + for (SearchHitPhase hitPhase : hitPhases) { + if (hitPhase.executionNeeded(context)) { + hitPhaseExecutionRequired = true; + break; } } - if (!context.parsedQuery().namedFilters().isEmpty()) { + if (hitPhaseExecutionRequired) { int readerIndex = context.searcher().readerIndex(docId); IndexReader subReader = context.searcher().subReaders()[readerIndex]; int subDoc = docId - context.searcher().docStarts()[readerIndex]; - List matchedFilters = Lists.newArrayListWithCapacity(2); - for (Map.Entry entry : context.parsedQuery().namedFilters().entrySet()) { - String name = entry.getKey(); - Filter filter = entry.getValue(); - filter = context.filterCache().cache(filter); - try { - DocIdSet docIdSet = filter.getDocIdSet(subReader); - if (docIdSet instanceof DocSet && ((DocSet) docIdSet).get(subDoc)) { - matchedFilters.add(name); - } - } catch (IOException e) { - // ignore + for (SearchHitPhase hitPhase : hitPhases) { + if (hitPhase.executionNeeded(context)) { + hitPhase.execute(context, searchHit, uid, subReader, subDoc); } } - searchHit.matchedFilters(matchedFilters.toArray(new String[matchedFilters.size()])); } - - doExplanation(context, docId, searchHit); } context.fetchResult().hits(new InternalSearchHits(hits, context.queryResult().topDocs().totalHits, context.queryResult().topDocs().getMaxScore())); - - highlightPhase.execute(context); - } - - private void doExplanation(SearchContext context, int docId, InternalSearchHit searchHit) { - if (context.explain()) { - try { - searchHit.explanation(context.searcher().explain(context.query(), docId)); - } catch (IOException e) { - throw new FetchPhaseExecutionException(context, "Failed to explain doc [" + docId + "]", e); - } - } } private byte[] extractSource(Document doc, DocumentMapper documentMapper) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java new file mode 100644 index 00000000000..e8b1bb573d3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java @@ -0,0 +1,44 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.apache.lucene.index.IndexReader; +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.internal.InternalSearchHit; +import org.elasticsearch.search.internal.SearchContext; + +import java.util.Map; + +/** + * @author kimchy (shay.banon) + */ +public interface SearchHitPhase { + + Map parseElements(); + + boolean executionNeeded(SearchContext context); + + /** + * Executes the hit level phase, with a reader and doc id (note, its a low level reader, and the matching doc). + */ + void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/ExplainParseElement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainParseElement.java similarity index 96% rename from modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/ExplainParseElement.java rename to modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainParseElement.java index 956ba758306..44da110eeef 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/ExplainParseElement.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainParseElement.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.search.fetch; +package org.elasticsearch.search.fetch.explain; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.search.SearchParseElement; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java new file mode 100644 index 00000000000..e94fcc3eefb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java @@ -0,0 +1,56 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.explain; + +import org.apache.lucene.index.IndexReader; +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.fetch.FetchPhaseExecutionException; +import org.elasticsearch.search.fetch.SearchHitPhase; +import org.elasticsearch.search.internal.InternalSearchHit; +import org.elasticsearch.search.internal.SearchContext; + +import java.io.IOException; +import java.util.Map; + +/** + * @author kimchy (shay.banon) + */ +public class ExplainSearchHitPhase implements SearchHitPhase { + + @Override public Map parseElements() { + return ImmutableMap.of("explain", new ExplainParseElement()); + } + + @Override public boolean executionNeeded(SearchContext context) { + return context.explain(); + } + + @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { + try { + // we use the top level doc id, since we work with the top level searcher + hit.explanation(context.searcher().explain(context.query(), hit.docId())); + } catch (IOException e) { + throw new FetchPhaseExecutionException(context, "Failed to explain doc [" + docId + "]", e); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java new file mode 100644 index 00000000000..dc257a99eb3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java @@ -0,0 +1,69 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.matchedfilters; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.DocIdSet; +import org.apache.lucene.search.Filter; +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.collect.Lists; +import org.elasticsearch.common.lucene.docset.DocSet; +import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.fetch.SearchHitPhase; +import org.elasticsearch.search.internal.InternalSearchHit; +import org.elasticsearch.search.internal.SearchContext; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * @author kimchy (shay.banon) + */ +public class MatchedFiltersSearchHitPhase implements SearchHitPhase { + + @Override public Map parseElements() { + return ImmutableMap.of(); + } + + @Override public boolean executionNeeded(SearchContext context) { + return !context.parsedQuery().namedFilters().isEmpty(); + } + + @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { + List matchedFilters = Lists.newArrayListWithCapacity(2); + for (Map.Entry entry : context.parsedQuery().namedFilters().entrySet()) { + String name = entry.getKey(); + Filter filter = entry.getValue(); + filter = context.filterCache().cache(filter); + try { + DocIdSet docIdSet = filter.getDocIdSet(reader); + if (docIdSet instanceof DocSet && ((DocSet) docIdSet).get(docId)) { + matchedFilters.add(name); + } + } catch (IOException e) { + // ignore + } + } + hit.matchedFilters(matchedFilters.toArray(new String[matchedFilters.size()])); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java new file mode 100644 index 00000000000..b6613042d1d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java @@ -0,0 +1,75 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.script; + +import org.apache.lucene.index.IndexReader; +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.search.SearchHitField; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.fetch.SearchHitPhase; +import org.elasticsearch.search.internal.InternalSearchHit; +import org.elasticsearch.search.internal.InternalSearchHitField; +import org.elasticsearch.search.internal.SearchContext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * @author kimchy (shay.banon) + */ +public class ScriptFieldsSearchHitPhase implements SearchHitPhase { + + @Inject public ScriptFieldsSearchHitPhase() { + } + + @Override public Map parseElements() { + ImmutableMap.Builder parseElements = ImmutableMap.builder(); + parseElements.put("script_fields", new ScriptFieldsParseElement()) + .put("scriptFields", new ScriptFieldsParseElement()); + return parseElements.build(); + } + + @Override public boolean executionNeeded(SearchContext context) { + return context.hasScriptFields(); + } + + @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { + for (ScriptFieldsContext.ScriptField scriptField : context.scriptFields().fields()) { + scriptField.script().setNextReader(reader); + + Object value = scriptField.script().execute(docId); + + if (hit.fieldsOrNull() == null) { + hit.fields(new HashMap(2)); + } + + SearchHitField hitField = hit.fields().get(scriptField.name()); + if (hitField == null) { + hitField = new InternalSearchHitField(scriptField.name(), new ArrayList(2)); + hit.fields().put(scriptField.name(), hitField); + } + hitField.values().add(value); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java index e98d40261e6..2218f7d2f51 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java @@ -26,10 +26,10 @@ import org.elasticsearch.ElasticSearchException; import org.elasticsearch.common.collect.ImmutableMap; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.FieldMapper; -import org.elasticsearch.search.SearchHit; +import org.elasticsearch.index.mapper.Uid; import org.elasticsearch.search.SearchParseElement; -import org.elasticsearch.search.SearchPhase; import org.elasticsearch.search.fetch.FetchPhaseExecutionException; +import org.elasticsearch.search.fetch.SearchHitPhase; import org.elasticsearch.search.internal.InternalSearchHit; import org.elasticsearch.search.internal.SearchContext; @@ -41,52 +41,44 @@ import static org.elasticsearch.common.collect.Maps.*; /** * @author kimchy (shay.banon) */ -public class HighlightPhase implements SearchPhase { +public class HighlightPhase implements SearchHitPhase { @Override public Map parseElements() { return ImmutableMap.of("highlight", new HighlighterParseElement()); } - @Override public void preProcess(SearchContext context) { + @Override public boolean executionNeeded(SearchContext context) { + return context.highlight() != null; } - @Override public void execute(SearchContext context) throws ElasticSearchException { - if (context.highlight() == null) { - return; - } - + @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { try { - for (SearchHit hit : context.fetchResult().hits().hits()) { - InternalSearchHit internalHit = (InternalSearchHit) hit; + DocumentMapper documentMapper = context.mapperService().type(hit.type()); - DocumentMapper documentMapper = context.mapperService().type(internalHit.type()); - int docId = internalHit.docId(); - - Map highlightFields = newHashMap(); - for (SearchContextHighlight.Field field : context.highlight().fields()) { - String fieldName = field.field(); - FieldMapper mapper = documentMapper.mappers().smartNameFieldMapper(field.field()); - if (mapper != null) { - fieldName = mapper.names().indexName(); - } - - FastVectorHighlighter highlighter = buildHighlighter(field); - FieldQuery fieldQuery = buildFieldQuery(highlighter, context.query(), context.searcher().getIndexReader(), field); - - String[] fragments; - try { - // a HACK to make highlighter do highlighting, even though its using the single frag list builder - int numberOfFragments = field.numberOfFragments() == 0 ? 1 : field.numberOfFragments(); - fragments = highlighter.getBestFragments(fieldQuery, context.searcher().getIndexReader(), docId, fieldName, field.fragmentCharSize(), numberOfFragments); - } catch (IOException e) { - throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + field.field() + "]", e); - } - HighlightField highlightField = new HighlightField(field.field(), fragments); - highlightFields.put(highlightField.name(), highlightField); + Map highlightFields = newHashMap(); + for (SearchContextHighlight.Field field : context.highlight().fields()) { + String fieldName = field.field(); + FieldMapper mapper = documentMapper.mappers().smartNameFieldMapper(field.field()); + if (mapper != null) { + fieldName = mapper.names().indexName(); } - internalHit.highlightFields(highlightFields); + FastVectorHighlighter highlighter = buildHighlighter(field); + FieldQuery fieldQuery = buildFieldQuery(highlighter, context.query(), reader, field); + + String[] fragments; + try { + // a HACK to make highlighter do highlighting, even though its using the single frag list builder + int numberOfFragments = field.numberOfFragments() == 0 ? 1 : field.numberOfFragments(); + fragments = highlighter.getBestFragments(fieldQuery, context.searcher().getIndexReader(), docId, fieldName, field.fragmentCharSize(), numberOfFragments); + } catch (IOException e) { + throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + field.field() + "]", e); + } + HighlightField highlightField = new HighlightField(field.field(), fragments); + highlightFields.put(highlightField.name(), highlightField); } + + hit.highlightFields(highlightFields); } finally { CustomFieldQuery.reader.remove(); CustomFieldQuery.highlightFilters.remove();