diff --git a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java index c252af5f9b..574ce8feec 100644 --- a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java +++ b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java @@ -1,460 +1,480 @@ -/* - * #%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 . - * #L% - */ -package org.alfresco.repo.search.impl.querymodel.impl.db; - -import static org.alfresco.repo.domain.node.AbstractNodeDAOImpl.CACHE_REGION_NODES; - -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.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.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; -import org.alfresco.repo.search.impl.querymodel.QueryEngine; -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.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.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; - -/** - * @author Andy - */ -@NotThreadSafe -public class DBQueryEngine implements QueryEngine -{ - protected static final Log logger = LogFactory.getLog(DBQueryEngine.class); - - protected static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery"; - - protected SqlSessionTemplate template; - - protected QNameDAO qnameDAO; - - private NodeDAO nodeDAO; - - protected DictionaryService dictionaryService; - - protected NamespaceService namespaceService; - - protected NodeService nodeService; - - private TenantService tenantService; - - private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2; - - protected PermissionService permissionService; - - private int maxPermissionChecks; - - private long maxPermissionCheckTimeMillis; - - protected EntityLookupCache nodesCache; - - 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; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) - { - this.template = sqlSessionTemplate; - } - - /** - * @param qnameDAO - * the qnameDAO to set - */ - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - /** - * @param dictionaryService - * the dictionaryService to set - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @param namespaceService - * the namespaceService to set - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * @param nodeService the nodeService to set - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param nodeDAO the nodeDAO to set - */ - public void setNodeDAO(NodeDAO nodeDAO) - { - this.nodeDAO = nodeDAO; - } - - /* - * (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, - * org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext) - */ - @Override - public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext) - { - long start = 0; - if (logger.isDebugEnabled()) - { - start = System.currentTimeMillis(); - logger.debug("Query request received"); - } - - Set selectorGroup = null; - if (query.getSource() != null) - { - List> selectorGroups = query.getSource().getSelectorGroups(functionContext); - - if (selectorGroups.size() == 0) - { - throw new QueryModelException("No selectors"); - } - - if (selectorGroups.size() > 1) - { - throw new QueryModelException("Advanced join is not supported"); - } - - selectorGroup = selectorGroups.get(0); - } - - DBQuery dbQuery = (DBQuery)query; - - if (options.getStores().size() > 1) - { - throw new QueryModelException("Multi-store queries are not supported"); - } - - // MT - StoreRef storeRef = options.getStores().get(0); - storeRef = storeRef != null ? tenantService.getName(storeRef) : null; - - Pair store = nodeDAO.getStore(storeRef); - if (store == null) - { - throw new QueryModelException("Unknown store: "+storeRef); - } - dbQuery.setStoreId(store.getFirst()); - Pair sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED); - if (sysDeletedType == null) - { - dbQuery.setSysDeletedType(-1L); - } - else - { - dbQuery.setSysDeletedType(sysDeletedType.getFirst()); - } - - Long sinceTxId = options.getSinceTxId(); - if (sinceTxId == null) - { - // By default, return search results for all transactions. - sinceTxId = -1L; - } - dbQuery.setSinceTxId(sinceTxId); - - logger.debug("- query is being prepared"); - dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup, - null, functionContext, metadataIndexCheck2.getPatchApplied()); - - ResultSet resultSet; - resultSet = selectNodesWithPermissions(options, dbQuery); - if (logger.isDebugEnabled()) - { - long ms = System.currentTimeMillis() - start; - logger.debug("Selected " + resultSet.length() + " nodes with permission resolution in "+ms+" ms"); - } - 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 selectNodesWithPermissions(QueryOptions options, DBQuery dbQuery) - { - Authority authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser()); - - NodePermissionAssessor permissionAssessor = createAssessor(authority); - int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks(); - long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0 - ? maxPermissionCheckTimeMillis - : options.getMaxPermissionCheckTimeMillis(); - permissionAssessor.setMaxPermissionChecks(maxPermsChecks); - permissionAssessor.setMaxPermissionCheckTimeMillis(maxPermCheckTimeMillis); - - FilteringResultSet resultSet = acceleratedNodeSelection(options, dbQuery, permissionAssessor); - - PagingLuceneResultSet plrs = new PagingLuceneResultSet(resultSet, options.getAsSearchParmeters(), nodeService); - plrs.setTrimmedResultSet(true); - return plrs; - } - - protected NodePermissionAssessor createAssessor(Authority authority) - { - return new NodePermissionAssessor(nodeService, permissionService, authority, nodesCache); - } - - FilteringResultSet acceleratedNodeSelection(QueryOptions options, DBQuery dbQuery, NodePermissionAssessor permissionAssessor) - { - List nodes = new ArrayList<>(); - int requiredNodes = computeRequiredNodesCount(options); - - logger.debug("- query sent to the database"); - template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler() - { - @Override - public void handleResult(ResultContext context) - { - doHandleResult(permissionAssessor, nodes, requiredNodes, context); - } - - private void doHandleResult(NodePermissionAssessor permissionAssessor, List nodes, - int requiredNodes, ResultContext context) - { - 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; - } - } - }); - - 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 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 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 key = new HashSet<>(); - key.add(""); - Map, ResultSet> answer = new HashMap<>(); - answer.put(key, paged); - - return new QueryEngineResults(answer); - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory() - */ - @Override - public QueryModelFactory getQueryModelFactory() - { - return new DBQueryModelFactory(); - } - - /** - * Injection of nodes cache for clean-up and warm up when required - * @param cache The node cache to set - */ - public void setNodesCache(SimpleCache cache) - { - this.nodesCache = new EntityLookupCache<>( - cache, - CACHE_REGION_NODES, - new ReadonlyLocalCallbackDAO()); - } - - void setNodesCache(EntityLookupCache nodesCache) - { - this.nodesCache = nodesCache; - } - - private class ReadonlyLocalCallbackDAO extends EntityLookupCallbackDAOAdaptor - { - @Override - public Pair createValue(Node value) - { - throw new UnsupportedOperationException("Node creation is done externally: " + value); - } - - @Override - public Pair findByKey(Long nodeId) - { - return null; - } - - @Override - public NodeRef getValueKey(Node value) - { - return value.getNodeRef(); - } - } +/* + * #%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 . + * #L% + */ +package org.alfresco.repo.search.impl.querymodel.impl.db; + +import static org.alfresco.repo.domain.node.AbstractNodeDAOImpl.CACHE_REGION_NODES; + +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.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.StoreEntity; +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; +import org.alfresco.repo.search.impl.querymodel.QueryEngine; +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.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.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; + +/** + * @author Andy + */ +@NotThreadSafe +public class DBQueryEngine implements QueryEngine +{ + protected static final Log logger = LogFactory.getLog(DBQueryEngine.class); + + protected static final String SELECT_BY_DYNAMIC_QUERY = "alfresco.metadata.query.select_byDynamicQuery"; + + protected SqlSessionTemplate template; + + protected QNameDAO qnameDAO; + + private NodeDAO nodeDAO; + + protected DictionaryService dictionaryService; + + protected NamespaceService namespaceService; + + protected NodeService nodeService; + + private TenantService tenantService; + + private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2; + + protected PermissionService permissionService; + + private int maxPermissionChecks; + + private long maxPermissionCheckTimeMillis; + + protected EntityLookupCache nodesCache; + + 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; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) + { + this.template = sqlSessionTemplate; + } + + /** + * @param qnameDAO + * the qnameDAO to set + */ + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + /** + * @param dictionaryService + * the dictionaryService to set + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param namespaceService + * the namespaceService to set + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param nodeService the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param nodeDAO the nodeDAO to set + */ + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + /* + * (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, + * org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext) + */ + @Override + public QueryEngineResults executeQuery(Query query, QueryOptions options, FunctionEvaluationContext functionContext) + { + long start = 0; + if (logger.isDebugEnabled()) + { + start = System.currentTimeMillis(); + logger.debug("Query request received"); + } + + Set selectorGroup = null; + if (query.getSource() != null) + { + List> selectorGroups = query.getSource().getSelectorGroups(functionContext); + + if (selectorGroups.size() == 0) + { + throw new QueryModelException("No selectors"); + } + + if (selectorGroups.size() > 1) + { + throw new QueryModelException("Advanced join is not supported"); + } + + selectorGroup = selectorGroups.get(0); + } + + DBQuery dbQuery = (DBQuery)query; + + if (options.getStores().size() > 1) + { + throw new QueryModelException("Multi-store queries are not supported"); + } + + // MT + StoreRef storeRef = options.getStores().get(0); + storeRef = storeRef != null ? tenantService.getName(storeRef) : null; + + Pair store = nodeDAO.getStore(storeRef); + if (store == null) + { + throw new QueryModelException("Unknown store: "+storeRef); + } + dbQuery.setStoreId(store.getFirst()); + Pair sysDeletedType = qnameDAO.getQName(ContentModel.TYPE_DELETED); + if (sysDeletedType == null) + { + dbQuery.setSysDeletedType(-1L); + } + else + { + dbQuery.setSysDeletedType(sysDeletedType.getFirst()); + } + + Long sinceTxId = options.getSinceTxId(); + if (sinceTxId == null) + { + // By default, return search results for all transactions. + sinceTxId = -1L; + } + dbQuery.setSinceTxId(sinceTxId); + + logger.debug("- query is being prepared"); + dbQuery.prepare(namespaceService, dictionaryService, qnameDAO, nodeDAO, tenantService, selectorGroup, + null, functionContext, metadataIndexCheck2.getPatchApplied()); + + ResultSet resultSet; + resultSet = selectNodesWithPermissions(options, dbQuery); + if (logger.isDebugEnabled()) + { + long ms = System.currentTimeMillis() - start; + logger.debug("Selected " + resultSet.length() + " nodes with permission resolution in "+ms+" ms"); + } + 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 selectNodesWithPermissions(QueryOptions options, DBQuery dbQuery) + { + Authority authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser()); + + NodePermissionAssessor permissionAssessor = createAssessor(authority); + int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks(); + long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0 + ? maxPermissionCheckTimeMillis + : options.getMaxPermissionCheckTimeMillis(); + permissionAssessor.setMaxPermissionChecks(maxPermsChecks); + permissionAssessor.setMaxPermissionCheckTimeMillis(maxPermCheckTimeMillis); + + FilteringResultSet resultSet = acceleratedNodeSelection(options, dbQuery, permissionAssessor); + + PagingLuceneResultSet plrs = new PagingLuceneResultSet(resultSet, options.getAsSearchParmeters(), nodeService); + plrs.setTrimmedResultSet(true); + return plrs; + } + + protected NodePermissionAssessor createAssessor(Authority authority) + { + return new NodePermissionAssessor(nodeService, permissionService, authority, nodesCache); + } + + FilteringResultSet acceleratedNodeSelection(QueryOptions options, DBQuery dbQuery, NodePermissionAssessor permissionAssessor) + { + List nodes = new ArrayList<>(); + int requiredNodes = computeRequiredNodesCount(options); + + logger.debug("- query sent to the database"); + template.select(pickQueryTemplate(options, dbQuery), dbQuery, new ResultHandler() + { + @Override + public void handleResult(ResultContext context) + { + doHandleResult(permissionAssessor, nodes, requiredNodes, context); + } + + private void doHandleResult(NodePermissionAssessor permissionAssessor, List nodes, + int requiredNodes, ResultContext context) + { + if (nodes.size() >= requiredNodes) + { + context.stop(); + return; + } + + Node node = context.getResultObject(); + addStoreInfo(node); + + 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; + } + } + }); + + 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 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 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 key = new HashSet<>(); + key.add(""); + Map, ResultSet> answer = new HashMap<>(); + answer.put(key, paged); + + return new QueryEngineResults(answer); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.search.impl.querymodel.QueryEngine#getQueryModelFactory() + */ + @Override + public QueryModelFactory getQueryModelFactory() + { + return new DBQueryModelFactory(); + } + + /** + * Injection of nodes cache for clean-up and warm up when required + * @param cache The node cache to set + */ + public void setNodesCache(SimpleCache cache) + { + this.nodesCache = new EntityLookupCache<>( + cache, + CACHE_REGION_NODES, + new ReadonlyLocalCallbackDAO()); + } + + void setNodesCache(EntityLookupCache nodesCache) + { + this.nodesCache = nodesCache; + } + + private class ReadonlyLocalCallbackDAO extends EntityLookupCallbackDAOAdaptor + { + @Override + public Pair createValue(Node value) + { + throw new UnsupportedOperationException("Node creation is done externally: " + value); + } + + @Override + public Pair 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()); + List> stores = nodeDAO.getStores(); + for (Pair storeRefPair : stores) + { + if (storeEntity.getId() == storeRefPair.getFirst()) + { + StoreRef storeRef = storeRefPair.getSecond(); + storeEntity.setIdentifier(storeRef.getIdentifier()); + storeEntity.setProtocol(storeRef.getProtocol()); + logger.debug("Added store info" + storeEntity.toString()); + break; + } + } + } } \ No newline at end of file diff --git a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java index d9dd72dd81..de25ef22c2 100644 --- a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java +++ b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java @@ -37,15 +37,19 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.StoreEntity; import org.alfresco.repo.search.impl.querymodel.QueryOptions; import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.util.Pair; import org.apache.ibatis.executor.result.DefaultResultContext; import org.apache.ibatis.session.ResultContext; import org.apache.ibatis.session.ResultHandler; @@ -63,6 +67,7 @@ public class DBQueryEngineTest private DBQuery dbQuery; private ResultContext resultContext; private QueryOptions options; + private NodeDAO nodeDAO; @SuppressWarnings("unchecked") @Before @@ -78,6 +83,10 @@ public class DBQueryEngineTest engine.setSqlSessionTemplate(template); engine.nodesCache = mock(EntityLookupCache.class); + + nodeDAO = mock(NodeDAO.class); + engine.setNodeDAO(nodeDAO); + mockStores(); } @Test @@ -260,4 +269,11 @@ public class DBQueryEngineTest return node; } + + private void mockStores() + { + Pair spacesStore = new Pair<>(6L, new StoreRef("workspace://SpacesStore")); + List> stores = Arrays.asList(spacesStore); + when(nodeDAO.getStores()).thenReturn(stores); + } }