MNT-22385 Cmis query GetTotalNumItems is returning wrong value (#504)

* Changes made to correct the value of totalItems when performing a TMDQ

* Fixes after review

- Slight change was made to NodePermissionAssessor to log when permission
  limits are exceeded

* Now pre-computing maxPermissionChecks value as per review suggestion

(cherry picked from commit cb636d1140)
This commit is contained in:
Nana Insaidoo
2021-05-28 09:16:38 +01:00
committed by Nana Insaidoo
parent 664d0b9704
commit b9b41a10e8
5 changed files with 542 additions and 469 deletions

View File

@@ -1,460 +1,505 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2021 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
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
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.domain.node.AbstractNodeDAOImpl.CACHE_REGION_NODES;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Objects;
import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.NotThreadSafe;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.admin.patch.OptionalPatchApplicationCheckBootstrapBean; import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.admin.patch.OptionalPatchApplicationCheckBootstrapBean;
import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor; import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.Node;
import org.alfresco.repo.domain.permissions.AclCrudDAO; import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.permissions.Authority; import org.alfresco.repo.domain.node.StoreEntity;
import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.domain.permissions.AclCrudDAO;
import org.alfresco.repo.search.SimpleResultSetMetaData; import org.alfresco.repo.domain.permissions.Authority;
import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet; import org.alfresco.repo.domain.qname.QNameDAO;
import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext; import org.alfresco.repo.search.SimpleResultSetMetaData;
import org.alfresco.repo.search.impl.querymodel.Query; import org.alfresco.repo.search.impl.lucene.PagingLuceneResultSet;
import org.alfresco.repo.search.impl.querymodel.QueryEngine; import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext;
import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; import org.alfresco.repo.search.impl.querymodel.Query;
import org.alfresco.repo.search.impl.querymodel.QueryModelException; import org.alfresco.repo.search.impl.querymodel.QueryEngine;
import org.alfresco.repo.search.impl.querymodel.QueryModelFactory; import org.alfresco.repo.search.impl.querymodel.QueryEngineResults;
import org.alfresco.repo.search.impl.querymodel.QueryOptions; import org.alfresco.repo.search.impl.querymodel.QueryModelException;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.search.impl.querymodel.QueryModelFactory;
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet; import org.alfresco.repo.search.impl.querymodel.QueryOptions;
import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.search.LimitBy; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.PermissionEvaluationMode; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.LimitBy;
import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.search.PermissionEvaluationMode;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.namespace.QName; import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.util.Pair; import org.alfresco.service.namespace.NamespaceService;
import org.apache.commons.logging.Log; import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.LogFactory; import org.alfresco.util.Pair;
import org.apache.ibatis.session.ResultContext; import org.apache.commons.logging.Log;
import org.apache.ibatis.session.ResultHandler; import org.apache.commons.logging.LogFactory;
import org.mybatis.spring.SqlSessionTemplate; import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
/** import org.mybatis.spring.SqlSessionTemplate;
* @author Andy
*/ /**
@NotThreadSafe * @author Andy
public class DBQueryEngine implements QueryEngine */
{ @NotThreadSafe
protected static final Log logger = LogFactory.getLog(DBQueryEngine.class); public class DBQueryEngine implements QueryEngine
{
protected static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery"; protected static final Log logger = LogFactory.getLog(DBQueryEngine.class);
protected SqlSessionTemplate template; protected static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery";
protected QNameDAO qnameDAO; protected SqlSessionTemplate template;
private NodeDAO nodeDAO; protected QNameDAO qnameDAO;
protected DictionaryService dictionaryService; private NodeDAO nodeDAO;
protected NamespaceService namespaceService; protected DictionaryService dictionaryService;
protected NodeService nodeService; protected NamespaceService namespaceService;
private TenantService tenantService; protected NodeService nodeService;
private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2; private TenantService tenantService;
protected PermissionService permissionService; private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2;
private int maxPermissionChecks; protected PermissionService permissionService;
private long maxPermissionCheckTimeMillis; private int maxPermissionChecks;
protected EntityLookupCache<Long, Node, NodeRef> nodesCache; private long maxPermissionCheckTimeMillis;
AclCrudDAO aclCrudDAO; private boolean maxPermissionCheckEnabled;
public void setAclCrudDAO(AclCrudDAO aclCrudDAO) protected EntityLookupCache<Long, Node, NodeRef> nodesCache;
{
this.aclCrudDAO = aclCrudDAO; private List<Pair<Long, StoreRef>> stores;
}
AclCrudDAO aclCrudDAO;
public void setMaxPermissionChecks(int maxPermissionChecks)
{ public void setAclCrudDAO(AclCrudDAO aclCrudDAO)
this.maxPermissionChecks = maxPermissionChecks; {
} this.aclCrudDAO = aclCrudDAO;
}
public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)
{ public void setMaxPermissionChecks(int maxPermissionChecks)
this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis; {
} this.maxPermissionChecks = maxPermissionChecks;
}
public void setTemplate(SqlSessionTemplate template)
{ public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)
this.template = template; {
} this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis;
}
public void setPermissionService(PermissionService permissionService)
{ public void setMaxPermissionCheckEnabled(boolean maxPermissionCheckEnabled)
this.permissionService = permissionService; {
} this.maxPermissionCheckEnabled = maxPermissionCheckEnabled;
}
public void setMetadataIndexCheck2(OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2)
{ public void setTemplate(SqlSessionTemplate template)
this.metadataIndexCheck2 = metadataIndexCheck2; {
} this.template = template;
}
public void setTenantService(TenantService tenantService)
{ public void setPermissionService(PermissionService permissionService)
this.tenantService = tenantService; {
} this.permissionService = permissionService;
}
public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
{ public void setMetadataIndexCheck2(OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2)
this.template = sqlSessionTemplate; {
} this.metadataIndexCheck2 = metadataIndexCheck2;
}
/**
* @param qnameDAO public void setTenantService(TenantService tenantService)
* the qnameDAO to set {
*/ this.tenantService = tenantService;
public void setQnameDAO(QNameDAO qnameDAO) }
{
this.qnameDAO = qnameDAO; public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
} {
this.template = sqlSessionTemplate;
/** }
* @param dictionaryService
* the dictionaryService to set /**
*/ * @param qnameDAO
public void setDictionaryService(DictionaryService dictionaryService) * the qnameDAO to set
{ */
this.dictionaryService = dictionaryService; public void setQnameDAO(QNameDAO qnameDAO)
} {
this.qnameDAO = qnameDAO;
/** }
* @param namespaceService
* the namespaceService to set /**
*/ * @param dictionaryService
public void setNamespaceService(NamespaceService namespaceService) * the dictionaryService to set
{ */
this.namespaceService = namespaceService; public void setDictionaryService(DictionaryService dictionaryService)
} {
this.dictionaryService = dictionaryService;
/** }
* @param nodeService the nodeService to set
*/ /**
public void setNodeService(NodeService nodeService) * @param namespaceService
{ * the namespaceService to set
this.nodeService = nodeService; */
} public void setNamespaceService(NamespaceService namespaceService)
{
/** this.namespaceService = namespaceService;
* @param nodeDAO the nodeDAO to set }
*/
public void setNodeDAO(NodeDAO nodeDAO) /**
{ * @param nodeService the nodeService to set
this.nodeDAO = nodeDAO; */
} public void setNodeService(NodeService nodeService)
{
/* this.nodeService = nodeService;
* (non-Javadoc) }
* @see
* org.alfresco.repo.search.impl.querymodel.QueryEngine#executeQuery(org.alfresco.repo.search.impl.querymodel.Query, /**
* org.alfresco.repo.search.impl.querymodel.QueryOptions, * @param nodeDAO the nodeDAO to set
* org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext) */
*/ public void setNodeDAO(NodeDAO nodeDAO)
@Override {
public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext) this.nodeDAO = nodeDAO;
{ }
long start = 0;
if (logger.isDebugEnabled()) /*
{ * (non-Javadoc)
start = System.currentTimeMillis(); * @see
logger.debug("Query request received"); * org.alfresco.repo.search.impl.querymodel.QueryEngine#executeQuery(org.alfresco.repo.search.impl.querymodel.Query,
} * org.alfresco.repo.search.impl.querymodel.QueryOptions,
* org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext)
Set<String> selectorGroup = null; */
if (query.getSource() != null) @Override
{ public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext)
List<Set<String>> selectorGroups = query.getSource().getSelectorGroups(functionContext); {
long start = 0;
if (selectorGroups.size() == 0) if (logger.isDebugEnabled())
{ {
throw new QueryModelException("No selectors"); start = System.currentTimeMillis();
} logger.debug("Query request received");
}
if (selectorGroups.size() > 1)
{ Set<String> selectorGroup = null;
throw new QueryModelException("Advanced join is not supported"); if (query.getSource() != null)
} {
List<Set<String>> selectorGroups = query.getSource().getSelectorGroups(functionContext);
selectorGroup = selectorGroups.get(0);
} if (selectorGroups.size() == 0)
{
DBQuery dbQuery = (DBQuery)query; throw new QueryModelException("No selectors");
}
if (options.getStores().size() > 1)
{ if (selectorGroups.size() > 1)
throw new QueryModelException("Multi-store queries are not supported"); {
} throw new QueryModelException("Advanced join is not supported");
}
// MT
StoreRef storeRef = options.getStores().get(0); selectorGroup = selectorGroups.get(0);
storeRef = storeRef != null ? tenantService.getName(storeRef) : null; }
Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef); DBQuery dbQuery = (DBQuery)query;
if (store == null)
{ if (options.getStores().size() > 1)
throw new QueryModelException("Unknown store: "+storeRef); {
} throw new QueryModelException("Multi-store queries are not supported");
dbQuery.setStoreId(store.getFirst()); }
Pair<Long, QName> sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED);
if (sysDeletedType == null) // MT
{ StoreRef storeRef = options.getStores().get(0);
dbQuery.setSysDeletedType(-1L); storeRef = storeRef != null ? tenantService.getName(storeRef) : null;
}
else Pair<Long, StoreRef> store = nodeDAO.getStore(storeRef);
{ if (store == null)
dbQuery.setSysDeletedType(sysDeletedType.getFirst()); {
} throw new QueryModelException("Unknown store: "+storeRef);
}
Long sinceTxId = options.getSinceTxId(); dbQuery.setStoreId(store.getFirst());
if (sinceTxId == null) Pair<Long, QName> sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED);
{ if (sysDeletedType == null)
// By default, return search results for all transactions. {
sinceTxId = -1L; dbQuery.setSysDeletedType(-1L);
} }
dbQuery.setSinceTxId(sinceTxId); else
{
logger.debug("- query is being prepared"); dbQuery.setSysDeletedType(sysDeletedType.getFirst());
dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup, }
null, functionContext, metadataIndexCheck2.getPatchApplied());
Long sinceTxId = options.getSinceTxId();
ResultSet resultSet; if (sinceTxId == null)
resultSet = selectNodesWithPermissions(options, dbQuery); {
if (logger.isDebugEnabled()) // By default, return search results for all transactions.
{ sinceTxId = -1L;
long ms = System.currentTimeMillis() - start; }
logger.debug("Selected " + resultSet.length() + " nodes with permission resolution in "+ms+" ms"); dbQuery.setSinceTxId(sinceTxId);
}
return asQueryEngineResults(resultSet); logger.debug("- query is being prepared");
} dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup,
null, functionContext, metadataIndexCheck2.getPatchApplied());
protected String pickQueryTemplate(QueryOptions options, DBQuery dbQuery)
{ ResultSet resultSet;
logger.debug("- using standard table for the query"); resultSet = selectNodesWithPermissions(options, dbQuery);
return SELECT_BY_DYNAMIC_QUERY; if (logger.isDebugEnabled())
} {
long ms = System.currentTimeMillis() - start;
private ResultSet selectNodesWithPermissions(QueryOptions options, DBQuery dbQuery) logger.debug("Selected " + resultSet.length() + " nodes with permission resolution in "+ms+" ms");
{ }
Authority authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser()); return asQueryEngineResults(resultSet);
}
NodePermissionAssessor permissionAssessor = createAssessor(authority);
int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks(); protected String pickQueryTemplate(QueryOptions options, DBQuery dbQuery)
long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0 {
? maxPermissionCheckTimeMillis logger.debug("- using standard table for the query");
: options.getMaxPermissionCheckTimeMillis(); return SELECT_BY_DYNAMIC_QUERY;
permissionAssessor.setMaxPermissionChecks(maxPermsChecks); }
permissionAssessor.setMaxPermissionCheckTimeMillis(maxPermCheckTimeMillis);
private ResultSet selectNodesWithPermissions(QueryOptions options, DBQuery dbQuery)
FilteringResultSet resultSet = acceleratedNodeSelection(options, dbQuery, permissionAssessor); {
Authority authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser());
PagingLuceneResultSet plrs = new PagingLuceneResultSet(resultSet, options.getAsSearchParmeters(), nodeService);
plrs.setTrimmedResultSet(true); NodePermissionAssessor permissionAssessor = createAssessor(authority);
return plrs; int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks();
} long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0
? maxPermissionCheckTimeMillis
protected NodePermissionAssessor createAssessor(Authority authority) : options.getMaxPermissionCheckTimeMillis();
{ permissionAssessor.setMaxPermissionChecks(maxPermsChecks);
return new NodePermissionAssessor(nodeService, permissionService, authority, nodesCache); permissionAssessor.setMaxPermissionCheckTimeMillis(maxPermCheckTimeMillis);
}
FilteringResultSet resultSet = acceleratedNodeSelection(options, dbQuery, permissionAssessor);
FilteringResultSet acceleratedNodeSelection(QueryOptions options, DBQuery dbQuery, NodePermissionAssessor permissionAssessor)
{ PagingLuceneResultSet plrs = new PagingLuceneResultSet(resultSet, options.getAsSearchParmeters(), nodeService);
List<Node> nodes = new ArrayList<>(); plrs.setTrimmedResultSet(true);
int requiredNodes = computeRequiredNodesCount(options); return plrs;
}
logger.debug("- query sent to the database");
template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler<Node>() protected NodePermissionAssessor createAssessor(Authority authority)
{ {
@Override return new NodePermissionAssessor(nodeService, permissionService, authority, nodesCache);
public void handleResult(ResultContext<? extends Node> context) }
{
doHandleResult(permissionAssessor, nodes, requiredNodes, context); FilteringResultSet acceleratedNodeSelection(QueryOptions options, DBQuery dbQuery, NodePermissionAssessor permissionAssessor)
} {
// get list of stores from database
private void doHandleResult(NodePermissionAssessor permissionAssessor, List<Node> nodes, stores = nodeDAO.getStores();
int requiredNodes, ResultContext<? extends Node> context)
{ List<Node> nodes = new ArrayList<>();
if (nodes.size() >= requiredNodes) int requiredNodes = computeRequiredNodesCount(options);
{
context.stop(); logger.debug("- query sent to the database");
return; template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler<Node>()
} {
@Override
Node node = context.getResultObject(); public void handleResult(ResultContext<? extends Node> context)
{
boolean shouldCache = nodes.size() >= options.getSkipCount(); if (!maxPermissionCheckEnabled && nodes.size() >= requiredNodes)
if(shouldCache) {
{ context.stop();
logger.debug("- selected node "+nodes.size()+": "+node.getUuid()+" "+node.getId()); return;
nodesCache.setValue(node.getId(), node); }
}
else Node node = context.getResultObject();
{ addStoreInfo(node);
logger.debug("- skipped node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
} boolean shouldCache = shouldCache(options, nodes, requiredNodes);
if(shouldCache)
if (permissionAssessor.isIncluded(node)) {
{ logger.debug("- selected node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
nodes.add(shouldCache ? node : null); nodesCache.setValue(node.getId(), node);
} }
else
if (permissionAssessor.shouldQuitChecks()) {
{ logger.debug("- skipped node "+nodes.size()+": "+node.getUuid()+" "+node.getId());
context.stop(); }
return;
} if (permissionAssessor.isIncluded(node))
} {
}); if (nodes.size() > requiredNodes)
{
int numberFound = nodes.size(); nodes.add(node);
nodes.removeAll(Collections.singleton(null)); }
else
DBResultSet rs = createResultSet(options, nodes, numberFound); {
FilteringResultSet frs = new FilteringResultSet(rs, formInclusionMask(nodes)); nodes.add(shouldCache ? node : null);
frs.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.UNLIMITED, PermissionEvaluationMode.EAGER, rs.getResultSetMetaData().getSearchParameters())); }
}
logger.debug("- query is completed, "+nodes.size()+" nodes loaded");
return frs; if (permissionAssessor.shouldQuitChecks())
} {
context.stop();
private DBResultSet createResultSet(QueryOptions options, List<Node> nodes, int numberFound) return;
{ }
DBResultSet dbResultSet = new DBResultSet(options.getAsSearchParmeters(), nodes, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE); }
dbResultSet.setNumberFound(numberFound);
return dbResultSet; private boolean shouldCache(QueryOptions options, List<Node> nodes, int requiredNodes)
} {
if (nodes.size() > requiredNodes)
private int computeRequiredNodesCount(QueryOptions options) {
{ return false;
int maxItems = options.getMaxItems(); }
if (maxItems == -1 || maxItems == Integer.MAX_VALUE) else
{ {
return Integer.MAX_VALUE; return nodes.size() >= options.getSkipCount();
} }
}
return maxItems + options.getSkipCount() + 1; });
}
int numberFound = nodes.size();
private BitSet formInclusionMask(List<Node> nodes) nodes.removeAll(Collections.singleton(null));
{
BitSet inclusionMask = new BitSet(nodes.size()); DBResultSet rs = createResultSet(options, nodes, numberFound);
for (int i=0; i < nodes.size(); i++) FilteringResultSet frs = new FilteringResultSet(rs, formInclusionMask(nodes));
{ frs.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.UNLIMITED, PermissionEvaluationMode.EAGER, rs.getResultSetMetaData().getSearchParameters()));
inclusionMask.set(i, true);
} logger.debug("- query is completed, "+nodes.size()+" nodes loaded");
return inclusionMask; return frs;
} }
private DBResultSet createResultSet(QueryOptions options, List<Node> nodes, int numberFound)
private QueryEngineResults asQueryEngineResults(ResultSet paged) {
{ DBResultSet dbResultSet = new DBResultSet(options.getAsSearchParmeters(), nodes, nodeDAO, nodeService, tenantService, Integer.MAX_VALUE);
HashSet<String> key = new HashSet<>(); dbResultSet.setNumberFound(numberFound);
key.add(""); return dbResultSet;
Map<Set<String>, ResultSet> answer = new HashMap<>(); }
answer.put(key, paged);
private int computeRequiredNodesCount(QueryOptions options)
return new QueryEngineResults(answer); {
} int maxItems = options.getMaxItems();
if (maxItems == -1 || maxItems == Integer.MAX_VALUE)
/* {
* (non-Javadoc) return Integer.MAX_VALUE;
* @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory() }
*/
@Override return maxItems + options.getSkipCount() + 1;
public QueryModelFactory getQueryModelFactory() }
{
return new DBQueryModelFactory(); private BitSet formInclusionMask(List<Node> nodes)
} {
BitSet inclusionMask = new BitSet(nodes.size());
/** for (int i=0; i < nodes.size(); i++)
* Injection of nodes cache for clean-up and warm up when required {
* @param cache The node cache to set inclusionMask.set(i, true);
*/ }
public void setNodesCache(SimpleCache<Serializable, Serializable> cache) return inclusionMask;
{ }
this.nodesCache = new EntityLookupCache<>(
cache,
CACHE_REGION_NODES, private QueryEngineResults asQueryEngineResults(ResultSet paged)
new ReadonlyLocalCallbackDAO()); {
} HashSet<String> key = new HashSet<>();
key.add("");
void setNodesCache(EntityLookupCache<Long, Node, NodeRef> nodesCache) Map<Set<String>, ResultSet> answer = new HashMap<>();
{ answer.put(key, paged);
this.nodesCache = nodesCache;
} return new QueryEngineResults(answer);
}
private class ReadonlyLocalCallbackDAO extends EntityLookupCallbackDAOAdaptor<Long, Node, NodeRef>
{ /*
@Override * (non-Javadoc)
public Pair<Long, Node> createValue(Node value) * @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory()
{ */
throw new UnsupportedOperationException("Node creation is done externally: " + value); @Override
} public QueryModelFactory getQueryModelFactory()
{
@Override return new DBQueryModelFactory();
public Pair<Long, Node> findByKey(Long nodeId) }
{
return null; /**
} * Injection of nodes cache for clean-up and warm up when required
* @param cache The node cache to set
@Override */
public NodeRef getValueKey(Node value) public void setNodesCache(SimpleCache<Serializable, Serializable> cache)
{ {
return value.getNodeRef(); 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();
}
}
private void addStoreInfo(Node node)
{
StoreEntity storeEntity = node.getStore();
logger.debug("Adding store info for store id " + storeEntity.getId());
for (Pair<Long, StoreRef> storeRefPair : stores)
{
if (Objects.equals(storeEntity.getId(), storeRefPair.getFirst()))
{
StoreRef storeRef = storeRefPair.getSecond();
storeEntity.setIdentifier(storeRef.getIdentifier());
storeEntity.setProtocol(storeRef.getProtocol());
logger.debug("Added store info" + storeEntity.toString());
break;
}
}
}
}

