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                            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    }