1616import org .apache .lucene .queryparser .flexible .standard .config .PointsConfig ;
1717import org .apache .lucene .search .BooleanClause ;
1818import org .apache .lucene .search .BooleanQuery ;
19+ import org .apache .lucene .search .ConstantScoreQuery ;
1920import org .apache .lucene .search .IndexSearcher ;
2021import org .apache .lucene .search .LRUQueryCache ;
2122import org .apache .lucene .search .MatchAllDocsQuery ;
2425import org .apache .lucene .search .ScoreDoc ;
2526import org .apache .lucene .search .Sort ;
2627import org .apache .lucene .search .SortField ;
28+ import org .apache .lucene .search .TopDocs ;
2729import org .apache .lucene .search .TopFieldDocs ;
2830import org .apache .lucene .search .UsageTrackingQueryCachingPolicy ;
2931import org .apache .lucene .store .Directory ;
6567import java .util .Map ;
6668import java .util .Set ;
6769import java .util .StringTokenizer ;
70+ import java .util .concurrent .ExecutorService ;
71+ import java .util .concurrent .Executors ;
6872import java .util .regex .Matcher ;
6973import java .util .regex .Pattern ;
7074
7579public class JBrowseLuceneSearch
7680{
7781 private static final Logger _log = LogHelper .getLogger (JBrowseLuceneSearch .class , "Logger related to JBrowse/Lucene indexing and queries" );
82+ private static final ExecutorService SEARCH_EXECUTOR = Executors .newFixedThreadPool (JBrowseServiceImpl .get ().getCoresForLuceneSearches ());
7883 private final JBrowseSession _session ;
7984 private final JsonFile _jsonFile ;
8085 private final User _user ;
@@ -108,6 +113,11 @@ public static JBrowseLuceneSearch create(String sessionId, String trackId, User
108113 private static synchronized CacheEntry getCacheEntryForSession (String trackObjectId , File indexPath ) throws IOException {
109114 CacheEntry cacheEntry = _cache .get (trackObjectId );
110115
116+ if (SEARCH_EXECUTOR .isShutdown () || SEARCH_EXECUTOR .isTerminated ())
117+ {
118+ throw new IllegalStateException ("The server is shutting down!" );
119+ }
120+
111121 // Open directory of lucene path, get a directory reader, and create the index search manager
112122 if (cacheEntry == null )
113123 {
@@ -116,7 +126,7 @@ private static synchronized CacheEntry getCacheEntryForSession(String trackObjec
116126 Directory indexDirectory = FSDirectory .open (indexPath .toPath ());
117127 LRUQueryCache queryCache = new LRUQueryCache (maxCachedQueries , maxRamBytesUsed );
118128 IndexReader indexReader = DirectoryReader .open (indexDirectory );
119- IndexSearcher indexSearcher = new IndexSearcher (indexReader );
129+ IndexSearcher indexSearcher = new IndexSearcher (indexReader , SEARCH_EXECUTOR );
120130 indexSearcher .setQueryCache (queryCache );
121131 indexSearcher .setQueryCachingPolicy (new ForceMatchAllDocsCachingPolicy ());
122132 cacheEntry = new CacheEntry (queryCache , indexSearcher , indexPath );
@@ -252,7 +262,7 @@ private SearchConfig createSearchConfig(User u, String searchString, final int p
252262
253263 if (searchString .equals (ALL_DOCS ))
254264 {
255- booleanQueryBuilder .add (new MatchAllDocsQuery (), BooleanClause .Occur .MUST );
265+ booleanQueryBuilder .add (new ConstantScoreQuery ( new MatchAllDocsQuery () ), BooleanClause .Occur .MUST );
256266 }
257267
258268 // Split input into tokens, 1 token per query separated by &
@@ -321,41 +331,46 @@ else if (numericQueryParserFields.containsKey(fieldName))
321331 }
322332
323333 private JSONObject paginateJSON (SearchConfig c ) throws IOException , ParseException {
324- // Get chunks of size {pageSize}. Default to 1 chunk -- add to the offset to get more.
325- // We then iterate over the range of documents we want based on the offset. This does grow in memory
326- // linearly with the number of documents, but my understanding is that these are just score,id pairs
327- // rather than full documents, so mem usage *should* still be pretty low.
328- // Perform the search with sorting
329- TopFieldDocs topDocs = c .cacheEntry .indexSearcher .search (c .query , c .pageSize * (c .offset + 1 ), c .sort );
334+ IndexSearcher searcher = c .cacheEntry .indexSearcher ;
335+ TopDocs topDocs ;
336+
337+ if (c .offset == 0 ) {
338+ topDocs = searcher .search (c .query , c .pageSize , c .sort );
339+ } else {
340+ TopFieldDocs prev = searcher .search (c .query , c .pageSize * c .offset , c .sort );
341+ long totalHits = prev .totalHits .value ;
342+ ScoreDoc [] prevHits = prev .scoreDocs ;
343+
344+ if (prevHits .length < c .pageSize * c .offset )
345+ {
346+ JSONObject results = new JSONObject ();
347+ results .put ("data" , Collections .emptyList ());
348+ results .put ("totalHits" , totalHits );
349+ return results ;
350+ }
351+
352+ ScoreDoc lastDoc = prevHits [c .pageSize * c .offset - 1 ];
353+ topDocs = searcher .searchAfter (lastDoc , c .query , c .pageSize , c .sort );
354+ }
355+
330356 JSONObject results = new JSONObject ();
357+ List <JSONObject > data = new ArrayList <>(topDocs .scoreDocs .length );
331358
332- // Iterate over the doc list, (either to the total end or until the page ends) grab the requested docs,
333- // and add to returned results
334- List <JSONObject > data = new ArrayList <>();
335- for (int i = c .pageSize * c .offset ; i < Math .min (c .pageSize * (c .offset + 1 ), topDocs .scoreDocs .length ); i ++)
359+ for (ScoreDoc sd : topDocs .scoreDocs )
336360 {
361+ Document doc = searcher .storedFields ().document (sd .doc );
337362 JSONObject elem = new JSONObject ();
338- Document doc = c .cacheEntry .indexSearcher .storedFields ().document (topDocs .scoreDocs [i ].doc );
339-
340- for (IndexableField field : doc .getFields ())
363+ for (IndexableField f : doc .getFields ())
341364 {
342- String fieldName = field .name ();
343- String [] fieldValues = doc .getValues (fieldName );
344- if (fieldValues .length > 1 )
345- {
346- elem .put (fieldName , fieldValues );
347- }
348- else
349- {
350- elem .put (fieldName , fieldValues [0 ]);
351- }
365+ String name = f .name ();
366+ String [] vals = doc .getValues (name );
367+ elem .put (name , vals .length > 1 ? Arrays .asList (vals ) : vals [0 ]);
352368 }
353369 data .add (elem );
354370 }
355371
356372 results .put ("data" , data );
357373 results .put ("totalHits" , topDocs .totalHits .value );
358-
359374 return results ;
360375 }
361376
@@ -679,17 +694,20 @@ public String getName()
679694 return "JBrowse-Lucene Shutdown Listener" ;
680695 }
681696
682- @ Override
683- public void shutdownPre ()
684- {
685-
686- }
687-
688697 @ Override
689698 public void shutdownStarted ()
690699 {
691700 _log .info ("Clearing all open JBrowse/Lucene cached readers" );
692701 JBrowseLuceneSearch .emptyCache ();
702+
703+ try
704+ {
705+ SEARCH_EXECUTOR .shutdown ();
706+ }
707+ catch (Exception e )
708+ {
709+ _log .error ("Error shutting down SEARCH_EXECUTOR" , e );
710+ }
693711 }
694712 }
695713
0 commit comments