diff --git a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java index 6f81241ff58..c38546ee116 100644 --- a/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java +++ b/solr/core/src/java/org/apache/solr/highlight/UnifiedSolrHighlighter.java @@ -110,256 +110,256 @@ import org.apache.solr.util.plugin.PluginInfoInitialized; */ public class UnifiedSolrHighlighter extends SolrHighlighter implements PluginInfoInitialized { - protected static final String SNIPPET_SEPARATOR = "\u0000"; - private static final String[] ZERO_LEN_STR_ARRAY = new String[0]; + protected static final String SNIPPET_SEPARATOR = "\u0000"; + private static final String[] ZERO_LEN_STR_ARRAY = new String[0]; - @Override - public void init(PluginInfo info) { + @Override + public void init(PluginInfo info) { + } + + @Override + public NamedList doHighlighting(DocList docs, Query query, SolrQueryRequest req, String[] defaultFields) throws IOException { + final SolrParams params = req.getParams(); + + // if highlighting isn't enabled, then why call doHighlighting? + if (!isHighlightingEnabled(params)) + return null; + + int[] docIDs = toDocIDs(docs); + + // fetch the unique keys + String[] keys = getUniqueKeys(req.getSearcher(), docIDs); + + // query-time parameters + String[] fieldNames = getHighlightFields(query, req, defaultFields); + + int maxPassages[] = new int[fieldNames.length]; + for (int i = 0; i < fieldNames.length; i++) { + maxPassages[i] = params.getFieldInt(fieldNames[i], HighlightParams.SNIPPETS, 1); } - @Override - public NamedList doHighlighting(DocList docs, Query query, SolrQueryRequest req, String[] defaultFields) throws IOException { - final SolrParams params = req.getParams(); + UnifiedHighlighter highlighter = getHighlighter(req); + Map snippets = highlighter.highlightFields(fieldNames, query, docIDs, maxPassages); + return encodeSnippets(keys, fieldNames, snippets); + } - // if highlighting isn't enabled, then why call doHighlighting? - if (!isHighlightingEnabled(params)) - return null; + /** + * Creates an instance of the Lucene {@link UnifiedHighlighter}. Provided for subclass extension so that + * a subclass can return a subclass of {@link SolrExtendedUnifiedHighlighter}. + */ + protected UnifiedHighlighter getHighlighter(SolrQueryRequest req) { + return new SolrExtendedUnifiedHighlighter(req); + } - int[] docIDs = toDocIDs(docs); - - // fetch the unique keys - String[] keys = getUniqueKeys(req.getSearcher(), docIDs); - - // query-time parameters - String[] fieldNames = getHighlightFields(query, req, defaultFields); - - int maxPassages[] = new int[fieldNames.length]; - for (int i = 0; i < fieldNames.length; i++) { - maxPassages[i] = params.getFieldInt(fieldNames[i], HighlightParams.SNIPPETS, 1); - } - - UnifiedHighlighter highlighter = getHighlighter(req); - Map snippets = highlighter.highlightFields(fieldNames, query, docIDs, maxPassages); - return encodeSnippets(keys, fieldNames, snippets); - } - - /** - * Creates an instance of the Lucene {@link UnifiedHighlighter}. Provided for subclass extension so that - * a subclass can return a subclass of {@link SolrExtendedUnifiedHighlighter}. - */ - protected UnifiedHighlighter getHighlighter(SolrQueryRequest req) { - return new SolrExtendedUnifiedHighlighter(req); - } - - /** - * Encodes the resulting snippets into a namedlist - * - * @param keys the document unique keys - * @param fieldNames field names to highlight in the order - * @param snippets map from field name to snippet array for the docs - * @return encoded namedlist of summaries - */ - protected NamedList encodeSnippets(String[] keys, String[] fieldNames, Map snippets) { - NamedList list = new SimpleOrderedMap<>(); - for (int i = 0; i < keys.length; i++) { - NamedList summary = new SimpleOrderedMap<>(); - for (String field : fieldNames) { - String snippet = snippets.get(field)[i]; - if (snippet == null) { - //TODO reuse logic of DefaultSolrHighlighter.alternateField - summary.add(field, ZERO_LEN_STR_ARRAY); - } else { - // we used a special snippet separator char and we can now split on it. - summary.add(field, snippet.split(SNIPPET_SEPARATOR)); - } - } - list.add(keys[i], summary); - } - return list; - } - - /** - * Converts solr's DocList to the int[] docIDs - */ - protected int[] toDocIDs(DocList docs) { - int[] docIDs = new int[docs.size()]; - DocIterator iterator = docs.iterator(); - for (int i = 0; i < docIDs.length; i++) { - if (!iterator.hasNext()) { - throw new AssertionError(); - } - docIDs[i] = iterator.nextDoc(); - } - if (iterator.hasNext()) { - throw new AssertionError(); - } - return docIDs; - } - - /** - * Retrieves the unique keys for the topdocs to key the results - */ - protected String[] getUniqueKeys(SolrIndexSearcher searcher, int[] docIDs) throws IOException { - IndexSchema schema = searcher.getSchema(); - SchemaField keyField = schema.getUniqueKeyField(); - if (keyField != null) { - Set selector = Collections.singleton(keyField.getName()); - String[] uniqueKeys = new String[docIDs.length]; - for (int i = 0; i < docIDs.length; i++) { - int docid = docIDs[i]; - Document doc = searcher.doc(docid, selector); - String id = schema.printableUniqueKey(doc); - uniqueKeys[i] = id; - } - return uniqueKeys; + /** + * Encodes the resulting snippets into a namedlist + * + * @param keys the document unique keys + * @param fieldNames field names to highlight in the order + * @param snippets map from field name to snippet array for the docs + * @return encoded namedlist of summaries + */ + protected NamedList encodeSnippets(String[] keys, String[] fieldNames, Map snippets) { + NamedList list = new SimpleOrderedMap<>(); + for (int i = 0; i < keys.length; i++) { + NamedList summary = new SimpleOrderedMap<>(); + for (String field : fieldNames) { + String snippet = snippets.get(field)[i]; + if (snippet == null) { + //TODO reuse logic of DefaultSolrHighlighter.alternateField + summary.add(field, ZERO_LEN_STR_ARRAY); } else { - return new String[docIDs.length]; + // we used a special snippet separator char and we can now split on it. + summary.add(field, snippet.split(SNIPPET_SEPARATOR)); } + } + list.add(keys[i], summary); + } + return list; + } + + /** + * Converts solr's DocList to the int[] docIDs + */ + protected int[] toDocIDs(DocList docs) { + int[] docIDs = new int[docs.size()]; + DocIterator iterator = docs.iterator(); + for (int i = 0; i < docIDs.length; i++) { + if (!iterator.hasNext()) { + throw new AssertionError(); + } + docIDs[i] = iterator.nextDoc(); + } + if (iterator.hasNext()) { + throw new AssertionError(); + } + return docIDs; + } + + /** + * Retrieves the unique keys for the topdocs to key the results + */ + protected String[] getUniqueKeys(SolrIndexSearcher searcher, int[] docIDs) throws IOException { + IndexSchema schema = searcher.getSchema(); + SchemaField keyField = schema.getUniqueKeyField(); + if (keyField != null) { + Set selector = Collections.singleton(keyField.getName()); + String[] uniqueKeys = new String[docIDs.length]; + for (int i = 0; i < docIDs.length; i++) { + int docid = docIDs[i]; + Document doc = searcher.doc(docid, selector); + String id = schema.printableUniqueKey(doc); + uniqueKeys[i] = id; + } + return uniqueKeys; + } else { + return new String[docIDs.length]; + } + } + + /** + * From {@link #getHighlighter(org.apache.solr.request.SolrQueryRequest)}. + */ + protected static class SolrExtendedUnifiedHighlighter extends UnifiedHighlighter { + protected final SolrParams params; + protected final IndexSchema schema; + + protected final RTimerTree loadFieldValuesTimer; + + public SolrExtendedUnifiedHighlighter(SolrQueryRequest req) { + super(req.getSearcher(), req.getSchema().getIndexAnalyzer()); + this.params = req.getParams(); + this.schema = req.getSchema(); + this.setMaxLength( + params.getInt(HighlightParams.MAX_CHARS, UnifiedHighlighter.DEFAULT_MAX_LENGTH)); + this.setCacheFieldValCharsThreshold( + params.getInt(HighlightParams.CACHE_FIELD_VAL_CHARS_THRESHOLD, DEFAULT_CACHE_CHARS_THRESHOLD)); + + // SolrRequestInfo is a thread-local singleton providing access to the ResponseBuilder to code that + // otherwise can't get it in a nicer way. + SolrQueryRequest request = SolrRequestInfo.getRequestInfo().getReq(); + final RTimerTree timerTree; + if (request.getRequestTimer() != null) { //It may be null if not used in a search context. + timerTree = request.getRequestTimer(); + } else { + timerTree = new RTimerTree(); // since null checks are annoying + } + loadFieldValuesTimer = timerTree.sub("loadFieldValues"); // we assume a new timer, state of STARTED + loadFieldValuesTimer.pause(); // state of PAUSED now with about zero time. Will fail if state isn't STARTED. + } + + @Override + protected OffsetSource getOffsetSource(String field) { + String sourceStr = params.getFieldParam(field, HighlightParams.OFFSET_SOURCE); + if (sourceStr != null) { + return OffsetSource.valueOf(sourceStr.toUpperCase(Locale.ROOT)); + } else { + return super.getOffsetSource(field); + } + } + + @Override + public int getMaxNoHighlightPassages(String field) { + boolean defaultSummary = params.getFieldBool(field, HighlightParams.DEFAULT_SUMMARY, false); + if (defaultSummary) { + return -1;// signifies return first hl.snippets passages worth of the content + } else { + return 0;// will return null + } + } + + @Override + protected PassageFormatter getFormatter(String fieldName) { + String preTag = params.getFieldParam(fieldName, HighlightParams.TAG_PRE, + params.getFieldParam(fieldName, HighlightParams.SIMPLE_PRE, "") + ); + + String postTag = params.getFieldParam(fieldName, HighlightParams.TAG_POST, + params.getFieldParam(fieldName, HighlightParams.SIMPLE_POST, "") + ); + String ellipsis = params.getFieldParam(fieldName, HighlightParams.TAG_ELLIPSIS, SNIPPET_SEPARATOR); + String encoder = params.getFieldParam(fieldName, HighlightParams.ENCODER, "simple"); + return new DefaultPassageFormatter(preTag, postTag, ellipsis, "html".equals(encoder)); + } + + @Override + protected PassageScorer getScorer(String fieldName) { + float k1 = params.getFieldFloat(fieldName, HighlightParams.SCORE_K1, 1.2f); + float b = params.getFieldFloat(fieldName, HighlightParams.SCORE_B, 0.75f); + float pivot = params.getFieldFloat(fieldName, HighlightParams.SCORE_PIVOT, 87f); + return new PassageScorer(k1, b, pivot); + } + + @Override + protected BreakIterator getBreakIterator(String field) { + String language = params.getFieldParam(field, HighlightParams.BS_LANGUAGE); + String country = params.getFieldParam(field, HighlightParams.BS_COUNTRY); + String variant = params.getFieldParam(field, HighlightParams.BS_VARIANT); + Locale locale = parseLocale(language, country, variant); + String type = params.getFieldParam(field, HighlightParams.BS_TYPE); + return parseBreakIterator(type, locale); } /** - * From {@link #getHighlighter(org.apache.solr.request.SolrQueryRequest)}. + * parse a break iterator type for the specified locale */ - protected static class SolrExtendedUnifiedHighlighter extends UnifiedHighlighter { - protected final SolrParams params; - protected final IndexSchema schema; - - protected final RTimerTree loadFieldValuesTimer; - - public SolrExtendedUnifiedHighlighter(SolrQueryRequest req) { - super(req.getSearcher(), req.getSchema().getIndexAnalyzer()); - this.params = req.getParams(); - this.schema = req.getSchema(); - this.setMaxLength( - params.getInt(HighlightParams.MAX_CHARS, UnifiedHighlighter.DEFAULT_MAX_LENGTH)); - this.setCacheFieldValCharsThreshold( - params.getInt(HighlightParams.CACHE_FIELD_VAL_CHARS_THRESHOLD, DEFAULT_CACHE_CHARS_THRESHOLD)); - - // SolrRequestInfo is a thread-local singleton providing access to the ResponseBuilder to code that - // otherwise can't get it in a nicer way. - SolrQueryRequest request = SolrRequestInfo.getRequestInfo().getReq(); - final RTimerTree timerTree; - if (request.getRequestTimer() != null) { //It may be null if not used in a search context. - timerTree = request.getRequestTimer(); - } else { - timerTree = new RTimerTree(); // since null checks are annoying - } - loadFieldValuesTimer = timerTree.sub("loadFieldValues"); // we assume a new timer, state of STARTED - loadFieldValuesTimer.pause(); // state of PAUSED now with about zero time. Will fail if state isn't STARTED. - } - - @Override - protected OffsetSource getOffsetSource(String field) { - String sourceStr = params.getFieldParam(field, HighlightParams.OFFSET_SOURCE); - if (sourceStr != null) { - return OffsetSource.valueOf(sourceStr.toUpperCase(Locale.ROOT)); - } else { - return super.getOffsetSource(field); - } - } - - @Override - public int getMaxNoHighlightPassages(String field) { - boolean defaultSummary = params.getFieldBool(field, HighlightParams.DEFAULT_SUMMARY, false); - if (defaultSummary) { - return -1;// signifies return first hl.snippets passages worth of the content - } else { - return 0;// will return null - } - } - - @Override - protected PassageFormatter getFormatter(String fieldName) { - String preTag = params.getFieldParam(fieldName, HighlightParams.TAG_PRE, - params.getFieldParam(fieldName, HighlightParams.SIMPLE_PRE, "") - ); - - String postTag = params.getFieldParam(fieldName, HighlightParams.TAG_POST, - params.getFieldParam(fieldName, HighlightParams.SIMPLE_POST, "") - ); - String ellipsis = params.getFieldParam(fieldName, HighlightParams.TAG_ELLIPSIS, SNIPPET_SEPARATOR); - String encoder = params.getFieldParam(fieldName, HighlightParams.ENCODER, "simple"); - return new DefaultPassageFormatter(preTag, postTag, ellipsis, "html".equals(encoder)); - } - - @Override - protected PassageScorer getScorer(String fieldName) { - float k1 = params.getFieldFloat(fieldName, HighlightParams.SCORE_K1, 1.2f); - float b = params.getFieldFloat(fieldName, HighlightParams.SCORE_B, 0.75f); - float pivot = params.getFieldFloat(fieldName, HighlightParams.SCORE_PIVOT, 87f); - return new PassageScorer(k1, b, pivot); - } - - @Override - protected BreakIterator getBreakIterator(String field) { - String language = params.getFieldParam(field, HighlightParams.BS_LANGUAGE); - String country = params.getFieldParam(field, HighlightParams.BS_COUNTRY); - String variant = params.getFieldParam(field, HighlightParams.BS_VARIANT); - Locale locale = parseLocale(language, country, variant); - String type = params.getFieldParam(field, HighlightParams.BS_TYPE); - return parseBreakIterator(type, locale); - } - - /** - * parse a break iterator type for the specified locale - */ - protected BreakIterator parseBreakIterator(String type, Locale locale) { - if (type == null || "SENTENCE".equals(type)) { - return BreakIterator.getSentenceInstance(locale); - } else if ("LINE".equals(type)) { - return BreakIterator.getLineInstance(locale); - } else if ("WORD".equals(type)) { - return BreakIterator.getWordInstance(locale); - } else if ("CHARACTER".equals(type)) { - return BreakIterator.getCharacterInstance(locale); - } else if ("WHOLE".equals(type)) { - return new WholeBreakIterator(); - } else { - throw new IllegalArgumentException("Unknown " + HighlightParams.BS_TYPE + ": " + type); - } - } - - /** - * parse a locale from a language+country+variant spec - */ - protected Locale parseLocale(String language, String country, String variant) { - if (language == null && country == null && variant == null) { - return Locale.ROOT; - } else if (language == null) { - throw new IllegalArgumentException("language is required if country or variant is specified"); - } else if (country == null && variant != null) { - throw new IllegalArgumentException("To specify variant, country is required"); - } else if (country != null && variant != null) { - return new Locale(language, country, variant); - } else if (country != null) { - return new Locale(language, country); - } else { - return new Locale(language); - } - } - - @Override - protected List loadFieldValues(String[] fields, DocIdSetIterator docIter, int - cacheCharsThreshold) throws IOException { - // Time loading field values. It can be an expensive part of highlighting. - loadFieldValuesTimer.resume(); - try { - return super.loadFieldValues(fields, docIter, cacheCharsThreshold); - } finally { - loadFieldValuesTimer.pause(); // note: doesn't need to be "stopped"; pause is fine. - } - } - - @Override - protected boolean shouldHandleMultiTermQuery(String field) { - return params.getFieldBool(field, HighlightParams.HIGHLIGHT_MULTI_TERM, true); - } - - @Override - protected boolean shouldHighlightPhrasesStrictly(String field) { - return params.getFieldBool(field, HighlightParams.USE_PHRASE_HIGHLIGHTER, true); - } - + protected BreakIterator parseBreakIterator(String type, Locale locale) { + if (type == null || "SENTENCE".equals(type)) { + return BreakIterator.getSentenceInstance(locale); + } else if ("LINE".equals(type)) { + return BreakIterator.getLineInstance(locale); + } else if ("WORD".equals(type)) { + return BreakIterator.getWordInstance(locale); + } else if ("CHARACTER".equals(type)) { + return BreakIterator.getCharacterInstance(locale); + } else if ("WHOLE".equals(type)) { + return new WholeBreakIterator(); + } else { + throw new IllegalArgumentException("Unknown " + HighlightParams.BS_TYPE + ": " + type); + } } + /** + * parse a locale from a language+country+variant spec + */ + protected Locale parseLocale(String language, String country, String variant) { + if (language == null && country == null && variant == null) { + return Locale.ROOT; + } else if (language == null) { + throw new IllegalArgumentException("language is required if country or variant is specified"); + } else if (country == null && variant != null) { + throw new IllegalArgumentException("To specify variant, country is required"); + } else if (country != null && variant != null) { + return new Locale(language, country, variant); + } else if (country != null) { + return new Locale(language, country); + } else { + return new Locale(language); + } + } + + @Override + protected List loadFieldValues(String[] fields, DocIdSetIterator docIter, int + cacheCharsThreshold) throws IOException { + // Time loading field values. It can be an expensive part of highlighting. + loadFieldValuesTimer.resume(); + try { + return super.loadFieldValues(fields, docIter, cacheCharsThreshold); + } finally { + loadFieldValuesTimer.pause(); // note: doesn't need to be "stopped"; pause is fine. + } + } + + @Override + protected boolean shouldHandleMultiTermQuery(String field) { + return params.getFieldBool(field, HighlightParams.HIGHLIGHT_MULTI_TERM, true); + } + + @Override + protected boolean shouldHighlightPhrasesStrictly(String field) { + return params.getFieldBool(field, HighlightParams.USE_PHRASE_HIGHLIGHTER, true); + } + + } + } \ No newline at end of file