mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2026-09-16 18:13:17 +00:00
ACS-11286 Backport Optimizing DisposableLifecycle FTS query along with introducing CMIS query mode (#4099)
Co-authored-by: Sayan Bhattacharya <sayan.bhattacharya@hyland.com>
This commit is contained in:
co-authored by
Sayan Bhattacharya
parent
250dd2006f
commit
cf8859174d
+8
@@ -74,6 +74,14 @@ rm.dispositionlifecycletrigger.cronexpression=0 0/5 * * * ?
|
||||
#
|
||||
rm.dispositionlifecycletrigger.batchsize=500
|
||||
|
||||
# Global RM retention lifecycle CMIS query upper bound per job run.
|
||||
# Default is 0.1M records (100000).
|
||||
rm.dispositionlifecycletrigger.cmisquerylimit=100000
|
||||
|
||||
# Query mode for RM disposition lifecycle trigger search execution.
|
||||
# Available values: fts (default), cmis.
|
||||
rm.dispositionlifecycletrigger.queryMode=fts
|
||||
|
||||
#
|
||||
# Global RM notify of records due for review cron job expression
|
||||
#
|
||||
|
||||
+2
@@ -82,6 +82,8 @@
|
||||
<property name="recordsManagementActionService" ref="recordsManagementActionService" />
|
||||
<property name="freezeService" ref="freezeService"/>
|
||||
<property name="batchSize" value="${rm.dispositionlifecycletrigger.batchsize}"/>
|
||||
<property name="cmisQueryLimit" value="${rm.dispositionlifecycletrigger.cmisquerylimit}"/>
|
||||
<property name="queryMode" value="${rm.dispositionlifecycletrigger.queryMode}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="scheduledDispositionLifecyceleSchedulerAccessor" class="org.alfresco.schedule.AlfrescoSchedulerAccessorBean">
|
||||
|
||||
+379
-105
@@ -30,30 +30,31 @@ package org.alfresco.module.org_alfresco_module_rm.job;
|
||||
import static org.alfresco.module.org_alfresco_module_rm.action.RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.QueryConsistency;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
|
||||
|
||||
/**
|
||||
* The Disposition Lifecycle Job Finds all disposition action nodes which are for disposition actions specified Where
|
||||
* asOf > now OR dispositionEventsEligible = true; Runs the cut off or retain action for eligible records.
|
||||
* The Disposition Lifecycle Job Finds all disposition action nodes which are for disposition actions specified Where asOf > now OR dispositionEventsEligible = true; Runs the cut off or retain action for eligible records.
|
||||
*
|
||||
* @author mrogers
|
||||
* @author Roy Wetherall
|
||||
@@ -62,9 +63,30 @@ import org.alfresco.service.cmr.security.PersonService;
|
||||
public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecuter
|
||||
{
|
||||
|
||||
public enum QueryMode
|
||||
{
|
||||
FTS, CMIS;
|
||||
|
||||
public static QueryMode getQueryMode(String queryMode)
|
||||
{
|
||||
if (StringUtils.isBlank(queryMode))
|
||||
{
|
||||
return FTS;
|
||||
}
|
||||
|
||||
String normalizedQueryMode = queryMode.trim();
|
||||
return EnumSet.allOf(QueryMode.class).stream()
|
||||
.filter(mode -> mode.name().equalsIgnoreCase(normalizedQueryMode))
|
||||
.findFirst()
|
||||
.orElse(FTS);
|
||||
}
|
||||
}
|
||||
|
||||
/** batching properties */
|
||||
private int batchSize;
|
||||
public static final int DEFAULT_BATCH_SIZE = 500;
|
||||
public static final int DEFAULT_CMIS_QUERY_LIMIT = 100000;
|
||||
private int cmisQueryLimit = DEFAULT_CMIS_QUERY_LIMIT;
|
||||
|
||||
/** list of disposition actions to automatically execute */
|
||||
private List<String> dispositionActions;
|
||||
@@ -87,8 +109,20 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
|
||||
/** freeze service */
|
||||
private FreezeService freezeService;
|
||||
|
||||
private QueryMode queryMode = QueryMode.FTS;
|
||||
|
||||
/**
|
||||
* @param freezeService freeze service
|
||||
*
|
||||
* @param queryMode
|
||||
*/
|
||||
public void setQueryMode(String queryMode)
|
||||
{
|
||||
this.queryMode = QueryMode.getQueryMode(queryMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param freezeService
|
||||
* freeze service
|
||||
*/
|
||||
public void setFreezeService(FreezeService freezeService)
|
||||
{
|
||||
@@ -98,7 +132,8 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
|
||||
/**
|
||||
* List of disposition actions to automatically execute when eligible.
|
||||
*
|
||||
* @param dispositionActions disposition actions
|
||||
* @param dispositionActions
|
||||
* disposition actions
|
||||
*/
|
||||
public void setDispositionActions(List<String> dispositionActions)
|
||||
{
|
||||
@@ -110,8 +145,14 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public void setCmisQueryLimit(int cmisQueryLimit)
|
||||
{
|
||||
this.cmisQueryLimit = cmisQueryLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recordsManagementActionService records management action service
|
||||
* @param recordsManagementActionService
|
||||
* records management action service
|
||||
*/
|
||||
public void setRecordsManagementActionService(RecordsManagementActionService recordsManagementActionService)
|
||||
{
|
||||
@@ -119,7 +160,8 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeService node service
|
||||
* @param nodeService
|
||||
* node service
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
@@ -127,53 +169,66 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchService search service
|
||||
* @param searchService
|
||||
* search service
|
||||
*/
|
||||
public void setSearchService(SearchService searchService)
|
||||
{
|
||||
this.searchService = searchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the search query string.
|
||||
*
|
||||
* @return job query string
|
||||
*/
|
||||
protected String getQuery()
|
||||
private String getActionFilterQuery()
|
||||
{
|
||||
if (query == null)
|
||||
String actionFilterQuery = null;
|
||||
StringBuilder sb = new StringBuilder("@rma\\:dispositionAction:(");
|
||||
boolean first = true;
|
||||
for (String dispositionAction : dispositionActions)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("TYPE:\"rma:dispositionAction\" AND ");
|
||||
sb.append("(@rma\\:dispositionAction:(");
|
||||
|
||||
boolean bFirst = true;
|
||||
for (String dispositionAction : dispositionActions)
|
||||
if (!first)
|
||||
{
|
||||
if (bFirst)
|
||||
{
|
||||
bFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" OR ");
|
||||
}
|
||||
|
||||
sb.append("\"").append(dispositionAction).append("\"");
|
||||
sb.append(" OR ");
|
||||
}
|
||||
|
||||
sb.append("))");
|
||||
sb.append(" AND ISUNSET:\"rma:dispositionActionCompletedAt\" ");
|
||||
sb.append(" AND ( ");
|
||||
sb.append("@rma\\:dispositionEventsEligible:true ");
|
||||
sb.append("OR @rma\\:dispositionAsOf:[MIN TO NOW] ");
|
||||
sb.append(") ");
|
||||
|
||||
query = sb.toString();
|
||||
sb.append("\"").append(dispositionAction).append("\"");
|
||||
first = false;
|
||||
}
|
||||
if (dispositionActions.size() > 1)
|
||||
{
|
||||
actionFilterQuery = sb.append(")").toString();
|
||||
}
|
||||
|
||||
return query;
|
||||
return actionFilterQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a transactional CMIS query for eligible disposition action nodes. The date cutoff is evaluated at call time so each job run uses the current date.
|
||||
*
|
||||
* @return CMIS SQL string
|
||||
*/
|
||||
public String getCmisQuery()
|
||||
{
|
||||
// Use end-of-current-day in UTC so that records due today are always included
|
||||
// regardless of what time within the day the job fires.
|
||||
String cutoff = LocalDate.now(ZoneOffset.UTC) + "T23:59:59.999Z";
|
||||
|
||||
StringBuilder sb = new StringBuilder("SELECT * FROM rma:dispositionAction WHERE ");
|
||||
sb.append("rma:dispositionAction IN (");
|
||||
boolean first = true;
|
||||
for (String action : dispositionActions)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append("'").append(action).append("'");
|
||||
first = false;
|
||||
}
|
||||
sb.append(") ");
|
||||
sb.append("AND rma:dispositionActionCompletedAt IS NULL ");
|
||||
sb.append("AND (rma:dispositionEventsEligible = true ");
|
||||
sb.append("OR rma:dispositionAsOf <= TIMESTAMP '").append(cutoff).append("')");
|
||||
|
||||
log.debug("Constructed CMIS query: {}", sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,10 +236,25 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
|
||||
*/
|
||||
@Override
|
||||
public void executeImpl()
|
||||
{
|
||||
if (queryMode == QueryMode.CMIS)
|
||||
{
|
||||
executeImplCmis();
|
||||
}
|
||||
else
|
||||
{
|
||||
executeImplFts();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FTS/index-based execution path (default). Paginates through search results using skipCount, relying on the search index (Elasticsearch or Solr) to resolve eligible nodes.
|
||||
*/
|
||||
private void executeImplFts()
|
||||
{
|
||||
try
|
||||
{
|
||||
log.debug("Job Starting");
|
||||
log.debug("Job Starting (FTS mode)");
|
||||
|
||||
if (dispositionActions == null || dispositionActions.isEmpty())
|
||||
{
|
||||
@@ -194,103 +264,307 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute
|
||||
|
||||
boolean hasMore = true;
|
||||
int skipCount = 0;
|
||||
List<NodeRef> resultNodes = new ArrayList<>();
|
||||
|
||||
if (batchSize < 1)
|
||||
{
|
||||
log.debug("Invalid value for batch size: " + batchSize + " default value used instead.");
|
||||
log.debug("Invalid value for batch size: {} default value used instead.", batchSize);
|
||||
batchSize = DEFAULT_BATCH_SIZE;
|
||||
}
|
||||
|
||||
log.trace("Using batch size of " + batchSize);
|
||||
log.trace("Using batch size of {}", batchSize);
|
||||
|
||||
int batchNumber = 0;
|
||||
int totalReturned = 0;
|
||||
int totalEligible = 0;
|
||||
int totalProcessed = 0;
|
||||
|
||||
while (hasMore)
|
||||
{
|
||||
batchNumber++;
|
||||
|
||||
SearchParameters params = new SearchParameters();
|
||||
params.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
|
||||
params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
|
||||
params.setQuery(getQuery());
|
||||
params.setSkipCount(skipCount);
|
||||
params.setMaxItems(batchSize);
|
||||
|
||||
// set query
|
||||
params.setQuery("TYPE:\"rma:dispositionAction\"");
|
||||
params.addFilterQuery(getActionFilterQuery());
|
||||
params.addFilterQuery("ISUNSET:\"rma:dispositionActionCompletedAt\"");
|
||||
params.addFilterQuery("(@rma\\:dispositionEventsEligible:true OR -@rma\\:dispositionAsOf:["
|
||||
+ LocalDate.now().plusDays(1) + " TO MAX])");
|
||||
params.setTrackScore(false);
|
||||
|
||||
// execute search
|
||||
ResultSet results = searchService.query(params);
|
||||
if(results != null)
|
||||
ResultSet results = executeSearch(params, batchNumber);
|
||||
if (results == null)
|
||||
{
|
||||
// filtering out the hold/freezed cases from the result set
|
||||
resultNodes =
|
||||
results.getNodeRefs().stream().filter(node -> nodeService.getPrimaryParent(node) == null ?
|
||||
!freezeService.isFrozenOrHasFrozenChildren(node) :
|
||||
!freezeService.isFrozenOrHasFrozenChildren(nodeService.getPrimaryParent(node).getParentRef())).collect(Collectors.toList());
|
||||
log.warn("Disposition lifecycle search returned null; stopping pagination.");
|
||||
break;
|
||||
}
|
||||
hasMore = results.hasMore();
|
||||
skipCount += resultNodes.size(); // increase by page size
|
||||
results.close();
|
||||
|
||||
log.debug("Processing " + resultNodes.size() + " nodes");
|
||||
|
||||
// process search results
|
||||
if (!resultNodes.isEmpty())
|
||||
// Declared outside try so it remains in scope after results.close().
|
||||
Map<NodeRef, ChildAssociationRef> eligibleNodes;
|
||||
int rawPageLength;
|
||||
try
|
||||
{
|
||||
executeAction(resultNodes);
|
||||
List<NodeRef> rawPage = results.getNodeRefs();
|
||||
// Advance skip by raw hit count so paging stays aligned with the index; post-search
|
||||
// freeze filtering must not shrink the skip step (would duplicate or skip hits).
|
||||
rawPageLength = rawPage.size();
|
||||
hasMore = results.hasMore();
|
||||
skipCount += rawPageLength;
|
||||
totalReturned += rawPageLength;
|
||||
|
||||
// Single getPrimaryParent call per node: result is carried forward so
|
||||
// executeAction can reuse it without a second DB round-trip.
|
||||
eligibleNodes = new LinkedHashMap<>(rawPageLength);
|
||||
for (NodeRef node : rawPage)
|
||||
{
|
||||
ChildAssociationRef parent = nodeService.getPrimaryParent(node);
|
||||
NodeRef freezeTarget = parent != null ? parent.getParentRef() : node;
|
||||
if (!freezeService.isFrozenOrHasFrozenChildren(freezeTarget))
|
||||
{
|
||||
eligibleNodes.put(node, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
results.close();
|
||||
}
|
||||
|
||||
int batchEligible = eligibleNodes.size();
|
||||
totalEligible += batchEligible;
|
||||
|
||||
log.debug("Batch {} — returned: {}, eligible (not frozen): {}, hasMore: {}",
|
||||
batchNumber, rawPageLength, batchEligible, hasMore);
|
||||
|
||||
int batchProcessed = 0;
|
||||
if (!eligibleNodes.isEmpty())
|
||||
{
|
||||
batchProcessed = retryingTransactionHelper.doInTransaction(() -> executeAction(eligibleNodes), false, true);
|
||||
}
|
||||
totalProcessed += batchProcessed;
|
||||
|
||||
log.debug("Batch {} — actioned: {}", batchNumber, batchProcessed);
|
||||
}
|
||||
log.debug("Job Finished");
|
||||
|
||||
log.debug("Job Finished (FTS mode) — batches: {}, total returned by search: {}, total eligible: {}, total actioned: {}",
|
||||
batchNumber, totalReturned, totalEligible, totalProcessed);
|
||||
}
|
||||
catch (AlfrescoRuntimeException exception)
|
||||
{
|
||||
log.debug(exception.getMessage());
|
||||
log.error("Disposition lifecycle job failed in FTS mode.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that executes a disposition action
|
||||
*
|
||||
* @param actionNodes - the disposition actions to execute
|
||||
* CMIS/DB-based execution path (enabled by {@code queryMode=CMIS}).
|
||||
*/
|
||||
private void executeAction(final List<NodeRef> actionNodes)
|
||||
private void executeImplCmis()
|
||||
{
|
||||
RetryingTransactionCallback<Boolean> processTranCB = () -> {
|
||||
for (NodeRef actionNode : actionNodes)
|
||||
try
|
||||
{
|
||||
log.debug("Job Starting (CMIS/DB mode)");
|
||||
|
||||
if (dispositionActions == null || dispositionActions.isEmpty())
|
||||
{
|
||||
if (!nodeService.exists(actionNode))
|
||||
log.debug("Job Finished as disposition action is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (batchSize < 1)
|
||||
{
|
||||
log.debug("Invalid value for batch size: {} default value used instead.", batchSize);
|
||||
batchSize = DEFAULT_BATCH_SIZE;
|
||||
}
|
||||
|
||||
if (cmisQueryLimit < 1)
|
||||
{
|
||||
log.debug("Invalid value for CMIS query limit: {} default value used instead.", cmisQueryLimit);
|
||||
cmisQueryLimit = DEFAULT_CMIS_QUERY_LIMIT;
|
||||
}
|
||||
|
||||
log.trace("Using batch size of {}", batchSize);
|
||||
log.trace("Using CMIS query limit of {}", cmisQueryLimit);
|
||||
|
||||
int skipCount = 0;
|
||||
int batchNumber = 0;
|
||||
int totalReturned = 0;
|
||||
int totalEligible = 0;
|
||||
int totalProcessed = 0;
|
||||
|
||||
while (totalReturned < cmisQueryLimit)
|
||||
{
|
||||
batchNumber++;
|
||||
|
||||
int[] batch = runCmisBatch(skipCount, batchNumber);
|
||||
int rawPageSize = batch[0];
|
||||
int eligible = batch[1];
|
||||
int processed = batch[2];
|
||||
|
||||
if (rawPageSize == 0)
|
||||
{
|
||||
continue;
|
||||
log.debug("No more eligible nodes found; job complete.");
|
||||
break;
|
||||
}
|
||||
totalReturned += rawPageSize;
|
||||
totalEligible += eligible;
|
||||
totalProcessed += processed;
|
||||
|
||||
final String dispAction = (String) nodeService.getProperty(actionNode, PROP_DISPOSITION_ACTION);
|
||||
|
||||
// Run disposition action
|
||||
if (dispAction == null || !dispositionActions.contains(dispAction))
|
||||
if (processed > 0)
|
||||
{
|
||||
continue;
|
||||
// Actioned nodes have dispositionActionCompletedAt set and will not reappear.
|
||||
// Reset skip so the next query starts from position 0 and picks up any nodes
|
||||
// that shifted into earlier positions as processed ones were removed.
|
||||
skipCount = 0;
|
||||
}
|
||||
|
||||
ChildAssociationRef parent = nodeService.getPrimaryParent(actionNode);
|
||||
if (!parent.getTypeQName().equals(ASSOC_NEXT_DISPOSITION_ACTION))
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Map<String, Serializable> props = Map.of(PARAM_NO_ERROR_CHECK, false);
|
||||
|
||||
try
|
||||
{
|
||||
// execute disposition action
|
||||
recordsManagementActionService
|
||||
.executeRecordsManagementAction(parent.getParentRef(), dispAction, props);
|
||||
|
||||
log.debug("Processed action: " + dispAction + "on" + parent);
|
||||
|
||||
}
|
||||
catch (AlfrescoRuntimeException exception)
|
||||
{
|
||||
log.debug(exception.getMessage());
|
||||
|
||||
// The entire batch was unprocessable (frozen, non-existent, or malformed data).
|
||||
// Advance skip past these nodes to avoid fetching the same stuck batch forever.
|
||||
log.warn("No nodes processed from a batch of {}; advancing skip by {} to bypass unprocessable records.", rawPageSize, rawPageSize);
|
||||
skipCount += rawPageSize;
|
||||
}
|
||||
}
|
||||
return Boolean.TRUE;
|
||||
};
|
||||
retryingTransactionHelper.doInTransaction(processTranCB, false, true);
|
||||
log.debug("Job Finished (CMIS/DB mode) — batches: {}, total returned by query: {}, total eligible: {}, total actioned: {}",
|
||||
batchNumber, totalReturned, totalEligible, totalProcessed);
|
||||
}
|
||||
catch (AlfrescoRuntimeException exception)
|
||||
{
|
||||
log.error("Disposition lifecycle job failed in CMIS mode.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one CMIS batch — search, freeze filter, and action execution — inside a single transaction.
|
||||
*
|
||||
* @param skipCount
|
||||
* current pagination offset
|
||||
* @param batchNumber
|
||||
* 1-based batch index used in log messages
|
||||
* @return int[3] — {rawPageSize, eligible, processed}; all zeros when no results found
|
||||
*/
|
||||
private int[] runCmisBatch(int skipCount, int batchNumber)
|
||||
{
|
||||
return retryingTransactionHelper.doInTransaction(() -> {
|
||||
SearchParameters params = new SearchParameters();
|
||||
params.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
|
||||
params.setLanguage(SearchService.LANGUAGE_CMIS_ALFRESCO);
|
||||
params.setQuery(getCmisQuery());
|
||||
params.setMaxItems(batchSize);
|
||||
params.setSkipCount(skipCount);
|
||||
// Force DB routing. CMIS goes to the DB by default but being explicit here
|
||||
// guards against any future routing change that could silently reintroduce
|
||||
// the async-indexing lag and the need for skip arithmetic.
|
||||
params.setQueryConsistency(QueryConsistency.TRANSACTIONAL);
|
||||
|
||||
ResultSet results = executeSearch(params, batchNumber);
|
||||
if (results == null)
|
||||
{
|
||||
log.warn("Disposition lifecycle CMIS query returned null; stopping.");
|
||||
return new int[]{0, 0, 0};
|
||||
}
|
||||
try
|
||||
{
|
||||
List<NodeRef> rawPage = results.getNodeRefs();
|
||||
int rawPageSize = rawPage.size();
|
||||
if (rawPageSize == 0)
|
||||
{
|
||||
return new int[]{0, 0, 0};
|
||||
}
|
||||
|
||||
Map<NodeRef, ChildAssociationRef> eligibleNodes = new LinkedHashMap<>(rawPageSize);
|
||||
for (NodeRef node : rawPage)
|
||||
{
|
||||
ChildAssociationRef parent = nodeService.getPrimaryParent(node);
|
||||
NodeRef freezeTarget = parent != null ? parent.getParentRef() : node;
|
||||
if (!freezeService.isFrozenOrHasFrozenChildren(freezeTarget))
|
||||
{
|
||||
eligibleNodes.put(node, parent);
|
||||
}
|
||||
}
|
||||
int eligible = eligibleNodes.size();
|
||||
log.debug("Batch {} — returned: {}, eligible (not frozen): {}, skip: {}",
|
||||
batchNumber, rawPageSize, eligible, skipCount);
|
||||
|
||||
int processed = eligibleNodes.isEmpty() ? 0 : executeAction(eligibleNodes);
|
||||
log.debug("Batch {} — actioned: {}", batchNumber, processed);
|
||||
|
||||
return new int[]{rawPageSize, eligible, processed};
|
||||
}
|
||||
finally
|
||||
{
|
||||
results.close();
|
||||
}
|
||||
}, false, true); // readOnly=false, requiresNew=true
|
||||
}
|
||||
|
||||
private ResultSet executeSearch(SearchParameters params, int batchNumber)
|
||||
{
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
ResultSet results = searchService.query(params);
|
||||
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
log.debug("Executed batch-{} search results in {} ms.", batchNumber, elapsed);
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the disposition action for each eligible node.
|
||||
*
|
||||
* <p>
|
||||
* Returns the number of nodes for which the action was actually executed. Nodes discarded by the three internal guards (does not exist, wrong action, wrong parent association) are not counted. The caller uses this count to detect a stuck batch — a non-empty batch that yields zero processed nodes — and advance the skip offset accordingly.
|
||||
* </p>
|
||||
*
|
||||
* @param eligibleNodes
|
||||
* map of disposition action node to its pre-computed primary parent
|
||||
* @return number of nodes for which the disposition action was successfully invoked
|
||||
*/
|
||||
private int executeAction(final Map<NodeRef, ChildAssociationRef> eligibleNodes)
|
||||
{
|
||||
int processedCount = 0;
|
||||
|
||||
for (Map.Entry<NodeRef, ChildAssociationRef> entry : eligibleNodes.entrySet())
|
||||
{
|
||||
NodeRef actionNode = entry.getKey();
|
||||
// Reuse the parent computed during the freeze-filter pass — no extra DB call.
|
||||
ChildAssociationRef parent = entry.getValue();
|
||||
|
||||
if (!nodeService.exists(actionNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
final String dispAction = (String) nodeService.getProperty(actionNode, PROP_DISPOSITION_ACTION);
|
||||
|
||||
// Run disposition action
|
||||
if (dispAction == null || !dispositionActions.contains(dispAction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parent == null || !parent.getTypeQName().equals(ASSOC_NEXT_DISPOSITION_ACTION))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Map<String, Serializable> props = Map.of(PARAM_NO_ERROR_CHECK, false);
|
||||
|
||||
try
|
||||
{
|
||||
recordsManagementActionService
|
||||
.executeRecordsManagementAction(parent.getParentRef(), dispAction, props);
|
||||
processedCount++;
|
||||
log.trace("Processed action: {} on {}", dispAction, parent);
|
||||
}
|
||||
catch (AlfrescoRuntimeException exception)
|
||||
{
|
||||
log.error("Failed to process disposition action '{}' for node {}.", dispAction, actionNode, exception);
|
||||
}
|
||||
}
|
||||
return processedCount;
|
||||
}
|
||||
|
||||
public PersonService getPersonService()
|
||||
|
||||
+189
-52
@@ -27,7 +27,6 @@
|
||||
|
||||
package org.alfresco.module.org_alfresco_module_rm.job;
|
||||
|
||||
import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateQName;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -42,10 +41,19 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateQName;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
|
||||
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
@@ -53,12 +61,6 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
/**
|
||||
* Disposition lifecycle job execution unit test.
|
||||
@@ -75,13 +77,15 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
private static final int BATCH_SIZE = 1;
|
||||
|
||||
/** test query snippet */
|
||||
private static final String QUERY = "\"" + CUTOFF + "\" OR \"" + RETAIN + "\"";
|
||||
|
||||
private static final String QUERY = "TYPE:\"rma:dispositionAction\"";
|
||||
private static final String FILTER_QUERY = "@rma\\:dispositionAction:(\"cutoff\" OR \"retain\")";
|
||||
/** mocked result set */
|
||||
@Mock ResultSet mockedResultSet;
|
||||
@Mock
|
||||
ResultSet mockedResultSet;
|
||||
|
||||
/** disposition lifecycle job executer */
|
||||
@InjectMocks DispositionLifecycleJobExecuter executer;
|
||||
@InjectMocks
|
||||
DispositionLifecycleJobExecuter executer;
|
||||
|
||||
/**
|
||||
* @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest#before()
|
||||
@@ -93,11 +97,11 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
super.before();
|
||||
|
||||
Answer<Object> doInTransactionAnswer = invocation -> {
|
||||
RetryingTransactionCallback callback = (RetryingTransactionCallback)invocation.getArguments()[0];
|
||||
RetryingTransactionCallback callback = (RetryingTransactionCallback) invocation.getArguments()[0];
|
||||
return callback.execute();
|
||||
};
|
||||
doAnswer(doInTransactionAnswer).when(mockedRetryingTransactionHelper).doInTransaction(any(RetryingTransactionCallback.class),
|
||||
anyBoolean(), anyBoolean());
|
||||
anyBoolean(), anyBoolean());
|
||||
|
||||
// setup data
|
||||
List<String> dispositionActions = buildList(CUTOFF, RETAIN);
|
||||
@@ -111,13 +115,16 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
|
||||
/**
|
||||
* Helper method to verify that the query has been executed and closed
|
||||
* @param numberOfInvocation number of times the query has been executed and closed
|
||||
*
|
||||
* @param numberOfInvocation
|
||||
* number of times the query has been executed and closed
|
||||
*/
|
||||
private void verifyQueryTimes(int numberOfInvocation)
|
||||
{
|
||||
ArgumentCaptor<SearchParameters> paramsCaptor = ArgumentCaptor.forClass(SearchParameters.class);
|
||||
verify(mockedSearchService, times(numberOfInvocation)).query(paramsCaptor.capture());
|
||||
assertTrue(paramsCaptor.getValue().getQuery().contains(QUERY));
|
||||
assertTrue(paramsCaptor.getValue().getFilterQueries().toString().contains(FILTER_QUERY));
|
||||
verify(mockedResultSet, times(numberOfInvocation)).getNodeRefs();
|
||||
verify(mockedResultSet, times(numberOfInvocation)).close();
|
||||
}
|
||||
@@ -146,7 +153,6 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
/**
|
||||
* When the disposition actions do not match those that can be processed automatically.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void dispositionActionDoesNotMatch()
|
||||
{
|
||||
@@ -159,12 +165,12 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
doReturn(DESTROY).when(mockedNodeService).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
|
||||
when(mockedResultSet.getNodeRefs())
|
||||
.thenReturn(buildList(node1))
|
||||
.thenReturn(buildList(node2));
|
||||
.thenReturn(buildList(node1))
|
||||
.thenReturn(buildList(node2));
|
||||
|
||||
when(mockedResultSet.hasMore())
|
||||
.thenReturn(true)
|
||||
.thenReturn(false);
|
||||
.thenReturn(true)
|
||||
.thenReturn(false);
|
||||
|
||||
// when
|
||||
executer.executeImpl();
|
||||
@@ -174,12 +180,10 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
// ensure the query is executed and closed
|
||||
verifyQueryTimes(2);
|
||||
|
||||
// ensure work is executed in transaction for each node processed
|
||||
// ensure node existence is checked for each result
|
||||
verify(mockedNodeService, times(2)).exists(any(NodeRef.class));
|
||||
verify(mockedRetryingTransactionHelper, times(2)).doInTransaction(any(RetryingTransactionCallback.class),
|
||||
anyBoolean(), anyBoolean());
|
||||
|
||||
// ensure each node is process correctly
|
||||
// ensure each node is processed correctly
|
||||
verify(mockedNodeService, times(1)).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
verify(mockedNodeService, times(1)).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
|
||||
@@ -221,7 +225,6 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
/**
|
||||
* When there are disposition actions eligible for processing
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void dispositionActionProcessed()
|
||||
{
|
||||
@@ -239,12 +242,12 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
doReturn(false).when(mockedFreezeService).isFrozen(parentAssoc.getParentRef());
|
||||
|
||||
when(mockedResultSet.getNodeRefs())
|
||||
.thenReturn(buildList(node1))
|
||||
.thenReturn(buildList(node2));
|
||||
.thenReturn(buildList(node1))
|
||||
.thenReturn(buildList(node2));
|
||||
|
||||
when(mockedResultSet.hasMore())
|
||||
.thenReturn(true)
|
||||
.thenReturn(false);
|
||||
.thenReturn(true)
|
||||
.thenReturn(false);
|
||||
|
||||
// when
|
||||
executer.executeImpl();
|
||||
@@ -254,19 +257,17 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
// ensure the query is executed and closed
|
||||
verifyQueryTimes(2);
|
||||
|
||||
// ensure work is executed in transaction for each node processed
|
||||
// ensure work is executed for each node
|
||||
verify(mockedNodeService, times(2)).exists(any(NodeRef.class));
|
||||
verify(mockedRetryingTransactionHelper, times(2)).doInTransaction(any(RetryingTransactionCallback.class),
|
||||
anyBoolean(), anyBoolean());
|
||||
|
||||
// ensure each node is process correctly
|
||||
// ensure each node is processed correctly
|
||||
// node1
|
||||
verify(mockedNodeService, times(1)).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
verify(mockedNodeService, times(3)).getPrimaryParent(node1);
|
||||
verify(mockedNodeService, times(1)).getPrimaryParent(node1);
|
||||
verify(mockedRecordsManagementActionService, times(1)).executeRecordsManagementAction(eq(parent), eq(CUTOFF), anyMap());
|
||||
// node2
|
||||
verify(mockedNodeService, times(1)).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
verify(mockedNodeService, times(3)).getPrimaryParent(node2);
|
||||
verify(mockedNodeService, times(1)).getPrimaryParent(node2);
|
||||
verify(mockedRecordsManagementActionService, times(1)).executeRecordsManagementAction(eq(parent), eq(RETAIN), anyMap());
|
||||
|
||||
// ensure no more interactions
|
||||
@@ -274,27 +275,164 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Brittle unit test that simply checks the generated query is an exact string when the supplied disposition actions
|
||||
* are "CUTOFF" and "RETAIN" (see {@link #before}).
|
||||
* Verify that getCmisQuery() generates a valid CMIS query containing the required action filters and conditions. The query includes a dynamic UTC timestamp cutoff, so we verify key components rather than exact string match.
|
||||
*/
|
||||
@Test
|
||||
public void testGetQuery()
|
||||
public void testGetCmisQuery()
|
||||
{
|
||||
String actual = executer.getQuery();
|
||||
String actual = executer.getCmisQuery();
|
||||
|
||||
String expected = "TYPE:\"rma:dispositionAction\" AND " +
|
||||
"(@rma\\:dispositionAction:(\"cutoff\" OR \"retain\")) " +
|
||||
"AND ISUNSET:\"rma:dispositionActionCompletedAt\" " +
|
||||
"AND ( @rma\\:dispositionEventsEligible:true OR @rma\\:dispositionAsOf:[MIN TO NOW] ) ";
|
||||
|
||||
assertEquals(expected, actual);
|
||||
// Verify the CMIS query contains all required components
|
||||
assertTrue("CMIS query should start with SELECT statement", actual.contains("SELECT * FROM rma:dispositionAction"));
|
||||
assertTrue("CMIS query should filter by disposition actions", actual.contains("rma:dispositionAction IN ('cutoff','retain')"));
|
||||
assertTrue("CMIS query should exclude completed actions", actual.contains("rma:dispositionActionCompletedAt IS NULL"));
|
||||
assertTrue("CMIS query should check for eligible events", actual.contains("rma:dispositionEventsEligible = true"));
|
||||
assertTrue("CMIS query should check asOf date with UTC timestamp", actual.contains("rma:dispositionAsOf <= TIMESTAMP"));
|
||||
assertTrue("CMIS query should use UTC timezone (Z suffix)", actual.contains("T23:59:59.999Z"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the maximum page of elements for search service is 2
|
||||
* and search service finds more than one page of elements
|
||||
* When the job executer runs
|
||||
* Then the executer retrieves both pages and iterates all elements
|
||||
* CMIS mode: when there are no results the job finishes after a single query.
|
||||
*/
|
||||
@Test
|
||||
public void cmisNoResultsInQuery()
|
||||
{
|
||||
executer.setQueryMode("CMIS");
|
||||
doReturn(Collections.EMPTY_LIST).when(mockedResultSet).getNodeRefs();
|
||||
|
||||
executer.executeImpl();
|
||||
|
||||
verify(mockedSearchService, times(1)).query(any(SearchParameters.class));
|
||||
verify(mockedResultSet, times(1)).getNodeRefs();
|
||||
verify(mockedResultSet, times(1)).close();
|
||||
verifyNoMoreInteractions(mockedNodeService, mockedRecordsManagementActionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* CMIS mode: when a node no longer exists it is skipped, no action is executed, and the skip offset advances so the job does not loop on the same batch forever.
|
||||
*/
|
||||
@Test
|
||||
public void cmisNodeDoesNotExist()
|
||||
{
|
||||
executer.setQueryMode("CMIS");
|
||||
NodeRef node1 = generateNodeRef(null, false); // exists = false
|
||||
ChildAssociationRef parentAssoc = new ChildAssociationRef(
|
||||
ASSOC_NEXT_DISPOSITION_ACTION, generateNodeRef(), generateQName(), generateNodeRef());
|
||||
doReturn(parentAssoc).when(mockedNodeService).getPrimaryParent(node1);
|
||||
doReturn(false).when(mockedFreezeService).isFrozenOrHasFrozenChildren(any(NodeRef.class));
|
||||
|
||||
// first batch returns the non-existent node; second batch (after skip advance) is empty
|
||||
when(mockedResultSet.getNodeRefs())
|
||||
.thenReturn(Collections.singletonList(node1))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
executer.executeImpl();
|
||||
|
||||
// two queries: one for the real batch, one after skip advance that finds nothing
|
||||
verify(mockedSearchService, times(2)).query(any(SearchParameters.class));
|
||||
verify(mockedNodeService, times(1)).getPrimaryParent(node1);
|
||||
verify(mockedNodeService, times(1)).exists(node1);
|
||||
verifyNoMoreInteractions(mockedRecordsManagementActionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* CMIS mode: when the disposition action on the node does not match any configured action the node is skipped and the skip offset advances.
|
||||
*/
|
||||
@Test
|
||||
public void cmisDispositionActionDoesNotMatch()
|
||||
{
|
||||
executer.setQueryMode("CMIS");
|
||||
NodeRef node1 = generateNodeRef();
|
||||
ChildAssociationRef parentAssoc = new ChildAssociationRef(
|
||||
ASSOC_NEXT_DISPOSITION_ACTION, generateNodeRef(), generateQName(), generateNodeRef());
|
||||
doReturn(DESTROY).when(mockedNodeService).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
doReturn(parentAssoc).when(mockedNodeService).getPrimaryParent(node1);
|
||||
doReturn(false).when(mockedFreezeService).isFrozenOrHasFrozenChildren(any(NodeRef.class));
|
||||
|
||||
when(mockedResultSet.getNodeRefs())
|
||||
.thenReturn(Collections.singletonList(node1))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
executer.executeImpl();
|
||||
|
||||
verify(mockedSearchService, times(2)).query(any(SearchParameters.class));
|
||||
verify(mockedNodeService, times(1)).getPrimaryParent(node1);
|
||||
verify(mockedNodeService, times(1)).exists(node1);
|
||||
verify(mockedNodeService, times(1)).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
verifyNoMoreInteractions(mockedRecordsManagementActionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* CMIS mode: when disposition actions are eligible they are processed, the skip offset is reset to zero after a successful batch, and the job continues until no more results.
|
||||
*/
|
||||
@Test
|
||||
public void cmisDispositionActionProcessed()
|
||||
{
|
||||
executer.setQueryMode("CMIS");
|
||||
NodeRef node1 = generateNodeRef();
|
||||
NodeRef node2 = generateNodeRef();
|
||||
NodeRef parent = generateNodeRef();
|
||||
ChildAssociationRef parentAssoc = new ChildAssociationRef(
|
||||
ASSOC_NEXT_DISPOSITION_ACTION, parent, generateQName(), generateNodeRef());
|
||||
|
||||
doReturn(CUTOFF).when(mockedNodeService).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
doReturn(RETAIN).when(mockedNodeService).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
doReturn(parentAssoc).when(mockedNodeService).getPrimaryParent(any(NodeRef.class));
|
||||
doReturn(false).when(mockedFreezeService).isFrozenOrHasFrozenChildren(any(NodeRef.class));
|
||||
|
||||
// batch 1 → node1 processed → skip resets to 0
|
||||
// batch 2 → node2 processed → skip resets to 0
|
||||
// batch 3 → empty → loop stops
|
||||
when(mockedResultSet.getNodeRefs())
|
||||
.thenReturn(Collections.singletonList(node1))
|
||||
.thenReturn(Collections.singletonList(node2))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
executer.executeImpl();
|
||||
|
||||
verify(mockedSearchService, times(3)).query(any(SearchParameters.class));
|
||||
|
||||
verify(mockedNodeService, times(1)).exists(node1);
|
||||
verify(mockedNodeService, times(1)).getPrimaryParent(node1);
|
||||
verify(mockedNodeService, times(1)).getProperty(node1, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
verify(mockedRecordsManagementActionService, times(1))
|
||||
.executeRecordsManagementAction(eq(parent), eq(CUTOFF), anyMap());
|
||||
|
||||
verify(mockedNodeService, times(1)).exists(node2);
|
||||
verify(mockedNodeService, times(1)).getPrimaryParent(node2);
|
||||
verify(mockedNodeService, times(1)).getProperty(node2, RecordsManagementModel.PROP_DISPOSITION_ACTION);
|
||||
verify(mockedRecordsManagementActionService, times(1))
|
||||
.executeRecordsManagementAction(eq(parent), eq(RETAIN), anyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* CMIS mode: the configured query limit caps how many rows are read in one run.
|
||||
*/
|
||||
@Test
|
||||
public void cmisQueryLimitStopsLoop()
|
||||
{
|
||||
executer.setQueryMode("CMIS");
|
||||
executer.setCmisQueryLimit(2);
|
||||
|
||||
NodeRef node1 = generateNodeRef();
|
||||
ChildAssociationRef parentAssoc = new ChildAssociationRef(
|
||||
ASSOC_NEXT_DISPOSITION_ACTION, generateNodeRef(), generateQName(), generateNodeRef());
|
||||
doReturn(parentAssoc).when(mockedNodeService).getPrimaryParent(node1);
|
||||
doReturn(false).when(mockedFreezeService).isFrozenOrHasFrozenChildren(any(NodeRef.class));
|
||||
doReturn(false).when(mockedNodeService).exists(node1);
|
||||
|
||||
when(mockedResultSet.getNodeRefs())
|
||||
.thenReturn(Collections.singletonList(node1));
|
||||
|
||||
executer.executeImpl();
|
||||
|
||||
verify(mockedSearchService, times(2)).query(any(SearchParameters.class));
|
||||
verify(mockedResultSet, times(2)).getNodeRefs();
|
||||
verify(mockedResultSet, times(2)).close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the maximum page of elements for search service is 2 and search service finds more than one page of elements When the job executer runs Then the executer retrieves both pages and iterates all elements
|
||||
*/
|
||||
@Test
|
||||
public void testPagination()
|
||||
@@ -338,8 +476,7 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a batch size < 1
|
||||
* Then the executer use default value instead
|
||||
* Given a batch size < 1 Then the executer use default value instead
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidBatchSize()
|
||||
@@ -348,8 +485,8 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest
|
||||
executer.executeImpl();
|
||||
|
||||
ArgumentCaptor<SearchParameters> paramsCaptor = ArgumentCaptor.forClass(SearchParameters.class);
|
||||
verify(mockedSearchService, times(1)).query(paramsCaptor.capture());;
|
||||
assertEquals(executer.DEFAULT_BATCH_SIZE, paramsCaptor.getValue().getMaxItems());
|
||||
verify(mockedSearchService, times(1)).query(paramsCaptor.capture());
|
||||
assertEquals(DispositionLifecycleJobExecuter.DEFAULT_BATCH_SIZE, paramsCaptor.getValue().getMaxItems());
|
||||
verify(mockedResultSet, times(1)).close();
|
||||
|
||||
executer.setBatchSize(BATCH_SIZE);
|
||||
|
||||
@@ -34,32 +34,27 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
import org.alfresco.api.AlfrescoPublicApi;
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.search.MLAnalysisMode;
|
||||
import org.alfresco.repo.search.impl.querymodel.QueryOptions;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
/**
|
||||
* 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. 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
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
@AlfrescoPublicApi
|
||||
public class SearchParameters implements BasicSearchParameters
|
||||
{
|
||||
/*
|
||||
* The default limit if someone asks for a limited result set but does not say how to limit....
|
||||
*/
|
||||
/* The default limit if someone asks for a limited result set but does not say how to limit.... */
|
||||
private static int DEFAULT_LIMIT = 500;
|
||||
|
||||
/*
|
||||
* Standard sort definitions for sorting in document and score order.
|
||||
*/
|
||||
/* Standard sort definitions for sorting in document and score order. */
|
||||
/**
|
||||
* Sort in the order docs were added to the index - oldest docs first
|
||||
*/
|
||||
@@ -81,8 +76,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
public static final SortDefinition SORT_IN_SCORE_ORDER_DESCENDING = new SortDefinition(SortDefinition.SortType.SCORE, null, false);
|
||||
|
||||
/**
|
||||
* An emum defining if the default action is to "and" or "or" unspecified components in the query register. Not all
|
||||
* search implementations will support this.
|
||||
* An emum defining if the default action is to "and" or "or" unspecified components in the query register. Not all search implementations will support this.
|
||||
*/
|
||||
public enum Operator
|
||||
{
|
||||
@@ -96,9 +90,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
AND
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose as constants
|
||||
*/
|
||||
/* Expose as constants */
|
||||
/**
|
||||
* OR
|
||||
*/
|
||||
@@ -113,10 +105,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
* A parameter that can be passed to Solr to indicate an alternative dictionary should be used.
|
||||
*/
|
||||
public static final String ALTERNATIVE_DICTIONARY = "alternativeDic";
|
||||
|
||||
/*
|
||||
* The parameters that can be set
|
||||
*/
|
||||
|
||||
/* The parameters that can be set */
|
||||
private String language;
|
||||
|
||||
private String query;
|
||||
@@ -162,31 +152,31 @@ public class SearchParameters implements BasicSearchParameters
|
||||
private long maxPermissionCheckTimeMillis = -1;
|
||||
|
||||
private String defaultFieldName = "TEXT";
|
||||
|
||||
|
||||
private ArrayList<FieldFacet> fieldFacets = new ArrayList<FieldFacet>();
|
||||
|
||||
|
||||
private List<String> facetQueries = new ArrayList<String>();
|
||||
|
||||
|
||||
private List<String> filterQueries = new ArrayList<String>();
|
||||
|
||||
private List<List<String>> pivots = new ArrayList<>();
|
||||
|
||||
private Boolean useInMemorySort;
|
||||
|
||||
|
||||
private Integer maxRawResultSetSizeForInMemorySort;
|
||||
|
||||
|
||||
private Map<String, String> extraParameters = new HashMap<String, String>();
|
||||
|
||||
|
||||
private boolean excludeTenantFilter = false;
|
||||
|
||||
private boolean isBulkFetchEnabled = true;
|
||||
|
||||
private QueryConsistency queryConsistency = QueryConsistency.DEFAULT;
|
||||
|
||||
|
||||
private Long sinceTxId;
|
||||
|
||||
|
||||
private String searchTerm;
|
||||
|
||||
|
||||
private boolean spellCheck;
|
||||
|
||||
private GeneralHighlightParameters highlight;
|
||||
@@ -194,18 +184,20 @@ public class SearchParameters implements BasicSearchParameters
|
||||
private IntervalParameters interval;
|
||||
|
||||
private List<StatsRequestParameters> stats;
|
||||
|
||||
|
||||
private List<RangeParameters> ranges;
|
||||
|
||||
|
||||
private boolean includeMetadata;
|
||||
|
||||
private String timezone;
|
||||
|
||||
|
||||
/**
|
||||
* Configure the limit to track the total hits on search results
|
||||
*/
|
||||
private int trackTotalHits;
|
||||
|
||||
private boolean trackScore = true;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
@@ -257,13 +249,15 @@ public class SearchParameters implements BasicSearchParameters
|
||||
sp.ranges = this.ranges;
|
||||
sp.timezone = this.timezone;
|
||||
sp.trackTotalHits = this.trackTotalHits;
|
||||
sp.trackScore = this.trackScore;
|
||||
return sp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct from Query Options
|
||||
*
|
||||
* @param options QueryOptions
|
||||
* @param options
|
||||
* QueryOptions
|
||||
*/
|
||||
public SearchParameters(QueryOptions options)
|
||||
{
|
||||
@@ -282,7 +276,23 @@ public class SearchParameters implements BasicSearchParameters
|
||||
setLimitBy(LimitBy.UNLIMITED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isTrackScore()
|
||||
{
|
||||
return trackScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls whether relevance scores are computed for search hits. This hint is only acted upon by the Elasticsearch search subsystem; Solr and DB query paths ignore it.
|
||||
*
|
||||
* @param trackScore
|
||||
* true to compute scores (default), false to skip scoring for better performance
|
||||
*/
|
||||
public void setTrackScore(boolean trackScore)
|
||||
{
|
||||
this.trackScore = trackScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the search language
|
||||
*
|
||||
@@ -304,8 +314,10 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
Sets parameters used for search highlighing
|
||||
* @param highlight GeneralHighlightParameters
|
||||
* Sets parameters used for search highlighing
|
||||
*
|
||||
* @param highlight
|
||||
* GeneralHighlightParameters
|
||||
*/
|
||||
public void setHighlight(GeneralHighlightParameters highlight)
|
||||
{
|
||||
@@ -328,8 +340,10 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
Sets parameters used for Intervals
|
||||
* @param interval IntervalParameters
|
||||
* Sets parameters used for Intervals
|
||||
*
|
||||
* @param interval
|
||||
* IntervalParameters
|
||||
*/
|
||||
public void setInterval(IntervalParameters interval)
|
||||
{
|
||||
@@ -349,8 +363,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Set the query language.
|
||||
*
|
||||
* @param language -
|
||||
* the query language.
|
||||
* @param language
|
||||
* - the query language.
|
||||
*/
|
||||
public void setLanguage(String language)
|
||||
{
|
||||
@@ -364,7 +378,9 @@ public class SearchParameters implements BasicSearchParameters
|
||||
|
||||
/**
|
||||
* Override the default TimeZone (UTC)
|
||||
* @param timezone any zone ID supported by @see java.time.ZoneId
|
||||
*
|
||||
* @param timezone
|
||||
* any zone ID supported by @see java.time.ZoneId
|
||||
*/
|
||||
public void setTimezone(String timezone)
|
||||
{
|
||||
@@ -373,19 +389,19 @@ public class SearchParameters implements BasicSearchParameters
|
||||
|
||||
public void addExtraParameter(String name, String value)
|
||||
{
|
||||
extraParameters.put(name, value);
|
||||
extraParameters.put(name, value);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, String> getExtraParameters()
|
||||
{
|
||||
return extraParameters;
|
||||
}
|
||||
return extraParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Set the query string.
|
||||
*
|
||||
* @param query -
|
||||
* the query string.
|
||||
* @param query
|
||||
* - the query string.
|
||||
*/
|
||||
public void setQuery(String query)
|
||||
{
|
||||
@@ -393,10 +409,10 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stores to be supported - currently there can be only one. Searching across multiple stores is on the todo
|
||||
* list.
|
||||
* Set the stores to be supported - currently there can be only one. Searching across multiple stores is on the todo list.
|
||||
*
|
||||
* @param store StoreRef
|
||||
* @param store
|
||||
* StoreRef
|
||||
*/
|
||||
public void addStore(StoreRef store)
|
||||
{
|
||||
@@ -406,7 +422,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Add parameter definitions for the query - used to parameterise the query string
|
||||
*
|
||||
* @param queryParameterDefinition QueryParameterDefinition
|
||||
* @param queryParameterDefinition
|
||||
* QueryParameterDefinition
|
||||
*/
|
||||
public void addQueryParameterDefinition(QueryParameterDefinition queryParameterDefinition)
|
||||
{
|
||||
@@ -414,13 +431,10 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, any data in the current transaction will be ignored in the search. You will not see anything you have
|
||||
* added in the current transaction. By default you will see data in the current transaction. This effectively gives
|
||||
* read committed isolation. There is a performance overhead for this, at least when using lucene. This flag may be
|
||||
* set to avoid that performance hit if you know you do not want to find results that are yet to be committed (this
|
||||
* includes creations, deletions and updates)
|
||||
* If true, any data in the current transaction will be ignored in the search. You will not see anything you have added in the current transaction. By default you will see data in the current transaction. This effectively gives read committed isolation. There is a performance overhead for this, at least when using lucene. This flag may be set to avoid that performance hit if you know you do not want to find results that are yet to be committed (this includes creations, deletions and updates)
|
||||
*
|
||||
* @param excludeDataInTheCurrentTransaction boolean
|
||||
* @param excludeDataInTheCurrentTransaction
|
||||
* boolean
|
||||
*/
|
||||
public void excludeDataInTheCurrentTransaction(boolean excludeDataInTheCurrentTransaction)
|
||||
{
|
||||
@@ -428,14 +442,12 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sort to the query (for those query languages that do not support it directly) The first sort added is
|
||||
* treated as primary, the second as secondary etc. A helper method to create SortDefinitions.
|
||||
* Add a sort to the query (for those query languages that do not support it directly) The first sort added is treated as primary, the second as secondary etc. A helper method to create SortDefinitions.
|
||||
*
|
||||
* @param field -
|
||||
* this is initially a direct attribute on a node not an attribute on the parent etc TODO: It could be a
|
||||
* relative path at some time.
|
||||
* @param ascending -
|
||||
* true to sort ascending, false for descending.
|
||||
* @param field
|
||||
* - this is initially a direct attribute on a node not an attribute on the parent etc TODO: It could be a relative path at some time.
|
||||
* @param ascending
|
||||
* - true to sort ascending, false for descending.
|
||||
*/
|
||||
public void addSort(String field, boolean ascending)
|
||||
{
|
||||
@@ -445,8 +457,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Add a sort definition.
|
||||
*
|
||||
* @param sortDefinition -
|
||||
* the sort definition to add. Use the static member variables for sorting in score and index order.
|
||||
* @param sortDefinition
|
||||
* - the sort definition to add. Use the static member variables for sorting in score and index order.
|
||||
*/
|
||||
public void addSort(SortDefinition sortDefinition)
|
||||
{
|
||||
@@ -455,6 +467,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
|
||||
/**
|
||||
* Gets the parameters used for search highlighing
|
||||
*
|
||||
* @return GeneralHighlightParameters - the highlighting parameters
|
||||
*/
|
||||
public GeneralHighlightParameters getHighlight()
|
||||
@@ -505,7 +518,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Set the default operator for query elements when they are not explicit in the query.
|
||||
*
|
||||
* @param defaultOperator Operator
|
||||
* @param defaultOperator
|
||||
* Operator
|
||||
*/
|
||||
public void setDefaultOperator(Operator defaultOperator)
|
||||
{
|
||||
@@ -536,7 +550,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Set how the result set should be limited.
|
||||
*
|
||||
* @param limitBy LimitBy
|
||||
* @param limitBy
|
||||
* LimitBy
|
||||
*/
|
||||
public void setLimitBy(LimitBy limitBy)
|
||||
{
|
||||
@@ -556,7 +571,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Set when permissions are evaluated.
|
||||
*
|
||||
* @param permissionEvaluation PermissionEvaluationMode
|
||||
* @param permissionEvaluation
|
||||
* PermissionEvaluationMode
|
||||
*/
|
||||
public void setPermissionEvaluation(PermissionEvaluationMode permissionEvaluation)
|
||||
{
|
||||
@@ -576,7 +592,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* If limiting the result set in some way, set the limiting value used.
|
||||
*
|
||||
* @param limit int
|
||||
* @param limit
|
||||
* int
|
||||
*/
|
||||
public void setLimit(int limit)
|
||||
{
|
||||
@@ -584,8 +601,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* @return - how locale related text is tokenised
|
||||
*/
|
||||
@@ -595,10 +611,10 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the way in which multilingual fields are treated durig a search. This controls in which locales an
|
||||
* multilingual fields will match.
|
||||
* Set the way in which multilingual fields are treated durig a search. This controls in which locales an multilingual fields will match.
|
||||
*
|
||||
* @param mlAnalaysisMode MLAnalysisMode
|
||||
* @param mlAnalaysisMode
|
||||
* MLAnalysisMode
|
||||
*/
|
||||
public void setMlAnalaysisMode(MLAnalysisMode mlAnalaysisMode)
|
||||
{
|
||||
@@ -608,7 +624,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Add a locale to include for multi-lingual text searches. If non are set, the default is to use the user's locale.
|
||||
*
|
||||
* @param locale Locale
|
||||
* @param locale
|
||||
* Locale
|
||||
*/
|
||||
public void addLocale(Locale locale)
|
||||
{
|
||||
@@ -628,8 +645,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Add a field for TEXT expansion
|
||||
*
|
||||
* @param attribute -
|
||||
* field/attribute in the index
|
||||
* @param attribute
|
||||
* - field/attribute in the index
|
||||
*/
|
||||
public void addTextAttribute(String attribute)
|
||||
{
|
||||
@@ -649,8 +666,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Add a field for ALL expansion
|
||||
*
|
||||
* @param attribute -
|
||||
* field/attribute in the index
|
||||
* @param attribute
|
||||
* - field/attribute in the index
|
||||
*/
|
||||
public void addAllAttribute(String attribute)
|
||||
{
|
||||
@@ -678,9 +695,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max number of rows for the result set.
|
||||
* A negative value implies unlimited
|
||||
* 0 will return no results.
|
||||
* Set the max number of rows for the result set. A negative value implies unlimited 0 will return no results.
|
||||
*
|
||||
* @param maxItems
|
||||
* the maxItems to set
|
||||
@@ -766,8 +781,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Set the default namespace
|
||||
*
|
||||
* @param namespace -
|
||||
* the uri or prefix for the default namespace.
|
||||
* @param namespace
|
||||
* - the uri or prefix for the default namespace.
|
||||
*/
|
||||
public void setNamespace(String namespace)
|
||||
{
|
||||
@@ -787,8 +802,10 @@ public class SearchParameters implements BasicSearchParameters
|
||||
/**
|
||||
* Add/replace a query template Not all languages support query templates
|
||||
*
|
||||
* @param name String
|
||||
* @param template String
|
||||
* @param name
|
||||
* String
|
||||
* @param template
|
||||
* String
|
||||
* @return any removed template or null
|
||||
*/
|
||||
public String addQueryTemplate(String name, String template)
|
||||
@@ -815,7 +832,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
{
|
||||
this.maxPermissionChecks = maxPermissionChecks;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the useInMemorySort
|
||||
*/
|
||||
@@ -825,7 +842,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* @param useInMemorySort the useInMemorySort to set
|
||||
* @param useInMemorySort
|
||||
* the useInMemorySort to set
|
||||
*/
|
||||
public void setUseInMemorySort(Boolean useInMemorySort)
|
||||
{
|
||||
@@ -841,7 +859,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxRawResultSetSizeForInMemorySort the maxRawResultSetSizeForInMemorySort to set
|
||||
* @param maxRawResultSetSizeForInMemorySort
|
||||
* the maxRawResultSetSizeForInMemorySort to set
|
||||
*/
|
||||
public void setMaxRawResultSetSizeForInMemorySort(Integer maxRawResultSetSizeForInMemorySort)
|
||||
{
|
||||
@@ -857,17 +876,16 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isBulkFetchEnabled boolean
|
||||
* @param isBulkFetchEnabled
|
||||
* boolean
|
||||
*/
|
||||
public void setBulkFetchEnabled(boolean isBulkFetchEnabled)
|
||||
{
|
||||
this.isBulkFetchEnabled = isBulkFetchEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A helper class for sort definition. Encapsulated using the lucene sortType, field name and a flag for
|
||||
* ascending/descending.
|
||||
* A helper class for sort definition. Encapsulated using the lucene sortType, field name and a flag for ascending/descending.
|
||||
*
|
||||
* @author Andy Hind
|
||||
*/
|
||||
@@ -947,40 +965,41 @@ public class SearchParameters implements BasicSearchParameters
|
||||
{
|
||||
return defaultFieldName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param defaultFieldName - the default field name to use
|
||||
* @param defaultFieldName
|
||||
* - the default field name to use
|
||||
*/
|
||||
public void setDefaultFieldName(String defaultFieldName)
|
||||
{
|
||||
this.defaultFieldName = defaultFieldName;
|
||||
this.defaultFieldName = defaultFieldName;
|
||||
}
|
||||
|
||||
public List<FieldFacet> getFieldFacets()
|
||||
{
|
||||
return fieldFacets;
|
||||
}
|
||||
|
||||
|
||||
public void addFieldFacet(FieldFacet fieldFacet)
|
||||
{
|
||||
fieldFacets.add(fieldFacet);
|
||||
}
|
||||
|
||||
|
||||
public List<String> getFacetQueries()
|
||||
{
|
||||
return facetQueries;
|
||||
}
|
||||
|
||||
|
||||
public void addFacetQuery(String facetQuery)
|
||||
{
|
||||
facetQueries.add(facetQuery);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<String> getFilterQueries()
|
||||
{
|
||||
return filterQueries;
|
||||
}
|
||||
|
||||
|
||||
public void addFilterQuery(String filterQuery)
|
||||
{
|
||||
filterQueries.add(filterQuery);
|
||||
@@ -1017,7 +1036,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
{
|
||||
this.excludeTenantFilter = excludeTenantFilter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -1025,7 +1044,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
{
|
||||
return excludeTenantFilter;
|
||||
}
|
||||
|
||||
|
||||
public void setQueryConsistency(QueryConsistency queryConsistency)
|
||||
{
|
||||
this.queryConsistency = queryConsistency;
|
||||
@@ -1039,9 +1058,9 @@ public class SearchParameters implements BasicSearchParameters
|
||||
return queryConsistency;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If not null, then the search should only include results from transactions after {@code sinceTxId}.
|
||||
*
|
||||
* @return sinceTxId
|
||||
*/
|
||||
public Long getSinceTxId()
|
||||
@@ -1051,7 +1070,9 @@ public class SearchParameters implements BasicSearchParameters
|
||||
|
||||
/**
|
||||
* If not null, then the search should only include results from transactions after {@code sinceTxId}.
|
||||
* @param sinceTxId Long
|
||||
*
|
||||
* @param sinceTxId
|
||||
* Long
|
||||
*/
|
||||
public void setSinceTxId(Long sinceTxId)
|
||||
{
|
||||
@@ -1063,7 +1084,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
*/
|
||||
public String getSearchTerm()
|
||||
{
|
||||
if((searchTerm == null) || (searchTerm.length() == 0))
|
||||
if ((searchTerm == null) || (searchTerm.length() == 0))
|
||||
{
|
||||
return getQuery();
|
||||
}
|
||||
@@ -1074,7 +1095,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchTerm the searchTerm to set
|
||||
* @param searchTerm
|
||||
* the searchTerm to set
|
||||
*/
|
||||
public void setSearchTerm(String searchTerm)
|
||||
{
|
||||
@@ -1088,35 +1110,36 @@ public class SearchParameters implements BasicSearchParameters
|
||||
{
|
||||
return this.spellCheck;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if faceting is used as part of the query, Search-347.
|
||||
*
|
||||
* @param searchParameters
|
||||
* @return true if exists
|
||||
*/
|
||||
public boolean hasFaceting()
|
||||
{
|
||||
if(facetQueries != null && !facetQueries.isEmpty())
|
||||
if (facetQueries != null && !facetQueries.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(fieldFacets != null && !fieldFacets.isEmpty())
|
||||
if (fieldFacets != null && !fieldFacets.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(interval != null)
|
||||
if (interval != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(pivots != null && !pivots.isEmpty())
|
||||
if (pivots != null && !pivots.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(ranges != null)
|
||||
if (ranges != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(stats != null && !stats.isEmpty())
|
||||
if (stats != null && !stats.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1124,7 +1147,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* @param spellCheck the spellCheck to set
|
||||
* @param spellCheck
|
||||
* the spellCheck to set
|
||||
*/
|
||||
public void setSpellCheck(boolean spellCheck)
|
||||
{
|
||||
@@ -1132,8 +1156,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
*
|
||||
* @see java.lang.Object#hashCode() */
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
@@ -1177,8 +1201,8 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object) */
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
@@ -1338,48 +1362,46 @@ public class SearchParameters implements BasicSearchParameters
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
*
|
||||
* @see java.lang.Object#toString() */
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
// used for debug logging
|
||||
StringBuilder builder = new StringBuilder(1000);
|
||||
builder.append("SearchParameters [language=").append(this.language).append(", query=").append(this.query)
|
||||
.append(", stores=").append(this.stores).append(", queryParameterDefinitions=")
|
||||
.append(this.queryParameterDefinitions).append(", excludeDataInTheCurrentTransaction=")
|
||||
.append(this.excludeDataInTheCurrentTransaction).append(", sortDefinitions=")
|
||||
.append(this.sortDefinitions).append(", locales=").append(this.locales)
|
||||
.append(", mlAnalaysisMode=").append(this.mlAnalaysisMode).append(", limitBy=")
|
||||
.append(this.limitBy).append(", permissionEvaluation=").append(this.permissionEvaluation)
|
||||
.append(", limit=").append(this.limit).append(", allAttributes=").append(this.allAttributes)
|
||||
.append(", textAttributes=").append(this.textAttributes).append(", maxItems=")
|
||||
.append(this.maxItems).append(", skipCount=").append(this.skipCount)
|
||||
.append(", defaultFTSOperator=").append(this.defaultFTSOperator)
|
||||
.append(", defaultFTSFieldOperator=").append(this.defaultFTSFieldOperator)
|
||||
.append(", queryTemplates=").append(this.queryTemplates).append(", namespace=")
|
||||
.append(this.namespace).append(", maxPermissionChecks=").append(this.maxPermissionChecks)
|
||||
.append(", maxPermissionCheckTimeMillis=").append(this.maxPermissionCheckTimeMillis)
|
||||
.append(", defaultFieldName=").append(this.defaultFieldName)
|
||||
.append(", fieldFacets=").append(this.fieldFacets)
|
||||
.append(", facetQueries=").append(this.facetQueries)
|
||||
.append(", filterQueries=").append(this.filterQueries)
|
||||
.append(", pivots=").append(this.pivots)
|
||||
.append(", stats=").append(this.stats)
|
||||
.append(", useInMemorySort=").append(this.useInMemorySort)
|
||||
.append(", maxRawResultSetSizeForInMemorySort=").append(this.maxRawResultSetSizeForInMemorySort)
|
||||
.append(", extraParameters=").append(this.extraParameters).append(", excludeTenantFilter=")
|
||||
.append(this.excludeTenantFilter).append(", isBulkFetchEnabled=").append(this.isBulkFetchEnabled)
|
||||
.append(", queryConsistency=").append(this.queryConsistency).append(", sinceTxId=")
|
||||
.append(this.sinceTxId).append(", searchTerm=").append(this.searchTerm)
|
||||
.append(", highlight=").append(this.highlight)
|
||||
.append(", interval=").append(this.interval)
|
||||
.append(", range=").append(this.ranges)
|
||||
.append(", timezone=").append(this.timezone)
|
||||
.append(", spellCheck=").append(this.spellCheck).append("]");
|
||||
.append(", stores=").append(this.stores).append(", queryParameterDefinitions=")
|
||||
.append(this.queryParameterDefinitions).append(", excludeDataInTheCurrentTransaction=")
|
||||
.append(this.excludeDataInTheCurrentTransaction).append(", sortDefinitions=")
|
||||
.append(this.sortDefinitions).append(", locales=").append(this.locales)
|
||||
.append(", mlAnalaysisMode=").append(this.mlAnalaysisMode).append(", limitBy=")
|
||||
.append(this.limitBy).append(", permissionEvaluation=").append(this.permissionEvaluation)
|
||||
.append(", limit=").append(this.limit).append(", allAttributes=").append(this.allAttributes)
|
||||
.append(", textAttributes=").append(this.textAttributes).append(", maxItems=")
|
||||
.append(this.maxItems).append(", skipCount=").append(this.skipCount)
|
||||
.append(", defaultFTSOperator=").append(this.defaultFTSOperator)
|
||||
.append(", defaultFTSFieldOperator=").append(this.defaultFTSFieldOperator)
|
||||
.append(", queryTemplates=").append(this.queryTemplates).append(", namespace=")
|
||||
.append(this.namespace).append(", maxPermissionChecks=").append(this.maxPermissionChecks)
|
||||
.append(", maxPermissionCheckTimeMillis=").append(this.maxPermissionCheckTimeMillis)
|
||||
.append(", defaultFieldName=").append(this.defaultFieldName)
|
||||
.append(", fieldFacets=").append(this.fieldFacets)
|
||||
.append(", facetQueries=").append(this.facetQueries)
|
||||
.append(", filterQueries=").append(this.filterQueries)
|
||||
.append(", pivots=").append(this.pivots)
|
||||
.append(", stats=").append(this.stats)
|
||||
.append(", useInMemorySort=").append(this.useInMemorySort)
|
||||
.append(", maxRawResultSetSizeForInMemorySort=").append(this.maxRawResultSetSizeForInMemorySort)
|
||||
.append(", extraParameters=").append(this.extraParameters).append(", excludeTenantFilter=")
|
||||
.append(this.excludeTenantFilter).append(", isBulkFetchEnabled=").append(this.isBulkFetchEnabled)
|
||||
.append(", queryConsistency=").append(this.queryConsistency).append(", sinceTxId=")
|
||||
.append(this.sinceTxId).append(", searchTerm=").append(this.searchTerm)
|
||||
.append(", highlight=").append(this.highlight)
|
||||
.append(", interval=").append(this.interval)
|
||||
.append(", range=").append(this.ranges)
|
||||
.append(", timezone=").append(this.timezone)
|
||||
.append(", spellCheck=").append(this.spellCheck).append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@@ -1393,17 +1415,17 @@ public class SearchParameters implements BasicSearchParameters
|
||||
.append(", defaultFTSOp=").append(this.defaultFTSOperator)
|
||||
.append(", defaultFTSFieldOp=").append(this.defaultFTSFieldOperator);
|
||||
|
||||
if ((queryTemplates != null) && (! queryTemplates.isEmpty()))
|
||||
if ((queryTemplates != null) && (!queryTemplates.isEmpty()))
|
||||
{
|
||||
builder.append(", queryTemplates=").append(this.queryTemplates);
|
||||
}
|
||||
|
||||
if ((filterQueries != null) && (! filterQueries.isEmpty()))
|
||||
if ((filterQueries != null) && (!filterQueries.isEmpty()))
|
||||
{
|
||||
builder.append(", filterQueries=").append(this.filterQueries);
|
||||
}
|
||||
|
||||
if ((searchTerm != null) && (! searchTerm.isEmpty()))
|
||||
if ((searchTerm != null) && (!searchTerm.isEmpty()))
|
||||
{
|
||||
builder.append(", searchTerm=").append(this.searchTerm);
|
||||
}
|
||||
@@ -1413,17 +1435,16 @@ public class SearchParameters implements BasicSearchParameters
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
public enum FieldFacetSort
|
||||
{
|
||||
COUNT, INDEX;
|
||||
}
|
||||
|
||||
|
||||
public enum FieldFacetMethod
|
||||
{
|
||||
ENUM, FC;
|
||||
}
|
||||
|
||||
|
||||
public static class FieldFacet
|
||||
{
|
||||
String field;
|
||||
@@ -1437,7 +1458,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
boolean countDocsMissingFacetField = false;
|
||||
FieldFacetMethod method = null;
|
||||
int enumMethodCacheMinDF = 0;
|
||||
|
||||
|
||||
public FieldFacet(String field)
|
||||
{
|
||||
this.field = field;
|
||||
@@ -1473,7 +1494,7 @@ public class SearchParameters implements BasicSearchParameters
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Deprecated
|
||||
/**
|
||||
* Will return 100 as the old default but this is now defined in configuration and will be wrong if no explicitly set
|
||||
*
|
||||
@@ -1489,13 +1510,12 @@ public class SearchParameters implements BasicSearchParameters
|
||||
{
|
||||
this.limitOrNull = limit;
|
||||
}
|
||||
|
||||
|
||||
public void setLimitOrNull(Integer limitOrNull)
|
||||
{
|
||||
this.limitOrNull = limitOrNull;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Integer getLimitOrNull()
|
||||
{
|
||||
return limitOrNull;
|
||||
@@ -1624,17 +1644,20 @@ public class SearchParameters implements BasicSearchParameters
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param length int
|
||||
* @param useInMemorySortDefault boolean
|
||||
* @param maxRawResultSetSizeForInMemorySortDefault int
|
||||
* @param length
|
||||
* int
|
||||
* @param useInMemorySortDefault
|
||||
* boolean
|
||||
* @param maxRawResultSetSizeForInMemorySortDefault
|
||||
* int
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean usePostSort(int length, boolean useInMemorySortDefault, int maxRawResultSetSizeForInMemorySortDefault)
|
||||
{
|
||||
boolean use = (useInMemorySort == null) ? useInMemorySortDefault : useInMemorySort.booleanValue();
|
||||
int max = (maxRawResultSetSizeForInMemorySort == null) ? maxRawResultSetSizeForInMemorySortDefault : maxRawResultSetSizeForInMemorySort.intValue();
|
||||
int max = (maxRawResultSetSizeForInMemorySort == null) ? maxRawResultSetSizeForInMemorySortDefault : maxRawResultSetSizeForInMemorySort.intValue();
|
||||
return use && (length <= max);
|
||||
}
|
||||
|
||||
@@ -1654,10 +1677,10 @@ public class SearchParameters implements BasicSearchParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a maximum value for the report of total hits. The reported number of hits will never exceed this limit even
|
||||
* if more are found. If unset, the engine’s default tracking limit is applied. To remove any limit, set to -1.
|
||||
* Set a maximum value for the report of total hits. The reported number of hits will never exceed this limit even if more are found. If unset, the engine’s default tracking limit is applied. To remove any limit, set to -1.
|
||||
*
|
||||
* @param trackTotalHits int
|
||||
* @param trackTotalHits
|
||||
* int
|
||||
*/
|
||||
public void setTrackTotalHits(int trackTotalHits)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user