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:
Alan Davis
2021-01-11 11:31:36 +00:00
committed by GitHub
parent e9f1b9f21b
commit 8fcaa7febb
22 changed files with 1438 additions and 261 deletions

View File

@@ -2,7 +2,7 @@
* #%L
* 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.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -25,6 +25,12 @@
*/
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.search.context.SearchRequestContext;
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.util.ParameterCheck;
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.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.StopWatch;
import org.springframework.util.StopWatch.TaskInfo;
/**
* 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,
InitializingBean
{
protected static final Log logger = LogFactory.getLog(SearchApiWebscript.class);
private ServiceRegistry serviceRegistry;
private SearchService searchService;
private SearchMapper searchMapper;
private ResultMapper resultMapper;
protected ApiAssistant assistant;
protected ResourceWebScriptHelper helper;
private boolean statsEnabled;
@Override
public void afterPropertiesSet()
@@ -82,7 +91,7 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP
@Override
public void execute(WebScriptRequest webScriptRequest, WebScriptResponse webScriptResponse) throws IOException
{
StopWatch apiStopWatch = new StopWatch();
try {
//Turn JSON into a Java object respresentation
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);
//Call searchService
apiStopWatch.start("nodes");
ResultSet results = searchService.query(searchParams);
apiStopWatch.stop();
//Turn solr results into JSON
apiStopWatch.start("props");
CollectionWithPagingInfo<Node> resultJson = resultMapper.toCollectionWithPagingInfo(params, searchRequestContext, searchQuery, results);
//Post-process the request and pass in params, eg. params.getFilter()
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
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
* @param webScriptRequest
@@ -165,4 +243,10 @@ public class SearchApiWebscript extends AbstractWebScript implements RecognizedP
{
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"));
}
}

View File

@@ -23,4 +23,7 @@
# See issue REPO-2575 for details.
alfresco.restApi.basicAuthScheme=false
# 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

View File

@@ -1010,6 +1010,7 @@
<property name="helper" ref="webscriptHelper" />
<property name="resultMapper" ref="searchapiResultMapper" />
<property name="searchMapper" ref="searchapiSearchMapper" />
<property name="statsEnabled" value="${webscripts.stats.enabled}" />
</bean>
<bean id="webscript.org.alfresco.api.SearchSQLApiWebscript.post"

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2018 Alfresco Software Limited
* 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
@@ -217,7 +217,7 @@ public class SqlSessionMetricsWrapper implements SqlSession
PassThroughMetricsResultsHandler passThroughHandler = new PassThroughMetricsResultsHandler(handler, startTime, statement);
try
{
this.select(statement, passThroughHandler);
this.sqlSession.select(statement, passThroughHandler);
}
finally
{

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* 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
@@ -117,7 +117,7 @@ import org.springframework.util.Assert;
public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
{
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_PROPERTIES = "N.P";
@@ -4868,12 +4868,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
public Long getMaxTxInNodeIdRange(Long fromNodeId, Long toNodeId)
{
return selectMaxTxInNodeIdRange(fromNodeId, toNodeId);
}
@Override
public Long getNextTxCommitTime(Long fromCommitTime)
{
return selectNextTxCommitTime(fromCommitTime);
}
@Override
public Long getNextTxCommitTime(Long fromCommitTime)
{
return selectNextTxCommitTime(fromCommitTime);
}
/*
@@ -5043,6 +5043,6 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
protected abstract Long selectMaxTxnId();
protected abstract Long selectMinUnusedTxnCommitTime();
protected abstract Long selectMinTxInNodeIdRange(Long fromNodeId, Long toNodeId);
protected abstract Long selectMaxTxInNodeIdRange(Long fromNodeId, Long toNodeId);
protected abstract Long selectMaxTxInNodeIdRange(Long fromNodeId, Long toNodeId);
protected abstract Long selectNextTxCommitTime(Long fromCommitTime);
}

View File

@@ -1,28 +1,28 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 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%
*/
/*
* #%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.domain.node;
import java.io.ByteArrayInputStream;
@@ -824,6 +824,27 @@ public class NodePropertyValue implements Cloneable, Serializable
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
*

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* 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
@@ -426,7 +426,11 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
{
NodeEntity node = new NodeEntity();
node.setId(id);
if (logger.isDebugEnabled())
{
logger.debug("+ Read node with id: "+id);
}
return template.selectOne(SELECT_NODE_BY_ID, node);
}
@@ -449,7 +453,11 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
// Originally for DB2 which has been EOLed, but might now be used by other databases.
}
node.setUuid(uuid);
if (logger.isDebugEnabled())
{
logger.debug("+ Read node with uuid: "+uuid);
}
return template.selectOne(SELECT_NODE_BY_NODEREF, node);
}

View File

@@ -1,28 +1,28 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 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%
*/
/*
* #%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.lucene;
import java.io.Serializable;
@@ -31,6 +31,7 @@ import java.util.Iterator;
import java.util.List;
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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -52,6 +53,8 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
SearchParameters searchParameters;
NodeService nodeService;
private boolean trimmedResultSet;
public PagingLuceneResultSet(ResultSet wrapped, SearchParameters searchParameters, NodeService nodeService)
{
@@ -116,17 +119,23 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
int max = searchParameters.getMaxItems();
int skip = searchParameters.getSkipCount();
if ((max >= 0) && (max < (wrapped.length() - skip)))
int length = getWrappedResultSetLength();
if ((max >= 0) && (max < (length - skip)))
{
return searchParameters.getMaxItems();
}
else
{
int lengthAfterSkipping = wrapped.length() - skip;
int lengthAfterSkipping = length - skip;
return lengthAfterSkipping < 0 ? 0 : lengthAfterSkipping;
}
}
private int getWrappedResultSetLength()
{
return trimmedResultSet ? wrapped.length() + searchParameters.getSkipCount() : wrapped.length();
}
/*
* (non-Javadoc)
*
@@ -134,7 +143,14 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
*/
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
public long getNumberFound()
{
return wrapped.getNumberFound();
if (trimmedResultSet && wrapped instanceof FilteringResultSet)
{
return ((FilteringResultSet) wrapped).getUnFilteredResultSet().getNumberFound();
}
else
{
return wrapped.getNumberFound();
}
}
@Override
@@ -274,4 +297,9 @@ public class PagingLuceneResultSet implements ResultSet, Serializable
{
return wrapped.getSpellCheckResult();
}
public void setTrimmedResultSet(boolean b)
{
this.trimmedResultSet = true;
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* 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
@@ -26,6 +26,7 @@
package org.alfresco.repo.search.impl.querymodel.impl.db;
import org.alfresco.repo.search.adaptor.LuceneFunction;
import org.alfresco.service.namespace.QName;
/**
* @author Andy
@@ -42,11 +43,13 @@ public class DBQueryBuilderPredicatePartCommand
String alias;
private DBQueryBuilderJoinCommandType joinCommandType;
private LuceneFunction function;
QName qName;
private Long qnameId;
DBQueryBuilderJoinCommandType joinCommandType;
LuceneFunction function;
Long qnameId;
/**
* @return the qnameId
@@ -160,6 +163,16 @@ public class DBQueryBuilderPredicatePartCommand
this.alias = alias;
}
public void setQName(QName propertyQName)
{
this.qName = propertyQName;
}
public QName getQName()
{
return this.qName;
}
/**
* @return the function
*/
@@ -198,5 +211,4 @@ public class DBQueryBuilderPredicatePartCommand
return alias +"." +fieldName;
}
}
}

