mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2026-09-16 18:13:17 +00:00
[ACS-12256] Backport search_after deep pagination to 25.N (#4325)
This commit is contained in:
@@ -44,7 +44,7 @@ import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
|
||||
/**
|
||||
* This class provides parameters to define a search. TODO - paging of results page number and page size - paging isolation - REPEATABLE READ, READ COMMITTED, may SEE ONCE tracking node refs in previous result sets - how long repeatable read may be held - limit by the number of permission evaluations
|
||||
* This class provides parameters to define a search.
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
@@ -198,6 +198,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
|
||||
private boolean trackScore = true;
|
||||
|
||||
private String searchAfterToken;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
@@ -250,6 +252,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
sp.timezone = this.timezone;
|
||||
sp.trackTotalHits = this.trackTotalHits;
|
||||
sp.trackScore = this.trackScore;
|
||||
sp.searchAfterToken = this.searchAfterToken;
|
||||
return sp;
|
||||
}
|
||||
|
||||
@@ -600,6 +603,16 @@ public class SearchParameters implements BasicSearchParameters
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public String getSearchAfterToken()
|
||||
{
|
||||
return searchAfterToken;
|
||||
}
|
||||
|
||||
public void setSearchAfterToken(String searchAfterToken)
|
||||
{
|
||||
this.searchAfterToken = searchAfterToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* The way in which multilingual fields are treated durig a search. By default, only the specified locale is used and it must be an exact match.
|
||||
*
|
||||
@@ -1197,6 +1210,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
result = prime * result + ((ranges == null) ? 0 : ranges.hashCode());
|
||||
result = prime * result + ((searchTerm == null) ? 0 : searchTerm.hashCode());
|
||||
result = prime * result + (spellCheck ? 1231 : 1237);
|
||||
result = prime * result + ((searchAfterToken == null) ? 0 : searchAfterToken.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1359,6 +1373,17 @@ public class SearchParameters implements BasicSearchParameters
|
||||
return false;
|
||||
if (spellCheck != other.spellCheck)
|
||||
return false;
|
||||
if (searchAfterToken == null)
|
||||
{
|
||||
if (other.searchAfterToken != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!searchAfterToken.equals(other.searchAfterToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1401,7 +1426,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
.append(", interval=").append(this.interval)
|
||||
.append(", range=").append(this.ranges)
|
||||
.append(", timezone=").append(this.timezone)
|
||||
.append(", spellCheck=").append(this.spellCheck).append("]");
|
||||
.append(", spellCheck=").append(this.spellCheck)
|
||||
.append(", searchAfterToken=").append(this.searchAfterToken).append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP
|
||||
{
|
||||
try
|
||||
{
|
||||
// Turn JSON into a Java object respresentation
|
||||
// Turn JSON into a Java object representation
|
||||
SearchQuery searchQuery = extractJsonContent(webScriptRequest, assistant.getJsonHelper(), SearchQuery.class);
|
||||
|
||||
// Parse the parameters
|
||||
|
||||
@@ -231,7 +231,11 @@ public class ResultMapper
|
||||
.map(resultSet -> toSearchContext(resultSet, searchRequestContext, searchQuery))
|
||||
.orElse(null);
|
||||
|
||||
return CollectionWithPagingInfo.asPaged(params.getPaging(), noderesults, results.hasMore(), setTotal(results), null, context);
|
||||
String nextSearchAfterToken = toSearchEngineResultSet(results)
|
||||
.map(SearchEngineResultSet::getNextSearchAfterToken)
|
||||
.orElse(null);
|
||||
|
||||
return CollectionWithPagingInfo.asPaged(params.getPaging(), noderesults, results.hasMore(), setTotal(results), null, context, nextSearchAfterToken);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -199,6 +199,10 @@ public class SearchMapper
|
||||
sp.setLimitBy(LimitBy.FINAL_SIZE);
|
||||
sp.setLimit(paging.getMaxItems());
|
||||
sp.setSkipCount(paging.getSkipCount());
|
||||
if (paging.getSearchAfterToken() != null)
|
||||
{
|
||||
sp.setSearchAfterToken(paging.getSearchAfterToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -103,6 +103,10 @@ public class SerializerOfCollectionWithPaging extends StdSerializer<Serializable
|
||||
jgen.writeNumberField(RecognizedParamsExtractor.PARAM_PAGING_SKIP, pagedCol.getPaging().getSkipCount());
|
||||
jgen.writeNumberField(RecognizedParamsExtractor.PARAM_PAGING_MAX, pagedCol.getPaging().getMaxItems());
|
||||
}
|
||||
if (pagedCol.getNextSearchAfterToken() != null)
|
||||
{
|
||||
jgen.writeStringField("nextSearchAfterToken", pagedCol.getNextSearchAfterToken());
|
||||
}
|
||||
jgen.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -70,4 +70,12 @@ public interface SerializablePagedCollection<T>
|
||||
* The search context for the collection
|
||||
*/
|
||||
SearchContext getContext();
|
||||
|
||||
/**
|
||||
* The search_after token for fetching the next page, or null if not applicable.
|
||||
*/
|
||||
default String getNextSearchAfterToken()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+40
-3
@@ -50,6 +50,7 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
private final Paging paging;
|
||||
private final Object sourceEntity;
|
||||
private final SearchContext context;
|
||||
private final String nextSearchAfterToken;
|
||||
|
||||
/**
|
||||
* Constructs a new CollectionWithPagingInfo.
|
||||
@@ -64,6 +65,11 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
* - The total number of items available.
|
||||
*/
|
||||
protected CollectionWithPagingInfo(Collection<T> collection, Paging paging, boolean hasMoreItems, Integer totalItems, Object sourceEntity, SearchContext context)
|
||||
{
|
||||
this(collection, paging, hasMoreItems, totalItems, sourceEntity, context, null);
|
||||
}
|
||||
|
||||
protected CollectionWithPagingInfo(Collection<T> collection, Paging paging, boolean hasMoreItems, Integer totalItems, Object sourceEntity, SearchContext context, String nextSearchAfterToken)
|
||||
{
|
||||
super();
|
||||
this.hasMoreItems = hasMoreItems;
|
||||
@@ -81,6 +87,7 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
}
|
||||
this.sourceEntity = sourceEntity;
|
||||
this.context = context;
|
||||
this.nextSearchAfterToken = nextSearchAfterToken;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +100,7 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
public static <T> CollectionWithPagingInfo<T> from(SerializablePagedCollection<T> pagedCollection)
|
||||
{
|
||||
return new CollectionWithPagingInfo<>(pagedCollection.getCollection(), pagedCollection.getPaging(), pagedCollection.hasMoreItems(), pagedCollection.getTotalItems(),
|
||||
pagedCollection.getSourceEntity(), pagedCollection.getContext());
|
||||
pagedCollection.getSourceEntity(), pagedCollection.getContext(), pagedCollection.getNextSearchAfterToken());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +150,7 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CollectionWithPagingInfo. Not for public use.
|
||||
* Constructs a new CollectionWithPagingInfo.
|
||||
*
|
||||
* @param paging
|
||||
* - Paging request info
|
||||
@@ -163,7 +170,7 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CollectionWithPagingInfo. Not for public use.
|
||||
* Constructs a new CollectionWithPagingInfo.
|
||||
*
|
||||
* @param paging
|
||||
* - Paging request info
|
||||
@@ -184,6 +191,30 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
return new CollectionWithPagingInfo<T>(aCollection, paging, hasMoreItems, totalItems, sourceEntity, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CollectionWithPagingInfo carrying a search_after token.
|
||||
*
|
||||
* @param paging
|
||||
* - Paging request info
|
||||
* @param aCollection
|
||||
* - the collection that needs to be paged.
|
||||
* @param hasMoreItems
|
||||
* - Are there more items after this Collection?
|
||||
* @param totalItems
|
||||
* - The total number of items available.
|
||||
* @param sourceEntity
|
||||
* - The parent/source entity responsible for the collection
|
||||
* @param context
|
||||
* - The search context
|
||||
* @param nextSearchAfterToken
|
||||
* - The search_after token for fetching the next page
|
||||
* @return CollectionWithPagingInfo
|
||||
*/
|
||||
public static <T> CollectionWithPagingInfo<T> asPaged(Paging paging, Collection<T> aCollection, boolean hasMoreItems, Integer totalItems, Object sourceEntity, SearchContext context, String nextSearchAfterToken)
|
||||
{
|
||||
return new CollectionWithPagingInfo<>(aCollection, paging, hasMoreItems, totalItems, sourceEntity, context, nextSearchAfterToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Collection object
|
||||
*
|
||||
@@ -240,4 +271,10 @@ public class CollectionWithPagingInfo<T> implements SerializablePagedCollection<
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNextSearchAfterToken()
|
||||
{
|
||||
return nextSearchAfterToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+31
-9
@@ -27,6 +27,8 @@ package org.alfresco.rest.framework.resource.parameters;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
|
||||
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
|
||||
@@ -46,8 +48,9 @@ public class Paging
|
||||
|
||||
private final int skipCount;
|
||||
private final int maxItems;
|
||||
private final String searchAfterToken;
|
||||
|
||||
private Paging(int skipCount, int maxItems)
|
||||
private Paging(int skipCount, int maxItems, String searchAfterToken)
|
||||
{
|
||||
super();
|
||||
if (skipCount < 0)
|
||||
@@ -60,6 +63,7 @@ public class Paging
|
||||
}
|
||||
this.skipCount = skipCount;
|
||||
this.maxItems = maxItems;
|
||||
this.searchAfterToken = searchAfterToken;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,21 +86,39 @@ public class Paging
|
||||
return this.maxItems;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static Paging valueOf(@JsonProperty("skipCount") int skipCount, @JsonProperty("maxItems") int maxItems)
|
||||
/**
|
||||
* The opaque search_after cursor for the next page, or null if not using cursor-based paging.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getSearchAfterToken()
|
||||
{
|
||||
return new Paging(skipCount, maxItems);
|
||||
return this.searchAfterToken;
|
||||
}
|
||||
|
||||
public static Paging valueOf(int skipCount, int maxItems)
|
||||
{
|
||||
return new Paging(skipCount, maxItems, null);
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static Paging valueOf(@JsonProperty("skipCount") int skipCount, @JsonProperty("maxItems") int maxItems,
|
||||
@JsonProperty("searchAfterToken") @JsonDeserialize(using = StringDeserializer.class) String searchAfterToken)
|
||||
{
|
||||
return new Paging(skipCount, maxItems, searchAfterToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Paging [skipCount=");
|
||||
builder.append(this.skipCount);
|
||||
builder.append(", maxItems=");
|
||||
builder.append(this.maxItems);
|
||||
builder.append("]");
|
||||
builder.append("Paging [skipCount=")
|
||||
.append(this.skipCount)
|
||||
.append(", maxItems=")
|
||||
.append(this.maxItems)
|
||||
.append(", searchAfterToken=")
|
||||
.append(this.searchAfterToken)
|
||||
.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ public class ResourceWebScriptHelper
|
||||
}
|
||||
}
|
||||
return CollectionWithPagingInfo.asPaged(collectionToWrap.getPaging(), resultCollection, collectionToWrap.hasMoreItems(),
|
||||
collectionToWrap.getTotalItems(), sourceEntity, collectionToWrap.getContext());
|
||||
collectionToWrap.getTotalItems(), sourceEntity, collectionToWrap.getContext(), collectionToWrap.getNextSearchAfterToken());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1265,7 +1265,7 @@
|
||||
<property name="resultMapper" ref="searchapiResultMapper" />
|
||||
<property name="searchMapper" ref="searchapiSearchMapper" />
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="webscript.org.alfresco.api.SearchSQLApiWebscript.post"
|
||||
class="org.alfresco.rest.api.search.SearchSQLApiWebscript" parent="webscript">
|
||||
<property name="serviceRegistry" ref="ServiceRegistry" />
|
||||
|
||||
@@ -194,6 +194,21 @@ public class SearchMapperTests
|
||||
assertEquals(searchParameters.getSkipCount(), paging.getSkipCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromSearchAfter() throws Exception
|
||||
{
|
||||
SearchParameters searchParameters = new SearchParameters();
|
||||
searchMapper.fromPaging(searchParameters, Paging.valueOf(0, 100));
|
||||
assertNull(searchParameters.getSearchAfterToken());
|
||||
|
||||
searchMapper.fromPaging(searchParameters, Paging.valueOf(0, 100, "SEARCH_AFTER_TOKEN"));
|
||||
assertEquals("SEARCH_AFTER_TOKEN", searchParameters.getSearchAfterToken());
|
||||
|
||||
// An explicit empty searchAfter starts a new cursor-paging session (first page).
|
||||
searchMapper.fromPaging(searchParameters, Paging.valueOf(0, 100, ""));
|
||||
assertEquals("", searchParameters.getSearchAfterToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromSort() throws Exception
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Data model classes
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -52,4 +52,9 @@ public interface SearchEngineResultSet extends ResultSet, SearchEngineResultMeta
|
||||
long getLastIndexedTxId();
|
||||
|
||||
boolean getProcessedDenies();
|
||||
|
||||
default String getNextSearchAfterToken()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user