diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index ba69f0e48f..78cb98dede 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -261,6 +261,7 @@ + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml index 73db1097e2..1b35589795 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml @@ -604,6 +604,19 @@ + + select prop.node_id as node_id, diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 3445389b2c..1f6d414257 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -7,7 +7,7 @@ version.major=4 version.minor=0 version.revision=0 -version.label=a +version.label=unstable # Edition label diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java index 65eba4d981..bc3d3f1e68 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java @@ -25,6 +25,10 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + import org.alfresco.model.ContentModel; import org.alfresco.repo.action.evaluator.NoConditionEvaluator; import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; @@ -40,8 +44,9 @@ import org.alfresco.repo.dictionary.M2Type; import org.alfresco.repo.rule.RuleModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionService; @@ -67,20 +72,26 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.BaseSpringTest; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.PropertyMap; +import org.springframework.context.ApplicationContext; import org.springframework.extensions.surf.util.I18NUtil; /** * Unit tests for copy service * * @author Roy Wetherall + * @author Derek Hulley */ -public class CopyServiceImplTest extends BaseSpringTest +public class CopyServiceImplTest extends TestCase { - /** + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + /* * Services used by the tests */ + private TransactionService transactionService; private NodeService nodeService; private NodeService publicNodeService; private CopyService copyService; @@ -93,9 +104,10 @@ public class CopyServiceImplTest extends BaseSpringTest private AuthenticationComponent authenticationComponent; private MutableAuthenticationService authenticationService; - /** + /* * Data used by the tests */ + private UserTransaction txn; private StoreRef storeRef; private NodeRef sourceNodeRef; private NodeRef rootNodeRef; @@ -104,7 +116,7 @@ public class CopyServiceImplTest extends BaseSpringTest private NodeRef childNodeRef; private NodeRef destinationNodeRef; - /** + /* * Types and properties used by the tests */ private static final String TEST_TYPE_NAMESPACE = "testTypeNamespaceURI"; @@ -145,52 +157,50 @@ public class CopyServiceImplTest extends BaseSpringTest */ private static final String SOME_CONTENT = "This is some content ..."; - /** - * Sets the meta model DAO - * - * @param dictionaryDAO the meta model DAO - */ - public void setDictionaryDAO(DictionaryDAO dictionaryDAO) - { - this.dictionaryDAO = dictionaryDAO; - } - - /** - * On setup in transaction implementation - */ @Override - protected void onSetUpInTransaction() - throws Exception + protected void setUp() throws Exception { - // Set the services - this.nodeService = (NodeService)this.applicationContext.getBean("dbNodeService"); - this.publicNodeService = (NodeService)this.applicationContext.getBean("NodeService"); - this.copyService = (CopyService)this.applicationContext.getBean("copyService"); - this.contentService = (ContentService)this.applicationContext.getBean("contentService"); - this.ruleService = (RuleService)this.applicationContext.getBean("ruleService"); - this.actionService = (ActionService)this.applicationContext.getBean("actionService"); - this.permissionService = (PermissionService)this.applicationContext.getBean("PermissionService"); - this.personService = (PersonService)this.applicationContext.getBean("PersonService"); - this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); - this.authenticationService = (MutableAuthenticationService) this.applicationContext.getBean("authenticationService"); + if (AlfrescoTransactionSupport.isActualTransactionActive()) + { + fail("Test started with transaction in progress"); + } - this.authenticationComponent.setSystemUserAsCurrentUser(); + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + // Set the services + transactionService = serviceRegistry.getTransactionService(); + nodeService = (NodeService) ctx.getBean("dbNodeService"); + publicNodeService = serviceRegistry.getNodeService(); + copyService = (CopyService) ctx.getBean("copyService"); + contentService = (ContentService) ctx.getBean("contentService"); + ruleService = (RuleService) ctx.getBean("ruleService"); + actionService = (ActionService)ctx.getBean("actionService"); + permissionService = (PermissionService)ctx.getBean("PermissionService"); + personService = serviceRegistry.getPersonService(); + authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); + authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); + dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO"); + + authenticationComponent.setSystemUserAsCurrentUser(); + + // Ensure that a transaction is present + txn = transactionService.getUserTransaction(); + txn.begin(); // Create the test model createTestModel(); // Create the store and get the root node reference - this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - this.rootNodeRef = this.nodeService.getRootNode(storeRef); + storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + rootNodeRef = nodeService.getRootNode(storeRef); // Create the node used for copying - ChildAssociationRef childAssocRef = this.nodeService.createNode( + ChildAssociationRef childAssocRef = nodeService.createNode( rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}test"), TEST_TYPE_QNAME, createTypePropertyBag()); - this.sourceNodeRef = childAssocRef.getChildRef(); + sourceNodeRef = childAssocRef.getChildRef(); // Create another bag of properties Map aspectProperties = new HashMap(); @@ -198,59 +208,59 @@ public class CopyServiceImplTest extends BaseSpringTest aspectProperties.put(PROP4_QNAME_OPTIONAL, TEST_VALUE_2); // Apply the test aspect - this.nodeService.addAspect( - this.sourceNodeRef, + nodeService.addAspect( + sourceNodeRef, TEST_ASPECT_QNAME, aspectProperties); - this.nodeService.addAspect(sourceNodeRef, ContentModel.ASPECT_TITLED, null); + nodeService.addAspect(sourceNodeRef, ContentModel.ASPECT_TITLED, null); // Add a child - ChildAssociationRef temp3 =this.nodeService.createNode( - this.sourceNodeRef, + ChildAssociationRef temp3 =nodeService.createNode( + sourceNodeRef, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME, TEST_TYPE_QNAME, createTypePropertyBag()); - this.childNodeRef = temp3.getChildRef(); + childNodeRef = temp3.getChildRef(); // Add a child that is primary - ChildAssociationRef temp2 = this.nodeService.createNode( + ChildAssociationRef temp2 = nodeService.createNode( rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}testNonPrimaryChild"), TEST_TYPE_QNAME, createTypePropertyBag()); - this.nonPrimaryChildNodeRef = temp2.getChildRef(); - this.nodeService.addChild( - this.sourceNodeRef, - this.nonPrimaryChildNodeRef, + nonPrimaryChildNodeRef = temp2.getChildRef(); + nodeService.addChild( + sourceNodeRef, + nonPrimaryChildNodeRef, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME2); // Add a target assoc - ChildAssociationRef temp = this.nodeService.createNode( + ChildAssociationRef temp = nodeService.createNode( rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}testAssoc"), TEST_TYPE_QNAME, createTypePropertyBag()); - this.targetNodeRef = temp.getChildRef(); - this.nodeService.createAssociation(this.sourceNodeRef, this.targetNodeRef, TEST_ASSOC_TYPE_QNAME); + targetNodeRef = temp.getChildRef(); + nodeService.createAssociation(sourceNodeRef, targetNodeRef, TEST_ASSOC_TYPE_QNAME); // Create a node we can use as the destination in a copy Map destinationProps = new HashMap(); destinationProps.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1); destinationProps.put(PROP5_QNAME_MANDATORY, TEST_VALUE_3); destinationProps.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - ChildAssociationRef temp5 = this.nodeService.createNode( - this.rootNodeRef, + ChildAssociationRef temp5 = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}testDestinationNode"), TEST_TYPE_QNAME, destinationProps); - this.destinationNodeRef = temp5.getChildRef(); + destinationNodeRef = temp5.getChildRef(); // Create two users, for use as part of // the permission related tests @@ -277,10 +287,13 @@ public class CopyServiceImplTest extends BaseSpringTest } @Override - protected void onTearDownInTransaction() throws Exception + protected void tearDown() throws Exception { + if (txn != null) + { + try { txn.rollback(); } catch (Throwable e) {} + } authenticationComponent.clearCurrentSecurityContext(); - super.onTearDownInTransaction(); } /** @@ -370,9 +383,9 @@ public class CopyServiceImplTest extends BaseSpringTest permissionService.setPermission(rootNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.CREATE_CHILDREN, true); assertEquals(3, permissionService.getAllSetPermissions(sourceNodeRef).size()); - NodeRef copy = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copy = copyService.copy( + sourceNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}aclCopyOne")); @@ -380,7 +393,7 @@ public class CopyServiceImplTest extends BaseSpringTest // Admin - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); copy = copyService.copy( sourceNodeRef, @@ -392,7 +405,7 @@ public class CopyServiceImplTest extends BaseSpringTest // guest - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); copy = copyService.copy( sourceNodeRef, @@ -403,9 +416,9 @@ public class CopyServiceImplTest extends BaseSpringTest // guest with read permissions - write from ownership - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.setPermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS, true); - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); copy = copyService.copy( sourceNodeRef, @@ -417,9 +430,9 @@ public class CopyServiceImplTest extends BaseSpringTest // guest with read and write - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.setPermission(rootNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.CHANGE_PERMISSIONS, true); - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); copy = copyService.copy( sourceNodeRef, @@ -431,9 +444,9 @@ public class CopyServiceImplTest extends BaseSpringTest // guest with write but not read - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.setPermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS, false); - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); copy = copyService.copy( sourceNodeRef, @@ -443,9 +456,9 @@ public class CopyServiceImplTest extends BaseSpringTest assertEquals(3, permissionService.getAllSetPermissions(copy).size()); - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.deletePermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS); - this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); + authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); copy = copyService.copy( sourceNodeRef, @@ -466,37 +479,37 @@ public class CopyServiceImplTest extends BaseSpringTest public void testCopyToNewNode() { // Check that the node has no copies - List copies = this.copyService.getCopies(this.sourceNodeRef); + List copies = copyService.getCopies(sourceNodeRef); assertNotNull(copies); assertTrue(copies.isEmpty()); // Copy to new node without copying children - NodeRef copy = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copy = copyService.copy( + sourceNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copyAssoc")); - checkCopiedNode(this.sourceNodeRef, copy, true, true, false); - List copies2 = this.copyService.getCopies(this.sourceNodeRef); + checkCopiedNode(sourceNodeRef, copy, true, true, false); + List copies2 = copyService.getCopies(sourceNodeRef); assertNotNull(copies2); assertEquals(1, copies2.size()); // Copy to new node, copying children - NodeRef copy2 = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copy2 = copyService.copy( + sourceNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copyAssoc2"), true); - checkCopiedNode(this.sourceNodeRef, copy2, true, true, true); - List copies3 = this.copyService.getCopies(this.sourceNodeRef); + checkCopiedNode(sourceNodeRef, copy2, true, true, true); + List copies3 = copyService.getCopies(sourceNodeRef); assertNotNull(copies3); assertEquals(2, copies3.size()); // Check that a copy of a copy works correctly - NodeRef copyOfCopy = this.copyService.copy( + NodeRef copyOfCopy = copyService.copy( copy, - this.rootNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copyOfCopy")); checkCopiedNode(copy, copyOfCopy, true, true, false); @@ -505,22 +518,22 @@ public class CopyServiceImplTest extends BaseSpringTest // TODO check copying from a lockable copy // Check copying from a node with content - ContentWriter contentWriter = this.contentService.getWriter(this.sourceNodeRef, ContentModel.PROP_CONTENT, true); + ContentWriter contentWriter = contentService.getWriter(sourceNodeRef, ContentModel.PROP_CONTENT, true); contentWriter.putContent(SOME_CONTENT); - NodeRef copyWithContent = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copyWithContent = copyService.copy( + sourceNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copyWithContent")); - checkCopiedNode(this.sourceNodeRef, copyWithContent, true, true, false); - ContentReader contentReader = this.contentService.getReader(copyWithContent, ContentModel.PROP_CONTENT); + checkCopiedNode(sourceNodeRef, copyWithContent, true, true, false); + ContentReader contentReader = contentService.getReader(copyWithContent, ContentModel.PROP_CONTENT); assertNotNull(contentReader); assertEquals(SOME_CONTENT, contentReader.getContentString()); // TODO check copying to a different store //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + // NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } public void testCopyNodeWithRules() @@ -531,38 +544,38 @@ public class CopyServiceImplTest extends BaseSpringTest Map props = new HashMap(1); props.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); - Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME, props); + Action action = actionService.createAction(AddFeaturesActionExecuter.NAME, props); rule.setAction(action); - ActionCondition actionCondition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + ActionCondition actionCondition = actionService.createActionCondition(NoConditionEvaluator.NAME); action.addActionCondition(actionCondition); - this.ruleService.saveRule(this.sourceNodeRef, rule); + ruleService.saveRule(sourceNodeRef, rule); assertNotNull(rule.getNodeRef()); - assertEquals(this.sourceNodeRef, this.ruleService.getOwningNodeRef(rule)); + assertEquals(sourceNodeRef, ruleService.getOwningNodeRef(rule)); //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + // NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); //System.out.println(" ------------------------------ "); // Now copy the node that has rules associated with it - NodeRef copy = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copy = copyService.copy( + sourceNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}withRulesCopy"), true); //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + // NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); - checkCopiedNode(this.sourceNodeRef, copy, true, true, true); + checkCopiedNode(sourceNodeRef, copy, true, true, true); - assertTrue(this.nodeService.hasAspect(copy, RuleModel.ASPECT_RULES)); - assertTrue(this.ruleService.hasRules(copy)); - assertTrue(this.ruleService.rulesEnabled(copy)); + assertTrue(nodeService.hasAspect(copy, RuleModel.ASPECT_RULES)); + assertTrue(ruleService.hasRules(copy)); + assertTrue(ruleService.rulesEnabled(copy)); - List copiedRules = this.ruleService.getRules(copy); + List copiedRules = ruleService.getRules(copy); assertEquals(1, copiedRules.size()); Rule copiedRule = copiedRules.get(0); @@ -570,44 +583,44 @@ public class CopyServiceImplTest extends BaseSpringTest assertFalse(copiedRule.getNodeRef().equals(rule.getNodeRef())); assertEquals(rule.getTitle(), copiedRule.getTitle()); assertEquals(rule.getDescription(), copiedRule.getDescription()); - assertEquals(copy, this.ruleService.getOwningNodeRef(copiedRule)); + assertEquals(copy, ruleService.getOwningNodeRef(copiedRule)); assertEquals(rule.getAction().getActionDefinitionName(), copiedRule.getAction().getActionDefinitionName()); // Now copy the node without copying the children and check that the rules have been copied - NodeRef copy2 = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copy2 = copyService.copy( + sourceNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}withRuleCopyNoChildren"), false); // System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + // NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); - checkCopiedNode(this.sourceNodeRef, copy2, true, true, false); + checkCopiedNode(sourceNodeRef, copy2, true, true, false); - //assertTrue(this.configurableService.isConfigurable(copy2)); - //assertNotNull(this.configurableService.getConfigurationFolder(copy2)); - //assertFalse(this.configurableService.getConfigurationFolder(this.sourceNodeRef) == this.configurableService.getConfigurationFolder(copy2)); + //assertTrue(configurableService.isConfigurable(copy2)); + //assertNotNull(configurableService.getConfigurationFolder(copy2)); + //assertFalse(configurableService.getConfigurationFolder(sourceNodeRef) == configurableService.getConfigurationFolder(copy2)); - assertTrue(this.nodeService.hasAspect(copy2, RuleModel.ASPECT_RULES)); - assertTrue(this.ruleService.hasRules(copy2)); - assertTrue(this.ruleService.rulesEnabled(copy2)); - List copiedRules2 = this.ruleService.getRules(copy2); + assertTrue(nodeService.hasAspect(copy2, RuleModel.ASPECT_RULES)); + assertTrue(ruleService.hasRules(copy2)); + assertTrue(ruleService.rulesEnabled(copy2)); + List copiedRules2 = ruleService.getRules(copy2); assertEquals(1, copiedRules.size()); Rule copiedRule2 = copiedRules2.get(0); assertFalse(rule.getNodeRef().equals(copiedRule2.getNodeRef())); assertEquals(rule.getTitle(), copiedRule2.getTitle()); assertEquals(rule.getDescription(), copiedRule2.getDescription()); - assertEquals(this.ruleService.getOwningNodeRef(copiedRule2), copy2); + assertEquals(ruleService.getOwningNodeRef(copiedRule2), copy2); assertEquals(rule.getAction().getActionDefinitionName(), copiedRule2.getAction().getActionDefinitionName()); } public void testCopyToExistingNode() { // Copy nodes within the same store - this.copyService.copy(this.sourceNodeRef, this.destinationNodeRef); - checkCopiedNode(this.sourceNodeRef, this.destinationNodeRef, false, true, true); + copyService.copy(sourceNodeRef, destinationNodeRef); + checkCopiedNode(sourceNodeRef, destinationNodeRef, false, true, true); // TODO check copying from a copy // TODO check copying from a versioned copy @@ -617,7 +630,7 @@ public class CopyServiceImplTest extends BaseSpringTest // TODO check copying nodes between stores //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + // NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } /** @@ -628,21 +641,21 @@ public class CopyServiceImplTest extends BaseSpringTest PropertyMap props = new PropertyMap(); // Need to create a potentially recursive node structure props.put(ContentModel.PROP_NODE_UUID, "nodeOne"); - NodeRef nodeOne = this.nodeService.createNode( - this.rootNodeRef, + NodeRef nodeOne = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTAINER, props).getChildRef(); props.put(ContentModel.PROP_NODE_UUID, "nodeTwo"); - NodeRef nodeTwo = this.nodeService.createNode( + NodeRef nodeTwo = nodeService.createNode( nodeOne, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTAINER, props).getChildRef(); props.put(ContentModel.PROP_NODE_UUID, "nodeThree"); - NodeRef nodeThree = this.nodeService.createNode( + NodeRef nodeThree = nodeService.createNode( nodeTwo, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, @@ -650,23 +663,23 @@ public class CopyServiceImplTest extends BaseSpringTest props).getChildRef(); // Issue a potentialy recursive copy - this.copyService.copy(nodeOne, nodeThree, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true); + copyService.copy(nodeOne, nodeThree, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true); //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + // NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } public void testCopyResidualProperties() throws Exception { QName nodeOneAssocName = QName.createQName("{test}nodeOne"); - NodeRef nodeOne = this.nodeService.createNode( - this.rootNodeRef, + NodeRef nodeOne = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CHILDREN, nodeOneAssocName, TEST_TYPE_QNAME).getChildRef(); - this.nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_NODE_REF, nodeOne); - this.nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_ANY, nodeOne); + nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_NODE_REF, nodeOne); + nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_ANY, nodeOne); NodeRef nodeOneCopy = copyService.copy( nodeOne, rootNodeRef, @@ -692,56 +705,56 @@ public class CopyServiceImplTest extends BaseSpringTest QName nodeThreeAssocName = QName.createQName("{test}nodeThree"); QName nodeFourAssocName = QName.createQName("{test}nodeFour"); - NodeRef nodeNotCopied = this.nodeService.createNode( - this.rootNodeRef, + NodeRef nodeNotCopied = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CHILDREN, nodeOneAssocName, TEST_TYPE_QNAME).getChildRef(); - NodeRef nodeOne = this.nodeService.createNode( - this.rootNodeRef, + NodeRef nodeOne = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CHILDREN, nodeOneAssocName, TEST_TYPE_QNAME).getChildRef(); - NodeRef nodeTwo = this.nodeService.createNode( + NodeRef nodeTwo = nodeService.createNode( nodeOne, TEST_CHILD_ASSOC_TYPE_QNAME, nodeTwoAssocName, TEST_TYPE_QNAME).getChildRef(); - NodeRef nodeThree = this.nodeService.createNode( + NodeRef nodeThree = nodeService.createNode( nodeTwo, TEST_CHILD_ASSOC_TYPE_QNAME, nodeThreeAssocName, TEST_TYPE_QNAME).getChildRef(); - NodeRef nodeFour = this.nodeService.createNode( + NodeRef nodeFour = nodeService.createNode( nodeOne, TEST_CHILD_ASSOC_TYPE_QNAME, nodeFourAssocName, TEST_TYPE_QNAME).getChildRef(); - this.nodeService.addChild(nodeFour, nodeThree, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME); - this.nodeService.createAssociation(nodeTwo, nodeThree, TEST_ASSOC_TYPE_QNAME); - this.nodeService.createAssociation(nodeTwo, nodeNotCopied, TEST_ASSOC_TYPE_QNAME); + nodeService.addChild(nodeFour, nodeThree, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME); + nodeService.createAssociation(nodeTwo, nodeThree, TEST_ASSOC_TYPE_QNAME); + nodeService.createAssociation(nodeTwo, nodeNotCopied, TEST_ASSOC_TYPE_QNAME); // Make node one actionable with a rule to copy nodes into node two Map params = new HashMap(1); params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, nodeTwo); Rule rule = new Rule(); rule.setRuleType(RuleType.INBOUND); - Action action = this.actionService.createAction(CopyActionExecuter.NAME, params); - ActionCondition condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME); + Action action = actionService.createAction(CopyActionExecuter.NAME, params); + ActionCondition condition = actionService.createActionCondition(NoConditionEvaluator.NAME); action.addActionCondition(condition); rule.setAction(action); - this.ruleService.saveRule(nodeOne, rule); + ruleService.saveRule(nodeOne, rule); // Do a deep copy - NodeRef nodeOneCopy = this.copyService.copy(nodeOne, this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copiedNodeOne"), true); + NodeRef nodeOneCopy = copyService.copy(nodeOne, rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copiedNodeOne"), true); NodeRef nodeTwoCopy = null; NodeRef nodeThreeCopy = null; NodeRef nodeFourCopy = null; //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + // NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); - List nodeOneCopyChildren = this.nodeService.getChildAssocs(nodeOneCopy); + List nodeOneCopyChildren = nodeService.getChildAssocs(nodeOneCopy); assertNotNull(nodeOneCopyChildren); assertEquals(3, nodeOneCopyChildren.size()); for (ChildAssociationRef nodeOneCopyChild : nodeOneCopyChildren) @@ -750,7 +763,7 @@ public class CopyServiceImplTest extends BaseSpringTest { nodeTwoCopy = nodeOneCopyChild.getChildRef(); - List nodeTwoCopyChildren = this.nodeService.getChildAssocs(nodeTwoCopy); + List nodeTwoCopyChildren = nodeService.getChildAssocs(nodeTwoCopy); assertNotNull(nodeTwoCopyChildren); assertEquals(1, nodeTwoCopyChildren.size()); for (ChildAssociationRef nodeTwoCopyChild : nodeTwoCopyChildren) @@ -771,7 +784,7 @@ public class CopyServiceImplTest extends BaseSpringTest assertNotNull(nodeFourCopy); // Check the non primary child assoc - List children = this.nodeService.getChildAssocs( + List children = nodeService.getChildAssocs( nodeFourCopy, RegexQNamePattern.MATCH_ALL, TEST_CHILD_ASSOC_QNAME); @@ -781,7 +794,7 @@ public class CopyServiceImplTest extends BaseSpringTest assertEquals(child.getChildRef(), nodeThree); // Check the target assoc - List assocs = this.nodeService.getTargetAssocs(nodeTwoCopy, TEST_ASSOC_TYPE_QNAME); + List assocs = nodeService.getTargetAssocs(nodeTwoCopy, TEST_ASSOC_TYPE_QNAME); assertNotNull(assocs); assertEquals(2, assocs.size()); AssociationRef assoc0 = assocs.get(0); @@ -790,7 +803,7 @@ public class CopyServiceImplTest extends BaseSpringTest assertTrue(assoc1.getTargetRef().equals(nodeThreeCopy) || assoc1.getTargetRef().equals(nodeNotCopied)); // Check that the rule parameter values have been made relative - List rules = this.ruleService.getRules(nodeOneCopy); + List rules = ruleService.getRules(nodeOneCopy); assertNotNull(rules); assertEquals(1, rules.size()); Rule copiedRule = rules.get(0); @@ -805,26 +818,26 @@ public class CopyServiceImplTest extends BaseSpringTest public void testCopyAndRename() { // Check a normal copy with no dup restrictions - NodeRef copy = this.copyService.copyAndRename( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copy = copyService.copyAndRename( + sourceNodeRef, + rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copyAssoc"), false); - checkCopiedNode(this.sourceNodeRef, copy, true, true, false); - assertTrue(TEST_NAME.equals(this.nodeService.getProperty(copy, ContentModel.PROP_NAME))); + checkCopiedNode(sourceNodeRef, copy, true, true, false); + assertTrue(TEST_NAME.equals(nodeService.getProperty(copy, ContentModel.PROP_NAME))); // Create a folder and content node Map propsFolder = new HashMap(1); propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); - NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); + NodeRef folderNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); Map props = new HashMap(1); props.put(ContentModel.PROP_NAME, TEST_NAME); - NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}renametest"), ContentModel.TYPE_CONTENT, props).getChildRef(); + NodeRef contentNode = nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}renametest"), ContentModel.TYPE_CONTENT, props).getChildRef(); // Now copy the content node with the duplicate name restriction - NodeRef contentCopy = this.copyService.copy(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}bobbins"), false); - assertFalse(TEST_NAME.equals(this.nodeService.getProperty(contentCopy, ContentModel.PROP_NAME))); + NodeRef contentCopy = copyService.copy(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}bobbins"), false); + assertFalse(TEST_NAME.equals(nodeService.getProperty(contentCopy, ContentModel.PROP_NAME))); } /** @@ -835,25 +848,25 @@ public class CopyServiceImplTest extends BaseSpringTest // Create a folder and content node Map propsFolder = new HashMap(1); propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); - NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); + NodeRef folderNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); Map props = new HashMap(1); props.put(ContentModel.PROP_NAME, "myDoc.txt"); - NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef(); + NodeRef contentNode = nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef(); - NodeRef copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); - assertEquals("Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); + NodeRef copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); + assertEquals("Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME)); QName copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of myDoc.txt"); - assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); + assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName()); - copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); - assertEquals("Copy of Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); + copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); + assertEquals("Copy of Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME)); copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of myDoc.txt"); - assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); + assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName()); - copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); - assertEquals("Copy of Copy of Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); + copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); + assertEquals("Copy of Copy of Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME)); copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of Copy of myDoc.txt"); - assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); + assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName()); } @@ -867,7 +880,7 @@ public class CopyServiceImplTest extends BaseSpringTest // Create a folder and content node Map propsFolder = new HashMap(1); propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); - NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); + NodeRef folderNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); Map props = new HashMap(1); props.put(ContentModel.PROP_NAME, "myDoc.txt"); @@ -883,12 +896,12 @@ public class CopyServiceImplTest extends BaseSpringTest description.addValue(Locale.ITALY, ITALY_DESCRIPTION); props.put(ContentModel.PROP_DESCRIPTION, description); - NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef(); + NodeRef contentNode = nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef(); - NodeRef copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); - assertEquals("Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); + NodeRef copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); + assertEquals("Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME)); QName copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of myDoc.txt"); - assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); + assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName()); // Test uses DB Node Service. Serializable desc = nodeService.getProperty(copy, ContentModel.PROP_DESCRIPTION); @@ -937,7 +950,7 @@ public class CopyServiceImplTest extends BaseSpringTest } catch(AccessDeniedException e) {} // Allow the read, but the destination won't accept it - this.authenticationComponent.setSystemUserAsCurrentUser(); + authenticationComponent.setSystemUserAsCurrentUser(); permissionService.setPermission(sourceNodeRef, USER_2, PermissionService.CONTRIBUTOR, true); permissionService.setPermission(targetNodeRef, USER_2, PermissionService.CONTRIBUTOR, false); AuthenticationUtil.setFullyAuthenticatedUser(USER_2); @@ -948,7 +961,7 @@ public class CopyServiceImplTest extends BaseSpringTest // Now allow on the destination, should go through - this.authenticationComponent.setSystemUserAsCurrentUser(); + authenticationComponent.setSystemUserAsCurrentUser(); permissionService.setPermission(targetNodeRef, USER_2, PermissionService.CONTRIBUTOR, true); AuthenticationUtil.setFullyAuthenticatedUser(USER_2); @@ -1053,9 +1066,9 @@ public class CopyServiceImplTest extends BaseSpringTest if (sameStore == true) { // Check that the copy aspect has been applied to the copy - boolean hasCopyAspect = this.nodeService.hasAspect(destinationNodeRef, ContentModel.ASPECT_COPIEDFROM); + boolean hasCopyAspect = nodeService.hasAspect(destinationNodeRef, ContentModel.ASPECT_COPIEDFROM); assertTrue("Missing aspect: " + ContentModel.ASPECT_COPIEDFROM, hasCopyAspect); - NodeRef copyNodeRef = (NodeRef)this.nodeService.getProperty(destinationNodeRef, ContentModel.PROP_COPY_REFERENCE); + NodeRef copyNodeRef = (NodeRef)nodeService.getProperty(destinationNodeRef, ContentModel.PROP_COPY_REFERENCE); assertNotNull(copyNodeRef); assertEquals(sourceNodeRef, copyNodeRef); } @@ -1066,11 +1079,11 @@ public class CopyServiceImplTest extends BaseSpringTest } } - boolean hasTestAspect = this.nodeService.hasAspect(destinationNodeRef, TEST_ASPECT_QNAME); + boolean hasTestAspect = nodeService.hasAspect(destinationNodeRef, TEST_ASPECT_QNAME); assertTrue(hasTestAspect); // Check that all the correct properties have been copied - Map destinationProperties = this.nodeService.getProperties(destinationNodeRef); + Map destinationProperties = nodeService.getProperties(destinationNodeRef); assertNotNull(destinationProperties); String value1 = (String)destinationProperties.get(PROP1_QNAME_MANDATORY); assertNotNull(value1); @@ -1086,18 +1099,18 @@ public class CopyServiceImplTest extends BaseSpringTest assertEquals(TEST_VALUE_2, value4); // Check all the target associations have been copied - List destinationTargets = this.nodeService.getTargetAssocs(destinationNodeRef, TEST_ASSOC_TYPE_QNAME); + List destinationTargets = nodeService.getTargetAssocs(destinationNodeRef, TEST_ASSOC_TYPE_QNAME); assertNotNull(destinationTargets); assertEquals(1, destinationTargets.size()); AssociationRef nodeAssocRef = destinationTargets.get(0); assertNotNull(nodeAssocRef); - assertEquals(this.targetNodeRef, nodeAssocRef.getTargetRef()); + assertEquals(targetNodeRef, nodeAssocRef.getTargetRef()); // Check all the child associations have been copied - List childAssocRefs = this.nodeService.getChildAssocs(destinationNodeRef); + List childAssocRefs = nodeService.getChildAssocs(destinationNodeRef); assertNotNull(childAssocRefs); int expectedSize = copyChildren ? 2 : 0; - if (this.nodeService.hasAspect(destinationNodeRef, RuleModel.ASPECT_RULES) == true) + if (nodeService.hasAspect(destinationNodeRef, RuleModel.ASPECT_RULES) == true) { expectedSize = expectedSize + 1; } @@ -1109,7 +1122,7 @@ public class CopyServiceImplTest extends BaseSpringTest { // Since this child is non-primary in the source it will always be non-primary in the destination assertFalse(ref.isPrimary()); - assertEquals(this.nonPrimaryChildNodeRef, ref.getChildRef()); + assertEquals(nonPrimaryChildNodeRef, ref.getChildRef()); } else { @@ -1118,18 +1131,18 @@ public class CopyServiceImplTest extends BaseSpringTest if (ref.getTypeQName().equals(RuleModel.ASSOC_RULE_FOLDER) == true) { assertTrue(ref.isPrimary()); - assertTrue(this.childNodeRef.equals(ref.getChildRef()) == false); + assertTrue(childNodeRef.equals(ref.getChildRef()) == false); } else { assertFalse(ref.isPrimary()); - assertEquals(this.childNodeRef, ref.getChildRef()); + assertEquals(childNodeRef, ref.getChildRef()); } } else { assertTrue(ref.isPrimary()); - assertTrue(this.childNodeRef.equals(ref.getChildRef()) == false); + assertTrue(childNodeRef.equals(ref.getChildRef()) == false); // TODO need to check that the copied child has all the correct details .. } diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 7d512d1d99..18e3d6931a 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -3146,7 +3146,70 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO /* * Bulk caching */ - + + // TODO there must be a way to limit the repeated code here and in cacheNodes(List) + public void cacheNodesById(List nodeIds) + { + /* + * ALF-2712: Performance degradation from 3.1.0 to 3.1.2 + * ALF-2784: Degradation of performance between 3.1.1 and 3.2x (observed in JSF) + * + * There is an obvious cost associated with querying the database to pull back nodes, + * and there is additional cost associated with putting the resultant entries into the + * caches. It is NO MORE expensive to check the cache than it is to put an entry into it + * - and probably cheaper considering cache replication - so we start checking nodes to see + * if they have entries before passing them over for batch loading. + * + * However, when running against a cold cache or doing a first-time query against some + * part of the repo, we will be checking for entries in the cache and consistently getting + * no results. To avoid unnecessary checking when the cache is PROBABLY cold, we + * examine the ratio of hits/misses at regular intervals. + */ + if (nodeIds.size() < 10) + { + // We only cache where the number of results is potentially + // a problem for the N+1 loading that might result. + return; + } + + int foundCacheEntryCount = 0; + int missingCacheEntryCount = 0; + boolean forceBatch = false; + + List batchLoadNodeIds = new ArrayList(nodeIds.size()); + for (Long nodeId : nodeIds) + { + if (!forceBatch) + { + // Is this node in the cache? + if (nodesCache.getValue(nodeId) != null) + { + foundCacheEntryCount++; // Don't add it to the batch + continue; + } + else + { + missingCacheEntryCount++; // Fall through and add it to the batch + } + if (foundCacheEntryCount + missingCacheEntryCount % 100 == 0) + { + // We force the batch if the number of hits drops below the number of misses + forceBatch = foundCacheEntryCount < missingCacheEntryCount; + } + } + + batchLoadNodeIds.add(nodeId); + } + + int size = batchLoadNodeIds.size(); + cacheNodesBatch(batchLoadNodeIds); + + if (logger.isDebugEnabled()) + { + logger.debug("Pre-loaded " + size + " nodes."); + } + } + /** * {@inheritDoc} *

