/* * Copyright (c) 2009-2010 Lockheed Martin Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eurekastreams.server.search.stream; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.search.Sort; import org.eurekastreams.commons.search.ProjectionSearchRequestBuilder; import org.hibernate.search.jpa.FullTextQuery; /** * PageFetcher to return a list of Activity IDs for a Lucene search query. */ public class ActivityIdSearchPageFetcher implements PageFetcher<Long> { /** * The logger. */ private Log log = LogFactory.getLog(ActivityIdSearchPageFetcher.class); /** * A multiplier of page size to use when the user is fetching a subsequent page of data - ask for more results than * we need so we minimize calls to search. */ private static Long pageSizeMultiplierForSubsequentPages; /** * The Lucene search query. */ private String searchQuery; /** * The search request builder. */ private ProjectionSearchRequestBuilder searchRequestBuilder; /** * The last Activity ID that the user saw - fetch results after that point. */ private Long lastSeenActivityId; /** * Constructor - taking the Lucene search query and the ProjectionSearchRequestBuilder. * * @param inSearchQuery * the native Lucene search query * @param inSearchRequestBuilder * the ProjectionSearchRequestBuilder to use to execute the query * @param inLastSeenActivityId * the last activity id that the user saw - results should have ids less than this * @param inPageSizeMultiplierForSubsequentPages * the multiplier to use against the requested page size when the user is looking for search results past * page 1. */ public ActivityIdSearchPageFetcher(final String inSearchQuery, final ProjectionSearchRequestBuilder inSearchRequestBuilder, final Long inLastSeenActivityId, final Long inPageSizeMultiplierForSubsequentPages) { searchQuery = inSearchQuery; searchRequestBuilder = inSearchRequestBuilder; lastSeenActivityId = inLastSeenActivityId; pageSizeMultiplierForSubsequentPages = inPageSizeMultiplierForSubsequentPages; if (lastSeenActivityId == null || lastSeenActivityId <= 0) { lastSeenActivityId = Long.MAX_VALUE; } } /** * Fetch a page of activity IDs. * * @param inStartIndex * the starting index of the page to fetch - not used * @param inPageSize * the page size * @return a list of Activity IDs */ @SuppressWarnings("unchecked") @Override public List<Long> fetchPage(final int inStartIndex, final int inPageSize) { int pageSize = inPageSize; if (lastSeenActivityId != Long.MAX_VALUE) { // If no new activities were added since the user saw the previous // page, we'd want to fetch [startIndex, startIndex+inPageSize). // We'll fetch a little bit more in case there were extra activities // thrown on top. pageSize *= pageSizeMultiplierForSubsequentPages; } FullTextQuery query = searchRequestBuilder.buildQueryFromNativeSearchString(searchQuery); query.setSort(new Sort("id", true)); List<Long> results = new ArrayList<Long>(); int totalResults = 0; int batchSize = 0; do { // get the page size results - don't trust the previous offset in // case new activities were added on top if (log.isTraceEnabled()) { log.trace("Preparing a query - search: '" + searchQuery + "'; paging:[0," + (pageSize - 1) + "]"); } searchRequestBuilder.setPaging(query, 0, pageSize - 1); List<Long> queryResults = query.getResultList(); if (totalResults == 0) { // only need to fetch this once - it describes the total results // regardless of paging totalResults = query.getResultSize(); } batchSize = queryResults.size(); if (log.isTraceEnabled()) { log.trace("Found: " + batchSize + " of " + totalResults + " results."); } for (Long activityId : queryResults) { if (results.size() >= inPageSize) { // all done break; } if (activityId < lastSeenActivityId) { if (log.isTraceEnabled()) { log.trace("Found activity id: " + activityId + ", which is lower than " + lastSeenActivityId + ", adding to the result list"); } // remember this id in case we have to get a second page of // results, to avoid adding it twice lastSeenActivityId = activityId; results.add(activityId); } } // get another page next round pageSize += pageSize; } while ( // we haven't filled the page yet results.size() < inPageSize // we got back less results than we asked for - must not be any // more && batchSize < totalResults); // the search request builder should be configured to only fetch ids, so // we'll have to populate the rest from // the database return results; } }