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