mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-09-17 14:21:39 +00:00
ACS-247 Query accelerator (#234)
Provides hook points for the query accelerator in alfresco-enterprise-repo. In addition to these, it also includes temporary code to add timing headers to REST API calls. These will be removed as part of REPO-5376. Commits mainly by Bruno and Nana. Merging from old projects and changes from master by Alan.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Remote API
|
* Alfresco Remote API
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -25,6 +25,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.rest.api.search;
|
package org.alfresco.rest.api.search;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.alfresco.repo.search.impl.querymodel.impl.db.DBStats;
|
||||||
|
import org.alfresco.repo.search.impl.querymodel.impl.db.SingleTaskRestartableWatch;
|
||||||
import org.alfresco.rest.api.model.Node;
|
import org.alfresco.rest.api.model.Node;
|
||||||
import org.alfresco.rest.api.search.context.SearchRequestContext;
|
import org.alfresco.rest.api.search.context.SearchRequestContext;
|
||||||
import org.alfresco.rest.api.search.impl.ResultMapper;
|
import org.alfresco.rest.api.search.impl.ResultMapper;
|
||||||
@@ -45,14 +51,14 @@ import org.alfresco.service.cmr.search.SearchParameters;
|
|||||||
import org.alfresco.service.cmr.search.SearchService;
|
import org.alfresco.service.cmr.search.SearchService;
|
||||||
import org.alfresco.util.ParameterCheck;
|
import org.alfresco.util.ParameterCheck;
|
||||||
import org.alfresco.util.PropertyCheck;
|
import org.alfresco.util.PropertyCheck;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.extensions.webscripts.AbstractWebScript;
|
import org.springframework.extensions.webscripts.AbstractWebScript;
|
||||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||||
|
import org.springframework.util.StopWatch;
|
||||||
import java.io.IOException;
|
import org.springframework.util.StopWatch.TaskInfo;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of the {{baseUrl}}/{{networkId}}/public/search/versions/1/search endpoint
|
* An implementation of the {{baseUrl}}/{{networkId}}/public/search/versions/1/search endpoint
|
||||||
@@ -62,12 +68,15 @@ import java.util.List;
|
|||||||
public class SearchApiWebscript extends AbstractWebScript implements RecognizedParamsExtractor, RequestReader, ResponseWriter,
|
public class SearchApiWebscript extends AbstractWebScript implements RecognizedParamsExtractor, RequestReader, ResponseWriter,
|
||||||
InitializingBean
|
InitializingBean
|
||||||
{
|
{
|
||||||
|
protected static final Log logger = LogFactory.getLog(SearchApiWebscript.class);
|
||||||
|
|
||||||
private ServiceRegistry serviceRegistry;
|
private ServiceRegistry serviceRegistry;
|
||||||
private SearchService searchService;
|
private SearchService searchService;
|
||||||
private SearchMapper searchMapper;
|
private SearchMapper searchMapper;
|
||||||
private ResultMapper resultMapper;
|
private ResultMapper resultMapper;
|
||||||
protected ApiAssistant assistant;
|
protected ApiAssistant assistant;
|
||||||
protected ResourceWebScriptHelper helper;
|
protected ResourceWebScriptHelper helper;
|
||||||
|
private boolean statsEnabled;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet()
|
public void afterPropertiesSet()
|
||||||
@@ -82,7 +91,7 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP
|
|||||||
@Override
|
@Override
|
||||||
public void execute(WebScriptRequest webScriptRequest, WebScriptResponse webScriptResponse) throws IOException
|
public void execute(WebScriptRequest webScriptRequest, WebScriptResponse webScriptResponse) throws IOException
|
||||||
{
|
{
|
||||||
|
StopWatch apiStopWatch = new StopWatch();
|
||||||
try {
|
try {
|
||||||
//Turn JSON into a Java object respresentation
|
//Turn JSON into a Java object respresentation
|
||||||
SearchQuery searchQuery = extractJsonContent(webScriptRequest, assistant.getJsonHelper(), SearchQuery.class);
|
SearchQuery searchQuery = extractJsonContent(webScriptRequest, assistant.getJsonHelper(), SearchQuery.class);
|
||||||
@@ -97,12 +106,43 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP
|
|||||||
SearchParameters searchParams = searchMapper.toSearchParameters(params, searchQuery, searchRequestContext);
|
SearchParameters searchParams = searchMapper.toSearchParameters(params, searchQuery, searchRequestContext);
|
||||||
|
|
||||||
//Call searchService
|
//Call searchService
|
||||||
|
apiStopWatch.start("nodes");
|
||||||
ResultSet results = searchService.query(searchParams);
|
ResultSet results = searchService.query(searchParams);
|
||||||
|
apiStopWatch.stop();
|
||||||
|
|
||||||
//Turn solr results into JSON
|
//Turn solr results into JSON
|
||||||
|
apiStopWatch.start("props");
|
||||||
CollectionWithPagingInfo<Node> resultJson = resultMapper.toCollectionWithPagingInfo(params, searchRequestContext, searchQuery, results);
|
CollectionWithPagingInfo<Node> resultJson = resultMapper.toCollectionWithPagingInfo(params, searchRequestContext, searchQuery, results);
|
||||||
|
|
||||||
//Post-process the request and pass in params, eg. params.getFilter()
|
//Post-process the request and pass in params, eg. params.getFilter()
|
||||||
Object toRender = helper.processAdditionsToTheResponse(null, null, null, params, resultJson);
|
Object toRender = helper.processAdditionsToTheResponse(null, null, null, params, resultJson);
|
||||||
|
apiStopWatch.stop();
|
||||||
|
|
||||||
|
// store execution stats in a special header if enabled
|
||||||
|
if (statsEnabled)
|
||||||
|
{
|
||||||
|
// store execution time in a special header
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append("api={");
|
||||||
|
sb.append("tot=").append(apiStopWatch.getTotalTimeMillis()).append("ms,");
|
||||||
|
addStopWatchStats(sb, apiStopWatch);
|
||||||
|
sb.append("}; ");
|
||||||
|
|
||||||
|
sb.append("db={");
|
||||||
|
addStopWatchStats(sb, DBStats.queryStopWatch());
|
||||||
|
sb.append("}; ");
|
||||||
|
|
||||||
|
sb.append("query={");
|
||||||
|
addStopWatchStats(sb, DBStats.handlerStopWatch());
|
||||||
|
sb.append(",");
|
||||||
|
addStopWatchStats(sb, DBStats.aclReadStopWatch());
|
||||||
|
sb.append(",");
|
||||||
|
addStopWatchStats(sb, DBStats.aclOwnerStopWatch());
|
||||||
|
sb.append("}");
|
||||||
|
|
||||||
|
webScriptResponse.addHeader("X-Response-Stats", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
//Write response
|
//Write response
|
||||||
setResponse(webScriptResponse, DEFAULT_SUCCESS);
|
setResponse(webScriptResponse, DEFAULT_SUCCESS);
|
||||||
@@ -113,6 +153,44 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addStopWatchStats(StringBuilder sb, StopWatch watch)
|
||||||
|
{
|
||||||
|
boolean first = true;
|
||||||
|
|
||||||
|
for (TaskInfo task : watch.getTaskInfo())
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(task.getTaskName())
|
||||||
|
.append("=")
|
||||||
|
.append(task.getTimeMillis())
|
||||||
|
.append("ms");
|
||||||
|
|
||||||
|
int pc = Math.round(100 * task.getTimeNanos() / watch.getTotalTimeNanos());
|
||||||
|
sb.append("(")
|
||||||
|
.append(pc).append("%")
|
||||||
|
.append(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStopWatchStats(StringBuilder sb, SingleTaskRestartableWatch watch)
|
||||||
|
{
|
||||||
|
long decimillis = (watch.getTotalTimeMicros()+5)/100;
|
||||||
|
double millis = decimillis/10.0;
|
||||||
|
|
||||||
|
sb.append(watch.getName())
|
||||||
|
.append("=")
|
||||||
|
.append(millis)
|
||||||
|
.append("ms");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the Params object, parameters come from the SearchQuery json not the request
|
* Gets the Params object, parameters come from the SearchQuery json not the request
|
||||||
* @param webScriptRequest
|
* @param webScriptRequest
|
||||||
@@ -165,4 +243,10 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP
|
|||||||
{
|
{
|
||||||
this.helper = helper;
|
this.helper = helper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// receiving as a string because of known issue: https://jira.spring.io/browse/SPR-9989
|
||||||
|
public void setStatsEnabled(String enabled) {
|
||||||
|
this.statsEnabled = Boolean.valueOf(enabled);
|
||||||
|
logger.info("API stats header: " + (this.statsEnabled ? "enabled" : "disabled"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,3 +24,6 @@
|
|||||||
alfresco.restApi.basicAuthScheme=false
|
alfresco.restApi.basicAuthScheme=false
|
||||||
# REPO-4388 allow CORS headers in transaction response
|
# REPO-4388 allow CORS headers in transaction response
|
||||||
webscripts.transaction.preserveHeadersPattern=Access-Control-.*
|
webscripts.transaction.preserveHeadersPattern=Access-Control-.*
|
||||||
|
|
||||||
|
# REPO-5371 enable stats header in API response (only search atm)
|
||||||
|
webscripts.stats.enabled=false
|
@@ -1010,6 +1010,7 @@
|
|||||||
<property name="helper" ref="webscriptHelper" />
|
<property name="helper" ref="webscriptHelper" />
|
||||||
<property name="resultMapper" ref="searchapiResultMapper" />
|
<property name="resultMapper" ref="searchapiResultMapper" />
|
||||||
<property name="searchMapper" ref="searchapiSearchMapper" />
|
<property name="searchMapper" ref="searchapiSearchMapper" />
|
||||||
|
<property name="statsEnabled" value="${webscripts.stats.enabled}" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="webscript.org.alfresco.api.SearchSQLApiWebscript.post"
|
<bean id="webscript.org.alfresco.api.SearchSQLApiWebscript.post"
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -217,7 +217,7 @@ public class SqlSessionMetricsWrapper implements SqlSession
|
|||||||
PassThroughMetricsResultsHandler passThroughHandler = new PassThroughMetricsResultsHandler(handler, startTime, statement);
|
PassThroughMetricsResultsHandler passThroughHandler = new PassThroughMetricsResultsHandler(handler, startTime, statement);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.select(statement, passThroughHandler);
|
this.sqlSession.select(statement, passThroughHandler);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -117,7 +117,7 @@ import org.springframework.util.Assert;
|
|||||||
public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
|
||||||
{
|
{
|
||||||
private static final String CACHE_REGION_ROOT_NODES = "N.RN";
|
private static final String CACHE_REGION_ROOT_NODES = "N.RN";
|
||||||
private static final String CACHE_REGION_NODES = "N.N";
|
public static final String CACHE_REGION_NODES = "N.N";
|
||||||
private static final String CACHE_REGION_ASPECTS = "N.A";
|
private static final String CACHE_REGION_ASPECTS = "N.A";
|
||||||
private static final String CACHE_REGION_PROPERTIES = "N.P";
|
private static final String CACHE_REGION_PROPERTIES = "N.P";
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -824,6 +824,27 @@ public class NodePropertyValue implements Cloneable, Serializable
|
|||||||
return valueType.getOrdinalNumber();
|
return valueType.getOrdinalNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an actual type qualified name, returns the <tt>int</tt> ordinal number
|
||||||
|
* that represents its persisted in the database.
|
||||||
|
*
|
||||||
|
* @param typeQName the type qualified name
|
||||||
|
* @param value the value going to be persisted (optional, null for the default)
|
||||||
|
* @return Returns the <tt>int</tt> representation of the type,
|
||||||
|
* e.g. <b>CONTENT.getOrdinalNumber()</b> for type <b>d:content</b>.
|
||||||
|
*/
|
||||||
|
public static int convertToPersistedTypeOrdinal(QName typeQName, Serializable value)
|
||||||
|
{
|
||||||
|
ValueType valueType = makeValueType(typeQName);
|
||||||
|
if (valueType == null)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException(
|
||||||
|
"Unable to get value type from type qname " + typeQName + " and value " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return valueType.getPersistedType(value).getOrdinalNumber();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If property value of the type <code>QName</code> is supported
|
* If property value of the type <code>QName</code> is supported
|
||||||
*
|
*
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -427,6 +427,10 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
|
|||||||
NodeEntity node = new NodeEntity();
|
NodeEntity node = new NodeEntity();
|
||||||
node.setId(id);
|
node.setId(id);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("+ Read node with id: "+id);
|
||||||
|
}
|
||||||
return template.selectOne(SELECT_NODE_BY_ID, node);
|
return template.selectOne(SELECT_NODE_BY_ID, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,6 +454,10 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
|
|||||||
}
|
}
|
||||||
node.setUuid(uuid);
|
node.setUuid(uuid);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("+ Read node with uuid: "+uuid);
|
||||||
|
}
|
||||||
return template.selectOne(SELECT_NODE_BY_NODEREF, node);
|
return template.selectOne(SELECT_NODE_BY_NODEREF, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -31,6 +31,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet;
|
||||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeService;
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
@@ -53,6 +54,8 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
|
|||||||
|
|
||||||
NodeService nodeService;
|
NodeService nodeService;
|
||||||
|
|
||||||
|
private boolean trimmedResultSet;
|
||||||
|
|
||||||
public PagingLuceneResultSet(ResultSet wrapped, SearchParameters searchParameters, NodeService nodeService)
|
public PagingLuceneResultSet(ResultSet wrapped, SearchParameters searchParameters, NodeService nodeService)
|
||||||
{
|
{
|
||||||
this.wrapped = wrapped;
|
this.wrapped = wrapped;
|
||||||
@@ -116,17 +119,23 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
|
|||||||
|
|
||||||
int max = searchParameters.getMaxItems();
|
int max = searchParameters.getMaxItems();
|
||||||
int skip = searchParameters.getSkipCount();
|
int skip = searchParameters.getSkipCount();
|
||||||
if ((max >= 0) && (max < (wrapped.length() - skip)))
|
int length = getWrappedResultSetLength();
|
||||||
|
if ((max >= 0) && (max < (length - skip)))
|
||||||
{
|
{
|
||||||
return searchParameters.getMaxItems();
|
return searchParameters.getMaxItems();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int lengthAfterSkipping = wrapped.length() - skip;
|
int lengthAfterSkipping = length - skip;
|
||||||
return lengthAfterSkipping < 0 ? 0 : lengthAfterSkipping;
|
return lengthAfterSkipping < 0 ? 0 : lengthAfterSkipping;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getWrappedResultSetLength()
|
||||||
|
{
|
||||||
|
return trimmedResultSet ? wrapped.length() + searchParameters.getSkipCount() : wrapped.length();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
@@ -134,7 +143,14 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
|
|||||||
*/
|
*/
|
||||||
public int getStart()
|
public int getStart()
|
||||||
{
|
{
|
||||||
return searchParameters.getSkipCount();
|
if (trimmedResultSet)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return searchParameters.getSkipCount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -254,7 +270,14 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
|
|||||||
@Override
|
@Override
|
||||||
public long getNumberFound()
|
public long getNumberFound()
|
||||||
{
|
{
|
||||||
return wrapped.getNumberFound();
|
if (trimmedResultSet && wrapped instanceof FilteringResultSet)
|
||||||
|
{
|
||||||
|
return ((FilteringResultSet) wrapped).getUnFilteredResultSet().getNumberFound();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return wrapped.getNumberFound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -274,4 +297,9 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
|
|||||||
{
|
{
|
||||||
return wrapped.getSpellCheckResult();
|
return wrapped.getSpellCheckResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTrimmedResultSet(boolean b)
|
||||||
|
{
|
||||||
|
this.trimmedResultSet = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||||
|
|
||||||
import org.alfresco.repo.search.adaptor.LuceneFunction;
|
import org.alfresco.repo.search.adaptor.LuceneFunction;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andy
|
* @author Andy
|
||||||
@@ -42,11 +43,13 @@ public class DBQueryBuilderPredicatePartCommand
|
|||||||
|
|
||||||
String alias;
|
String alias;
|
||||||
|
|
||||||
private DBQueryBuilderJoinCommandType joinCommandType;
|
QName qName;
|
||||||
|
|
||||||
private LuceneFunction function;
|
DBQueryBuilderJoinCommandType joinCommandType;
|
||||||
|
|
||||||
private Long qnameId;
|
LuceneFunction function;
|
||||||
|
|
||||||
|
Long qnameId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the qnameId
|
* @return the qnameId
|
||||||
@@ -160,6 +163,16 @@ public class DBQueryBuilderPredicatePartCommand
|
|||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setQName(QName propertyQName)
|
||||||
|
{
|
||||||
|
this.qName = propertyQName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QName getQName()
|
||||||
|
{
|
||||||
|
return this.qName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the function
|
* @return the function
|
||||||
*/
|
*/
|
||||||
@@ -198,5 +211,4 @@ public class DBQueryBuilderPredicatePartCommand
|
|||||||
return alias +"." +fieldName;
|
return alias +"." +fieldName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -25,7 +25,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||||
|
|
||||||
|
import static org.alfresco.repo.domain.node.AbstractNodeDAOImpl.CACHE_REGION_NODES;
|
||||||
|
import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.aclOwnerStopWatch;
|
||||||
|
import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.aclReadStopWatch;
|
||||||
|
import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.handlerStopWatch;
|
||||||
|
import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.resetStopwatches;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@@ -33,11 +42,20 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.admin.patch.OptionalPatchApplicationCheckBootstrapBean;
|
import org.alfresco.repo.admin.patch.OptionalPatchApplicationCheckBootstrapBean;
|
||||||
|
import org.alfresco.repo.cache.SimpleCache;
|
||||||
|
import org.alfresco.repo.cache.lookup.EntityLookupCache;
|
||||||
|
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
|
||||||
import org.alfresco.repo.domain.node.Node;
|
import org.alfresco.repo.domain.node.Node;
|
||||||
import org.alfresco.repo.domain.node.NodeDAO;
|
import org.alfresco.repo.domain.node.NodeDAO;
|
||||||
|
import org.alfresco.repo.domain.node.NodeVersionKey;
|
||||||
|
import org.alfresco.repo.domain.permissions.AclCrudDAO;
|
||||||
|
import org.alfresco.repo.domain.permissions.Authority;
|
||||||
import org.alfresco.repo.domain.qname.QNameDAO;
|
import org.alfresco.repo.domain.qname.QNameDAO;
|
||||||
|
import org.alfresco.repo.search.SimpleResultSetMetaData;
|
||||||
import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet;
|
import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet;
|
||||||
import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext;
|
import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext;
|
||||||
import org.alfresco.repo.search.impl.querymodel.Query;
|
import org.alfresco.repo.search.impl.querymodel.Query;
|
||||||
@@ -46,26 +64,42 @@ import org.alfresco.repo.search.impl.querymodel.QueryEngineResults;
|
|||||||
import org.alfresco.repo.search.impl.querymodel.QueryModelException;
|
import org.alfresco.repo.search.impl.querymodel.QueryModelException;
|
||||||
import org.alfresco.repo.search.impl.querymodel.QueryModelFactory;
|
import org.alfresco.repo.search.impl.querymodel.QueryModelFactory;
|
||||||
import org.alfresco.repo.search.impl.querymodel.QueryOptions;
|
import org.alfresco.repo.search.impl.querymodel.QueryOptions;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
|
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet;
|
||||||
import org.alfresco.repo.tenant.TenantService;
|
import org.alfresco.repo.tenant.TenantService;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeService;
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
import org.alfresco.service.cmr.repository.StoreRef;
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||||
|
import org.alfresco.service.cmr.search.LimitBy;
|
||||||
|
import org.alfresco.service.cmr.search.PermissionEvaluationMode;
|
||||||
import org.alfresco.service.cmr.search.ResultSet;
|
import org.alfresco.service.cmr.search.ResultSet;
|
||||||
|
import org.alfresco.service.cmr.security.PermissionService;
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.alfresco.util.EqualsHelper;
|
||||||
import org.alfresco.util.Pair;
|
import org.alfresco.util.Pair;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.ibatis.session.ResultContext;
|
||||||
|
import org.apache.ibatis.session.ResultHandler;
|
||||||
import org.mybatis.spring.SqlSessionTemplate;
|
import org.mybatis.spring.SqlSessionTemplate;
|
||||||
|
import org.springframework.util.StopWatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andy
|
* @author Andy
|
||||||
*/
|
*/
|
||||||
|
@NotThreadSafe
|
||||||
public class DBQueryEngine implements QueryEngine
|
public class DBQueryEngine implements QueryEngine
|
||||||
{
|
{
|
||||||
|
private static final Log logger = LogFactory.getLog(DBQueryEngine.class);
|
||||||
|
|
||||||
private static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery";
|
private static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery";
|
||||||
|
|
||||||
private SqlSessionTemplate template;
|
protected SqlSessionTemplate template;
|
||||||
|
|
||||||
private QNameDAO qnameDAO;
|
protected QNameDAO qnameDAO;
|
||||||
|
|
||||||
private NodeDAO nodeDAO;
|
private NodeDAO nodeDAO;
|
||||||
|
|
||||||
@@ -79,6 +113,45 @@ public class DBQueryEngine implements QueryEngine
|
|||||||
|
|
||||||
private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
|
private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
|
||||||
|
|
||||||
|
private PermissionService permissionService;
|
||||||
|
|
||||||
|
private int maxPermissionChecks;
|
||||||
|
|
||||||
|
private long maxPermissionCheckTimeMillis;
|
||||||
|
|
||||||
|
private SimpleCache<NodeVersionKey, Map<QName, Serializable>> propertiesCache;
|
||||||
|
|
||||||
|
EntityLookupCache<Long, Node, NodeRef> nodesCache;
|
||||||
|
|
||||||
|
private SimpleCache<NodeVersionKey, Set<QName>> aspectsCache;
|
||||||
|
|
||||||
|
private AclCrudDAO aclCrudDAO;
|
||||||
|
|
||||||
|
public void setAclCrudDAO(AclCrudDAO aclCrudDAO)
|
||||||
|
{
|
||||||
|
this.aclCrudDAO = aclCrudDAO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxPermissionChecks(int maxPermissionChecks)
|
||||||
|
{
|
||||||
|
this.maxPermissionChecks = maxPermissionChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)
|
||||||
|
{
|
||||||
|
this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTemplate(SqlSessionTemplate template)
|
||||||
|
{
|
||||||
|
this.template = template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissionService(PermissionService permissionService)
|
||||||
|
{
|
||||||
|
this.permissionService = permissionService;
|
||||||
|
}
|
||||||
|
|
||||||
public void setMetadataIndexCheck2(OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2)
|
public void setMetadataIndexCheck2(OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2)
|
||||||
{
|
{
|
||||||
this.metadataIndexCheck2 = metadataIndexCheck2;
|
this.metadataIndexCheck2 = metadataIndexCheck2;
|
||||||
@@ -147,6 +220,9 @@ public class DBQueryEngine implements QueryEngine
|
|||||||
@Override
|
@Override
|
||||||
public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext)
|
public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext)
|
||||||
{
|
{
|
||||||
|
logger.debug("Query request received");
|
||||||
|
resetStopwatches();
|
||||||
|
|
||||||
Set<String> selectorGroup = null;
|
Set<String> selectorGroup = null;
|
||||||
if (query.getSource() != null)
|
if (query.getSource() != null)
|
||||||
{
|
{
|
||||||
@@ -165,13 +241,9 @@ public class DBQueryEngine implements QueryEngine
|
|||||||
selectorGroup = selectorGroups.get(0);
|
selectorGroup = selectorGroups.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HashSet<String> key = new HashSet<String>();
|
|
||||||
key.add("");
|
|
||||||
Map<Set<String>, ResultSet> answer = new HashMap<Set<String>, ResultSet>();
|
|
||||||
DBQuery dbQuery = (DBQuery)query;
|
DBQuery dbQuery = (DBQuery)query;
|
||||||
|
|
||||||
if(options.getStores().size() > 1)
|
if (options.getStores().size() > 1)
|
||||||
{
|
{
|
||||||
throw new QueryModelException("Multi-store queries are not supported");
|
throw new QueryModelException("Multi-store queries are not supported");
|
||||||
}
|
}
|
||||||
@@ -181,13 +253,13 @@ public class DBQueryEngine implements QueryEngine
|
|||||||
storeRef = storeRef != null ? tenantService.getName(storeRef) : null;
|
storeRef = storeRef != null ? tenantService.getName(storeRef) : null;
|
||||||
|
|
||||||
Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef);
|
Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef);
|
||||||
if(store == null)
|
if (store == null)
|
||||||
{
|
{
|
||||||
throw new QueryModelException("Unknown store: "+storeRef);
|
throw new QueryModelException("Unknown store: "+storeRef);
|
||||||
}
|
}
|
||||||
dbQuery.setStoreId(store.getFirst());
|
dbQuery.setStoreId(store.getFirst());
|
||||||
Pair<Long, QName> sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED);
|
Pair<Long, QName> sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED);
|
||||||
if(sysDeletedType == null)
|
if (sysDeletedType == null)
|
||||||
{
|
{
|
||||||
dbQuery.setSysDeletedType(-1L);
|
dbQuery.setSysDeletedType(-1L);
|
||||||
}
|
}
|
||||||
@@ -204,21 +276,205 @@ public class DBQueryEngine implements QueryEngine
|
|||||||
}
|
}
|
||||||
dbQuery.setSinceTxId(sinceTxId);
|
dbQuery.setSinceTxId(sinceTxId);
|
||||||
|
|
||||||
dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup, null, functionContext, metadataIndexCheck2.getPatchApplied());
|
logger.debug("- query is being prepared");
|
||||||
List<Node> nodes = template.selectList(SELECT_BY_DYNAMIC_QUERY, dbQuery);
|
dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup,
|
||||||
LinkedHashSet<Long> set = new LinkedHashSet<Long>(nodes.size());
|
null, functionContext, metadataIndexCheck2.getPatchApplied());
|
||||||
for(Node node : nodes)
|
|
||||||
{
|
|
||||||
set.add(node.getId());
|
|
||||||
}
|
|
||||||
List<Long> nodeIds = new ArrayList<Long>(set);
|
|
||||||
ResultSet rs = new DBResultSet(options.getAsSearchParmeters(), nodeIds, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE);
|
|
||||||
ResultSet paged = new PagingLuceneResultSet(rs, options.getAsSearchParmeters(), nodeService);
|
|
||||||
|
|
||||||
|
ResultSet resultSet;
|
||||||
|
// TEMPORARY - this first branch of the if statement simply allows us to easily clear the caches for now; it will be removed afterwards
|
||||||
|
if (cleanCacheRequest(options))
|
||||||
|
{
|
||||||
|
nodesCache.clear();
|
||||||
|
propertiesCache.clear();
|
||||||
|
aspectsCache.clear();
|
||||||
|
logger.info("Nodes cache cleared");
|
||||||
|
resultSet = new DBResultSet(options.getAsSearchParmeters(), Collections.emptyList(), nodeDAO, nodeService,
|
||||||
|
tenantService, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
else if (forceOldPermissionResolution(options))
|
||||||
|
{
|
||||||
|
resultSet = selectNodesStandard(options, dbQuery);
|
||||||
|
logger.debug("Selected " +resultSet.length()+ " nodes with standard permission resolution");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resultSet = selectNodesWithPermissions(options, dbQuery);
|
||||||
|
logger.debug("Selected " +resultSet.length()+ " nodes with accelerated permission resolution");
|
||||||
|
}
|
||||||
|
|
||||||
|
return asQueryEngineResults(resultSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String pickQueryTemplate(QueryOptions options, DBQuery dbQuery)
|
||||||
|
{
|
||||||
|
logger.debug("- using standard table for the query");
|
||||||
|
return SELECT_BY_DYNAMIC_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResultSet selectNodesStandard(QueryOptions options, DBQuery dbQuery)
|
||||||
|
{
|
||||||
|
List<Node> nodes = removeDuplicates(template.selectList(pickQueryTemplate(options, dbQuery), dbQuery));
|
||||||
|
DBResultSet rs = new DBResultSet(options.getAsSearchParmeters(), nodes, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE);
|
||||||
|
return new PagingLuceneResultSet(rs, options.getAsSearchParmeters(), nodeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResultSet selectNodesWithPermissions(QueryOptions options, DBQuery dbQuery)
|
||||||
|
{
|
||||||
|
NodePermissionAssessor permissionAssessor = createAssessor(options);
|
||||||
|
|
||||||
|
FilteringResultSet resultSet = acceleratedNodeSelection(options, dbQuery, permissionAssessor);
|
||||||
|
|
||||||
|
PagingLuceneResultSet plrs = new PagingLuceneResultSet(resultSet, options.getAsSearchParmeters(), nodeService);
|
||||||
|
plrs.setTrimmedResultSet(true);
|
||||||
|
return plrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodePermissionAssessor createAssessor(QueryOptions options)
|
||||||
|
{
|
||||||
|
NodePermissionAssessor permissionAssessor = new NodePermissionAssessor();
|
||||||
|
int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks();
|
||||||
|
long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0
|
||||||
|
? maxPermissionCheckTimeMillis
|
||||||
|
: options.getMaxPermissionCheckTimeMillis();
|
||||||
|
permissionAssessor.setMaxPermissionChecks(maxPermsChecks);
|
||||||
|
permissionAssessor.setMaxPermissionCheckTimeMillis(maxPermCheckTimeMillis);
|
||||||
|
return permissionAssessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
FilteringResultSet acceleratedNodeSelection(QueryOptions options, DBQuery dbQuery, NodePermissionAssessor permissionAssessor)
|
||||||
|
{
|
||||||
|
StopWatch sw = DBStats.queryStopWatch();
|
||||||
|
List<Node> nodes = new ArrayList<>();
|
||||||
|
int requiredNodes = computeRequiredNodesCount(options);
|
||||||
|
|
||||||
|
logger.debug("- query sent to the database");
|
||||||
|
sw.start("ttfr");
|
||||||
|
template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler<Node>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handleResult(ResultContext<? extends Node> context)
|
||||||
|
{
|
||||||
|
handlerStopWatch().start();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
doHandleResult(permissionAssessor, sw, nodes, requiredNodes, context);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
handlerStopWatch().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doHandleResult(NodePermissionAssessor permissionAssessor, StopWatch sw, List<Node> nodes,
|
||||||
|
int requiredNodes, ResultContext<? extends Node> context)
|
||||||
|
{
|
||||||
|
if (permissionAssessor.isFirstRecord())
|
||||||
|
{
|
||||||
|
sw.stop();
|
||||||
|
sw.start("ttlr");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.size() >= requiredNodes)
|
||||||
|
{
|
||||||
|
context.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node node = context.getResultObject();
|
||||||
|
|
||||||
|
boolean shouldCache = nodes.size() >= options.getSkipCount();
|
||||||
|
if(shouldCache)
|
||||||
|
{
|
||||||
|
logger.debug("- selected node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
|
||||||
|
nodesCache.setValue(node.getId(), node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.debug("- skipped node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionAssessor.isIncluded(node))
|
||||||
|
{
|
||||||
|
nodes.add(shouldCache ? node : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionAssessor.shouldQuitChecks())
|
||||||
|
{
|
||||||
|
context.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sw.stop();
|
||||||
|
|
||||||
|
int numberFound = nodes.size();
|
||||||
|
nodes.removeAll(Collections.singleton(null));
|
||||||
|
|
||||||
|
DBResultSet rs = createResultSet(options, nodes, numberFound);
|
||||||
|
FilteringResultSet frs = new FilteringResultSet(rs, formInclusionMask(nodes));
|
||||||
|
frs.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.UNLIMITED, PermissionEvaluationMode.EAGER, rs.getResultSetMetaData().getSearchParameters()));
|
||||||
|
|
||||||
|
logger.debug("- query is completed, "+nodes.size()+" nodes loaded");
|
||||||
|
return frs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DBResultSet createResultSet(QueryOptions options, List<Node> nodes, int numberFound)
|
||||||
|
{
|
||||||
|
DBResultSet dbResultSet = new DBResultSet(options.getAsSearchParmeters(), nodes, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE);
|
||||||
|
dbResultSet.setNumberFound(numberFound);
|
||||||
|
return dbResultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeRequiredNodesCount(QueryOptions options)
|
||||||
|
{
|
||||||
|
int maxItems = options.getMaxItems();
|
||||||
|
if (maxItems == -1 || maxItems == Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxItems + options.getSkipCount() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitSet formInclusionMask(List<Node> nodes)
|
||||||
|
{
|
||||||
|
BitSet inclusionMask = new BitSet(nodes.size());
|
||||||
|
for (int i=0; i < nodes.size(); i++)
|
||||||
|
{
|
||||||
|
inclusionMask.set(i, true);
|
||||||
|
}
|
||||||
|
return inclusionMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private QueryEngineResults asQueryEngineResults(ResultSet paged)
|
||||||
|
{
|
||||||
|
HashSet<String> key = new HashSet<>();
|
||||||
|
key.add("");
|
||||||
|
Map<Set<String>, ResultSet> answer = new HashMap<>();
|
||||||
answer.put(key, paged);
|
answer.put(key, paged);
|
||||||
|
|
||||||
return new QueryEngineResults(answer);
|
return new QueryEngineResults(answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Node> removeDuplicates(List<Node> nodes)
|
||||||
|
{
|
||||||
|
LinkedHashSet<Node> uniqueNodes = new LinkedHashSet<>(nodes.size());
|
||||||
|
List<Long> checkedNodeIds = new ArrayList<>(nodes.size());
|
||||||
|
|
||||||
|
for (Node node : nodes)
|
||||||
|
{
|
||||||
|
if (!checkedNodeIds.contains(node.getId()))
|
||||||
|
{
|
||||||
|
checkedNodeIds.add(node.getId());
|
||||||
|
uniqueNodes.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<Node>(uniqueNodes);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory()
|
* @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory()
|
||||||
@@ -229,4 +485,242 @@ public class DBQueryEngine implements QueryEngine
|
|||||||
return new DBQueryModelFactory();
|
return new DBQueryModelFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class NodePermissionAssessor
|
||||||
|
{
|
||||||
|
private final boolean isAdminReading;
|
||||||
|
|
||||||
|
private final Authority authority;
|
||||||
|
|
||||||
|
private final Map<Long, Boolean> aclReadCache = new HashMap<>();
|
||||||
|
|
||||||
|
private int checksPerformed;
|
||||||
|
|
||||||
|
private long startTime;
|
||||||
|
|
||||||
|
private int maxPermissionChecks;
|
||||||
|
|
||||||
|
private long maxPermissionCheckTimeMillis;
|
||||||
|
|
||||||
|
public NodePermissionAssessor()
|
||||||
|
{
|
||||||
|
this.checksPerformed = 0;
|
||||||
|
this.maxPermissionChecks = Integer.MAX_VALUE;
|
||||||
|
this.maxPermissionCheckTimeMillis = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
Set<String> authorisations = permissionService.getAuthorisations();
|
||||||
|
this.isAdminReading = authorisations.contains(AuthenticationUtil.getAdminRoleName());
|
||||||
|
|
||||||
|
authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIncluded(Node node)
|
||||||
|
{
|
||||||
|
if (isFirstRecord())
|
||||||
|
{
|
||||||
|
this.startTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
checksPerformed++;
|
||||||
|
return isReallyIncluded(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFirstRecord()
|
||||||
|
{
|
||||||
|
return checksPerformed == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isReallyIncluded(Node node)
|
||||||
|
{
|
||||||
|
return isAdminReading ||
|
||||||
|
canRead(node.getAclId()) ||
|
||||||
|
isOwnerReading(node, authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxPermissionChecks(int maxPermissionChecks)
|
||||||
|
{
|
||||||
|
this.maxPermissionChecks = maxPermissionChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldQuitChecks()
|
||||||
|
{
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
if (checksPerformed >= maxPermissionChecks)
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((System.currentTimeMillis() - startTime) >= maxPermissionCheckTimeMillis)
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)
|
||||||
|
{
|
||||||
|
this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canRead(Long aclId)
|
||||||
|
{
|
||||||
|
aclReadStopWatch().start();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Boolean res = aclReadCache.get(aclId);
|
||||||
|
if (res == null)
|
||||||
|
{
|
||||||
|
res = canCurrentUserRead(aclId);
|
||||||
|
aclReadCache.put(aclId, res);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
aclReadStopWatch().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canCurrentUserRead(Long aclId)
|
||||||
|
{
|
||||||
|
// cache resolved ACLs
|
||||||
|
Set<String> authorities = permissionService.getAuthorisations();
|
||||||
|
|
||||||
|
Set<String> aclReadersDenied = permissionService.getReadersDenied(aclId);
|
||||||
|
for (String auth : aclReadersDenied)
|
||||||
|
{
|
||||||
|
if (authorities.contains(auth))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> aclReaders = permissionService.getReaders(aclId);
|
||||||
|
for (String auth : aclReaders)
|
||||||
|
{
|
||||||
|
if (authorities.contains(auth))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isOwnerReading(Node node, Authority authority)
|
||||||
|
{
|
||||||
|
aclOwnerStopWatch().start();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String owner = getOwner(node);
|
||||||
|
if (EqualsHelper.nullSafeEquals(authority.getAuthority(), owner))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
aclOwnerStopWatch().stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOwner(Node node)
|
||||||
|
{
|
||||||
|
nodesCache.setValue(node.getId(), node);
|
||||||
|
Set<QName> nodeAspects = nodeService.getAspects(node.getNodeRef());
|
||||||
|
|
||||||
|
String userName = null;
|
||||||
|
if (nodeAspects.contains(ContentModel.ASPECT_AUDITABLE))
|
||||||
|
{
|
||||||
|
userName = node.getAuditableProperties().getAuditCreator();
|
||||||
|
}
|
||||||
|
else if (nodeAspects.contains(ContentModel.ASPECT_OWNABLE))
|
||||||
|
{
|
||||||
|
Serializable owner = nodeService.getProperty(node.getNodeRef(), ContentModel.PROP_OWNER);
|
||||||
|
userName = DefaultTypeConverter.INSTANCE.convert(String.class, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean cleanCacheRequest(QueryOptions options)
|
||||||
|
{
|
||||||
|
return "xxx".equals(getLocaleLanguage(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
char getMagicCharFromLocale(QueryOptions options, int index)
|
||||||
|
{
|
||||||
|
String lang = getLocaleLanguage(options);
|
||||||
|
return lang.length() > index ? lang.charAt(index) : ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean forceOldPermissionResolution(QueryOptions options)
|
||||||
|
{
|
||||||
|
return getMagicCharFromLocale(options, 2) == 's';
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLocaleLanguage(QueryOptions options)
|
||||||
|
{
|
||||||
|
return options.getLocales().size() == 1 ? options.getLocales().get(0).getLanguage() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection of nodes cache for clean-up and warm up when required
|
||||||
|
* @param cache The node cache to set
|
||||||
|
*/
|
||||||
|
public void setNodesCache(SimpleCache<Serializable, Serializable> cache)
|
||||||
|
{
|
||||||
|
this.nodesCache = new EntityLookupCache<>(
|
||||||
|
cache,
|
||||||
|
CACHE_REGION_NODES,
|
||||||
|
new ReadonlyLocalCallbackDAO());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNodesCache(EntityLookupCache<Long, Node, NodeRef> nodesCache)
|
||||||
|
{
|
||||||
|
this.nodesCache = nodesCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReadonlyLocalCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Node, NodeRef>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Pair<Long, Node> createValue(Node value)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Node creation is done externally: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<Long, Node> findByKey(Long nodeId)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeRef getValueKey(Node value)
|
||||||
|
{
|
||||||
|
return value.getNodeRef();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TEMPORARY - Injection of nodes cache for clean-up when required
|
||||||
|
*/
|
||||||
|
public void setPropertiesCache(SimpleCache<NodeVersionKey, Map<QName, Serializable>> propertiesCache)
|
||||||
|
{
|
||||||
|
this.propertiesCache = propertiesCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TEMPORARY - Injection of nodes cache for clean-up when required
|
||||||
|
*/
|
||||||
|
public void setAspectsCache(SimpleCache<NodeVersionKey, Set<QName>> aspectsCache)
|
||||||
|
{
|
||||||
|
this.aspectsCache = aspectsCache;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.alfresco.repo.domain.node.Node;
|
||||||
import org.alfresco.repo.domain.node.NodeDAO;
|
import org.alfresco.repo.domain.node.NodeDAO;
|
||||||
import org.alfresco.repo.search.AbstractResultSet;
|
import org.alfresco.repo.search.AbstractResultSet;
|
||||||
import org.alfresco.repo.search.SimpleResultSetMetaData;
|
import org.alfresco.repo.search.SimpleResultSetMetaData;
|
||||||
@@ -42,7 +42,6 @@ import org.alfresco.service.cmr.search.PermissionEvaluationMode;
|
|||||||
import org.alfresco.service.cmr.search.ResultSetMetaData;
|
import org.alfresco.service.cmr.search.ResultSetMetaData;
|
||||||
import org.alfresco.service.cmr.search.ResultSetRow;
|
import org.alfresco.service.cmr.search.ResultSetRow;
|
||||||
import org.alfresco.service.cmr.search.SearchParameters;
|
import org.alfresco.service.cmr.search.SearchParameters;
|
||||||
import org.alfresco.util.Pair;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andy
|
* @author Andy
|
||||||
@@ -50,9 +49,7 @@ import org.alfresco.util.Pair;
|
|||||||
*/
|
*/
|
||||||
public class DBResultSet extends AbstractResultSet
|
public class DBResultSet extends AbstractResultSet
|
||||||
{
|
{
|
||||||
private List<Long> dbids;
|
private List<Node> nodes;
|
||||||
|
|
||||||
private NodeRef[] nodeRefs;
|
|
||||||
|
|
||||||
private NodeDAO nodeDao;
|
private NodeDAO nodeDao;
|
||||||
|
|
||||||
@@ -64,14 +61,16 @@ public class DBResultSet extends AbstractResultSet
|
|||||||
|
|
||||||
private BitSet prefetch;
|
private BitSet prefetch;
|
||||||
|
|
||||||
public DBResultSet(SearchParameters searchParameters, List<Long> dbids, NodeDAO nodeDao, NodeService nodeService, TenantService tenantService, int maximumResultsFromUnlimitedQuery)
|
private int numberFound;
|
||||||
|
|
||||||
|
public DBResultSet(SearchParameters searchParameters, List<Node> nodes, NodeDAO nodeDao, NodeService nodeService, TenantService tenantService, int maximumResultsFromUnlimitedQuery)
|
||||||
{
|
{
|
||||||
this.nodeDao = nodeDao;
|
this.nodeDao = nodeDao;
|
||||||
this.dbids = dbids;
|
this.nodes = nodes;
|
||||||
this.nodeService = nodeService;
|
this.nodeService = nodeService;
|
||||||
this.tenantService = tenantService;
|
this.tenantService = tenantService;
|
||||||
this.prefetch = new BitSet(dbids.size());
|
this.prefetch = new BitSet(nodes.size());
|
||||||
nodeRefs= new NodeRef[(dbids.size())];
|
this.numberFound = nodes.size();
|
||||||
|
|
||||||
final LimitBy limitBy;
|
final LimitBy limitBy;
|
||||||
int maxResults = -1;
|
int maxResults = -1;
|
||||||
@@ -96,7 +95,7 @@ public class DBResultSet extends AbstractResultSet
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.resultSetMetaData = new SimpleResultSetMetaData(
|
this.resultSetMetaData = new SimpleResultSetMetaData(
|
||||||
maxResults > 0 && dbids.size() < maxResults ? LimitBy.UNLIMITED : limitBy,
|
maxResults > 0 && nodes.size() < maxResults ? LimitBy.UNLIMITED : limitBy,
|
||||||
PermissionEvaluationMode.EAGER, searchParameters);
|
PermissionEvaluationMode.EAGER, searchParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +105,12 @@ public class DBResultSet extends AbstractResultSet
|
|||||||
@Override
|
@Override
|
||||||
public int length()
|
public int length()
|
||||||
{
|
{
|
||||||
return dbids.size();
|
return nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumberFound(int numFound)
|
||||||
|
{
|
||||||
|
this.numberFound = numFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -115,7 +119,7 @@ public class DBResultSet extends AbstractResultSet
|
|||||||
@Override
|
@Override
|
||||||
public long getNumberFound()
|
public long getNumberFound()
|
||||||
{
|
{
|
||||||
return dbids.size();
|
return numberFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -124,8 +128,9 @@ public class DBResultSet extends AbstractResultSet
|
|||||||
@Override
|
@Override
|
||||||
public NodeRef getNodeRef(int n)
|
public NodeRef getNodeRef(int n)
|
||||||
{
|
{
|
||||||
prefetch(n);
|
NodeRef nodeRef = nodes.get(n).getNodeRef();
|
||||||
return nodeRefs[n];
|
nodeRef = tenantService.getBaseName(nodeRef);
|
||||||
|
return nodeRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -190,61 +195,13 @@ public class DBResultSet extends AbstractResultSet
|
|||||||
return new DBResultSetRowIterator(this);
|
return new DBResultSetRowIterator(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prefetch(int n)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (prefetch.get(n))
|
|
||||||
{
|
|
||||||
// The document was already processed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Start at 'n' and process the the next bulk set
|
|
||||||
int bulkFetchSize = getBulkFetchSize();
|
|
||||||
if (bulkFetchSize < 1)
|
|
||||||
{
|
|
||||||
Pair<Long, NodeRef> nodePair = nodeDao.getNodePair(dbids.get(n));
|
|
||||||
NodeRef nodeRef = nodePair == null ? null : nodePair.getSecond();
|
|
||||||
nodeRefs[n] = nodeRef == null ? null : tenantService.getBaseName(nodeRef);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Long> fetchList = new ArrayList<Long>(bulkFetchSize);
|
|
||||||
BitSet done = new BitSet(bulkFetchSize);
|
|
||||||
int totalHits = dbids.size();
|
|
||||||
for (int i = 0; i < bulkFetchSize; i++)
|
|
||||||
{
|
|
||||||
int next = n + i;
|
|
||||||
if (next >= totalHits)
|
|
||||||
{
|
|
||||||
// We've hit the end
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (prefetch.get(next))
|
|
||||||
{
|
|
||||||
// This one is in there already
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// We store the node and mark it as prefetched
|
|
||||||
prefetch.set(next);
|
|
||||||
|
|
||||||
fetchList.add(dbids.get(next));
|
|
||||||
done.set(next);
|
|
||||||
}
|
|
||||||
// Now bulk fetch
|
|
||||||
if (fetchList.size() > 1)
|
|
||||||
{
|
|
||||||
nodeDao.cacheNodesById(fetchList);
|
|
||||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1))
|
|
||||||
{
|
|
||||||
NodeRef nodeRef = nodeDao.getNodePair(fetchList.get(i)).getSecond();
|
|
||||||
nodeRefs[n+1] = nodeRef == null ? null : tenantService.getBaseName(nodeRef);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeService getNodeService()
|
public NodeService getNodeService()
|
||||||
{
|
{
|
||||||
return nodeService;
|
return nodeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Node getNode(int n)
|
||||||
|
{
|
||||||
|
return nodes.get(n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||||
|
|
||||||
|
import org.springframework.util.StopWatch;
|
||||||
|
|
||||||
|
public final class DBStats
|
||||||
|
{
|
||||||
|
public static final ThreadLocal<StopWatch> QUERY_STOPWATCH = new ThreadLocal<StopWatch>();
|
||||||
|
public static final ThreadLocal<SingleTaskRestartableWatch> ACL_READ_STOPWATCH = new ThreadLocal<SingleTaskRestartableWatch>();
|
||||||
|
public static final ThreadLocal<SingleTaskRestartableWatch> ACL_OWNER_STOPWATCH = new ThreadLocal<SingleTaskRestartableWatch>();
|
||||||
|
public static final ThreadLocal<SingleTaskRestartableWatch> HANDLER_STOPWATCH = new ThreadLocal<SingleTaskRestartableWatch>();
|
||||||
|
|
||||||
|
private DBStats() {}
|
||||||
|
|
||||||
|
public static void resetStopwatches()
|
||||||
|
{
|
||||||
|
QUERY_STOPWATCH.set(new StopWatch());
|
||||||
|
HANDLER_STOPWATCH.set(new SingleTaskRestartableWatch("tot"));
|
||||||
|
ACL_READ_STOPWATCH.set(new SingleTaskRestartableWatch("acl"));
|
||||||
|
ACL_OWNER_STOPWATCH.set(new SingleTaskRestartableWatch("own"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StopWatch queryStopWatch()
|
||||||
|
{
|
||||||
|
return QUERY_STOPWATCH.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SingleTaskRestartableWatch aclReadStopWatch()
|
||||||
|
{
|
||||||
|
return ACL_READ_STOPWATCH.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SingleTaskRestartableWatch aclOwnerStopWatch()
|
||||||
|
{
|
||||||
|
return ACL_OWNER_STOPWATCH.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SingleTaskRestartableWatch handlerStopWatch()
|
||||||
|
{
|
||||||
|
return HANDLER_STOPWATCH.get();
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -241,6 +241,7 @@ public class PropertySupport implements DBQueryBuilderComponent
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
command.setQName(propertyQName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -350,12 +351,12 @@ public class PropertySupport implements DBQueryBuilderComponent
|
|||||||
|
|
||||||
command.setFieldName(fieldName);
|
command.setFieldName(fieldName);
|
||||||
command.setFunction(luceneFunction);
|
command.setFunction(luceneFunction);
|
||||||
|
command.setQName(propertyQName);
|
||||||
predicatePartCommands.add(command);
|
predicatePartCommands.add(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param luceneFunction LuceneFunction
|
* @param luceneFunction LuceneFunction
|
||||||
*/
|
*/
|
||||||
@@ -371,5 +372,4 @@ public class PropertySupport implements DBQueryBuilderComponent
|
|||||||
{
|
{
|
||||||
this.leftOuter = leftOuter;
|
this.leftOuter = leftOuter;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -0,0 +1,76 @@
|
|||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* Alfresco Remote API
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
|
||||||
|
@NotThreadSafe
|
||||||
|
public class SingleTaskRestartableWatch
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private long startTimeNanos;
|
||||||
|
private long totalTimeNanos;
|
||||||
|
|
||||||
|
public SingleTaskRestartableWatch(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
startTimeNanos = System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
long elapsedNanos = System.nanoTime() - startTimeNanos;
|
||||||
|
totalTimeNanos += elapsedNanos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalTimeNanos()
|
||||||
|
{
|
||||||
|
return totalTimeNanos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalTimeMicros()
|
||||||
|
{
|
||||||
|
return TimeUnit.NANOSECONDS.toMicros(totalTimeNanos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalTimeMillis()
|
||||||
|
{
|
||||||
|
return TimeUnit.NANOSECONDS.toMillis(totalTimeNanos);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -54,7 +54,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
|
|||||||
|
|
||||||
OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
|
OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param metadataIndexCheck1 the metadataIndexCheck1 to set
|
* @param metadataIndexCheck1 the metadataIndexCheck1 to set
|
||||||
*/
|
*/
|
||||||
@@ -63,7 +62,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
|
|||||||
this.metadataIndexCheck1 = metadataIndexCheck1;
|
this.metadataIndexCheck1 = metadataIndexCheck1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param metadataIndexCheck2 the metadataIndexCheck2 to set
|
* @param metadataIndexCheck2 the metadataIndexCheck2 to set
|
||||||
*/
|
*/
|
||||||
@@ -72,7 +70,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
|
|||||||
this.metadataIndexCheck2 = metadataIndexCheck2;
|
this.metadataIndexCheck2 = metadataIndexCheck2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the query engine
|
* Set the query engine
|
||||||
*
|
*
|
||||||
@@ -83,7 +80,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
|
|||||||
this.queryEngine = queryEngine;
|
this.queryEngine = queryEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param cmisDictionaryService the cmisDictionaryService to set
|
* @param cmisDictionaryService the cmisDictionaryService to set
|
||||||
*/
|
*/
|
||||||
@@ -110,7 +106,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private ResultSet executeQueryImpl(SearchParameters searchParameters)
|
private ResultSet executeQueryImpl(SearchParameters searchParameters)
|
||||||
{
|
{
|
||||||
CMISQueryOptions options = CMISQueryOptions.create(searchParameters);
|
CMISQueryOptions options = CMISQueryOptions.create(searchParameters);
|
||||||
@@ -123,25 +118,8 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
|
|||||||
functionContext.setValidScopes(validScopes);
|
functionContext.setValidScopes(validScopes);
|
||||||
|
|
||||||
CMISQueryParser parser = new CMISQueryParser(options, cmisDictionaryService, joinSupport);
|
CMISQueryParser parser = new CMISQueryParser(options, cmisDictionaryService, joinSupport);
|
||||||
org.alfresco.repo.search.impl.querymodel.Query queryModelQuery = parser.parse(new DBQueryModelFactory(), functionContext);
|
org.alfresco.repo.search.impl.querymodel.Query queryModelQuery = parser.parse(queryEngine.getQueryModelFactory(), functionContext);
|
||||||
|
|
||||||
// TODO: Remove as this appears to be dead code
|
|
||||||
// // build lucene query
|
|
||||||
// Set<String> selectorGroup = null;
|
|
||||||
// if (queryModelQuery.getSource() != null)
|
|
||||||
// {
|
|
||||||
// List<Set<String>> selectorGroups = queryModelQuery.getSource().getSelectorGroups(functionContext);
|
|
||||||
// if (selectorGroups.size() == 0)
|
|
||||||
// {
|
|
||||||
// throw new UnsupportedOperationException("No selectors");
|
|
||||||
// }
|
|
||||||
// if (selectorGroups.size() > 1)
|
|
||||||
// {
|
|
||||||
// throw new UnsupportedOperationException("Advanced join is not supported");
|
|
||||||
// }
|
|
||||||
// selectorGroup = selectorGroups.get(0);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
QueryEngineResults results = queryEngine.executeQuery(queryModelQuery, options, functionContext);
|
QueryEngineResults results = queryEngine.executeQuery(queryModelQuery, options, functionContext);
|
||||||
ResultSet resultSet = results.getResults().values().iterator().next();
|
ResultSet resultSet = results.getResults().values().iterator().next();
|
||||||
return resultSet;
|
return resultSet;
|
||||||
|
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
<mapper namespace="alfresco.metadata.query">
|
<mapper namespace="alfresco.metadata.query">
|
||||||
|
|
||||||
<select id="select_byDynamicQuery" fetchSize="200" parameterType="org.alfresco.repo.search.impl.querymodel.impl.db.DBQuery" resultMap="alfresco.node.result_NodeRef">
|
<select id="select_byDynamicQuery" fetchSize="200" parameterType="org.alfresco.repo.search.impl.querymodel.impl.db.DBQuery" resultMap="alfresco.node.result_Node">
|
||||||
<include refid="sql_select_byDynamicQuery"/>
|
<include refid="sql_select_byDynamicQuery"/>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
@@ -6,9 +6,26 @@
|
|||||||
|
|
||||||
<sql id="sql_select_byDynamicQuery">
|
<sql id="sql_select_byDynamicQuery">
|
||||||
select
|
select
|
||||||
node.id as id
|
node.id as id,
|
||||||
|
node.version as version,
|
||||||
|
store.id as store_id,
|
||||||
|
store.protocol as protocol,
|
||||||
|
store.identifier as identifier,
|
||||||
|
node.uuid as uuid,
|
||||||
|
node.type_qname_id as type_qname_id,
|
||||||
|
node.locale_id as locale_id,
|
||||||
|
node.acl_id as acl_id,
|
||||||
|
txn.id as txn_id,
|
||||||
|
txn.change_txn_id as txn_change_id,
|
||||||
|
node.audit_creator as audit_creator,
|
||||||
|
node.audit_created as audit_created,
|
||||||
|
node.audit_modifier as audit_modifier,
|
||||||
|
node.audit_modified as audit_modified,
|
||||||
|
node.audit_accessed as audit_accessed
|
||||||
from
|
from
|
||||||
alf_node node
|
alf_node node
|
||||||
|
join alf_store store on (store.id = node.store_id)
|
||||||
|
join alf_transaction txn on (txn.id = node.transaction_id)
|
||||||
<foreach item="item" index="index" collection="joins">
|
<foreach item="item" index="index" collection="joins">
|
||||||
<choose>
|
<choose>
|
||||||
<when test="item.type == 'PARENT'">
|
<when test="item.type == 'PARENT'">
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<mapper namespace="alfresco.metadata.query">
|
<mapper namespace="alfresco.metadata.query">
|
||||||
|
|
||||||
<select id="select_byDynamicQuery" fetchSize="-2147483648" parameterType="org.alfresco.repo.search.impl.querymodel.impl.db.DBQuery" resultMap="alfresco.node.result_NodeRef">
|
<select id="select_byDynamicQuery" fetchSize="-2147483648" parameterType="org.alfresco.repo.search.impl.querymodel.impl.db.DBQuery" resultMap="alfresco.node.result_Node">
|
||||||
<include refid="sql_select_byDynamicQuery"/>
|
<include refid="sql_select_byDynamicQuery"/>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
@@ -107,6 +107,7 @@
|
|||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="search.dbQueryEngineImpl" class="org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine" >
|
<bean id="search.dbQueryEngineImpl" class="org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine" >
|
||||||
|
<property name="permissionService" ref="permissionService"/>
|
||||||
<property name="dictionaryService" ref="dictionaryService" />
|
<property name="dictionaryService" ref="dictionaryService" />
|
||||||
<property name="namespaceService" ref="namespaceService" />
|
<property name="namespaceService" ref="namespaceService" />
|
||||||
<property name="sqlSessionTemplate" ref="repoSqlSessionTemplate"/>
|
<property name="sqlSessionTemplate" ref="repoSqlSessionTemplate"/>
|
||||||
@@ -114,9 +115,19 @@
|
|||||||
<property name="nodeService" ref="nodeService"/>
|
<property name="nodeService" ref="nodeService"/>
|
||||||
<property name="nodeDAO" ref="nodeDAO"/>
|
<property name="nodeDAO" ref="nodeDAO"/>
|
||||||
<property name="tenantService" ref="tenantService"/>
|
<property name="tenantService" ref="tenantService"/>
|
||||||
|
<property name="nodesCache" ref="node.nodesCache"/>
|
||||||
|
<property name="aspectsCache" ref="node.aspectsCache"/>
|
||||||
|
<property name="propertiesCache" ref="node.propertiesCache"/>
|
||||||
|
<property name="aclCrudDAO" ref="aclCrudDAO"/>
|
||||||
<property name="metadataIndexCheck2">
|
<property name="metadataIndexCheck2">
|
||||||
<ref bean="metadataQueryIndexesCheck2" />
|
<ref bean="metadataQueryIndexesCheck2" />
|
||||||
</property>
|
</property>
|
||||||
|
<property name="maxPermissionChecks">
|
||||||
|
<value>${system.acl.maxPermissionChecks}</value>
|
||||||
|
</property>
|
||||||
|
<property name="maxPermissionCheckTimeMillis">
|
||||||
|
<value>${system.acl.maxPermissionCheckTimeMillis}</value>
|
||||||
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="search.dbQueryEngine" class="org.springframework.aop.framework.ProxyFactoryBean">
|
<bean id="search.dbQueryEngine" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -74,6 +74,7 @@ import org.alfresco.util.test.junitrules.TemporaryNodes;
|
|||||||
import org.alfresco.util.testing.category.LuceneTests;
|
import org.alfresco.util.testing.category.LuceneTests;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
@@ -193,6 +194,22 @@ public class QuickShareServiceIntegrationTest
|
|||||||
"Quick Share Test Node Content");
|
"Quick Share Test Node Content");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After public void clearTestData()
|
||||||
|
{
|
||||||
|
if (testNode != null)
|
||||||
|
{
|
||||||
|
AuthenticationUtil.runAs(new RunAsWork<NodeRef>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public NodeRef doWork() throws Exception
|
||||||
|
{
|
||||||
|
nodeService.deleteNode(testNode);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, user1.getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test public void getMetaDataFromNodeRefByOwner()
|
@Test public void getMetaDataFromNodeRefByOwner()
|
||||||
{
|
{
|
||||||
Map<String, Object> metadata = AuthenticationUtil.runAs(new RunAsWork<Map<String,Object>>(){
|
Map<String, Object> metadata = AuthenticationUtil.runAs(new RunAsWork<Map<String,Object>>(){
|
||||||
@@ -532,7 +549,8 @@ public class QuickShareServiceIntegrationTest
|
|||||||
}, user1.getUsername());
|
}, user1.getUsername());
|
||||||
|
|
||||||
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
|
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
|
||||||
Assert.assertFalse(nodeService.exists(node));
|
final NodeRef archivedNode = nodeArchiveService.getArchivedNode(node);
|
||||||
|
assertNotNull("Node " + node.toString() + " hasn't been archived hence the deletion was unsuccessful", archivedNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.repo.cache.SimpleCache;
|
||||||
|
import org.alfresco.repo.cache.lookup.EntityLookupCache;
|
||||||
|
import org.alfresco.repo.domain.node.Node;
|
||||||
|
import org.alfresco.repo.domain.node.NodeVersionKey;
|
||||||
|
import org.alfresco.repo.domain.node.StoreEntity;
|
||||||
|
import org.alfresco.repo.domain.permissions.AuthorityEntity;
|
||||||
|
import org.alfresco.repo.search.impl.querymodel.QueryOptions;
|
||||||
|
import org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine.NodePermissionAssessor;
|
||||||
|
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet;
|
||||||
|
import org.alfresco.service.cmr.search.ResultSet;
|
||||||
|
import org.alfresco.service.cmr.search.SearchParameters;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.apache.ibatis.executor.result.DefaultResultContext;
|
||||||
|
import org.apache.ibatis.session.ResultContext;
|
||||||
|
import org.apache.ibatis.session.ResultHandler;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mybatis.spring.SqlSessionTemplate;
|
||||||
|
|
||||||
|
public class DBQueryEngineTest
|
||||||
|
{
|
||||||
|
private static final String SQL_TEMPLATE_PATH = "alfresco.metadata.query.select_byDynamicQuery";
|
||||||
|
|
||||||
|
private DBQueryEngine engine;
|
||||||
|
private SqlSessionTemplate template;
|
||||||
|
private NodePermissionAssessor assessor;
|
||||||
|
private DBQuery dbQuery;
|
||||||
|
private ResultContext<Node> resultContext;
|
||||||
|
private QueryOptions options;
|
||||||
|
private SimpleCache<NodeVersionKey, Map<QName, Serializable>> propertiesCache;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Before
|
||||||
|
public void setup()
|
||||||
|
{
|
||||||
|
engine = new DBQueryEngine();
|
||||||
|
assessor = mock(NodePermissionAssessor.class);
|
||||||
|
dbQuery = mock(DBQuery.class);
|
||||||
|
resultContext = spy(new DefaultResultContext<>());
|
||||||
|
options = createQueryOptions();
|
||||||
|
|
||||||
|
template = mock(SqlSessionTemplate.class);
|
||||||
|
engine.setSqlSessionTemplate(template);
|
||||||
|
|
||||||
|
propertiesCache = mock(SimpleCache.class);
|
||||||
|
engine.setPropertiesCache(propertiesCache);
|
||||||
|
|
||||||
|
engine.nodesCache = mock(EntityLookupCache.class);
|
||||||
|
|
||||||
|
DBStats.resetStopwatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetAFilteringResultSetFromAcceleratedNodeSelection()
|
||||||
|
{
|
||||||
|
withMaxItems(10);
|
||||||
|
|
||||||
|
ResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||||
|
|
||||||
|
assertTrue(result instanceof FilteringResultSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResultSetHaveExpectedAmountOfRequiredNodesBasedOnMaxItems()
|
||||||
|
{
|
||||||
|
withMaxItems(5);
|
||||||
|
prepareTemplate(dbQuery, createNodes(20));
|
||||||
|
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||||
|
|
||||||
|
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||||
|
|
||||||
|
assertEquals(6, result.length());
|
||||||
|
assertNodePresent(0, result);
|
||||||
|
assertNodePresent(1, result);
|
||||||
|
assertNodePresent(2, result);
|
||||||
|
assertNodePresent(3, result);
|
||||||
|
assertNodePresent(4, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResultContextBeClosedWhenMaxItemsReached()
|
||||||
|
{
|
||||||
|
withMaxItems(5);
|
||||||
|
prepareTemplate(dbQuery, createNodes(20));
|
||||||
|
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||||
|
|
||||||
|
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||||
|
|
||||||
|
verify(resultContext).stop();
|
||||||
|
assertEquals(6, result.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResultSetHaveCorrectAmountOfRequiredNodesWhenSkipCountIsUsed()
|
||||||
|
{
|
||||||
|
withMaxItems(5);
|
||||||
|
withSkipCount(10);
|
||||||
|
prepareTemplate(dbQuery, createNodes(20));
|
||||||
|
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||||
|
|
||||||
|
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||||
|
|
||||||
|
assertEquals(6, result.length());
|
||||||
|
assertNodePresent(10, result);
|
||||||
|
assertNodePresent(11, result);
|
||||||
|
assertNodePresent(12, result);
|
||||||
|
assertNodePresent(13, result);
|
||||||
|
assertNodePresent(14, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResultSetHaveCorrectAmountOfRequiredNodesWhenSomeAreExcludedDueToDeclinedPermission()
|
||||||
|
{
|
||||||
|
withMaxItems(5);
|
||||||
|
List<Node> nodes = createNodes(20);
|
||||||
|
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||||
|
when(assessor.isIncluded(nodes.get(0))).thenReturn(false);
|
||||||
|
when(assessor.isIncluded(nodes.get(1))).thenReturn(false);
|
||||||
|
when(assessor.isIncluded(nodes.get(2))).thenReturn(false);
|
||||||
|
prepareTemplate(dbQuery, nodes);
|
||||||
|
|
||||||
|
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||||
|
|
||||||
|
assertEquals(6, result.length());
|
||||||
|
assertNodePresent(3, result);
|
||||||
|
assertNodePresent(4, result);
|
||||||
|
assertNodePresent(5, result);
|
||||||
|
assertNodePresent(6, result);
|
||||||
|
assertNodePresent(7, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotConsiderInaccessibleNodesInResultSetWhenSkippingNodes()
|
||||||
|
{
|
||||||
|
withMaxItems(2);
|
||||||
|
withSkipCount(2);
|
||||||
|
List<Node> nodes = createNodes(6);
|
||||||
|
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||||
|
when(assessor.isIncluded(nodes.get(2))).thenReturn(false);
|
||||||
|
when(assessor.isIncluded(nodes.get(3))).thenReturn(false);
|
||||||
|
prepareTemplate(dbQuery, nodes);
|
||||||
|
|
||||||
|
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||||
|
|
||||||
|
assertEquals(2, result.length());
|
||||||
|
assertNodePresent(4, result);
|
||||||
|
assertNodePresent(5, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldQuitCheckingNodePermissionsWhenImposedLimitsAreReached()
|
||||||
|
{
|
||||||
|
prepareTemplate(dbQuery, createNodes(20));
|
||||||
|
when(assessor.shouldQuitChecks()).thenReturn(true);
|
||||||
|
|
||||||
|
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||||
|
|
||||||
|
assertEquals(0, result.length());
|
||||||
|
verify(resultContext).stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareTemplate(DBQuery dbQuery, List<Node> nodes)
|
||||||
|
{
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
ResultHandler<Node> handler = (ResultHandler<Node>)invocation.getArgument(2);
|
||||||
|
for (Node node: nodes)
|
||||||
|
{
|
||||||
|
if (!resultContext.isStopped())
|
||||||
|
{
|
||||||
|
when(resultContext.getResultObject()).thenReturn(node);
|
||||||
|
handler.handleResult(resultContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}).when(template).select(eq(SQL_TEMPLATE_PATH), eq(dbQuery), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private QueryOptions createQueryOptions()
|
||||||
|
{
|
||||||
|
QueryOptions options = mock(QueryOptions.class);
|
||||||
|
SearchParameters searchParams = mock(SearchParameters.class);
|
||||||
|
when(options.getAsSearchParmeters()).thenReturn(searchParams);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNodePresent(long id, FilteringResultSet result)
|
||||||
|
{
|
||||||
|
DBResultSet rs = (DBResultSet)result.getUnFilteredResultSet();
|
||||||
|
for(int i = 0; i < rs.length(); i++)
|
||||||
|
{
|
||||||
|
if(rs.getNode(i).getId().equals(id))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail("Node with id " + id + " was not found in the result set");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void withMaxItems(int maxItems)
|
||||||
|
{
|
||||||
|
when(options.getMaxItems()).thenReturn(maxItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void withSkipCount(int skipCount)
|
||||||
|
{
|
||||||
|
when(options.getSkipCount()).thenReturn(skipCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Node> createNodes(int amount)
|
||||||
|
{
|
||||||
|
List<Node> nodes = new ArrayList<>();
|
||||||
|
|
||||||
|
for(int i = 0; i < amount; i++)
|
||||||
|
{
|
||||||
|
nodes.add(createNode(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createNode(int id)
|
||||||
|
{
|
||||||
|
Node node = spy(Node.class);
|
||||||
|
|
||||||
|
when(node.getId()).thenReturn((long)id);
|
||||||
|
|
||||||
|
StoreEntity store = mock(StoreEntity.class);
|
||||||
|
when(node.getStore()).thenReturn(store );
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import org.alfresco.repo.domain.node.Node;
|
||||||
|
import org.alfresco.repo.domain.permissions.AclCrudDAO;
|
||||||
|
import org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine.NodePermissionAssessor;
|
||||||
|
import org.alfresco.service.cmr.security.PermissionService;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class NodePermissionAssessorTest
|
||||||
|
{
|
||||||
|
private NodePermissionAssessor assessor;
|
||||||
|
private Node node;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup()
|
||||||
|
{
|
||||||
|
node = mock(Node.class);
|
||||||
|
assessor = createAssessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotQuitAssessingPermissionsWhenMaxPermissionChecksLimitIsNotReached()
|
||||||
|
{
|
||||||
|
assessor.setMaxPermissionChecks(5);
|
||||||
|
|
||||||
|
performChecks(3);
|
||||||
|
|
||||||
|
assertFalse(assessor.shouldQuitChecks());
|
||||||
|
verify(assessor, times(3)).isReallyIncluded(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldQuitAssessingPermissionsWhenMaxPermissionChecksLimitIsReached()
|
||||||
|
{
|
||||||
|
assessor.setMaxPermissionChecks(5);
|
||||||
|
|
||||||
|
performChecks(20);
|
||||||
|
|
||||||
|
assertTrue(assessor.shouldQuitChecks());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotAssessPermissionsWhenMaxPermissionCheckTimeIsUp() throws Exception
|
||||||
|
{
|
||||||
|
assessor.setMaxPermissionCheckTimeMillis(100);
|
||||||
|
|
||||||
|
assessor.isIncluded(node);
|
||||||
|
Thread.sleep(200);
|
||||||
|
|
||||||
|
assertTrue(assessor.shouldQuitChecks());
|
||||||
|
verify(assessor).isReallyIncluded(node);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldAssessPermissionsWhenMaxPermissionCheckTimeIsNotUp() throws Exception
|
||||||
|
{
|
||||||
|
assessor.setMaxPermissionCheckTimeMillis(500);
|
||||||
|
Thread.sleep(200);
|
||||||
|
|
||||||
|
assessor.isIncluded(node);
|
||||||
|
|
||||||
|
assertFalse(assessor.shouldQuitChecks());
|
||||||
|
verify(assessor, atLeastOnce()).isReallyIncluded(node);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performChecks(int checks)
|
||||||
|
{
|
||||||
|
for (int i=0; i < checks; i++)
|
||||||
|
{
|
||||||
|
assessor.isIncluded(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodePermissionAssessor createAssessor()
|
||||||
|
{
|
||||||
|
AclCrudDAO aclCrudDAO = mock(AclCrudDAO.class);
|
||||||
|
PermissionService permissionService = mock(PermissionService.class);
|
||||||
|
|
||||||
|
DBQueryEngine engine = new DBQueryEngine();
|
||||||
|
engine.setPermissionService(permissionService);
|
||||||
|
engine.setAclCrudDAO(aclCrudDAO);
|
||||||
|
|
||||||
|
NodePermissionAssessor assessor = spy(engine.new NodePermissionAssessor());
|
||||||
|
doReturn(true).when(assessor).isReallyIncluded(any(Node.class));
|
||||||
|
return assessor;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user