public SearchHits<ExtensionSearch> search(Options options) {
        var resultWindow = options.requestedOffset + options.requestedSize;
        if(resultWindow > getMaxResultWindow()) {
            return new SearchHitsImpl<>(0, TotalHitsRelation.OFF, 0f, null, null, Collections.emptyList(), null, null);
        }

        var queryBuilder = new NativeQueryBuilder();
        queryBuilder.withQuery(builder -> builder.bool(boolQuery -> createSearchQuery(boolQuery, options)));

        // Sort search results according to 'sortOrder' and 'sortBy' options
        sortResults(queryBuilder, options.sortOrder, options.sortBy);

        var pages = new ArrayList<Pageable>();
        pages.add(PageRequest.of(options.requestedOffset / options.requestedSize, options.requestedSize));
        if(options.requestedOffset % options.requestedSize > 0) {
            // size is not exact multiple of offset; this means we need to get two pages
            // 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
            pages.add(pages.get(0).next());
        }

        var searchHitsList = new ArrayList<SearchHits<ExtensionSearch>>(pages.size());
        for(var page : pages) {
            queryBuilder.withPageable(page);
            try {
                rwLock.readLock().lock();
                var searchHits = searchOperations.search(queryBuilder.build(), ExtensionSearch.class, searchOperations.indexOps(ExtensionSearch.class).getIndexCoordinates());
                searchHitsList.add(searchHits);
            } finally {
                rwLock.readLock().unlock();
            }
        }

        if(searchHitsList.size() == 2) {
            var firstSearchHitsPage = searchHitsList.get(0);
            var secondSearchHitsPage = searchHitsList.get(1);

            List<SearchHit<ExtensionSearch>> searchHits = new ArrayList<>(firstSearchHitsPage.getSearchHits());
            searchHits.addAll(secondSearchHitsPage.getSearchHits());
            var endIndex = Math.min(searchHits.size(), options.requestedOffset + options.requestedSize);
            var startIndex = Math.min(endIndex, options.requestedOffset);
            searchHits = searchHits.subList(startIndex, endIndex);
            return new SearchHitsImpl<>(
                    firstSearchHitsPage.getTotalHits(),
                    firstSearchHitsPage.getTotalHitsRelation(),
                    firstSearchHitsPage.getMaxScore(),
                    null,
                    null,
                    searchHits,
                    null,
                    null
            );
        } else {
            return searchHitsList.get(0);
        }
    }
@Test
    public void testSearchResultWindowTooLarge() {
        mockIndex(true);

        var options = new ISearchService.Options("foo", "bar", "universal", 50, 10000, null, null, false);
        var searchHits = search.search(options);
        assertThat(searchHits.getSearchHits()).isEmpty();
        assertThat(searchHits.getTotalHits()).isEqualTo(0L);
    }