001
014
015 package com.liferay.portal.search.lucene;
016
017 import com.browseengine.bobo.api.BoboBrowser;
018 import com.browseengine.bobo.api.BoboIndexReader;
019 import com.browseengine.bobo.api.BoboSubBrowser;
020 import com.browseengine.bobo.api.Browsable;
021 import com.browseengine.bobo.api.BrowseHit;
022 import com.browseengine.bobo.api.BrowseRequest;
023 import com.browseengine.bobo.api.BrowseResult;
024 import com.browseengine.bobo.api.FacetAccessible;
025 import com.browseengine.bobo.api.FacetSpec;
026 import com.browseengine.bobo.api.FacetSpec.FacetSortSpec;
027 import com.browseengine.bobo.facets.FacetHandler;
028 import com.browseengine.bobo.facets.FacetHandler.TermCountSize;
029 import com.browseengine.bobo.facets.impl.MultiValueFacetHandler;
030 import com.browseengine.bobo.facets.impl.RangeFacetHandler;
031 import com.browseengine.bobo.facets.impl.SimpleFacetHandler;
032
033 import com.liferay.portal.kernel.dao.orm.QueryUtil;
034 import com.liferay.portal.kernel.dao.search.SearchPaginationUtil;
035 import com.liferay.portal.kernel.json.JSONArray;
036 import com.liferay.portal.kernel.json.JSONObject;
037 import com.liferay.portal.kernel.log.Log;
038 import com.liferay.portal.kernel.log.LogFactoryUtil;
039 import com.liferay.portal.kernel.search.BaseIndexSearcher;
040 import com.liferay.portal.kernel.search.Document;
041 import com.liferay.portal.kernel.search.DocumentImpl;
042 import com.liferay.portal.kernel.search.Field;
043 import com.liferay.portal.kernel.search.Hits;
044 import com.liferay.portal.kernel.search.HitsImpl;
045 import com.liferay.portal.kernel.search.ParseException;
046 import com.liferay.portal.kernel.search.Query;
047 import com.liferay.portal.kernel.search.QueryConfig;
048 import com.liferay.portal.kernel.search.QueryTranslatorUtil;
049 import com.liferay.portal.kernel.search.SearchContext;
050 import com.liferay.portal.kernel.search.SearchException;
051 import com.liferay.portal.kernel.search.Sort;
052 import com.liferay.portal.kernel.search.facet.Facet;
053 import com.liferay.portal.kernel.search.facet.MultiValueFacet;
054 import com.liferay.portal.kernel.search.facet.RangeFacet;
055 import com.liferay.portal.kernel.search.facet.SimpleFacet;
056 import com.liferay.portal.kernel.search.facet.collector.FacetCollector;
057 import com.liferay.portal.kernel.search.facet.config.FacetConfiguration;
058 import com.liferay.portal.kernel.util.ArrayUtil;
059 import com.liferay.portal.kernel.util.ReflectionUtil;
060 import com.liferay.portal.kernel.util.StringPool;
061 import com.liferay.portal.kernel.util.StringUtil;
062 import com.liferay.portal.kernel.util.Time;
063 import com.liferay.portal.kernel.util.Validator;
064 import com.liferay.portal.search.BoboFacetCollector;
065 import com.liferay.portal.util.PropsValues;
066
067 import java.io.IOException;
068
069 import java.util.ArrayList;
070 import java.util.HashSet;
071 import java.util.List;
072 import java.util.Map;
073 import java.util.Set;
074
075 import org.apache.lucene.document.NumericField;
076 import org.apache.lucene.index.IndexReader;
077 import org.apache.lucene.search.BooleanQuery;
078 import org.apache.lucene.search.Explanation;
079 import org.apache.lucene.search.IndexSearcher;
080 import org.apache.lucene.search.ScoreDoc;
081 import org.apache.lucene.search.SortField;
082 import org.apache.lucene.search.TopFieldDocs;
083 import org.apache.lucene.search.highlight.Formatter;
084 import org.apache.lucene.search.highlight.TokenGroup;
085
086
089 public class LuceneIndexSearcher extends BaseIndexSearcher {
090
091 @Override
092 public Hits search(SearchContext searchContext, Query query)
093 throws SearchException {
094
095 if (_log.isDebugEnabled()) {
096 _log.debug("Query " + query);
097 }
098
099 Hits hits = null;
100
101 IndexSearcher indexSearcher = null;
102 Map<String, Facet> facets = null;
103 BoboBrowser boboBrowser = null;
104 BrowseRequest browseRequest = null;
105
106 try {
107 indexSearcher = LuceneHelperUtil.getIndexSearcher(
108 searchContext.getCompanyId());
109
110 List<FacetHandler<?>> facetHandlers =
111 new ArrayList<FacetHandler<?>>();
112
113 facets = searchContext.getFacets();
114
115 for (Facet facet : facets.values()) {
116 if (facet.isStatic()) {
117 continue;
118 }
119
120 FacetConfiguration facetConfiguration =
121 facet.getFacetConfiguration();
122
123 if (facet instanceof MultiValueFacet) {
124 MultiValueFacetHandler multiValueFacetHandler =
125 new MultiValueFacetHandler(
126 facetConfiguration.getFieldName(),
127 facetConfiguration.getFieldName());
128
129 JSONObject dataJSONObject = facetConfiguration.getData();
130
131 if (dataJSONObject.has("maxTerms")) {
132 multiValueFacetHandler.setMaxItems(
133 dataJSONObject.getInt("maxTerms"));
134 }
135
136 facetHandlers.add(multiValueFacetHandler);
137 }
138 else if (facet instanceof RangeFacet) {
139 List<String> ranges = new ArrayList<String>();
140
141 JSONObject dataJSONObject = facetConfiguration.getData();
142
143 JSONArray rangesJSONArray = dataJSONObject.getJSONArray(
144 "ranges");
145
146 if (rangesJSONArray != null) {
147 for (int i = 0; i < rangesJSONArray.length(); i++) {
148 JSONObject rangeJSONObject =
149 rangesJSONArray.getJSONObject(i);
150
151 ranges.add(rangeJSONObject.getString("range"));
152 }
153 }
154
155 RangeFacetHandler rangeFacetHandler = new RangeFacetHandler(
156 facetConfiguration.getFieldName(),
157 facetConfiguration.getFieldName(), ranges);
158
159 rangeFacetHandler.setTermCountSize(TermCountSize.large);
160
161 facetHandlers.add(rangeFacetHandler);
162 }
163 else if (facet instanceof SimpleFacet) {
164 SimpleFacetHandler simpleFacetHandler =
165 new SimpleFacetHandler(
166 facetConfiguration.getFieldName(),
167 facetConfiguration.getFieldName());
168
169 facetHandlers.add(simpleFacetHandler);
170 }
171 }
172
173 BoboIndexReader boboIndexReader = BoboIndexReader.getInstance(
174 indexSearcher.getIndexReader(), facetHandlers);
175
176 SortField[] sortFields = new SortField[0];
177
178 Sort[] sorts = searchContext.getSorts();
179
180 if (sorts != null) {
181 sortFields = new SortField[sorts.length];
182
183 for (int i = 0; i < sorts.length; i++) {
184 Sort sort = sorts[i];
185
186 if ((sort.getType() == Sort.STRING_TYPE) &&
187 (searchContext.getLocale() != null)) {
188
189 sortFields[i] = new SortField(
190 sort.getFieldName(), searchContext.getLocale(),
191 sort.isReverse());
192 }
193 else {
194 sortFields[i] = new SortField(
195 sort.getFieldName(), sort.getType(),
196 sort.isReverse());
197 }
198 }
199 }
200
201 browseRequest = new BrowseRequest();
202
203 for (Facet facet : facets.values()) {
204 if (facet.isStatic()) {
205 continue;
206 }
207
208 FacetConfiguration facetConfiguration =
209 facet.getFacetConfiguration();
210
211 FacetSpec facetSpec = new FacetSpec();
212
213 facetSpec.setOrderBy(
214 FacetSortSpec.valueOf(facetConfiguration.getOrder()));
215
216 browseRequest.setFacetSpec(facet.getFieldName(), facetSpec);
217 }
218
219 int end = searchContext.getEnd();
220
221 if ((end == QueryUtil.ALL_POS) ||
222 (end > PropsValues.INDEX_SEARCH_LIMIT)) {
223
224 end = PropsValues.INDEX_SEARCH_LIMIT;
225 }
226
227 browseRequest.setCount(end);
228
229 browseRequest.setOffset(0);
230 browseRequest.setQuery(
231 (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
232 query));
233 browseRequest.setSort(sortFields);
234
235 boboBrowser = new BoboBrowser(boboIndexReader);
236
237 long startTime = System.currentTimeMillis();
238
239 BrowseResult browseResult = boboBrowser.browse(browseRequest);
240
241 long endTime = System.currentTimeMillis();
242
243 float searchTime = (float)(endTime - startTime) / Time.SECOND;
244
245 hits = toHits(
246 indexSearcher, new HitDocs(browseResult), query, startTime,
247 searchTime, searchContext.getStart(), searchContext.getEnd());
248
249 Map<String, FacetAccessible> facetMap = browseResult.getFacetMap();
250
251 for (Map.Entry<String, FacetAccessible> entry :
252 facetMap.entrySet()) {
253
254 Facet facet = facets.get(entry.getKey());
255
256 FacetAccessible facetAccessible = entry.getValue();
257
258 FacetCollector facetCollector = new BoboFacetCollector(
259 entry.getKey(), facetAccessible);
260
261 facet.setFacetCollector(facetCollector);
262 }
263 }
264 catch (BooleanQuery.TooManyClauses tmc) {
265 int maxClauseCount = BooleanQuery.getMaxClauseCount();
266
267 BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);
268
269 try {
270 long startTime = System.currentTimeMillis();
271
272 BrowseResult browseResult = boboBrowser.browse(browseRequest);
273
274 long endTime = System.currentTimeMillis();
275
276 float searchTime = (float)(endTime - startTime) / Time.SECOND;
277
278 hits = toHits(
279 indexSearcher, new HitDocs(browseResult), query, startTime,
280 searchTime, searchContext.getStart(),
281 searchContext.getEnd());
282
283 Map<String, FacetAccessible> facetMap =
284 browseResult.getFacetMap();
285
286 for (Map.Entry<String, FacetAccessible> entry :
287 facetMap.entrySet()) {
288
289 Facet facet = facets.get(entry.getKey());
290
291 FacetAccessible facetAccessible = entry.getValue();
292
293 FacetCollector facetCollector = new BoboFacetCollector(
294 entry.getKey(), facetAccessible);
295
296 facet.setFacetCollector(facetCollector);
297 }
298 }
299 catch (Exception e) {
300 throw new SearchException(e);
301 }
302 finally {
303 BooleanQuery.setMaxClauseCount(maxClauseCount);
304 }
305 }
306 catch (ParseException pe) {
307 _log.error("Query " + query, pe);
308
309 return new HitsImpl();
310 }
311 catch (Exception e) {
312 throw new SearchException(e);
313 }
314 finally {
315 cleanUp(boboBrowser);
316
317 try {
318 LuceneHelperUtil.releaseIndexSearcher(
319 searchContext.getCompanyId(), indexSearcher);
320 }
321 catch (IOException ioe) {
322 _log.error("Unable to release searcher", ioe);
323 }
324 }
325
326 if (_log.isDebugEnabled()) {
327 _log.debug(
328 "Search found " + hits.getLength() + " results in " +
329 hits.getSearchTime() + "ms");
330 }
331
332 return hits;
333 }
334
335 @Override
336 public Hits search(
337 String searchEngineId, long companyId, Query query, Sort[] sorts,
338 int start, int end)
339 throws SearchException {
340
341 if (_log.isDebugEnabled()) {
342 _log.debug("Query " + query);
343 }
344
345 Hits hits = null;
346
347 IndexSearcher indexSearcher = null;
348 org.apache.lucene.search.Sort luceneSort = null;
349
350 try {
351 indexSearcher = LuceneHelperUtil.getSearcher(companyId, true);
352
353 if (sorts != null) {
354 SortField[] sortFields = new SortField[sorts.length];
355
356 for (int i = 0; i < sorts.length; i++) {
357 Sort sort = sorts[i];
358
359 sortFields[i] = new SortField(
360 sort.getFieldName(), sort.getType(), sort.isReverse());
361 }
362
363 luceneSort = new org.apache.lucene.search.Sort(sortFields);
364 }
365 else {
366 luceneSort = new org.apache.lucene.search.Sort();
367 }
368
369 long startTime = System.currentTimeMillis();
370
371 TopFieldDocs topFieldDocs = indexSearcher.search(
372 (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
373 query),
374 null, PropsValues.INDEX_SEARCH_LIMIT, luceneSort);
375
376 long endTime = System.currentTimeMillis();
377
378 float searchTime = (float)(endTime - startTime) / Time.SECOND;
379
380 hits = toHits(
381 indexSearcher, new HitDocs(topFieldDocs), query, startTime,
382 searchTime, start, end);
383 }
384 catch (BooleanQuery.TooManyClauses tmc) {
385 int maxClauseCount = BooleanQuery.getMaxClauseCount();
386
387 BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);
388
389 try {
390 long startTime = System.currentTimeMillis();
391
392 TopFieldDocs topFieldDocs = indexSearcher.search(
393 (org.apache.lucene.search.Query)
394 QueryTranslatorUtil.translate(query),
395 null, PropsValues.INDEX_SEARCH_LIMIT, luceneSort);
396
397 long endTime = System.currentTimeMillis();
398
399 float searchTime = (float)(endTime - startTime) / Time.SECOND;
400
401 hits = toHits(
402 indexSearcher, new HitDocs(topFieldDocs), query, startTime,
403 searchTime, start, end);
404 }
405 catch (Exception e) {
406 throw new SearchException(e);
407 }
408 finally {
409 BooleanQuery.setMaxClauseCount(maxClauseCount);
410 }
411 }
412 catch (ParseException pe) {
413 _log.error("Query " + query, pe);
414
415 return new HitsImpl();
416 }
417 catch (Exception e) {
418 throw new SearchException(e);
419 }
420 finally {
421 LuceneHelperUtil.cleanUp(indexSearcher);
422 }
423
424 if (_log.isDebugEnabled()) {
425 _log.debug(
426 "Search found " + hits.getLength() + " results in " +
427 hits.getSearchTime() + "ms");
428 }
429
430 return hits;
431 }
432
433 protected void cleanUp(BoboBrowser boboBrowser) {
434 if (boboBrowser == null) {
435 return;
436 }
437
438 try {
439 boboBrowser.close();
440 }
441 catch (IOException ioe) {
442 _log.error(ioe, ioe);
443 }
444
445 Browsable[] browsables = boboBrowser.getSubBrowsers();
446
447 for (Browsable browsable : browsables) {
448 if (!(browsable instanceof BoboSubBrowser)) {
449 continue;
450 }
451
452 BoboSubBrowser boboSubBrowser = (BoboSubBrowser)browsable;
453
454 BoboIndexReader boboIndexReader = boboSubBrowser.getIndexReader();
455
456 try {
457 ThreadLocal<?> threadLocal =
458 (ThreadLocal<?>)_runtimeFacetDataMapField.get(
459 boboIndexReader);
460
461 threadLocal.remove();
462
463 _runtimeFacetDataMapField.set(boboIndexReader, null);
464 }
465 catch (Exception e) {
466 _log.error(
467 "Unable to clean up BoboIndexReader#_runtimeFacetDataMap",
468 e);
469 }
470
471 try {
472 ThreadLocal<?> threadLocal =
473 (ThreadLocal<?>)_runtimeFacetHandlerMapField.get(
474 boboIndexReader);
475
476 threadLocal.remove();
477
478 _runtimeFacetHandlerMapField.set(boboIndexReader, null);
479 }
480 catch (Exception e) {
481 _log.error(
482 "Unable to clean up BoboIndexReader#" +
483 "_runtimeFacetHandlerMap",
484 e);
485 }
486 }
487 }
488
489 protected DocumentImpl getDocument(
490 org.apache.lucene.document.Document oldDocument) {
491
492 DocumentImpl newDocument = new DocumentImpl();
493
494 List<org.apache.lucene.document.Fieldable> oldFieldables =
495 oldDocument.getFields();
496
497 for (org.apache.lucene.document.Fieldable oldFieldable :
498 oldFieldables) {
499
500 Field newField = null;
501
502 String[] values = oldDocument.getValues(oldFieldable.name());
503
504 if ((values != null) && (values.length > 1)) {
505 newField = new Field(oldFieldable.name(), values);
506 }
507 else {
508 newField = new Field(
509 oldFieldable.name(), oldFieldable.stringValue());
510 }
511
512 newField.setNumeric(oldFieldable instanceof NumericField);
513 newField.setTokenized(oldFieldable.isTokenized());
514
515 newDocument.add(newField);
516 }
517
518 return newDocument;
519 }
520
521 protected Set<String> getQueryTerms(Query query) {
522 Set<String> queryTerms = new HashSet<String>();
523
524 try {
525 queryTerms = LuceneHelperUtil.getQueryTerms(
526 (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
527 query));
528 }
529 catch (ParseException pe) {
530 _log.error("Query " + query, pe);
531 }
532
533 return queryTerms;
534 }
535
536 protected String getSnippet(
537 org.apache.lucene.document.Document doc, Query query,
538 QueryConfig queryConfig, String field, Document hitDoc,
539 Set<String> matchingTerms)
540 throws IOException {
541
542 String snippetField = DocumentImpl.getLocalizedName(
543 queryConfig.getLocale(), field);
544 String snippet = null;
545
546 try {
547 org.apache.lucene.search.Query luceneQuery =
548 (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
549 query);
550
551 String[] values = doc.getValues(snippetField);
552
553 TermCollectingFormatter termCollectingFormatter =
554 new TermCollectingFormatter();
555
556 if (ArrayUtil.isNotEmpty(values)) {
557 snippet = getSnippet(
558 luceneQuery, snippetField, values, queryConfig,
559 termCollectingFormatter);
560 }
561
562 if (ArrayUtil.isEmpty(values) || Validator.isNull(snippet)) {
563 snippetField = field;
564
565 values = doc.getValues(snippetField);
566
567 if (ArrayUtil.isEmpty(values)) {
568 return StringPool.BLANK;
569 }
570
571 snippet = getSnippet(
572 luceneQuery, field, values, queryConfig,
573 termCollectingFormatter);
574 }
575
576 if (Validator.isNull(snippet)) {
577 return StringPool.BLANK;
578 }
579
580 matchingTerms.addAll(termCollectingFormatter.getTerms());
581 }
582 catch (ParseException pe) {
583 _log.error("Query " + query, pe);
584 }
585
586 hitDoc.addText(
587 Field.SNIPPET.concat(StringPool.UNDERLINE).concat(snippetField),
588 snippet);
589
590 return snippet;
591 }
592
593 protected String getSnippet(
594 org.apache.lucene.search.Query luceneQuery, String field,
595 String[] values, QueryConfig queryConfig,
596 TermCollectingFormatter termCollectingFormatter)
597 throws IOException {
598
599 return LuceneHelperUtil.getSnippet(
600 luceneQuery, field, StringUtil.merge(values),
601 queryConfig.getHighlightSnippetSize(),
602 queryConfig.getHighlightFragmentSize(), StringPool.TRIPLE_PERIOD,
603 termCollectingFormatter);
604 }
605
606 protected Hits toHits(
607 IndexSearcher indexSearcher, HitDocs hitDocs, Query query,
608 long startTime, float searchTime, int start, int end)
609 throws IOException, ParseException {
610
611 int total = hitDocs.getTotalHits();
612
613 if (total > PropsValues.INDEX_SEARCH_LIMIT) {
614 total = PropsValues.INDEX_SEARCH_LIMIT;
615 }
616
617 if ((start == QueryUtil.ALL_POS) && (end == QueryUtil.ALL_POS)) {
618 start = 0;
619 end = total;
620 }
621
622 int[] startAndEnd = SearchPaginationUtil.calculateStartAndEnd(
623 start, end, total);
624
625 start = startAndEnd[0];
626 end = startAndEnd[1];
627
628 Set<String> queryTerms = new HashSet<String>();
629
630 IndexReader indexReader = indexSearcher.getIndexReader();
631
632 List<String> indexedFieldNames = new ArrayList<String> (
633 indexReader.getFieldNames(IndexReader.FieldOption.INDEXED));
634
635 org.apache.lucene.search.Query luceneQuery =
636 (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
637 query);
638
639 int scoredFieldNamesCount = -1;
640
641 Hits hits = new HitsImpl();
642
643 if ((start < 0) || (start > end)) {
644 return hits;
645 }
646
647 int subsetTotal = end - start;
648
649 if (subsetTotal > hitDocs.getSize()) {
650 subsetTotal = hitDocs.getSize();
651 }
652
653 List<Document> subsetDocs = new ArrayList<Document>(subsetTotal);
654 List<Float> subsetScores = new ArrayList<Float>(subsetTotal);
655
656 QueryConfig queryConfig = query.getQueryConfig();
657
658 for (int i = start; i < start + subsetTotal; i++) {
659 int docId = hitDocs.getDocId(i);
660
661 org.apache.lucene.document.Document document = indexSearcher.doc(
662 docId);
663
664 Document subsetDocument = getDocument(document);
665
666 getSnippet(
667 document, query, queryConfig, Field.ASSET_CATEGORY_TITLES,
668 subsetDocument, queryTerms);
669
670 if (queryConfig.isHighlightEnabled()) {
671 getSnippet(
672 document, query, queryConfig, Field.CONTENT, subsetDocument,
673 queryTerms);
674 getSnippet(
675 document, query, queryConfig, Field.DESCRIPTION,
676 subsetDocument, queryTerms);
677 getSnippet(
678 document, query, queryConfig, Field.TITLE, subsetDocument,
679 queryTerms);
680 }
681
682 subsetDocs.add(subsetDocument);
683
684 Float subsetScore = hitDocs.getScore(i);
685
686 if (subsetScore > 0) {
687 if (scoredFieldNamesCount == -1) {
688 scoredFieldNamesCount =
689 LuceneHelperUtil.countScoredFieldNames(
690 luceneQuery,
691 ArrayUtil.toStringArray(
692 indexedFieldNames.toArray()));
693 }
694
695 if (scoredFieldNamesCount > 0) {
696 subsetScore = subsetScore / scoredFieldNamesCount;
697 }
698 }
699
700 subsetScores.add(subsetScore);
701
702 if (_log.isDebugEnabled()) {
703 try {
704 Explanation explanation = indexSearcher.explain(
705 luceneQuery, docId);
706
707 _log.debug(explanation.toString());
708 }
709 catch (Exception e) {
710 }
711 }
712 }
713
714 if (!queryConfig.isHighlightEnabled()) {
715 queryTerms = getQueryTerms(query);
716 }
717
718 hits.setDocs(subsetDocs.toArray(new Document[subsetDocs.size()]));
719 hits.setLength(total);
720 hits.setQuery(query);
721 hits.setQueryTerms(queryTerms.toArray(new String[queryTerms.size()]));
722 hits.setScores(subsetScores.toArray(new Float[subsetScores.size()]));
723 hits.setSearchTime(searchTime);
724 hits.setStart(startTime);
725
726 return hits;
727 }
728
729 private static Log _log = LogFactoryUtil.getLog(LuceneIndexSearcher.class);
730
731 private static java.lang.reflect.Field _runtimeFacetDataMapField;
732 private static java.lang.reflect.Field _runtimeFacetHandlerMapField;
733
734 static {
735 try {
736 _runtimeFacetDataMapField = ReflectionUtil.getDeclaredField(
737 BoboIndexReader.class, "_runtimeFacetDataMap");
738 _runtimeFacetHandlerMapField = ReflectionUtil.getDeclaredField(
739 BoboIndexReader.class, "_runtimeFacetHandlerMap");
740 }
741 catch (Exception e) {
742 throw new ExceptionInInitializerError(e);
743 }
744 }
745
746 private class HitDocs {
747
748 public HitDocs(BrowseResult browseResult) {
749 _browseHits = browseResult.getHits();
750 _browseResult = browseResult;
751 }
752
753 public HitDocs(TopFieldDocs topFieldDocs) {
754 _topFieldDocs = topFieldDocs;
755 }
756
757 public int getDocId(int i) {
758 if (_topFieldDocs != null) {
759 ScoreDoc scoreDoc = _topFieldDocs.scoreDocs[i];
760
761 return scoreDoc.doc;
762 }
763 else if (_browseHits != null) {
764 return _browseHits[i].getDocid();
765 }
766
767 throw new IllegalStateException();
768 }
769
770 public float getScore(int i) {
771 if (_topFieldDocs != null) {
772 ScoreDoc scoreDoc = _topFieldDocs.scoreDocs[i];
773
774 return scoreDoc.score;
775 }
776 else if (_browseHits != null) {
777 return _browseHits[i].getScore();
778 }
779
780 throw new IllegalStateException();
781 }
782
783 public int getTotalHits() {
784 if (_topFieldDocs != null) {
785 return _topFieldDocs.totalHits;
786 }
787 else if (_browseResult != null) {
788 return _browseResult.getNumHits();
789 }
790
791 throw new IllegalStateException();
792 }
793
794 public int getSize() {
795 if (_topFieldDocs != null) {
796 return _topFieldDocs.scoreDocs.length;
797 }
798 else if (_browseHits != null) {
799 return _browseHits.length;
800 }
801
802 throw new IllegalStateException();
803 }
804
805 private BrowseHit[] _browseHits;
806 private BrowseResult _browseResult;
807 private TopFieldDocs _topFieldDocs;
808
809 }
810
811 private class TermCollectingFormatter implements Formatter {
812
813 public Set<String> getTerms() {
814 return _terms;
815 }
816
817 @Override
818 public String highlightTerm(
819 String originalText, TokenGroup tokenGroup) {
820
821 if (tokenGroup.getTotalScore() > 0) {
822 _terms.add(originalText);
823 }
824
825 return originalText;
826 }
827
828 private Set<String> _terms = new HashSet<String>();
829
830 }
831
832 }