View File

@@ -41,9 +41,13 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper; import org.alfresco.util.EqualsHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class NodePermissionAssessor public class NodePermissionAssessor
{ {
protected static final Log logger = LogFactory.getLog(NodePermissionAssessor.class);
private final boolean isSystemReading; private final boolean isSystemReading;
private final boolean isAdminReading; private final boolean isAdminReading;
private final boolean isNullReading; private final boolean isNullReading;
@@ -138,24 +142,31 @@ public class NodePermissionAssessor
public void setMaxPermissionChecks(int maxPermissionChecks) public void setMaxPermissionChecks(int maxPermissionChecks)
{ {
this.maxPermissionChecks = maxPermissionChecks; if (maxPermissionChecks == Integer.MAX_VALUE)
{
this.maxPermissionChecks = maxPermissionChecks;
}
else
{
this.maxPermissionChecks = maxPermissionChecks + 1;
}
} }
public boolean shouldQuitChecks() public boolean shouldQuitChecks()
{ {
boolean result = false;
if (checksPerformed >= maxPermissionChecks) if (checksPerformed >= maxPermissionChecks)
{ {
result = true; logger.warn("Maximum permission checks exceeded (" + maxPermissionChecks + ")");
return true;
} }
if ((System.currentTimeMillis() - startTime) >= maxPermissionCheckTimeMillis) if ((System.currentTimeMillis() - startTime) >= maxPermissionCheckTimeMillis)
{ {
result = true; logger.warn("Maximum permission checks time exceeded (" + maxPermissionCheckTimeMillis + ")");
return true;
} }
return result; return false;
} }
public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis) public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis)