View File

@@ -1,31 +1,40 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 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%
*/
/*
* #%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.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.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -33,11 +42,20 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
import org.alfresco.model.ContentModel;
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.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.search.SimpleResultSetMetaData;
import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet;
import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext;
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.QueryModelFactory;
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.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.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.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
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.springframework.util.StopWatch;
/**
* @author Andy
*/
@NotThreadSafe
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 SqlSessionTemplate template;
protected SqlSessionTemplate template;
private QNameDAO qnameDAO;
protected QNameDAO qnameDAO;
private NodeDAO nodeDAO;
@@ -79,6 +113,45 @@ public class DBQueryEngine implements QueryEngine
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)
{
this.metadataIndexCheck2 = metadataIndexCheck2;
@@ -147,6 +220,9 @@ public class DBQueryEngine implements QueryEngine
@Override
public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext)
{
logger.debug("Query request received");
resetStopwatches();
Set<String> selectorGroup = null;
if (query.getSource() != null)
{
@@ -165,13 +241,9 @@ public class DBQueryEngine implements QueryEngine
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;
if(options.getStores().size() > 1)
if (options.getStores().size() > 1)
{
throw new QueryModelException("Multi-store queries are not supported");
}
@@ -179,15 +251,15 @@ public class DBQueryEngine implements QueryEngine
// MT
StoreRef storeRef = options.getStores().get(0);
storeRef = storeRef != null ? tenantService.getName(storeRef) : null;
Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef);
if(store == null)
{
throw new QueryModelException("Unknown store: "+storeRef);
Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef);
if (store == null)
{
throw new QueryModelException("Unknown store: "+storeRef);
}
dbQuery.setStoreId(store.getFirst());
Pair<Long, QName> sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED);
if(sysDeletedType == null)
if (sysDeletedType == null)
{
dbQuery.setSysDeletedType(-1L);
}
@@ -204,20 +276,204 @@ public class DBQueryEngine implements QueryEngine
}
dbQuery.setSinceTxId(sinceTxId);
dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup, null, functionContext, metadataIndexCheck2.getPatchApplied());
List<Node> nodes = template.selectList(SELECT_BY_DYNAMIC_QUERY, dbQuery);
LinkedHashSet<Long> set = new LinkedHashSet<Long>(nodes.size());
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);
logger.debug("- query is being prepared");
dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup,
null, functionContext, metadataIndexCheck2.getPatchApplied());
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);
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)
@@ -229,4 +485,242 @@ public class DBQueryEngine implements QueryEngine
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;
}
}

