diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 9b277cd379..6e918687d5 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -131,6 +131,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest public static final QName PROP_QNAME_NULL_VALUE = QName.createQName(NAMESPACE, "nullValue"); public static final QName PROP_QNAME_MULTI_VALUE = QName.createQName(NAMESPACE, "multiValue"); public static final QName PROP_QNAME_MULTI_ML_VALUE = QName.createQName(NAMESPACE, "multiMLValue"); + public static final QName PROP_QNAME_MARKER_PROP = QName.createQName(NAMESPACE, "markerProp"); public static final QName PROP_QNAME_PROP1 = QName.createQName(NAMESPACE, "prop1"); public static final QName PROP_QNAME_PROP2 = QName.createQName(NAMESPACE, "prop2"); public static final QName ASSOC_TYPE_QNAME_TEST_CHILDREN = ContentModel.ASSOC_CHILDREN; @@ -669,6 +670,73 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest propertiesAfter.size()); } + public void testAspectsAddedAutomatically() throws Exception + { + // Add the test:titled properties + Map properties = new HashMap(20); + fillProperties(BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED, properties); + // Create a regular base node + NodeRef nodeRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(BaseNodeServiceTest.NAMESPACE, "test-container"), + ContentModel.TYPE_CONTAINER, + properties).getChildRef(); + // Ensure that the aspect was automatically added + assertTrue("Aspect not automatically added during 'createNode'", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED)); + + // Remove the aspect and test using setProperties + nodeService.removeAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED); + properties = nodeService.getProperties(nodeRef); + assertFalse("test:titled properties not removed", + properties.containsKey(BaseNodeServiceTest.PROP_QNAME_TEST_TITLE)); + assertFalse("test:titled properties not removed", + properties.containsKey(BaseNodeServiceTest.PROP_QNAME_TEST_DESCRIPTION)); + properties.put(BaseNodeServiceTest.PROP_QNAME_TEST_DESCRIPTION, "A description"); + nodeService.setProperties(nodeRef, properties); + assertTrue("Aspect not automatically added during 'setProperties'", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED)); + + // Remove the aspect and test using addProperties + nodeService.removeAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED); + properties = new HashMap(5); + properties.put(BaseNodeServiceTest.PROP_QNAME_TEST_DESCRIPTION, "A description"); + nodeService.addProperties(nodeRef, properties); + assertTrue("Aspect not automatically added during 'addProperties'", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED)); + + // Remove the aspect and test using setProperty + nodeService.removeAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED); + nodeService.setProperty(nodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_DESCRIPTION, "A description"); + assertTrue("Aspect not automatically added during 'setProperty'", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_TITLED)); + + // Check that aspects with further mandatory aspects are added properly + nodeService.setProperty(nodeRef, BaseNodeServiceTest.PROP_QNAME_MARKER_PROP, "Marker value"); + assertTrue("Aspect not automatically added during 'setProperty'", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_MARKER)); + assertTrue("Aspect not automatically added during 'setProperty' (second-level)", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_QNAME_TEST_MARKER2)); + + // Check that child association creation adds the aspect to the parent + NodeRef childNodeRef = nodeService.createNode( + nodeRef, + BaseNodeServiceTest.ASSOC_ASPECT_CHILD_ASSOC, + BaseNodeServiceTest.ASSOC_ASPECT_CHILD_ASSOC, + ContentModel.TYPE_CMOBJECT).getChildRef(); + assertTrue("Aspect not automatically added by child association during 'createNode'", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_WITH_ASSOCIATIONS)); + + nodeService.removeAspect(nodeRef, BaseNodeServiceTest.ASPECT_WITH_ASSOCIATIONS); + assertFalse("Child node should have been deleted", nodeService.exists(childNodeRef)); + + // Check that normal association creation adds the aspect to the source + nodeService.createAssociation(nodeRef, rootNodeRef, BaseNodeServiceTest.ASSOC_ASPECT_NORMAL_ASSOC); + assertTrue("Aspect not automatically added by child association during 'createAssociation'", + nodeService.hasAspect(nodeRef, BaseNodeServiceTest.ASPECT_WITH_ASSOCIATIONS)); + } + public void testAspectRemoval() throws Exception { // Create a node to add the aspect to @@ -1237,7 +1305,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest } } - public void setAddProperties() throws Exception + public void testAddProperties() throws Exception { Map properties = nodeService.getProperties(rootNodeRef); // Add an aspect with a default value diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml b/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml index 49f5f11b5a..504bbff993 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml @@ -324,6 +324,11 @@ Marker Aspect + + + d:text + + test:marker2 diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 57f0352947..7207329966 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -77,6 +77,7 @@ import org.alfresco.util.EqualsHelper; import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; @@ -329,11 +330,16 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeOnCreateNode(childAssocRef); invokeOnCreateChildAssociation(childAssocRef, true); addIntrinsicProperties(childNodePair, propertiesAfter); + Map propertiesBefore = PropertyMap.EMPTY_MAP; invokeOnUpdateProperties( childAssocRef.getChildRef(), - Collections.emptyMap(), + propertiesBefore, propertiesAfter); + // Add missing aspects + addMissingAspects(childNodePair, propertiesBefore, propertiesAfter); + addMissingAspects(parentNodePair, assocTypeQName); + // Index nodeIndexer.indexCreateNode(childAssocRef); @@ -853,6 +859,9 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Invoke policy behaviours invokeOnCreateChildAssociation(childAssocRef, false); + + // Add missing aspects + addMissingAspects(parentNodePair, assocTypeQName); // Index nodeIndexer.indexCreateChildAssociation(childAssocRef); @@ -1081,6 +1090,96 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // done return nodeProperties; } + + /** + * Find any aspects that are missing for the node, given the properties before and after an update. + */ + private void addMissingAspects( + Pair nodePair, + Map propertiesBefore, + Map propertiesAfter) + { + Long nodeId = nodePair.getFirst(); + NodeRef nodeRef = nodePair.getSecond(); + Set aspectQNamesToAdd = new HashSet(5); + Set newProperties = new HashSet(propertiesAfter.keySet()); + newProperties.removeAll(propertiesBefore.entrySet()); + Set existingAspectsQNames = nodeDaoService.getNodeAspects(nodeId); + for (QName newPropertyQName : newProperties) + { + PropertyDefinition propDef = dictionaryService.getProperty(newPropertyQName); + if (propDef == null) + { + continue; // Ignore undefined properties + } + if (!propDef.getContainerClass().isAspect()) + { + continue; + } + QName containerClassQName = propDef.getContainerClass().getName(); + // Remove this aspect - it is there + if (existingAspectsQNames.contains(containerClassQName)) + { + // Already there + continue; + } + aspectQNamesToAdd.add(containerClassQName); + } + // Add the aspects and any missing, default properties + if (aspectQNamesToAdd.size() > 0) + { + for (QName aspectQNameToAdd : aspectQNamesToAdd) + { + invokeBeforeAddAspect(nodeRef, aspectQNameToAdd); + } + nodeDaoService.addNodeAspects(nodeId, aspectQNamesToAdd); + // Add the aspects and then their appropriate default values. + for (QName aspectQNameToAdd : aspectQNamesToAdd) + { + addDefaultProperties(nodePair, propertiesAfter, aspectQNameToAdd); + addDefaultAspects(nodePair, aspectQNameToAdd); + } + for (QName aspectQNameToAdd : aspectQNamesToAdd) + { + invokeOnAddAspect(nodeRef, aspectQNameToAdd); + } + } + } + + /** + * Find any aspects that are missing for the node, given the association type. + */ + private void addMissingAspects( + Pair nodePair, + QName assocTypeQName) + { + Long nodeId = nodePair.getFirst(); + NodeRef nodeRef = nodePair.getSecond(); + Set existingAspectsQNames = nodeDaoService.getNodeAspects(nodeId); + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (assocDef == null) + { + return; // Ignore undefined properties + } + if (!assocDef.getSourceClass().isAspect()) + { + return; + } + QName aspectQNameToAdd = assocDef.getSourceClass().getName(); + // Remove this aspect - it is there + if (existingAspectsQNames.contains(aspectQNameToAdd)) + { + // Already there + return; + } + // Add the aspects and any missing, default properties + invokeBeforeAddAspect(nodeRef, aspectQNameToAdd); + nodeDaoService.addNodeAspects(nodeId, Collections.singleton(aspectQNameToAdd)); + // Add the aspects and then their appropriate default values. + addDefaultProperties(nodePair, aspectQNameToAdd); + addDefaultAspects(nodePair, aspectQNameToAdd); + invokeOnAddAspect(nodeRef, aspectQNameToAdd); + } /** * Gets the properties map, sets the value (null is allowed) and checks that the new set @@ -1118,6 +1217,9 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + // Add any missing aspects + addMissingAspects(nodePair, propertiesBefore, propertiesAfter); + // Index nodeIndexer.indexUpdateNode(nodeRef); } @@ -1180,6 +1282,9 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + // Add any missing aspects + addMissingAspects(nodePair, propertiesBefore, propertiesAfter); + // Index nodeIndexer.indexUpdateNode(nodeRef); } @@ -1208,6 +1313,9 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl invokeOnUpdateNode(nodeRef); invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + // Add any missing aspects + addMissingAspects(nodePair, propertiesBefore, propertiesAfter); + // Index nodeIndexer.indexUpdateNode(nodeRef); } @@ -1263,42 +1371,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl nodeIndexer.indexUpdateNode(nodeRef); } -// private Map convertProperties(Map properties) throws InvalidNodeRefException -// { -// Map convertedProperties = new HashMap(17); -// -// // check the property type and copy the values across -// for (QName propertyQName : properties.keySet()) -// { -// PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); -// Serializable value = properties.get(propertyQName); -// // get a persistable value -// PropertyValue propertyValue = makePropertyValue(propertyDef, value); -// convertedProperties.put(propertyQName, propertyValue); -// } -// -// // Return the converted properties -// return convertedProperties; -// } -// -// private Map convertPropertyValues(Map propertyValues) throws InvalidNodeRefException -// { -// Map convertedProperties = new HashMap(17); -// -// // check the property type and copy the values across -// for (Map.Entry entry : propertyValues.entrySet()) -// { -// QName propertyQName = entry.getKey(); -// PropertyValue propertyValue = entry.getValue(); -// PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); -// Serializable property = makeSerializableValue(propertyDef, propertyValue); -// convertedProperties.put(propertyQName, property); -// } -// -// // Return the converted properties -// return convertedProperties; -// } -// public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException { // Get the node @@ -1554,6 +1626,9 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // Invoke policy behaviours invokeOnCreateAssociation(assocRef); + // Add missing aspects + addMissingAspects(sourceNodePair, assocTypeQName); + return assocRef; } diff --git a/source/java/org/alfresco/service/cmr/repository/StoreRef.java b/source/java/org/alfresco/service/cmr/repository/StoreRef.java index 9b9a95dd5c..5c527748b5 100644 --- a/source/java/org/alfresco/service/cmr/repository/StoreRef.java +++ b/source/java/org/alfresco/service/cmr/repository/StoreRef.java @@ -40,6 +40,7 @@ public final class StoreRef implements EntityRef, Serializable public static final String PROTOCOL_WORKSPACE = "workspace"; public static final String PROTOCOL_ARCHIVE = "archive"; public static final String PROTOCOL_AVM = "avm"; + public static final String PROTOCOL_TEST = "test"; public static final StoreRef STORE_REF_WORKSPACE_SPACESSTORE = new StoreRef(PROTOCOL_WORKSPACE, "SpacesStore"); public static final StoreRef STORE_REF_ARCHIVE_SPACESSTORE = new StoreRef(PROTOCOL_ARCHIVE, "SpacesStore"); diff --git a/source/java/org/alfresco/util/PropertyMap.java b/source/java/org/alfresco/util/PropertyMap.java index a3f8934368..76425c04a2 100644 --- a/source/java/org/alfresco/util/PropertyMap.java +++ b/source/java/org/alfresco/util/PropertyMap.java @@ -25,7 +25,9 @@ package org.alfresco.util; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; import org.alfresco.service.namespace.QName; @@ -41,6 +43,11 @@ public class PropertyMap extends HashMap { private static final long serialVersionUID = 8052326301073209645L; + /** + * A static empty map to us when having to deal with nulls + */ + public static final Map EMPTY_MAP = Collections.emptyMap(); + /** * @see HashMap#HashMap(int, float) */