View File

@@ -153,6 +153,7 @@ system.cache.parentAssocs.limitFactor=8
system.acl.maxPermissionCheckTimeMillis=10000 system.acl.maxPermissionCheckTimeMillis=10000
# The maximum number of search results to perform permission checks against # The maximum number of search results to perform permission checks against
system.acl.maxPermissionChecks=1000 system.acl.maxPermissionChecks=1000
system.acl.maxPermissionCheckEnabled=false
# The maximum number of filefolder list results # The maximum number of filefolder list results
system.filefolderservice.defaultListMaxResults=5000 system.filefolderservice.defaultListMaxResults=5000

View File

@@ -126,6 +126,9 @@
<property name="maxPermissionCheckTimeMillis"> <property name="maxPermissionCheckTimeMillis">
<value>${system.acl.maxPermissionCheckTimeMillis}</value> <value>${system.acl.maxPermissionCheckTimeMillis}</value>
</property> </property>
<property name="maxPermissionCheckEnabled">
<value>${system.acl.maxPermissionCheckEnabled}</value>
</property>
</bean> </bean>
<bean id="search.dbQueryEngine" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="search.dbQueryEngine" class="org.springframework.aop.framework.ProxyFactoryBean">

View File

@@ -158,7 +158,20 @@ public class DBQueryEngineTest
assertNodePresent(6, result); assertNodePresent(6, result);
assertNodePresent(7, result); assertNodePresent(7, result);
} }
@Test
public void shouldResultSetLengthMatchTheAmountOfAllAccessibleNodesWhenMaxPermissionCheckEnabled()
{
withMaxItems(5);
prepareTemplate(dbQuery, createNodes(10));
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
engine.setMaxPermissionCheckEnabled(true);
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
assertEquals(10, result.length());
}
@Test @Test
public void shouldNotConsiderInaccessibleNodesInResultSetWhenSkippingNodes() public void shouldNotConsiderInaccessibleNodesInResultSetWhenSkippingNodes()
{ {