View File

@@ -1,35 +1,35 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 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%
*/
/*
* #%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 java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import org.alfresco.repo.domain.node.Node;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.search.AbstractResultSet;
import org.alfresco.repo.search.SimpleResultSetMetaData;
@@ -41,8 +41,7 @@ import org.alfresco.service.cmr.search.LimitBy;
import org.alfresco.service.cmr.search.PermissionEvaluationMode;
import org.alfresco.service.cmr.search.ResultSetMetaData;
import org.alfresco.service.cmr.search.ResultSetRow;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.util.Pair;
import org.alfresco.service.cmr.search.SearchParameters;
/**
* @author Andy
@@ -50,9 +49,7 @@ import org.alfresco.util.Pair;
*/
public class DBResultSet extends AbstractResultSet
{
private List<Long> dbids;
private NodeRef[] nodeRefs;
private List<Node> nodes;
private NodeDAO nodeDao;
@@ -64,14 +61,16 @@ public class DBResultSet extends AbstractResultSet
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.dbids = dbids;
this.nodes = nodes;
this.nodeService = nodeService;
this.tenantService = tenantService;
this.prefetch = new BitSet(dbids.size());
nodeRefs= new NodeRef[(dbids.size())];
this.prefetch = new BitSet(nodes.size());
this.numberFound = nodes.size();
final LimitBy limitBy;
int maxResults = -1;
@@ -96,7 +95,7 @@ public class DBResultSet extends AbstractResultSet
}
this.resultSetMetaData = new SimpleResultSetMetaData(
maxResults > 0 && dbids.size() < maxResults ? LimitBy.UNLIMITED : limitBy,
maxResults > 0 && nodes.size() < maxResults ? LimitBy.UNLIMITED : limitBy,
PermissionEvaluationMode.EAGER, searchParameters);
}
@@ -106,7 +105,12 @@ public class DBResultSet extends AbstractResultSet
@Override
public int length()
{
return dbids.size();
return nodes.size();
}
public void setNumberFound(int numFound)
{
this.numberFound = numFound;
}
/* (non-Javadoc)
@@ -115,7 +119,7 @@ public class DBResultSet extends AbstractResultSet
@Override
public long getNumberFound()
{
return dbids.size();
return numberFound;
}
/* (non-Javadoc)
@@ -124,8 +128,9 @@ public class DBResultSet extends AbstractResultSet
@Override
public NodeRef getNodeRef(int n)
{
prefetch(n);
return nodeRefs[n];
NodeRef nodeRef = nodes.get(n).getNodeRef();
nodeRef = tenantService.getBaseName(nodeRef);
return nodeRef;
}
/* (non-Javadoc)
@@ -190,61 +195,13 @@ public class DBResultSet extends AbstractResultSet
return new DBResultSetRowIterator(this);
}
private void prefetch(int n)
public NodeService getNodeService()
{
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()
{
return nodeService;
}
return nodeService;
}
public Node getNode(int n)
{
return nodes.get(n);
}
}

View File

@@ -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();
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* 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
@@ -240,7 +240,8 @@ public class PropertySupport implements DBQueryBuilderComponent
predicatePartCommands.add(command);
break;
}
command.setQName(propertyQName);
}
else
{
@@ -350,11 +351,11 @@ public class PropertySupport implements DBQueryBuilderComponent
command.setFieldName(fieldName);
command.setFunction(luceneFunction);
command.setQName(propertyQName);
predicatePartCommands.add(command);
}
}
/**
* @param luceneFunction LuceneFunction
@@ -371,5 +372,4 @@ public class PropertySupport implements DBQueryBuilderComponent
{
this.leftOuter = leftOuter;
}
}

View File

@@ -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);
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* 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
@@ -53,7 +53,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck1;
OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
/**
* @param metadataIndexCheck1 the metadataIndexCheck1 to set
@@ -63,7 +62,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
this.metadataIndexCheck1 = metadataIndexCheck1;
}
/**
* @param metadataIndexCheck2 the metadataIndexCheck2 to set
*/
@@ -72,7 +70,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
this.metadataIndexCheck2 = metadataIndexCheck2;
}
/**
* Set the query engine
*
@@ -82,7 +79,6 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
{
this.queryEngine = queryEngine;
}
/**
* @param cmisDictionaryService the cmisDictionaryService to set
@@ -109,8 +105,7 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
throw new QueryModelException("The patch to add the indexes to support in-transactional metadata queries has not been applied");
}
}
private ResultSet executeQueryImpl(SearchParameters searchParameters)
{
CMISQueryOptions options = CMISQueryOptions.create(searchParameters);
@@ -123,25 +118,8 @@ public class DbCmisQueryLanguage extends AbstractLuceneQueryLanguage
functionContext.setValidScopes(validScopes);
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);
ResultSet resultSet = results.getResults().values().iterator().next();
return resultSet;

View File

@@ -4,8 +4,8 @@
<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">
<include refid="sql_select_byDynamicQuery"/>
<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"/>
</select>
</mapper>

View File

@@ -6,9 +6,26 @@
<sql id="sql_select_byDynamicQuery">
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
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">
<choose>
<when test="item.type == 'PARENT'">

View File

@@ -4,7 +4,7 @@
<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"/>
</select>

View File

@@ -107,6 +107,7 @@
</bean>
<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="namespaceService" ref="namespaceService" />
<property name="sqlSessionTemplate" ref="repoSqlSessionTemplate"/>
@@ -114,9 +115,19 @@
<property name="nodeService" ref="nodeService"/>
<property name="nodeDAO" ref="nodeDAO"/>
<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">
<ref bean="metadataQueryIndexesCheck2" />
</property>
<property name="maxPermissionChecks">
<value>${system.acl.maxPermissionChecks}</value>
</property>
<property name="maxPermissionCheckTimeMillis">
<value>${system.acl.maxPermissionCheckTimeMillis}</value>
</property>
</bean>
<bean id="search.dbQueryEngine" class="org.springframework.aop.framework.ProxyFactoryBean">

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* 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
@@ -74,6 +74,7 @@ import org.alfresco.util.test.junitrules.TemporaryNodes;
import org.alfresco.util.testing.category.LuceneTests;
import org.apache.commons.codec.binary.Base64;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -192,6 +193,22 @@ public class QuickShareServiceIntegrationTest
user1.getUsername(),
"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()
{
@@ -532,7 +549,8 @@ public class QuickShareServiceIntegrationTest
}, user1.getUsername());
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);
}
/**

View File

@@ -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;
}
}

View File

@@ -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;
}
}