diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index 21e3a7b5bf..fe5ac5a120 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -227,22 +227,27 @@ Created d:datetime + true Creator d:text + true Modified d:datetime + true Modifier d:text + true Accessed d:datetime + true diff --git a/config/alfresco/rule-services-context.xml b/config/alfresco/rule-services-context.xml index b038b4bcc9..1ddff2a2fa 100644 --- a/config/alfresco/rule-services-context.xml +++ b/config/alfresco/rule-services-context.xml @@ -71,14 +71,14 @@ - - + @@ -100,6 +100,9 @@ + + + diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index cb8435823b..215e811dee 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -275,14 +275,20 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl addDefaultAspects(nodeTypeDef, node, childAssocRef.getChildRef(), properties); // set the properties - it is a new node so only set properties if there are any + Map propertiesBefore = getProperties(childAssocRef.getChildRef()); + Map propertiesAfter = null; if (properties.size() > 0) { - this.setProperties(node.getNodeRef(), properties); + propertiesAfter = setPropertiesImpl(childAssocRef.getChildRef(), properties); } // Invoke policy behaviour invokeOnCreateNode(childAssocRef); invokeOnUpdateNode(parentRef); + if (propertiesAfter != null) + { + invokeOnUpdateProperties(childAssocRef.getChildRef(), propertiesBefore, propertiesAfter); + } // done return childAssocRef; @@ -853,6 +859,26 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); + // Do the set properties + Map propertiesBefore = getProperties(nodeRef); + Map propertiesAfter = setPropertiesImpl(nodeRef, properties); + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + } + + /** + * Does the work of setting the property values. Returns a map containing the state of the properties after the set + * operation is complete. + * + * @param nodeRef the node reference + * @param properties the map of property values + * @return the map of property values after the set operation is complete + * @throws InvalidNodeRefException + */ + private Map setPropertiesImpl(NodeRef nodeRef, Map properties) throws InvalidNodeRefException + { if (properties == null) { throw new IllegalArgumentException("Properties may not be null"); @@ -863,8 +889,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // find the node Node node = getNodeNotNull(nodeRef); - // get the properties before - Map propertiesBefore = getProperties(nodeRef); // copy properties onto node Map nodeProperties = node.getProperties(); @@ -880,15 +904,11 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl nodeProperties.put(propertyQName, propertyValue); } - // store the properties after the change - Map propertiesAfter = Collections.unmodifiableMap(properties); - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); - // update the node status nodeDaoService.recordChangeId(nodeRef); + + // Return the properties after + return Collections.unmodifiableMap(properties); } /** @@ -904,26 +924,40 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Invoke policy behaviours invokeBeforeUpdateNode(nodeRef); + // Do the set operation + Map propertiesBefore = getProperties(nodeRef); + Map propertiesAfter = setPropertyImpl(nodeRef, qname, value); + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + } + + /** + * Does the work of setting a property value. Returns the values of the properties after the set operation is + * complete. + * + * @param nodeRef the node reference + * @param qname the qname of the property + * @param value the value of the property + * @return the values of the properties after the set operation is complete + * @throws InvalidNodeRefException + */ + public Map setPropertyImpl(NodeRef nodeRef, QName qname, Serializable value) throws InvalidNodeRefException + { // get the node Node node = getNodeNotNull(nodeRef); - // get properties before - Map propertiesBefore = getProperties(nodeRef); Map properties = node.getProperties(); PropertyDefinition propertyDef = dictionaryService.getProperty(qname); // get a persistable value PropertyValue propertyValue = makePropertyValue(propertyDef, value); properties.put(qname, propertyValue); - - // get properties after the change - Map propertiesAfter = getProperties(nodeRef); - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); - + // update the node status nodeDaoService.recordChangeId(nodeRef); + + return getProperties(nodeRef); } /** diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index 5cf83e3703..284909b402 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -1294,6 +1294,134 @@ public class RuleServiceCoverageTest extends TestCase ContentModel.ASPECT_VERSIONABLE)); } + public void testInboundRuleType() + { + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + Rule rule = createRule( + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Create a non-content node + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTAINER).getChildRef(); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a content node + NodeRef contentNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a node to be moved + NodeRef moveNode = this.nodeService.createNode( + newNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + addContentToNode(moveNode); + assertFalse(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.moveNode( + moveNode, + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children")); + assertTrue(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); + + // Enusre the rule type does not get fired when the node is updated + this.nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, "name.txt"); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + public void testUpdateRuleType() + { + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + Rule rule = createRule( + RuleType.UPDATE, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Create a non-content node + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_FOLDER).getChildRef(); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Update the non-content node + this.nodeService.setProperty(newNodeRef, ContentModel.PROP_NAME, "testName"); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a content node + NodeRef contentNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a non content node, setting a property at the same time + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "testName"); + NodeRef nodeRef2 = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_FOLDER, + props).getChildRef(); + assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName"); + assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName2"); + assertTrue(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); + + TransactionUtil.executeInUserTransaction(this.transactionService, new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "testName"); + NodeRef nodeRef3 = RuleServiceCoverageTest.this.nodeService.createNode( + RuleServiceCoverageTest.this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_FOLDER, + props).getChildRef(); + assertFalse(RuleServiceCoverageTest.this.nodeService.hasAspect(nodeRef3, ContentModel.ASPECT_VERSIONABLE)); + RuleServiceCoverageTest.this.nodeService.setProperty(nodeRef3, ContentModel.PROP_NAME, "testName2"); + assertFalse(RuleServiceCoverageTest.this.nodeService.hasAspect(nodeRef3, ContentModel.ASPECT_VERSIONABLE)); + + return null; + } + }); + } + /** * Test: * rule type: outbound diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java index 6caa2ed7fb..e5889ef60a 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java @@ -16,6 +16,7 @@ */ package org.alfresco.repo.rule.ruletrigger; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -78,5 +79,8 @@ public class CreateNodeRuleTrigger extends SingleChildAssocRefPolicyRuleTrigger triggerRules(childAssocRef.getParentRef(), childAssocRef.getChildRef()); } + + // Reguadless of whether the rule is triggered, mark this transaction as having created this node + AlfrescoTransactionSupport.bindResource(childAssocRef.getChildRef().toString(), childAssocRef.getChildRef().toString()); } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java index c21a7fe61b..c288a17bd2 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java @@ -17,15 +17,20 @@ package org.alfresco.repo.rule.ruletrigger; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import java.util.Map; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,6 +72,50 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase new JavaBehaviour(this, "onUpdateProperties")); } + private boolean havePropertiesBeenModified(NodeRef nodeRef, Map before, Map after) + { + List remainder = new ArrayList(after.keySet()); + List modifiedProperties = new ArrayList(); + for (QName name : before.keySet()) + { + if (after.containsKey(name) == true) + { + Serializable beforeValue = before.get(name); + Serializable afterValue = after.get(name); + if (EqualsHelper.nullSafeEquals(beforeValue, afterValue) != true) + { + // The property has been changed + modifiedProperties.add(name); + } + + // Remove the property from the remainder list + remainder.remove(name); + } + } + + // Add any properties now remaining whose values have been added for the first time + if (remainder.size() != 0) + { + modifiedProperties.addAll(remainder); + } + + // Filter out the protected and content type properties from the list of modified properties + for (QName propertyName : new ArrayList(modifiedProperties)) + { + PropertyDefinition propertyDefinition = this.dictionaryService.getProperty(propertyName); + if (propertyDefinition != null) + { + if (propertyDefinition.isProtected() == true || propertyDefinition.getDataType().getName().equals(DataTypeDefinition.CONTENT) == true) + { + // Remove the protected property from the list + modifiedProperties.remove(propertyName); + } + } + } + + return (modifiedProperties.isEmpty() == false); + } + /** * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) */ @@ -77,17 +126,23 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase logger.debug("OnPropertyUpdate rule triggered fired; nodeRef=" + nodeRef.toString() + "; triggerParentRules=" + this.triggerParentRules); } - if (triggerParentRules == true) - { - List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef parentAssocRef : parentsAssocRefs) - { - triggerRules(parentAssocRef.getParentRef(), nodeRef); - } - } - else + Object createdNodeRef = AlfrescoTransactionSupport.getResource(nodeRef.toString()); + + // Only try and trigger the rules if a non protected propety has been modified + if (createdNodeRef == null && havePropertiesBeenModified(nodeRef, before, after) == true) { - triggerRules(nodeRef, nodeRef); + if (triggerParentRules == true) + { + List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef parentAssocRef : parentsAssocRefs) + { + triggerRules(parentAssocRef.getParentRef(), nodeRef); + } + } + else + { + triggerRules(nodeRef, nodeRef); + } } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java index e993b289ab..b684f496b6 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerAbstractBase.java @@ -21,6 +21,7 @@ import java.util.Set; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.rule.RuleType; @@ -50,9 +51,11 @@ public abstract class RuleTriggerAbstractBase implements RuleTrigger /** * The authentication Component */ - protected AuthenticationComponent authenticationComponent; + /** The dictionary service */ + protected DictionaryService dictionaryService; + /** * Set the policy component * @@ -78,12 +81,21 @@ public abstract class RuleTriggerAbstractBase implements RuleTrigger /** * Set the authenticationComponent */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) { this.authenticationComponent = authenticationComponent; } + /** + * Set the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + /** * Registration of an interested rule type */ diff --git a/source/java/org/alfresco/service/cmr/rule/RuleType.java b/source/java/org/alfresco/service/cmr/rule/RuleType.java index 46ae8387b9..10fcb50d70 100644 --- a/source/java/org/alfresco/service/cmr/rule/RuleType.java +++ b/source/java/org/alfresco/service/cmr/rule/RuleType.java @@ -30,7 +30,8 @@ public interface RuleType * Some rule type constants */ public static final String INBOUND = "inbound"; - public static final String OUTGOING = "outgoing"; + public static final String UPDATE = "update"; + public static final String OUTBOUND = "outbound"; /** * Get the name of the rule type.