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