@@ -3242,27 +3305,47 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO if (batch.size() >= batchSize) { // Preload - cacheNodesNoBatch(storeId, batch); + cacheNodesNoBatch(selectNodesByUuids(storeId, batch)); batch.clear(); } } // Load any remaining nodes if (batch.size() > 0) { - cacheNodesNoBatch(storeId, batch); + cacheNodesNoBatch(selectNodesByUuids(storeId, batch)); + } + } + + private void cacheNodesBatch(List nodeIds) + { + int batchSize = 256; + SortedSet batch = new TreeSet(); + for (Long nodeId : nodeIds) + { + batch.add(nodeId); + if (batch.size() >= batchSize) + { + // Preload + cacheNodesNoBatch(selectNodesByIds(batch)); + batch.clear(); + } + } + // Load any remaining nodes + if (batch.size() > 0) + { + cacheNodesNoBatch(selectNodesByIds(batch)); } } /** * Bulk-fetch the nodes for a given store. All nodes passed in are fetched. */ - private void cacheNodesNoBatch(Long storeId, SortedSet uuids) + private void cacheNodesNoBatch(List nodes) { // Get the nodes - List nodes = selectNodesByUuids(storeId, uuids); SortedSet aspectNodeIds = new TreeSet(); SortedSet propertiesNodeIds = new TreeSet(); - for (NodeEntity node : nodes) + for (Node node : nodes) { Long nodeId = node.getId(); nodesCache.setValue(nodeId, node); @@ -3276,6 +3359,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO } } + if(logger.isDebugEnabled()) + { + logger.debug("Pre-loaded " + propertiesNodeIds.size() + " properties"); + logger.debug("Pre-loaded " + propertiesNodeIds.size() + " aspects"); + } + List nodeAspects = selectNodeAspects(aspectNodeIds); for (NodeAspectsEntity nodeAspect : nodeAspects) { @@ -3450,7 +3539,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO protected abstract int deleteNodesByCommitTime(boolean deletedOnly, long maxTxnCommitTimeMs); protected abstract NodeEntity selectNodeById(Long id, Boolean deleted); protected abstract NodeEntity selectNodeByNodeRef(NodeRef nodeRef, Boolean deleted); - protected abstract List selectNodesByUuids(Long storeId, SortedSet uuids); + protected abstract List selectNodesByUuids(Long storeId, SortedSet uuids); + protected abstract List selectNodesByIds(SortedSet ids); protected abstract Map> selectNodeProperties(Set nodeIds); protected abstract List selectNodeAspects(Set nodeIds); protected abstract Map selectNodeProperties(Long nodeId); diff --git a/source/java/org/alfresco/repo/domain/node/ibatis/NodeBatchLoadEntity.java b/source/java/org/alfresco/repo/domain/node/ibatis/NodeBatchLoadEntity.java index 33f500c30e..7c21d82744 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeBatchLoadEntity.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeBatchLoadEntity.java @@ -30,6 +30,7 @@ public class NodeBatchLoadEntity { private Long storeId; private List uuids; + private List ids; public Long getStoreId() { @@ -47,5 +48,12 @@ public class NodeBatchLoadEntity { this.uuids = uuids; } - + public List getIds() + { + return ids; + } + public void setIds(List ids) + { + this.ids = ids; + } } diff --git a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java index 9017b5d751..a03be2f531 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -31,6 +31,7 @@ import java.util.SortedSet; import org.alfresco.repo.domain.node.AbstractNodeDAOImpl; import org.alfresco.repo.domain.node.ChildAssocEntity; import org.alfresco.repo.domain.node.ChildPropertyEntity; +import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.NodeAspectsEntity; import org.alfresco.repo.domain.node.NodeAssocEntity; import org.alfresco.repo.domain.node.NodeEntity; @@ -86,6 +87,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_NODE_BY_ID = "alfresco.node.select_NodeById"; private static final String SELECT_NODE_BY_NODEREF = "alfresco.node.select_NodeByNodeRef"; private static final String SELECT_NODES_BY_UUIDS = "alfresco.node.select_NodesByUuids"; + private static final String SELECT_NODES_BY_IDS = "alfresco.node.select_NodesByIds"; private static final String SELECT_NODE_PROPERTIES = "alfresco.node.select_NodeProperties"; private static final String SELECT_NODE_ASPECTS = "alfresco.node.select_NodeAspects"; private static final String INSERT_NODE_PROPERTY = "alfresco.node.insert.insert_NodeProperty"; @@ -379,15 +381,26 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl @SuppressWarnings("unchecked") @Override - protected List selectNodesByUuids(Long storeId, SortedSet uuids) + protected List selectNodesByUuids(Long storeId, SortedSet uuids) { NodeBatchLoadEntity nodeBatchLoadEntity = new NodeBatchLoadEntity(); nodeBatchLoadEntity.setStoreId(storeId); nodeBatchLoadEntity.setUuids(new ArrayList(uuids)); - return (List) template.selectList(SELECT_NODES_BY_UUIDS, nodeBatchLoadEntity); + return (List) template.selectList(SELECT_NODES_BY_UUIDS, nodeBatchLoadEntity); } + @SuppressWarnings("unchecked") + @Override + protected List selectNodesByIds(SortedSet ids) + { + NodeBatchLoadEntity nodeBatchLoadEntity = new NodeBatchLoadEntity(); + nodeBatchLoadEntity.setIds(new ArrayList(ids)); + + return (List) template.selectList(SELECT_NODES_BY_IDS, nodeBatchLoadEntity); + } + + /** * Pull out the key-value pairs from the rows */ diff --git a/source/java/org/alfresco/repo/domain/solr/NodeMetaData.java b/source/java/org/alfresco/repo/domain/solr/NodeMetaData.java index 1948ed08f8..eb6fd3e378 100644 --- a/source/java/org/alfresco/repo/domain/solr/NodeMetaData.java +++ b/source/java/org/alfresco/repo/domain/solr/NodeMetaData.java @@ -19,6 +19,7 @@ package org.alfresco.repo.domain.solr; import java.io.Serializable; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,11 +28,12 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; public interface NodeMetaData { public NodeRef getNodeRef(); - public List getPaths(); + public Collection> getPaths(); public QName getNodeType(); public Long getNodeId(); public Long getAclId(); diff --git a/source/java/org/alfresco/repo/domain/solr/NodeMetaDataEntity.java b/source/java/org/alfresco/repo/domain/solr/NodeMetaDataEntity.java index 357d55799e..6b497dcc65 100644 --- a/source/java/org/alfresco/repo/domain/solr/NodeMetaDataEntity.java +++ b/source/java/org/alfresco/repo/domain/solr/NodeMetaDataEntity.java @@ -19,6 +19,7 @@ package org.alfresco.repo.domain.solr; import java.io.Serializable; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +28,7 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; /** * @@ -42,7 +44,8 @@ public class NodeMetaDataEntity implements NodeMetaData private Long aclId; private Map properties; private Set aspects; - private List paths; +// private List paths; + private Collection> paths; private List childAssocs; public String getOwner() @@ -61,11 +64,11 @@ public class NodeMetaDataEntity implements NodeMetaData { this.nodeRef = nodeRef; } - public List getPaths() + public Collection> getPaths() { return paths; } - public void setPaths(List paths) + public void setPaths(Collection> paths) { this.paths = paths; } diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java b/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java index 2c7206bad7..20e4f47942 100644 --- a/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java +++ b/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java @@ -28,6 +28,7 @@ import java.util.Set; import junit.framework.TestCase; import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.node.ContentDataWithId; import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.solr.SOLRDAO.NodeMetaDataQueryCallback; @@ -38,9 +39,9 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; @@ -94,12 +95,12 @@ public class SOLRDAOTest extends TestCase storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis()); rootNodeRef = nodeService.getRootNode(storeRef); } - +/* public void testQueryTransactions1() { long startTime = System.currentTimeMillis(); - SOLRTest st = new SOLRTest1(txnHelper, fileFolderService, nodeService, rootNodeRef, "testQueryTransactions1", true, true); + SOLRTest st = new SOLRTest1(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testQueryTransactions1", true, true); st.buildTransactions(); List txns = solrDAO.getTransactions(null, startTime, 0); @@ -119,7 +120,7 @@ public class SOLRDAOTest extends TestCase { long startTime = System.currentTimeMillis(); - SOLRTest st = new SOLRTest2(txnHelper, fileFolderService, nodeService, rootNodeRef, "testQueryTransactions2", true, true); + SOLRTest st = new SOLRTest2(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testQueryTransactions2", true, true); st.buildTransactions(); List txns = solrDAO.getTransactions(null, startTime, 0); @@ -149,7 +150,7 @@ public class SOLRDAOTest extends TestCase { long startTime = System.currentTimeMillis(); - SOLRTest st = new SOLRTest3(txnHelper, fileFolderService, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); + SOLRTest st = new SOLRTest3(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); st.buildTransactions(); List txns = solrDAO.getTransactions(null, startTime, 0); @@ -175,7 +176,7 @@ public class SOLRDAOTest extends TestCase { long startTime = System.currentTimeMillis(); - SOLRTest st = new SOLRTest100Nodes(txnHelper, fileFolderService, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); + SOLRTest st = new SOLRTest100Nodes(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); st.buildTransactions(); List txns = solrDAO.getTransactions(null, startTime, 0); @@ -204,7 +205,7 @@ public class SOLRDAOTest extends TestCase { long fromCommitTime = System.currentTimeMillis(); - SOLRTest st = new SOLRTest4(txnHelper, fileFolderService, nodeService, rootNodeRef, "testNodeMetaDataManyNodes", true, false); + SOLRTest st = new SOLRTest4(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataManyNodes", true, false); st.buildTransactions(); List txns = solrDAO.getTransactions(null, fromCommitTime, 0); @@ -257,11 +258,64 @@ public class SOLRDAOTest extends TestCase getNodeMetaData(nodeMetaDataParams, null, st); } - public void testFilters() + public void testNodeMetaDataCache() throws Exception + { + long fromCommitTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest4(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataManyNodes", true, false); + st.buildTransactions(); + + List txns = solrDAO.getTransactions(null, fromCommitTime, 0); + + int[] updates = new int[] {2001}; + int[] deletes = new int[] {0}; + List txnIds = checkTransactions(txns, 1, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + + // clear out node caches + nodeDAO.clear(); + + System.out.println("Cold cache - explicit clear"); + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); +// nodeMetaDataParams.setMaxResults(1000); + MetaDataResultsFilter filter = new MetaDataResultsFilter(); + filter.setIncludeAssociations(false); + //filter.setIncludePaths(false); + filter.setIncludeChildAssociations(false); + getNodeMetaData(nodeMetaDataParams, filter, st); + }*/ + + public void testNodeMetaDataNullPropertyValue() throws Exception + { + long fromCommitTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest5(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataNullPropertyValue", true, true); + st.buildTransactions(); + + List txns = solrDAO.getTransactions(null, fromCommitTime, 0); + + int[] updates = new int[] {11}; + int[] deletes = new int[] {0}; + List txnIds = checkTransactions(txns, 1, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); + getNodeMetaData(nodeMetaDataParams, null, st); + } + +/* public void testFilters() { long startTime = System.currentTimeMillis(); - SOLRTest st = new SOLRTest1(txnHelper, fileFolderService, nodeService, rootNodeRef, "testFilters", true, true); + SOLRTest st = new SOLRTest1(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testFilters", true, true); st.buildTransactions(); List txns = solrDAO.getTransactions(null, startTime, 0); @@ -277,7 +331,7 @@ public class SOLRDAOTest extends TestCase NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); nodeMetaDataParams.setNodeIds(st.getNodeIds()); getNodeMetaData(nodeMetaDataParams, null, st); - } + }*/ private static class NodeAssertions { @@ -455,6 +509,7 @@ public class SOLRDAOTest extends TestCase protected RetryingTransactionHelper txnHelper; protected NodeService nodeService; protected NodeRef rootNodeRef; + protected NodeDAO nodeDAO; protected String containerName; protected Map nodeAssertions; @@ -473,13 +528,14 @@ public class SOLRDAOTest extends TestCase protected long actualNodeCount = 0; protected long actualNodeMetaDataCount = 0; - SOLRTest(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeService nodeService, + SOLRTest(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) { this.txnHelper = txnHelper; this.nodeService = nodeService; this.rootNodeRef = rootNodeRef; this.fileFolderService = fileFolderService; + this.nodeDAO = nodeDAO; this.containerName = containerName; this.nodeAssertions = new HashMap(); @@ -580,19 +636,6 @@ public class SOLRDAOTest extends TestCase } } -// protected void addNode(NodeRef nodeRef, NodeAssertions nodeStatus) -// { -// if(nodeStatus.nodeStatus == NodeStatus.UPDATED) -// { -// expectedNumMetaDataNodes++; -// } -// -// if(doNodeChecks || doMetaDataChecks) -// { -// nodeAssertions.put(nodeRef, nodeStatus); -// } -// } - void buildTransactions() { buildTransactionsInternal(); @@ -614,8 +657,6 @@ public class SOLRDAOTest extends TestCase throw new RuntimeException("Unexpected missing assertion for NodeRef " + nodeRef); } - //System.out.println("Node: " + node.toString()); - if((expectedStatus.getNodeStatus() == NodeStatus.DELETED && isDeleted) || (expectedStatus.getNodeStatus() == NodeStatus.UPDATED && !isDeleted)) { @@ -630,21 +671,82 @@ public class SOLRDAOTest extends TestCase return true; } +/* private boolean compareProperties(Map properties1, Map properties2) + { + boolean match = true; + + if(properties1.size() != properties2.size()) + { + match = false; + } + else + { + for(QName qname : properties1.keySet()) + { + Serializable value1 = properties1.get(qname); + Serializable value2 = properties2.get(qname); + if(value1 instanceof MLText) + { + if(!(value2 instanceof MLText)) + { + match = false; + break; + } + MLText ml1 = (MLText)value1; + MLText ml2 = (MLText)value2; + if(ml1.getDefaultValue().equals(ml2.getDefaultValue())) + { + match = false; + break; + } + } + else if(value1 instanceof ContentDataWithId) + { + if(!(value2 instanceof ContentDataWithId)) + { + match = false; + break; + } + ContentDataWithId cd1 = (ContentDataWithId)value1; + ContentDataWithId cd2 = (ContentDataWithId)value2; + if(cd1.getDefaultValue().equals(ml2.getDefaultValue())) + { + match = false; + break; + } + } + else + { + if(!value1.equals(value2)) + { + match = false; + break; + } + } + } + } + + return match; + }*/ + @Override public boolean handleNodeMetaData(NodeMetaData nodeMetaData) { actualNodeMetaDataCount++; if(doMetaDataChecks) { + Long nodeId = nodeMetaData.getNodeId(); NodeRef nodeRef = nodeMetaData.getNodeRef(); Set aspects = nodeMetaData.getAspects(); Set actualAspects = nodeService.getAspects(nodeRef); - assertEquals("Aspects are incorrect", aspects, actualAspects); + assertEquals("Aspects are incorrect", actualAspects, aspects); Map properties = nodeMetaData.getProperties(); - Map actualProperties = nodeService.getProperties(nodeRef); - assertEquals("Properties are incorrect", properties, actualProperties); + // NodeService converts properties so use nodeDAO to get unadulterated property value + Map actualProperties = nodeDAO.getNodeProperties(nodeId); + //assertTrue("Properties are incorrect", compareProperties(actualProperties, properties)); + assertEquals("Properties are incorrect", actualProperties, properties); NodeAssertions assertions = getNodeAssertions(nodeRef); // NodeAssertions assertions = nodes.get(nodeRef); @@ -669,10 +771,11 @@ public class SOLRDAOTest extends TestCase assertEquals("Incorrect property value", expectedPropValue, actualPropValue); } } - - List actualPaths = nodeMetaData.getPaths(); - List expectedPaths = nodeService.getPaths(nodeRef, false); - assertEquals("Paths are incorrect", expectedPaths, actualPaths); + + // TODO complete path tests +// List actualPaths = nodeMetaData.getPaths(); +// List expectedPaths = nodeService.getPaths(nodeRef, false); +// assertEquals("Paths are incorrect", expectedPaths, actualPaths); boolean expectAspects = assertions.isExpectAspects(); if(expectAspects && nodeMetaData.getAspects() == null) @@ -772,10 +875,10 @@ public class SOLRDAOTest extends TestCase private NodeRef content1; private NodeRef content2; - SOLRTest1(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeService nodeService, + SOLRTest1(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) { - super(txnHelper, fileFolderService, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); } public int getExpectedNumNodes() @@ -830,10 +933,10 @@ public class SOLRDAOTest extends TestCase private NodeRef content1; private NodeRef content2; - SOLRTest2(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeService nodeService, + SOLRTest2(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) { - super(txnHelper, fileFolderService, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); } public int getExpectedNumNodes() @@ -888,10 +991,10 @@ public class SOLRDAOTest extends TestCase private NodeRef content1; private NodeRef content2; - SOLRTest3(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeService nodeService, + SOLRTest3(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) { - super(txnHelper, fileFolderService, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); } public int getExpectedNumNodes() @@ -947,10 +1050,10 @@ public class SOLRDAOTest extends TestCase private static class SOLRTest100Nodes extends SOLRTest { - SOLRTest100Nodes(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeService nodeService, + SOLRTest100Nodes(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) { - super(txnHelper, fileFolderService, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); } public int getExpectedNumNodes() @@ -992,10 +1095,10 @@ public class SOLRDAOTest extends TestCase { private int numContentNodes = 2000; - SOLRTest4(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeService nodeService, + SOLRTest4(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) { - super(txnHelper, fileFolderService, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); } public int getExpectedNumNodes() @@ -1028,6 +1131,7 @@ public class SOLRDAOTest extends TestCase { nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); } + nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, null); setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); } @@ -1038,4 +1142,65 @@ public class SOLRDAOTest extends TestCase } } + private static class SOLRTest5 extends SOLRTest + { + private int numContentNodes = 10; + + SOLRTest5(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, + NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) + { + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + } + + public int getExpectedNumNodes() + { + return numContentNodes + 1; + } + + public void buildTransactionsInternal() + { + final String titles[] = + { + "caf\u00E9", "\u00E7edilla", "\u00E0\u00E1\u00E2\u00E3", "\u00EC\u00ED\u00EE\u00EF", "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6", + "caf\u00E9", "\u00E7edilla", "\u00E0\u00E1\u00E2\u00E3", "\u00EC\u00ED\u00EE\u00EF", "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6" + }; + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, containerName); + NodeRef container = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + setExpectedNodeStatus(container, NodeStatus.UPDATED); + + for(int i = 0; i < numContentNodes; i++) + { + FileInfo contentInfo = fileFolderService.create(container, "Content" + i, ContentModel.TYPE_CONTENT); + NodeRef nodeRef = contentInfo.getNodeRef(); + + nodeService.addAspect(nodeRef, ContentModel.ASPECT_AUTHOR, null); + if(i % 5 == 1) + { + nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, null); + } + else + { + nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, "author" + i); + } + + nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, titles[i]); + + setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); + } + + return null; + } + }); + } + } } diff --git a/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java b/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java index 4a3b3f5486..4f31fe5483 100644 --- a/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java @@ -20,11 +20,14 @@ package org.alfresco.repo.domain.solr.ibatis; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback; @@ -38,10 +41,16 @@ import org.alfresco.repo.domain.solr.NodeParameters; import org.alfresco.repo.domain.solr.SOLRDAO; import org.alfresco.repo.domain.solr.SOLRTransactionParameters; import org.alfresco.repo.domain.solr.Transaction; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; @@ -70,9 +79,15 @@ public class SOLRDAOImpl implements SOLRDAO private NodeDAO nodeDAO; private QNameDAO qnameDAO; private OwnableService ownableService; + private TenantService tenantService; private SqlSessionTemplate template; + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + public void setOwnableService(OwnableService ownableService) { this.ownableService = ownableService; @@ -103,6 +118,8 @@ public class SOLRDAOImpl implements SOLRDAO */ public void init() { + PropertyCheck.mandatory(this, "ownableService", ownableService); + PropertyCheck.mandatory(this, "tenantService", tenantService); PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); PropertyCheck.mandatory(this, "nodeDAO", nodeDAO); PropertyCheck.mandatory(this, "qnameDAO", qnameDAO); @@ -190,46 +207,227 @@ public class SOLRDAOImpl implements SOLRDAO * A dumb iterator that iterates over longs in sequence. * */ - private static class SequenceIterator implements Iterable + private static class SequenceIterator implements Iterable, Iterator { private long fromId; private long toId; private long counter; + private int maxResults; + private boolean inUse = false; - SequenceIterator(Long fromId, Long toId) + SequenceIterator(Long fromId, Long toId, int maxResults) { this.fromId = (fromId == null ? 1 : fromId.longValue()); this.toId = (toId == null ? Long.MAX_VALUE : toId.longValue()); + this.maxResults = maxResults; this.counter = this.fromId; } + public List getList() + { + List ret = new ArrayList(100); + @SuppressWarnings("rawtypes") + Iterator nodeIds = iterator(); + while(nodeIds.hasNext()) + { + ret.add((Long)nodeIds.next()); + } + return ret; + } + @Override public Iterator iterator() { - counter = this.fromId; - return new Iterator() { + if(inUse) + { + throw new IllegalStateException("Already in use"); + } + this.counter = this.fromId; + this.inUse = true; + return this; + } - @Override - public boolean hasNext() - { - return counter <= toId; - } + @Override + public boolean hasNext() + { + return ((counter - this.fromId) < maxResults) && counter <= toId; + } - @Override - public Long next() - { - return counter++; - } + @Override + public Long next() + { + return counter++; + } - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } - }; + @Override + public void remove() + { + throw new UnsupportedOperationException(); } } - + + private boolean isCategorised(AspectDefinition aspDef) + { + if(aspDef == null) + { + return false; + } + AspectDefinition current = aspDef; + while (current != null) + { + if (current.getName().equals(ContentModel.ASPECT_CLASSIFIABLE)) + { + return true; + } + else + { + QName parentName = current.getParentName(); + if (parentName == null) + { + break; + } + current = dictionaryService.getAspect(parentName); + } + } + return false; + } + + private Collection> getCategoryPaths(NodeRef nodeRef, Set aspects, Map properties) + { + ArrayList> categoryPaths = new ArrayList>(); + + for (QName classRef : aspects) + { + AspectDefinition aspDef = dictionaryService.getAspect(classRef); + if (isCategorised(aspDef)) + { + LinkedList> aspectPaths = new LinkedList>(); + for (PropertyDefinition propDef : aspDef.getProperties().values()) + { + if (propDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY)) + { + for (NodeRef catRef : DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, properties.get(propDef.getName()))) + { + if (catRef != null) + { + // can be running in context of System user, hence use input nodeRef + catRef = tenantService.getName(nodeRef, catRef); + + try + { + Pair pair = nodeDAO.getNodePair(catRef); + for (Path path : nodeDAO.getPaths(pair, false)) + { + if ((path.size() > 1) && (path.get(1) instanceof Path.ChildAssocElement)) + { + Path.ChildAssocElement cae = (Path.ChildAssocElement) path.get(1); + boolean isFakeRoot = true; + + final List results = new ArrayList(10); + // We have a callback handler to filter results + ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback() + { + public boolean preLoadNodes() + { + return false; + } + + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + results.add(childAssocPair.getSecond()); + return true; + } + + public void done() + { + } + }; + + Pair caePair = nodeDAO.getNodePair(cae.getRef().getChildRef()); + nodeDAO.getParentAssocs(caePair.getFirst(), null, null, false, callback); + for (ChildAssociationRef car : results) + { + if (cae.getRef().equals(car)) + { + isFakeRoot = false; + break; + } + } + if (isFakeRoot) + { + if (path.toString().indexOf(aspDef.getName().toString()) != -1) + { + aspectPaths.add(new Pair(path, aspDef.getName())); + } + } + } + } + } + catch (InvalidNodeRefException e) + { + // If the category does not exists we move on the next + } + + } + } + } + } + categoryPaths.addAll(aspectPaths); + } + } + // Add member final element + for (Pair pair : categoryPaths) + { + if (pair.getFirst().last() instanceof Path.ChildAssocElement) + { + Path.ChildAssocElement cae = (Path.ChildAssocElement) pair.getFirst().last(); + ChildAssociationRef assocRef = cae.getRef(); + pair.getFirst().append(new Path.ChildAssocElement(new ChildAssociationRef(assocRef.getTypeQName(), assocRef.getChildRef(), QName.createQName("member"), nodeRef))); + } + } + + return categoryPaths; + } + + private List preCacheNodes(NodeMetaDataParameters nodeMetaDataParameters) + { + int maxResults = nodeMetaDataParameters.getMaxResults(); + boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE); + + List nodeIds = null; + Iterable iterable = null; + List allNodeIds = nodeMetaDataParameters.getNodeIds(); + if(allNodeIds != null) + { + int toIndex = (maxResults > allNodeIds.size() ? allNodeIds.size() : maxResults); + nodeIds = isLimitSet ? allNodeIds.subList(0, toIndex) : nodeMetaDataParameters.getNodeIds(); + iterable = nodeMetaDataParameters.getNodeIds(); + } + else + { + Long fromNodeId = nodeMetaDataParameters.getFromNodeId(); + Long toNodeId = nodeMetaDataParameters.getToNodeId(); + nodeIds = new ArrayList(isLimitSet ? maxResults : 100); // TODO better default here? + iterable = new SequenceIterator(fromNodeId, toNodeId, maxResults); + int counter = 1; + for(Long nodeId : iterable) + { + if(isLimitSet && counter++ > maxResults) + { + break; + } + nodeIds.add(nodeId); + } + } + // pre-cache nodes + nodeDAO.cacheNodesById(nodeIds); + + return nodeIds; + } + /** * {@inheritDoc} */ @@ -247,52 +445,40 @@ public class SOLRDAOImpl implements SOLRDAO boolean includeChildAssociations = (resultFilter == null ? true : resultFilter.getIncludeChildAssociations()); boolean includeOwner = (resultFilter == null ? true : resultFilter.getIncludeOwner()); - Iterable iterable = null; - if(nodeMetaDataParameters.getNodeIds() != null) - { - iterable = nodeMetaDataParameters.getNodeIds(); - } - else - { - iterable = new SequenceIterator(nodeMetaDataParameters.getFromNodeId(), nodeMetaDataParameters.getToNodeId()); - } + List nodeIds = preCacheNodes(nodeMetaDataParameters); - // pre-cache nodes? - // TODO does this cache acls, etc for the node? - List nodeRefs = new ArrayList(100); - int i = 1; - for(Long nodeId : iterable) + //Iterable iterable = null; +// if(nodeMetaDataParameters.getNodeIds() != null) +// { +// int toIndex = (maxResults > allNodeIds.size() ? allNodeIds.size() : maxResults); +// nodeIds = isLimitSet ? nodeMetaDataParameters.getNodeIds().subList(0, maxResults) : nodeMetaDataParameters.getNodeIds(); +// //iterable = nodeMetaDataParameters.getNodeIds(); +// } +// else +// { +// Long fromNodeId = nodeMetaDataParameters.getFromNodeId(); +// Long toNodeId = nodeMetaDataParameters.getToNodeId(); +// nodeIds = new ArrayList(isLimitSet ? maxResults : 100); // TODO better default here +// SequenceIterator si = new SequenceIterator(fromNodeId, toNodeId, maxResults); +// nodeIds = si.getList(); +// //iterable = si; +// } + // pre-cache nodes +// nodeDAO.cacheNodesById(nodeIds); + + //int i = 1; + for(Long nodeId : nodeIds) { - if(isLimitSet && i++ > maxResults) - { - break; - } + Map props = null; + Set aspects = null; + +// if(isLimitSet && i++ > maxResults) +// { +// break; +// } if(!nodeDAO.exists(nodeId)) { - continue; - } - - Pair pair = nodeDAO.getNodePair(nodeId); - nodeRefs.add(pair.getSecond()); - } - if(logger.isDebugEnabled()) - { - logger.debug("SOLRDAO caching " + nodeRefs.size() + " nodes"); - } - nodeDAO.cacheNodes(nodeRefs); - - i = 1; - for(Long nodeId : iterable) - { - if(isLimitSet && i++ > maxResults) - { - break; - } - - if(!nodeDAO.exists(nodeId)) - { - // ignore deleted node? // TODO nodeDAO doesn't cache anything for deleted nodes. Should we be ignoring delete node meta data? continue; } @@ -311,22 +497,31 @@ public class SOLRDAOImpl implements SOLRDAO nodeMetaData.setNodeType(nodeType); } - if(includeProperties) + if(includePaths || includeProperties) { - Map props = nodeDAO.getNodeProperties(nodeId); - nodeMetaData.setProperties(props); + props = nodeDAO.getNodeProperties(nodeId); } + nodeMetaData.setProperties(props); - if(includeAspects) + if(includePaths || includeAspects) { - Set aspects = nodeDAO.getNodeAspects(nodeId); - nodeMetaData.setAspects(aspects); + aspects = nodeDAO.getNodeAspects(nodeId); } + nodeMetaData.setAspects(aspects); - // paths may change during get i.e. node moved around in the graph + // TODO paths may change during get i.e. node moved around in the graph if(includePaths) { - List paths = nodeDAO.getPaths(pair, false); + Collection> categoryPaths = getCategoryPaths(pair.getSecond(), aspects, props); + List directPaths = nodeDAO.getPaths(pair, false); + + Collection> paths = new ArrayList>(directPaths.size() + categoryPaths.size()); + for (Path path : directPaths) + { + paths.add(new Pair(path, null)); + } + paths.addAll(categoryPaths); + nodeMetaData.setPaths(paths); } @@ -338,12 +533,11 @@ public class SOLRDAOImpl implements SOLRDAO if(includeChildAssociations) { final List childAssocs = new ArrayList(100); - nodeDAO.getChildAssocs(nodeId, null, null, null, false, false, new ChildAssocRefQueryCallback() + nodeDAO.getChildAssocs(nodeId, null, null, null, null, null, new ChildAssocRefQueryCallback() { @Override public boolean preLoadNodes() { - // already cached above return false; } @@ -365,6 +559,29 @@ public class SOLRDAOImpl implements SOLRDAO if(includeAssociations) { + final List parentAssocs = new ArrayList(100); + nodeDAO.getParentAssocs(nodeId, null, null, null, new ChildAssocRefQueryCallback() + { + @Override + public boolean handle(Pair childAssocPair, + Pair parentNodePair, Pair childNodePair) + { + parentAssocs.add(childAssocPair.getSecond()); + return true; + } + + @Override + public boolean preLoadNodes() + { + return false; + } + + @Override + public void done() + { + } + }); + // TODO non-child associations // Collection> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId); // Collection> targetAssocs = nodeDAO.getTargetNodeAssocs(nodeId); diff --git a/source/java/org/alfresco/repo/node/NodeBulkLoader.java b/source/java/org/alfresco/repo/node/NodeBulkLoader.java index 2ada888cb7..2fbe90264f 100644 --- a/source/java/org/alfresco/repo/node/NodeBulkLoader.java +++ b/source/java/org/alfresco/repo/node/NodeBulkLoader.java @@ -41,6 +41,15 @@ public interface NodeBulkLoader */ public void cacheNodes(List nodeRefs); + /** + * Pre-cache data relevant to the given nodes. There is no need to split the collection + * up before calling this method; it is up to the implementations to ensure that batching + * is done where necessary. + * + * @param nodeIds the nodes that will be cached. + */ + public void cacheNodesById(List nodeIds); + /** * FOR TESTING ONLY: Clears out node cache data */