001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
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    /**
088     * @author Bruno Farache
089     */
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                            browseRequest.setCount(PropsValues.INDEX_SEARCH_LIMIT);
221                            browseRequest.setOffset(0);
222                            browseRequest.setQuery(
223                                    (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
224                                            query));
225                            browseRequest.setSort(sortFields);
226    
227                            boboBrowser = new BoboBrowser(boboIndexReader);
228    
229                            long startTime = System.currentTimeMillis();
230    
231                            BrowseResult browseResult = boboBrowser.browse(browseRequest);
232    
233                            BrowseHit[] browseHits = browseResult.getHits();
234    
235                            long endTime = System.currentTimeMillis();
236    
237                            float searchTime = (float)(endTime - startTime) / Time.SECOND;
238    
239                            hits = toHits(
240                                    indexSearcher, new HitDocs(browseHits), query, startTime,
241                                    searchTime, searchContext.getStart(), searchContext.getEnd());
242    
243                            Map<String, FacetAccessible> facetMap = browseResult.getFacetMap();
244    
245                            for (Map.Entry<String, FacetAccessible> entry :
246                                            facetMap.entrySet()) {
247    
248                                    Facet facet = facets.get(entry.getKey());
249    
250                                    FacetAccessible facetAccessible = entry.getValue();
251    
252                                    FacetCollector facetCollector = new BoboFacetCollector(
253                                            entry.getKey(), facetAccessible);
254    
255                                    facet.setFacetCollector(facetCollector);
256                            }
257                    }
258                    catch (BooleanQuery.TooManyClauses tmc) {
259                            int maxClauseCount = BooleanQuery.getMaxClauseCount();
260    
261                            BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);
262    
263                            try {
264                                    long startTime = System.currentTimeMillis();
265    
266                                    BrowseResult browseResult = boboBrowser.browse(browseRequest);
267    
268                                    BrowseHit[] browseHits = browseResult.getHits();
269    
270                                    long endTime = System.currentTimeMillis();
271    
272                                    float searchTime = (float)(endTime - startTime) / Time.SECOND;
273    
274                                    hits = toHits(
275                                            indexSearcher, new HitDocs(browseHits), query, startTime,
276                                            searchTime, searchContext.getStart(),
277                                            searchContext.getEnd());
278    
279                                    Map<String, FacetAccessible> facetMap =
280                                            browseResult.getFacetMap();
281    
282                                    for (Map.Entry<String, FacetAccessible> entry :
283                                                    facetMap.entrySet()) {
284    
285                                            Facet facet = facets.get(entry.getKey());
286    
287                                            FacetAccessible facetAccessible = entry.getValue();
288    
289                                            FacetCollector facetCollector = new BoboFacetCollector(
290                                                    entry.getKey(), facetAccessible);
291    
292                                            facet.setFacetCollector(facetCollector);
293                                    }
294                            }
295                            catch (Exception e) {
296                                    throw new SearchException(e);
297                            }
298                            finally {
299                                    BooleanQuery.setMaxClauseCount(maxClauseCount);
300                            }
301                    }
302                    catch (ParseException pe) {
303                            _log.error("Query " + query, pe);
304    
305                            return new HitsImpl();
306                    }
307                    catch (Exception e) {
308                            throw new SearchException(e);
309                    }
310                    finally {
311                            cleanUp(boboBrowser);
312    
313                            try {
314                                    LuceneHelperUtil.releaseIndexSearcher(
315                                            searchContext.getCompanyId(), indexSearcher);
316                            }
317                            catch (IOException ioe) {
318                                    _log.error("Unable to release searcher", ioe);
319                            }
320                    }
321    
322                    if (_log.isDebugEnabled()) {
323                            _log.debug(
324                                    "Search found " + hits.getLength() + " results in " +
325                                            hits.getSearchTime() + "ms");
326                    }
327    
328                    return hits;
329            }
330    
331            @Override
332            public Hits search(
333                            String searchEngineId, long companyId, Query query, Sort[] sorts,
334                            int start, int end)
335                    throws SearchException {
336    
337                    if (_log.isDebugEnabled()) {
338                            _log.debug("Query " + query);
339                    }
340    
341                    Hits hits = null;
342    
343                    IndexSearcher indexSearcher = null;
344                    org.apache.lucene.search.Sort luceneSort = null;
345    
346                    try {
347                            indexSearcher = LuceneHelperUtil.getSearcher(companyId, true);
348    
349                            if (sorts != null) {
350                                    SortField[] sortFields = new SortField[sorts.length];
351    
352                                    for (int i = 0; i < sorts.length; i++) {
353                                            Sort sort = sorts[i];
354    
355                                            sortFields[i] = new SortField(
356                                                    sort.getFieldName(), sort.getType(), sort.isReverse());
357                                    }
358    
359                                    luceneSort = new org.apache.lucene.search.Sort(sortFields);
360                            }
361                            else {
362                                    luceneSort = new org.apache.lucene.search.Sort();
363                            }
364    
365                            long startTime = System.currentTimeMillis();
366    
367                            TopFieldDocs topFieldDocs = indexSearcher.search(
368                                    (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
369                                            query),
370                                    null, PropsValues.INDEX_SEARCH_LIMIT, luceneSort);
371    
372                            long endTime = System.currentTimeMillis();
373    
374                            float searchTime = (float)(endTime - startTime) / Time.SECOND;
375    
376                            hits = toHits(
377                                    indexSearcher, new HitDocs(topFieldDocs), query, startTime,
378                                    searchTime, start, end);
379                    }
380                    catch (BooleanQuery.TooManyClauses tmc) {
381                            int maxClauseCount = BooleanQuery.getMaxClauseCount();
382    
383                            BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);
384    
385                            try {
386                                    long startTime = System.currentTimeMillis();
387    
388                                    TopFieldDocs topFieldDocs = indexSearcher.search(
389                                            (org.apache.lucene.search.Query)
390                                                    QueryTranslatorUtil.translate(query),
391                                            null, PropsValues.INDEX_SEARCH_LIMIT, luceneSort);
392    
393                                    long endTime = System.currentTimeMillis();
394    
395                                    float searchTime = (float)(endTime - startTime) / Time.SECOND;
396    
397                                    hits = toHits(
398                                            indexSearcher, new HitDocs(topFieldDocs), query, startTime,
399                                            searchTime, start, end);
400                            }
401                            catch (Exception e) {
402                                    throw new SearchException(e);
403                            }
404                            finally {
405                                    BooleanQuery.setMaxClauseCount(maxClauseCount);
406                            }
407                    }
408                    catch (ParseException pe) {
409                            _log.error("Query " + query, pe);
410    
411                            return new HitsImpl();
412                    }
413                    catch (Exception e) {
414                            throw new SearchException(e);
415                    }
416                    finally {
417                            LuceneHelperUtil.cleanUp(indexSearcher);
418                    }
419    
420                    if (_log.isDebugEnabled()) {
421                            _log.debug(
422                                    "Search found " + hits.getLength() + " results in " +
423                                            hits.getSearchTime() + "ms");
424                    }
425    
426                    return hits;
427            }
428    
429            protected void cleanUp(BoboBrowser boboBrowser) {
430                    if (boboBrowser == null) {
431                            return;
432                    }
433    
434                    try {
435                            boboBrowser.close();
436                    }
437                    catch (IOException ioe) {
438                            _log.error(ioe, ioe);
439                    }
440    
441                    Browsable[] browsables = boboBrowser.getSubBrowsers();
442    
443                    for (Browsable browsable : browsables) {
444                            if (!(browsable instanceof BoboSubBrowser)) {
445                                    continue;
446                            }
447    
448                            BoboSubBrowser boboSubBrowser = (BoboSubBrowser)browsable;
449    
450                            BoboIndexReader boboIndexReader = boboSubBrowser.getIndexReader();
451    
452                            try {
453                                    ThreadLocal<?> threadLocal =
454                                            (ThreadLocal<?>)_runtimeFacetDataMapField.get(
455                                                    boboIndexReader);
456    
457                                    threadLocal.remove();
458    
459                                    _runtimeFacetDataMapField.set(boboIndexReader, null);
460                            }
461                            catch (Exception e) {
462                                    _log.error(
463                                            "Unable to clean up BoboIndexReader#_runtimeFacetDataMap",
464                                            e);
465                            }
466    
467                            try {
468                                    ThreadLocal<?> threadLocal =
469                                            (ThreadLocal<?>)_runtimeFacetHandlerMapField.get(
470                                                    boboIndexReader);
471    
472                                    threadLocal.remove();
473    
474                                    _runtimeFacetHandlerMapField.set(boboIndexReader, null);
475                            }
476                            catch (Exception e) {
477                                    _log.error(
478                                            "Unable to clean up BoboIndexReader#" +
479                                                    "_runtimeFacetHandlerMap",
480                                            e);
481                            }
482                    }
483            }
484    
485            protected DocumentImpl getDocument(
486                    org.apache.lucene.document.Document oldDocument) {
487    
488                    DocumentImpl newDocument = new DocumentImpl();
489    
490                    List<org.apache.lucene.document.Fieldable> oldFieldables =
491                            oldDocument.getFields();
492    
493                    for (org.apache.lucene.document.Fieldable oldFieldable :
494                                    oldFieldables) {
495    
496                            Field newField = null;
497    
498                            String[] values = oldDocument.getValues(oldFieldable.name());
499    
500                            if ((values != null) && (values.length > 1)) {
501                                    newField = new Field(oldFieldable.name(), values);
502                            }
503                            else {
504                                    newField = new Field(
505                                            oldFieldable.name(), oldFieldable.stringValue());
506                            }
507    
508                            newField.setNumeric(oldFieldable instanceof NumericField);
509                            newField.setTokenized(oldFieldable.isTokenized());
510    
511                            newDocument.add(newField);
512                    }
513    
514                    return newDocument;
515            }
516    
517            protected Set<String> getQueryTerms(Query query) {
518                    Set<String> queryTerms = new HashSet<String>();
519    
520                    try {
521                            queryTerms = LuceneHelperUtil.getQueryTerms(
522                                    (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
523                                            query));
524                    }
525                    catch (ParseException pe) {
526                            _log.error("Query " + query, pe);
527                    }
528    
529                    return queryTerms;
530            }
531    
532            protected String getSnippet(
533                            org.apache.lucene.document.Document doc, Query query, String field,
534                            Locale locale, Document hitDoc, Set<String> matchingTerms)
535                    throws IOException {
536    
537                    String snippetField = DocumentImpl.getLocalizedName(locale, field);
538                    String snippet = null;
539    
540                    try {
541                            org.apache.lucene.search.Query luceneQuery =
542                                    (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
543                                            query);
544    
545                            String[] values = doc.getValues(snippetField);
546    
547                            TermCollectingFormatter termCollectingFormatter =
548                                    new TermCollectingFormatter();
549    
550                            if (ArrayUtil.isNotEmpty(values)) {
551                                    snippet = LuceneHelperUtil.getSnippet(
552                                            luceneQuery, snippetField, StringUtil.merge(values),
553                                            termCollectingFormatter);
554                            }
555    
556                            if (ArrayUtil.isEmpty(values) || Validator.isNull(snippet)) {
557                                    snippetField = field;
558    
559                                    values = doc.getValues(snippetField);
560    
561                                    if (ArrayUtil.isEmpty(values)) {
562                                            return StringPool.BLANK;
563                                    }
564    
565                                    snippet = LuceneHelperUtil.getSnippet(
566                                            luceneQuery, field, StringUtil.merge(values),
567                                            termCollectingFormatter);
568                            }
569    
570                            if (Validator.isNull(snippet)) {
571                                    return StringPool.BLANK;
572                            }
573    
574                            matchingTerms.addAll(termCollectingFormatter.getTerms());
575                    }
576                    catch (ParseException pe) {
577                            _log.error("Query " + query, pe);
578                    }
579    
580                    hitDoc.addText(
581                            Field.SNIPPET.concat(StringPool.UNDERLINE).concat(snippetField),
582                            snippet);
583    
584                    return snippet;
585            }
586    
587            protected Hits toHits(
588                            IndexSearcher indexSearcher, HitDocs hitDocs, Query query,
589                            long startTime, float searchTime, int start, int end)
590                    throws IOException, ParseException {
591    
592                    int total = hitDocs.getTotalHits();
593    
594                    if ((start == QueryUtil.ALL_POS) && (end == QueryUtil.ALL_POS)) {
595                            start = 0;
596                            end = total;
597                    }
598    
599                    int[] startAndEnd = SearchPaginationUtil.calculateStartAndEnd(
600                            start, end, total);
601    
602                    start = startAndEnd[0];
603                    end = startAndEnd[1];
604    
605                    Set<String> queryTerms = new HashSet<String>();
606    
607                    IndexReader indexReader = indexSearcher.getIndexReader();
608    
609                    List<String> indexedFieldNames = new ArrayList<String> (
610                            indexReader.getFieldNames(IndexReader.FieldOption.INDEXED));
611    
612                    org.apache.lucene.search.Query luceneQuery =
613                            (org.apache.lucene.search.Query)QueryTranslatorUtil.translate(
614                                    query);
615    
616                    int scoredFieldNamesCount = LuceneHelperUtil.countScoredFieldNames(
617                            luceneQuery, ArrayUtil.toStringArray(indexedFieldNames.toArray()));
618    
619                    Hits hits = new HitsImpl();
620    
621                    if ((start < 0) || (start > end)) {
622                            return hits;
623                    }
624    
625                    int subsetTotal = end - start;
626    
627                    if (subsetTotal > PropsValues.INDEX_SEARCH_LIMIT) {
628                            subsetTotal = PropsValues.INDEX_SEARCH_LIMIT;
629                    }
630    
631                    List<Document> subsetDocs = new ArrayList<Document>(subsetTotal);
632                    List<Float> subsetScores = new ArrayList<Float>(subsetTotal);
633    
634                    QueryConfig queryConfig = query.getQueryConfig();
635    
636                    for (int i = start; i < start + subsetTotal; i++) {
637                            int docId = hitDocs.getDocId(i);
638    
639                            org.apache.lucene.document.Document document = indexSearcher.doc(
640                                    docId);
641    
642                            Document subsetDocument = getDocument(document);
643    
644                            if (queryConfig.isHighlightEnabled()) {
645                                    Locale locale = queryConfig.getLocale();
646    
647                                    getSnippet(
648                                            document, query, Field.CONTENT, locale, subsetDocument,
649                                            queryTerms);
650                                    getSnippet(
651                                            document, query, Field.DESCRIPTION, locale, subsetDocument,
652                                            queryTerms);
653                                    getSnippet(
654                                            document, query, Field.TITLE, locale, subsetDocument,
655                                            queryTerms);
656                            }
657    
658                            subsetDocs.add(subsetDocument);
659    
660                            Float subsetScore = hitDocs.getScore(i);
661    
662                            if (scoredFieldNamesCount > 0) {
663                                    subsetScore = subsetScore / scoredFieldNamesCount;
664                            }
665    
666                            subsetScores.add(subsetScore);
667    
668                            if (_log.isDebugEnabled()) {
669                                    try {
670                                            Explanation explanation = indexSearcher.explain(
671                                                    luceneQuery, docId);
672    
673                                            _log.debug(explanation.toString());
674                                    }
675                                    catch (Exception e) {
676                                    }
677                            }
678                    }
679    
680                    if (!queryConfig.isHighlightEnabled()) {
681                            queryTerms = getQueryTerms(query);
682                    }
683    
684                    hits.setDocs(subsetDocs.toArray(new Document[subsetDocs.size()]));
685                    hits.setLength(total);
686                    hits.setQuery(query);
687                    hits.setQueryTerms(queryTerms.toArray(new String[queryTerms.size()]));
688                    hits.setScores(subsetScores.toArray(new Float[subsetScores.size()]));
689                    hits.setSearchTime(searchTime);
690                    hits.setStart(startTime);
691    
692                    return hits;
693            }
694    
695            private static Log _log = LogFactoryUtil.getLog(LuceneIndexSearcher.class);
696    
697            private static java.lang.reflect.Field _runtimeFacetDataMapField;
698            private static java.lang.reflect.Field _runtimeFacetHandlerMapField;
699    
700            static {
701                    try {
702                            _runtimeFacetDataMapField = ReflectionUtil.getDeclaredField(
703                                    BoboIndexReader.class, "_runtimeFacetDataMap");
704                            _runtimeFacetHandlerMapField = ReflectionUtil.getDeclaredField(
705                                    BoboIndexReader.class, "_runtimeFacetHandlerMap");
706                    }
707                    catch (Exception e) {
708                            throw new ExceptionInInitializerError(e);
709                    }
710            }
711    
712            private class HitDocs {
713    
714                    public HitDocs(BrowseHit[] browseHits) {
715                            _browseHits = browseHits;
716                    }
717    
718                    public HitDocs(TopFieldDocs topFieldDocs) {
719                            _topFieldDocs = topFieldDocs;
720                    }
721    
722                    public int getDocId(int i) {
723                            if (_topFieldDocs != null) {
724                                    ScoreDoc scoreDoc = _topFieldDocs.scoreDocs[i];
725    
726                                    return scoreDoc.doc;
727                            }
728                            else if (_browseHits != null) {
729                                    return _browseHits[i].getDocid();
730                            }
731    
732                            throw new IllegalStateException();
733                    }
734    
735                    public float getScore(int i) {
736                            if (_topFieldDocs != null) {
737                                    ScoreDoc scoreDoc = _topFieldDocs.scoreDocs[i];
738    
739                                    return scoreDoc.score;
740                            }
741                            else if (_browseHits != null) {
742                                    return _browseHits[i].getScore();
743                            }
744    
745                            throw new IllegalStateException();
746                    }
747    
748                    public int getTotalHits() {
749                            if (_topFieldDocs != null) {
750                                    return _topFieldDocs.totalHits;
751                            }
752                            else if (_browseHits != null) {
753                                    return _browseHits.length;
754                            }
755    
756                            throw new IllegalStateException();
757                    }
758    
759                    private BrowseHit[] _browseHits;
760                    private TopFieldDocs _topFieldDocs;
761    
762            }
763    
764            private class TermCollectingFormatter implements Formatter {
765    
766                    public Set<String> getTerms() {
767                            return _terms;
768                    }
769    
770                    @Override
771                    public String highlightTerm(
772                            String originalText, TokenGroup tokenGroup) {
773    
774                            if (tokenGroup.getTotalScore() > 0) {
775                                    _terms.add(originalText);
776                            }
777    
778                            return originalText;
779                    }
780    
781                    private Set<String> _terms = new HashSet<String>();
782    
783            }
784    
785    }