mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-09-17 14:21:39 +00:00
ACS-247 Query accelerator (#234)
Provides hook points for the query accelerator in alfresco-enterprise-repo. In addition to these, it also includes temporary code to add timing headers to REST API calls. These will be removed as part of REPO-5376. Commits mainly by Bruno and Nana. Merging from old projects and changes from master by Alan.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
import org.alfresco.repo.cache.lookup.EntityLookupCache;
|
||||
import org.alfresco.repo.domain.node.Node;
|
||||
import org.alfresco.repo.domain.node.NodeVersionKey;
|
||||
import org.alfresco.repo.domain.node.StoreEntity;
|
||||
import org.alfresco.repo.domain.permissions.AuthorityEntity;
|
||||
import org.alfresco.repo.search.impl.querymodel.QueryOptions;
|
||||
import org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine.NodePermissionAssessor;
|
||||
import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.ibatis.executor.result.DefaultResultContext;
|
||||
import org.apache.ibatis.session.ResultContext;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
|
||||
public class DBQueryEngineTest
|
||||
{
|
||||
private static final String SQL_TEMPLATE_PATH = "alfresco.metadata.query.select_byDynamicQuery";
|
||||
|
||||
private DBQueryEngine engine;
|
||||
private SqlSessionTemplate template;
|
||||
private NodePermissionAssessor assessor;
|
||||
private DBQuery dbQuery;
|
||||
private ResultContext<Node> resultContext;
|
||||
private QueryOptions options;
|
||||
private SimpleCache<NodeVersionKey, Map<QName, Serializable>> propertiesCache;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
engine = new DBQueryEngine();
|
||||
assessor = mock(NodePermissionAssessor.class);
|
||||
dbQuery = mock(DBQuery.class);
|
||||
resultContext = spy(new DefaultResultContext<>());
|
||||
options = createQueryOptions();
|
||||
|
||||
template = mock(SqlSessionTemplate.class);
|
||||
engine.setSqlSessionTemplate(template);
|
||||
|
||||
propertiesCache = mock(SimpleCache.class);
|
||||
engine.setPropertiesCache(propertiesCache);
|
||||
|
||||
engine.nodesCache = mock(EntityLookupCache.class);
|
||||
|
||||
DBStats.resetStopwatches();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetAFilteringResultSetFromAcceleratedNodeSelection()
|
||||
{
|
||||
withMaxItems(10);
|
||||
|
||||
ResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||
|
||||
assertTrue(result instanceof FilteringResultSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldResultSetHaveExpectedAmountOfRequiredNodesBasedOnMaxItems()
|
||||
{
|
||||
withMaxItems(5);
|
||||
prepareTemplate(dbQuery, createNodes(20));
|
||||
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||
|
||||
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||
|
||||
assertEquals(6, result.length());
|
||||
assertNodePresent(0, result);
|
||||
assertNodePresent(1, result);
|
||||
assertNodePresent(2, result);
|
||||
assertNodePresent(3, result);
|
||||
assertNodePresent(4, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldResultContextBeClosedWhenMaxItemsReached()
|
||||
{
|
||||
withMaxItems(5);
|
||||
prepareTemplate(dbQuery, createNodes(20));
|
||||
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||
|
||||
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||
|
||||
verify(resultContext).stop();
|
||||
assertEquals(6, result.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldResultSetHaveCorrectAmountOfRequiredNodesWhenSkipCountIsUsed()
|
||||
{
|
||||
withMaxItems(5);
|
||||
withSkipCount(10);
|
||||
prepareTemplate(dbQuery, createNodes(20));
|
||||
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||
|
||||
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||
|
||||
assertEquals(6, result.length());
|
||||
assertNodePresent(10, result);
|
||||
assertNodePresent(11, result);
|
||||
assertNodePresent(12, result);
|
||||
assertNodePresent(13, result);
|
||||
assertNodePresent(14, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldResultSetHaveCorrectAmountOfRequiredNodesWhenSomeAreExcludedDueToDeclinedPermission()
|
||||
{
|
||||
withMaxItems(5);
|
||||
List<Node> nodes = createNodes(20);
|
||||
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||
when(assessor.isIncluded(nodes.get(0))).thenReturn(false);
|
||||
when(assessor.isIncluded(nodes.get(1))).thenReturn(false);
|
||||
when(assessor.isIncluded(nodes.get(2))).thenReturn(false);
|
||||
prepareTemplate(dbQuery, nodes);
|
||||
|
||||
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||
|
||||
assertEquals(6, result.length());
|
||||
assertNodePresent(3, result);
|
||||
assertNodePresent(4, result);
|
||||
assertNodePresent(5, result);
|
||||
assertNodePresent(6, result);
|
||||
assertNodePresent(7, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotConsiderInaccessibleNodesInResultSetWhenSkippingNodes()
|
||||
{
|
||||
withMaxItems(2);
|
||||
withSkipCount(2);
|
||||
List<Node> nodes = createNodes(6);
|
||||
when(assessor.isIncluded(any(Node.class))).thenReturn(true);
|
||||
when(assessor.isIncluded(nodes.get(2))).thenReturn(false);
|
||||
when(assessor.isIncluded(nodes.get(3))).thenReturn(false);
|
||||
prepareTemplate(dbQuery, nodes);
|
||||
|
||||
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||
|
||||
assertEquals(2, result.length());
|
||||
assertNodePresent(4, result);
|
||||
assertNodePresent(5, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldQuitCheckingNodePermissionsWhenImposedLimitsAreReached()
|
||||
{
|
||||
prepareTemplate(dbQuery, createNodes(20));
|
||||
when(assessor.shouldQuitChecks()).thenReturn(true);
|
||||
|
||||
FilteringResultSet result = engine.acceleratedNodeSelection(options, dbQuery, assessor);
|
||||
|
||||
assertEquals(0, result.length());
|
||||
verify(resultContext).stop();
|
||||
}
|
||||
|
||||
private void prepareTemplate(DBQuery dbQuery, List<Node> nodes)
|
||||
{
|
||||
doAnswer(invocation -> {
|
||||
ResultHandler<Node> handler = (ResultHandler<Node>)invocation.getArgument(2);
|
||||
for (Node node: nodes)
|
||||
{
|
||||
if (!resultContext.isStopped())
|
||||
{
|
||||
when(resultContext.getResultObject()).thenReturn(node);
|
||||
handler.handleResult(resultContext);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}).when(template).select(eq(SQL_TEMPLATE_PATH), eq(dbQuery), any());
|
||||
}
|
||||
|
||||
private QueryOptions createQueryOptions()
|
||||
{
|
||||
QueryOptions options = mock(QueryOptions.class);
|
||||
SearchParameters searchParams = mock(SearchParameters.class);
|
||||
when(options.getAsSearchParmeters()).thenReturn(searchParams);
|
||||
return options;
|
||||
}
|
||||
|
||||
private void assertNodePresent(long id, FilteringResultSet result)
|
||||
{
|
||||
DBResultSet rs = (DBResultSet)result.getUnFilteredResultSet();
|
||||
for(int i = 0; i < rs.length(); i++)
|
||||
{
|
||||
if(rs.getNode(i).getId().equals(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("Node with id " + id + " was not found in the result set");
|
||||
}
|
||||
|
||||
private void withMaxItems(int maxItems)
|
||||
{
|
||||
when(options.getMaxItems()).thenReturn(maxItems);
|
||||
}
|
||||
|
||||
private void withSkipCount(int skipCount)
|
||||
{
|
||||
when(options.getSkipCount()).thenReturn(skipCount);
|
||||
}
|
||||
|
||||
private List<Node> createNodes(int amount)
|
||||
{
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
|
||||
for(int i = 0; i < amount; i++)
|
||||
{
|
||||
nodes.add(createNode(i));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private Node createNode(int id)
|
||||
{
|
||||
Node node = spy(Node.class);
|
||||
|
||||
when(node.getId()).thenReturn((long)id);
|
||||
|
||||
StoreEntity store = mock(StoreEntity.class);
|
||||
when(node.getStore()).thenReturn(store );
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.search.impl.querymodel.impl.db;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.alfresco.repo.domain.node.Node;
|
||||
import org.alfresco.repo.domain.permissions.AclCrudDAO;
|
||||
import org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine.NodePermissionAssessor;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class NodePermissionAssessorTest
|
||||
{
|
||||
private NodePermissionAssessor assessor;
|
||||
private Node node;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
node = mock(Node.class);
|
||||
assessor = createAssessor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotQuitAssessingPermissionsWhenMaxPermissionChecksLimitIsNotReached()
|
||||
{
|
||||
assessor.setMaxPermissionChecks(5);
|
||||
|
||||
performChecks(3);
|
||||
|
||||
assertFalse(assessor.shouldQuitChecks());
|
||||
verify(assessor, times(3)).isReallyIncluded(node);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldQuitAssessingPermissionsWhenMaxPermissionChecksLimitIsReached()
|
||||
{
|
||||
assessor.setMaxPermissionChecks(5);
|
||||
|
||||
performChecks(20);
|
||||
|
||||
assertTrue(assessor.shouldQuitChecks());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAssessPermissionsWhenMaxPermissionCheckTimeIsUp() throws Exception
|
||||
{
|
||||
assessor.setMaxPermissionCheckTimeMillis(100);
|
||||
|
||||
assessor.isIncluded(node);
|
||||
Thread.sleep(200);
|
||||
|
||||
assertTrue(assessor.shouldQuitChecks());
|
||||
verify(assessor).isReallyIncluded(node);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAssessPermissionsWhenMaxPermissionCheckTimeIsNotUp() throws Exception
|
||||
{
|
||||
assessor.setMaxPermissionCheckTimeMillis(500);
|
||||
Thread.sleep(200);
|
||||
|
||||
assessor.isIncluded(node);
|
||||
|
||||
assertFalse(assessor.shouldQuitChecks());
|
||||
verify(assessor, atLeastOnce()).isReallyIncluded(node);
|
||||
|
||||
}
|
||||
|
||||
private void performChecks(int checks)
|
||||
{
|
||||
for (int i=0; i < checks; i++)
|
||||
{
|
||||
assessor.isIncluded(node);
|
||||
}
|
||||
}
|
||||
|
||||
private NodePermissionAssessor createAssessor()
|
||||
{
|
||||
AclCrudDAO aclCrudDAO = mock(AclCrudDAO.class);
|
||||
PermissionService permissionService = mock(PermissionService.class);
|
||||
|
||||
DBQueryEngine engine = new DBQueryEngine();
|
||||
engine.setPermissionService(permissionService);
|
||||
engine.setAclCrudDAO(aclCrudDAO);
|
||||
|
||||
NodePermissionAssessor assessor = spy(engine.new NodePermissionAssessor());
|
||||
doReturn(true).when(assessor).isReallyIncluded(any(Node.class));
|
||||
return assessor;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user