4141import org .springframework .data .elasticsearch .core .query .IndexQueryBuilder ;
4242import org .springframework .retry .annotation .Retryable ;
4343import org .springframework .scheduling .annotation .Async ;
44- import org .springframework .stereotype .Component ;
44+ import org .springframework .stereotype .Service ;
4545import org .springframework .util .StopWatch ;
4646
4747import java .time .ZoneId ;
5252
5353import static org .eclipse .openvsx .cache .CacheService .CACHE_AVERAGE_REVIEW_RATING ;
5454
55- @ Component
55+ @ Service
5656public class ElasticSearchService implements ISearchService {
5757
5858 protected final ReadWriteLock rwLock = new ReentrantReadWriteLock ();
@@ -68,7 +68,7 @@ public class ElasticSearchService implements ISearchService {
6868 @ Value ("${ovsx.elasticsearch.clear-on-start:false}" )
6969 boolean clearOnStart ;
7070
71- private Long maxResultWindow ;
71+ private long maxResultWindow ;
7272
7373 public ElasticSearchService (
7474 RepositoryService repositories ,
@@ -81,7 +81,7 @@ public ElasticSearchService(
8181 this .relevanceService = relevanceService ;
8282 this .scheduler = scheduler ;
8383 }
84-
84+
8585 public boolean isEnabled () {
8686 return enableSearch ;
8787 }
@@ -90,29 +90,43 @@ public boolean isEnabled() {
9090 * Application start listener that initializes the search index. If the application property
9191 * {@code ovsx.elasticsearch.clear-on-start} is set to {@code true}, the index is cleared
9292 * and rebuilt from scratch. If the property is {@code false} and the search index does
93- * not exist yet, it is created and initialized. Otherwise nothing happens.
93+ * not exist yet, it is created and initialized. Otherwise, nothing happens.
9494 */
9595 @ EventListener
96- @ Retryable (DataAccessResourceFailureException .class )
96+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
9797 @ CacheEvict (value = CACHE_AVERAGE_REVIEW_RATING , allEntries = true )
9898 public void initSearchIndex (ApplicationStartedEvent event ) {
99- scheduler . scheduleRecurrently ( "ElasticSearchUpdateIndex" , Cron . daily ( 4 ), ZoneId . of ( "UTC" ), new HandlerJobRequest <>( ElasticSearchUpdateIndexJobRequestHandler . class ));
100- if (! isEnabled () || ! clearOnStart && searchOperations . indexOps ( ExtensionSearch . class ). exists ()) {
99+ if (! isEnabled ()) {
100+ scheduler . deleteRecurringJob ( "ElasticSearchUpdateIndex" );
101101 return ;
102102 }
103- var stopWatch = new StopWatch ();
104- stopWatch .start ();
105- updateSearchIndex (clearOnStart );
106- stopWatch .stop ();
107- logger .info ("Initialized search index in {} ms" , stopWatch .getTotalTimeMillis ());
103+
104+ // schedule recurring job to update the search index
105+ scheduler .scheduleRecurrently (
106+ "ElasticSearchUpdateIndex" ,
107+ Cron .daily (4 ),
108+ ZoneId .of ("UTC" ),
109+ new HandlerJobRequest <>(ElasticSearchUpdateIndexJobRequestHandler .class )
110+ );
111+
112+ if (clearOnStart || !searchOperations .indexOps (ExtensionSearch .class ).exists ()) {
113+ var stopWatch = new StopWatch ();
114+ stopWatch .start ();
115+ updateSearchIndex (clearOnStart );
116+ stopWatch .stop ();
117+ logger .info ("Initialized search index in {} ms" , stopWatch .getTotalTimeMillis ());
118+ }
119+
120+ var settings = searchOperations .indexOps (ExtensionSearch .class ).getSettings (true );
121+ maxResultWindow = Long .parseLong (settings .getOrDefault ("index.max_result_window" , "10000" ).toString ());
108122 }
109123
110124 /**
111125 * Soft-update the search index, because the relevance of index entries
112126 * consider the extension publishing timestamps in relation to the current
113127 * time or the extension rating.
114128 */
115- @ Retryable (DataAccessResourceFailureException .class )
129+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
116130 @ CacheEvict (value = CACHE_AVERAGE_REVIEW_RATING , allEntries = true )
117131 public void updateSearchIndex () {
118132 if (!isEnabled ()) {
@@ -134,7 +148,7 @@ public void updateSearchIndex() {
134148 * In any case, this method scans all extensions in the database and indexes their
135149 * relevant metadata.
136150 */
137- @ Retryable (DataAccessResourceFailureException .class )
151+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
138152 public void updateSearchIndex (boolean clear ) {
139153 var locked = false ;
140154 try {
@@ -180,12 +194,12 @@ public void updateSearchIndex(boolean clear) {
180194 }
181195
182196 @ Async
183- @ Retryable (DataAccessResourceFailureException .class )
197+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
184198 public void updateSearchEntriesAsync (List <Extension > extensions ) {
185199 updateSearchEntries (extensions );
186200 }
187201
188- @ Retryable (DataAccessResourceFailureException .class )
202+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
189203 public void updateSearchEntries (List <Extension > extensions ) {
190204 if (!isEnabled () || extensions .isEmpty ()) {
191205 return ;
@@ -205,7 +219,7 @@ public void updateSearchEntries(List<Extension> extensions) {
205219 }
206220 }
207221
208- @ Retryable (DataAccessResourceFailureException .class )
222+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
209223 public void updateSearchEntry (Extension extension ) {
210224 if (!isEnabled ()) {
211225 return ;
@@ -223,7 +237,7 @@ public void updateSearchEntry(Extension extension) {
223237 }
224238 }
225239
226- @ Retryable (DataAccessResourceFailureException .class )
240+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
227241 public void removeSearchEntries (Collection <Long > ids ) {
228242 if (!isEnabled ()) {
229243 return ;
@@ -235,7 +249,7 @@ public void removeSearchEntries(Collection<Long> ids) {
235249 }
236250
237251
238- @ Retryable (DataAccessResourceFailureException .class )
252+ @ Retryable (retryFor = DataAccessResourceFailureException .class )
239253 public void removeSearchEntry (Extension extension ) {
240254 if (!isEnabled ()) {
241255 return ;
@@ -251,7 +265,7 @@ public void removeSearchEntry(Extension extension) {
251265
252266 public SearchResult search (Options options ) {
253267 var resultWindow = options .requestedOffset () + options .requestedSize ();
254- if (resultWindow > getMaxResultWindow () ) {
268+ if (resultWindow > maxResultWindow ) {
255269 return new SearchResult (0L , Collections .emptyList ());
256270 }
257271
@@ -263,15 +277,16 @@ public SearchResult search(Options options) {
263277
264278 var pages = new ArrayList <Pageable >();
265279 pages .add (PageRequest .of (options .requestedOffset () / options .requestedSize (), options .requestedSize ()));
266- if (options .requestedOffset () % options .requestedSize () > 0 ) {
280+ if (options .requestedOffset () % options .requestedSize () > 0 ) {
267281 // size is not exact multiple of offset; this means we need to get two pages
268282 // e.g. when offset is 20 and size is 50, you want results 20 to 70 which span pages 0 and 1 of a 50 item page
269- pages .add (pages .get ( 0 ).next ());
283+ pages .add (pages .getFirst ( ).next ());
270284 }
271285
272286 var searchHitsList = new ArrayList <SearchHits <ExtensionSearch >>(pages .size ());
273- for (var page : pages ) {
287+ for (var page : pages ) {
274288 queryBuilder .withPageable (page );
289+ queryBuilder .withTrackTotalHits (true );
275290 try {
276291 rwLock .readLock ().lock ();
277292 var searchHits = searchOperations .search (queryBuilder .build (), ExtensionSearch .class , searchOperations .indexOps (ExtensionSearch .class ).getIndexCoordinates ());
@@ -283,7 +298,7 @@ public SearchResult search(Options options) {
283298
284299 var firstSearchHitsPage = searchHitsList .get (0 );
285300 List <SearchHit <ExtensionSearch >> searchHits = new ArrayList <>(firstSearchHitsPage .getSearchHits ());
286- if (searchHitsList .size () == 2 ) {
301+ if (searchHitsList .size () == 2 ) {
287302 var secondSearchHitsPage = searchHitsList .get (1 );
288303
289304 searchHits .addAll (secondSearchHitsPage .getSearchHits ());
@@ -383,7 +398,7 @@ private void sortResults(NativeQueryBuilder queryBuilder, String sortOrder, Stri
383398 );
384399
385400 var type = types .get (sortBy );
386- if (type == null ) {
401+ if (type == null ) {
387402 throw new ErrorResultException ("sortBy parameter must be " + SortBy .OPTIONS + "." );
388403 }
389404
@@ -392,13 +407,4 @@ private void sortResults(NativeQueryBuilder queryBuilder, String sortOrder, Stri
392407 var sortOptions = sortBy .equals (SortBy .RELEVANCE ) ? List .of (scoreSort , fieldSort ) : List .of (fieldSort , scoreSort );
393408 queryBuilder .withSort (sortOptions );
394409 }
395-
396- private long getMaxResultWindow () {
397- if (maxResultWindow == null ) {
398- var settings = searchOperations .indexOps (ExtensionSearch .class ).getSettings (true );
399- maxResultWindow = Long .parseLong (settings .getOrDefault ("index.max_result_window" , "10000" ).toString ());
400- }
401-
402- return maxResultWindow ;
403- }
404410}
0 commit comments