diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index f21de2a407..49cdb2b876 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -64,6 +64,8 @@ + alfresco.messages.system-messages + alfresco.messages.dictionary-messages alfresco.messages.version-service alfresco.messages.permissions-service alfresco.messages.content-service @@ -71,7 +73,6 @@ alfresco.messages.template-service alfresco.messages.lock-service alfresco.messages.patch-service - alfresco.messages.dictionary-messages diff --git a/config/alfresco/messages/dictionary-messages.properties b/config/alfresco/messages/dictionary-messages.properties index 1f326a5fbb..65e94c9866 100644 --- a/config/alfresco/messages/dictionary-messages.properties +++ b/config/alfresco/messages/dictionary-messages.properties @@ -19,6 +19,8 @@ d_dictionary.property.err.property_type_not_specified=Property type of property d_dictionary.property.err.property_type_not_found=Property type ''{0}'' of property ''{1}'' is not found d_dictionary.property.err.single_valued_content=Content properties must be single-valued d_dictionary.property.err.duplicate_constraint_on_property=Found duplicate constraint definition ''{0}'' within property ''{1}'' +d_dictionary.property.err.cannot_relax_mandatory=Cannot relax mandatory attribute of property ''{0} +d_dictionary.property.err.cannot_relax_mandatory_enforcement=Cannot relax mandatory attribute enforcement of property ''{0} d_dictionary.constraint.regex.no_match=Value ''{0}'' does not match regular expression: {1} d_dictionary.constraint.regex.match=Value ''{0}'' matches regular expression: {1} \ No newline at end of file diff --git a/config/alfresco/messages/system-messages.properties b/config/alfresco/messages/system-messages.properties new file mode 100644 index 0000000000..6dc3a63764 --- /dev/null +++ b/config/alfresco/messages/system-messages.properties @@ -0,0 +1,3 @@ +# System-related messages + +system.err.property_not_set=Property ''{0}'' has not been set: {1} diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index f099ab2655..962c61fb0a 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -30,7 +30,7 @@ Name d:text - true + true @@ -356,34 +356,42 @@ Publisher d:text + true Contributor d:text + true Type d:text + true Identifier d:text + true Source d:text + true Coverage d:text + true Rights d:text + true Subject d:text + true @@ -392,20 +400,6 @@ - - Basable diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml index bd707a45b7..44b3ef8410 100644 --- a/config/alfresco/model/systemModel.xml +++ b/config/alfresco/model/systemModel.xml @@ -28,22 +28,22 @@ d:text - true + true d:text - true + true d:text - true + true d:text d:int - true + true 0 @@ -81,7 +81,7 @@ d:noderef - true + true @@ -101,19 +101,24 @@ d:text - true + true d:text - true + true d:text - true + true + + + Incomplete + + \ No newline at end of file diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index cf2676e6b7..af7200df3c 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -111,5 +111,18 @@ 5 + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java index 2157a6827d..30b61e3627 100644 --- a/source/java/org/alfresco/model/ContentModel.java +++ b/source/java/org/alfresco/model/ContentModel.java @@ -36,6 +36,9 @@ public interface ContentModel static final QName PROP_STORE_IDENTIFIER = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "store-identifier"); static final QName PROP_NODE_UUID = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "node-uuid"); + // tag for incomplete nodes + static final QName ASPECT_INCOMPLETE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "incomplete"); + // referenceable aspect constants static final QName TYPE_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); static final QName PROP_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index 5c24a4aca0..11136a30bf 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -18,12 +18,14 @@ package org.alfresco.repo.dictionary; import java.util.ArrayList; import java.util.List; +import java.util.Map; import junit.framework.TestCase; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.dictionary.constraint.RegexConstraint; import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -144,6 +146,42 @@ public class DictionaryDAOTest extends TestCase assertTrue("Expected type REGEX constraint", constraint instanceof RegexConstraint); } + public void testMandatoryEnforced() + { + // get the properties for the test type + QName testEnforcedQName = QName.createQName(TEST_URL, "enforced"); + ClassDefinition testEnforcedClassDef = service.getClass(testEnforcedQName); + Map testEnforcedPropertyDefs = testEnforcedClassDef.getProperties(); + + PropertyDefinition propertyDef = null; + + QName testMandatoryEnforcedQName = QName.createQName(TEST_URL, "mandatory-enforced"); + propertyDef = testEnforcedPropertyDefs.get(testMandatoryEnforcedQName); + assertNotNull("Property not found: " + testMandatoryEnforcedQName, + propertyDef); + assertTrue("Expected property to be mandatory: " + testMandatoryEnforcedQName, + propertyDef.isMandatory()); + assertTrue("Expected property to be mandatory-enforced: " + testMandatoryEnforcedQName, + propertyDef.isMandatoryEnforced()); + + QName testMandatoryNotEnforcedQName = QName.createQName(TEST_URL, "mandatory-not-enforced"); + propertyDef = testEnforcedPropertyDefs.get(testMandatoryNotEnforcedQName); + assertNotNull("Property not found: " + testMandatoryNotEnforcedQName, + propertyDef); + assertTrue("Expected property to be mandatory: " + testMandatoryNotEnforcedQName, + propertyDef.isMandatory()); + assertFalse("Expected property to be mandatory-not-enforced: " + testMandatoryNotEnforcedQName, + propertyDef.isMandatoryEnforced()); + + QName testMandatoryDefaultEnforcedQName = QName.createQName(TEST_URL, "mandatory-default-enforced"); + propertyDef = testEnforcedPropertyDefs.get(testMandatoryDefaultEnforcedQName); + assertNotNull("Property not found: " + testMandatoryDefaultEnforcedQName, + propertyDef); + assertTrue("Expected property to be mandatory: " + testMandatoryDefaultEnforcedQName, + propertyDef.isMandatory()); + assertFalse("Expected property to be mandatory-not-enforced: " + testMandatoryDefaultEnforcedQName, + propertyDef.isMandatoryEnforced()); + } public void testSubClassOf() { diff --git a/source/java/org/alfresco/repo/dictionary/M2Property.java b/source/java/org/alfresco/repo/dictionary/M2Property.java index e2435ccb2a..3082a726a9 100644 --- a/source/java/org/alfresco/repo/dictionary/M2Property.java +++ b/source/java/org/alfresco/repo/dictionary/M2Property.java @@ -34,6 +34,7 @@ public class M2Property private String propertyType = null; private boolean isProtected = false; private boolean isMandatory = false; + private boolean isMandatoryEnforced = false; private boolean isMultiValued = false; private String defaultValue = null; private boolean isIndexed = true; @@ -118,12 +119,20 @@ public class M2Property return isMandatory; } - public void setMandatory(boolean isMandatory) { this.isMandatory = isMandatory; } + public boolean isMandatoryEnforced() + { + return isMandatoryEnforced; + } + + public void setMandatoryEnforced(boolean isMandatoryEnforced) + { + this.isMandatoryEnforced = isMandatoryEnforced; + } public boolean isMultiValued() { diff --git a/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java b/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java index 8f8e10a73f..a9507dca28 100644 --- a/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java @@ -139,15 +139,27 @@ import org.alfresco.service.namespace.QName; property.setDefaultValue(defaultValue == null ? propertyDef.getDefaultValue() : defaultValue); // Process Mandatory Value - Boolean isMandatory = override.isMandatory(); - if (isMandatory != null) + Boolean isOverrideMandatory = override.isMandatory(); + boolean isOverrideMandatoryEnforced = override.isMandatoryEnforced(); + if (isOverrideMandatory != null && propertyDef.isMandatory()) { - if (propertyDef.isMandatory() == true && isMandatory == false) + // the override specified whether the property should be mandatory or not + // check that the mandatory enforcement is not relaxed + if (!isOverrideMandatory) { - throw new DictionaryException("Cannot relax mandatory attribute of property " + propertyDef.getName().toPrefixString()); + throw new DictionaryException( + "d_dictionary.property.err.cannot_relax_mandatory", + propertyDef.getName().toPrefixString()); + } + else if (!isOverrideMandatoryEnforced && propertyDef.isMandatoryEnforced()) + { + throw new DictionaryException( + "d_dictionary.property.err.cannot_relax_mandatory_enforcement", + propertyDef.getName().toPrefixString()); } } - property.setMandatory(isMandatory == null ? propertyDef.isMandatory() : isMandatory); + property.setMandatory(isOverrideMandatory == null ? propertyDef.isMandatory() : isOverrideMandatory); + property.setMandatoryEnforced(isOverrideMandatoryEnforced); // Copy all other properties as they are property.setDescription(propertyDef.getDescription()); @@ -260,7 +272,11 @@ import org.alfresco.service.namespace.QName; return m2Property.isMandatory(); } - + public boolean isMandatoryEnforced() + { + return m2Property.isMandatoryEnforced(); + } + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.PropertyDefinition#isProtected() */ diff --git a/source/java/org/alfresco/repo/dictionary/M2PropertyOverride.java b/source/java/org/alfresco/repo/dictionary/M2PropertyOverride.java index e87cf77dae..b709796018 100644 --- a/source/java/org/alfresco/repo/dictionary/M2PropertyOverride.java +++ b/source/java/org/alfresco/repo/dictionary/M2PropertyOverride.java @@ -27,6 +27,7 @@ public class M2PropertyOverride { private String name; private Boolean isMandatory; + private boolean isMandatoryEnforced = false; private String defaultValue; @@ -58,6 +59,10 @@ public class M2PropertyOverride this.isMandatory = isMandatory; } + public boolean isMandatoryEnforced() + { + return isMandatoryEnforced; + } public String getDefaultValue() { diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index b9f6f4b464..fef9e4f2f0 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -121,16 +121,31 @@ test:base - d:text true - - + + + + test:base + + + d:text + true + + + d:text + true + + + d:text + true + + diff --git a/source/java/org/alfresco/repo/dictionary/m2binding.xml b/source/java/org/alfresco/repo/dictionary/m2binding.xml index 90f0d4d8ff..a14f27f488 100644 --- a/source/java/org/alfresco/repo/dictionary/m2binding.xml +++ b/source/java/org/alfresco/repo/dictionary/m2binding.xml @@ -71,7 +71,10 @@ - + + + + @@ -97,7 +100,10 @@ - + + + + diff --git a/source/java/org/alfresco/repo/node/index/NodeIndexer.java b/source/java/org/alfresco/repo/node/index/NodeIndexer.java index 4005ab375e..f853e709d6 100644 --- a/source/java/org/alfresco/repo/node/index/NodeIndexer.java +++ b/source/java/org/alfresco/repo/node/index/NodeIndexer.java @@ -65,7 +65,7 @@ public class NodeIndexer /** * Registers the policy behaviour methods */ - private void init() + public void init() { policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "beforeCreateStore"), diff --git a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java new file mode 100644 index 0000000000..29dcc6b8ed --- /dev/null +++ b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node.integrity; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Component that tags {@link org.alfresco.model.ContentModel#ASPECT_INCOMPLETE incomplete} nodes. + * + * @author Derek Hulley + */ +public class IncompleteNodeTagger + extends TransactionListenerAdapter + implements NodeServicePolicies.OnCreateNodePolicy, + NodeServicePolicies.OnUpdatePropertiesPolicy, + NodeServicePolicies.OnAddAspectPolicy, + NodeServicePolicies.OnRemoveAspectPolicy +{ + private static Log logger = LogFactory.getLog(IncompleteNodeTagger.class); + + /** key against which the set of nodes to check is stored in the current transaction */ + private static final String KEY_NODE_SET = "IncompleteNodeTagger.NodeSet"; + + private PolicyComponent policyComponent; + private DictionaryService dictionaryService; + private NodeService nodeService; + + public IncompleteNodeTagger() + { + } + + /** + * @param policyComponent the component to register behaviour with + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * @param dictionaryService the dictionary against which to confirm model details + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param nodeService the node service to use for browsing node structures + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Registers the system-level policy behaviours + */ + public void init() + { + // check that required properties have been set + PropertyCheck.mandatory("IncompleteNodeTagger", "dictionaryService", dictionaryService); + PropertyCheck.mandatory("IncompleteNodeTagger", "nodeService", nodeService); + PropertyCheck.mandatory("IncompleteNodeTagger", "policyComponent", policyComponent); + + // register behaviour + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), + this, + new JavaBehaviour(this, "onCreateNode")); + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), + this, + new JavaBehaviour(this, "onUpdateProperties")); + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), + this, + new JavaBehaviour(this, "onAddAspect")); + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), + this, + new JavaBehaviour(this, "onRemoveAspect")); + } + + /** + * @return Returns the set of nodes to check, or null if none were registered + */ + @SuppressWarnings("unchecked") + private Set getNodeSet() + { + return (Set) AlfrescoTransactionSupport.getResource(KEY_NODE_SET); + } + + /** + * Ensures that this service is registered with the transaction and saves the node + * reference for use later. + * + * @param nodeRef + */ + private void save(NodeRef nodeRef) + { + // register this service + AlfrescoTransactionSupport.bindListener(this); + + // get the event list + Set nodeRefs = getNodeSet(); + if (nodeRefs == null) + { + nodeRefs = new HashSet(31, 0.75F); + AlfrescoTransactionSupport.bindResource(KEY_NODE_SET, nodeRefs); + } + // add node to the set + nodeRefs.add(nodeRef); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Added node reference to set: " + nodeRef); + } + } + + public void onCreateNode(ChildAssociationRef childAssocRef) + { + NodeRef nodeRef = childAssocRef.getChildRef(); + save(nodeRef); + } + + public void onUpdateProperties( + NodeRef nodeRef, + Map before, + Map after) + { + save(nodeRef); + } + + /** + * Save the node for checking of properties. + * The {@link org.alfresco.model.ContentModel#ASPECT_INCOMPLETE incomplete} aspect is + * not processed. + */ + public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) + { + if (aspectTypeQName.equals(ContentModel.ASPECT_INCOMPLETE)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignoring aspect addition: " + ContentModel.ASPECT_INCOMPLETE); + } + } + save(nodeRef); + } + + /** + * Recheck the node as an aspect was removed. + */ + public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) + { + if (aspectTypeQName.equals(ContentModel.ASPECT_INCOMPLETE)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignoring aspect removal: " + ContentModel.ASPECT_INCOMPLETE); + } + } + save(nodeRef); + } + + /** + * Process all the nodes that require checking within the transaction. + */ + @Override + public void beforeCommit(boolean readOnly) + { + Set nodeRefs = getNodeSet(); + // clear the set out of the transaction + // there may be processes that react to the addition/removal of the aspect, + // and these will, in turn, lead to further events + AlfrescoTransactionSupport.unbindResource(KEY_NODE_SET); + // process each node + for (NodeRef nodeRef : nodeRefs) + { + processNode(nodeRef); + } + } + + private void processNode(NodeRef nodeRef) + { + // ignore the node if the marker aspect is already present + boolean isTagged = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE); + + // get the node properties + Map nodeProperties = nodeService.getProperties(nodeRef); + // get the node type + QName nodeTypeQName = nodeService.getType(nodeRef); + // get property definitions for the node type + TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName); + if (typeDef == null) + { + throw new AlfrescoRuntimeException("Node type is not recognised: " + nodeTypeQName); + } + Collection propertyDefs = typeDef.getProperties().values(); + // check them + boolean classPropertiesOK = checkProperties(propertyDefs, nodeProperties); + + // were there outstanding properties to check? + if (!classPropertiesOK) + { + addOrRemoveTag(nodeRef, true, isTagged); + // no further checking required + return; + } + + // get the node aspects + Set aspectTypeQNames = nodeService.getAspects(nodeRef); + for (QName aspectTypeQName : aspectTypeQNames) + { + // get property definitions for the aspect + AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); + propertyDefs = aspectDef.getProperties().values(); + // check them + boolean aspectPropertiesOK = checkProperties(propertyDefs, nodeProperties); + // were there outstanding properties to check? + if (!aspectPropertiesOK) + { + addOrRemoveTag(nodeRef, true, isTagged); + // no further checking required + return; + } + } + // all properties passed (both class- and aspect-defined) - remove aspect + addOrRemoveTag(nodeRef, false, isTagged); + } + + /** + * @param propertyDefs the property definitions to check + * @param properties the properties + * @return Returns true if the property definitions were all satisified + */ + private boolean checkProperties( + Collection propertyDefs, + Map properties) + { + for (PropertyDefinition propertyDef : propertyDefs) + { + if (!propertyDef.isMandatory()) + { + // The property isn't mandatory in any way + continue; + } + else if (propertyDef.isMandatoryEnforced()) + { + // The mandatory nature of the property is fully enforced + // Leave these for integrity + continue; + } + // The mandatory nature of the property is 'soft' a.k.a. 'required' + // Check that the property value has been supplied + if (properties.get(propertyDef.getName()) == null) + { + // property NOT supplied + return false; + } + } + // all properties were present + return true; + } + + /** + * Adds or removes the {@link ContentModel#ASPECT_INCOMPLETE incomplete} marker aspect. + * This only performs the operation if the tag aspect is or is not present, depending + * on the operation required. + * + * @param nodeRef the node to apply the change to + * @param addTag true to add the tag and false to remove the tag + * @param isTagged true if the node already has the tag aspect applied, + * otherwise false + */ + private void addOrRemoveTag(NodeRef nodeRef, boolean addTag, boolean isTagged) + { + if (addTag && !isTagged) + { + nodeService.addAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE, null); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Tagged node as INCOMPLETE: " + nodeRef); + } + } + else if (!addTag && isTagged) + { + nodeService.removeAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Untagged node as INCOMPLETE: " + nodeRef); + } + } + } +} diff --git a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTaggerTest.java b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTaggerTest.java new file mode 100644 index 0000000000..ed0bf18b8b --- /dev/null +++ b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTaggerTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node.integrity; + +import java.io.InputStream; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.node.BaseNodeServiceTest; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; + +/** + * Checks that tagging of incomplete nodes is done properly. + * + * @author Derek Hulley + */ +@SuppressWarnings("unused") +public class IncompleteNodeTaggerTest extends TestCase +{ + private static Log logger = LogFactory.getLog(IncompleteNodeTaggerTest.class); + + private static ApplicationContext ctx; + static + { + ctx = ApplicationContextHelper.getApplicationContext(); + } + + private IncompleteNodeTagger tagger; + private ServiceRegistry serviceRegistry; + private NodeService nodeService; + private NodeRef rootNodeRef; + private PropertyMap properties; + private UserTransaction txn; + private AuthenticationComponent authenticationComponent; + + public void setUp() throws Exception + { + DictionaryDAO dictionaryDao = (DictionaryDAO) ctx.getBean("dictionaryDAO"); + ClassLoader cl = BaseNodeServiceTest.class.getClassLoader(); + // load the test model + InputStream modelStream = cl.getResourceAsStream("org/alfresco/repo/node/integrity/IntegrityTest_model.xml"); + assertNotNull(modelStream); + M2Model model = M2Model.createModel(modelStream); + dictionaryDao.putModel(model); + + tagger = (IncompleteNodeTagger) ctx.getBean("incompleteNodeTagger"); + + serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + nodeService = serviceRegistry.getNodeService(); + this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); + + this.authenticationComponent.setSystemUserAsCurrentUser(); + + // begin a transaction + TransactionService transactionService = serviceRegistry.getTransactionService(); + txn = transactionService.getUserTransaction(); + txn.begin(); + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, getName()); + if (!nodeService.exists(storeRef)) + { + nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier()); + } + rootNodeRef = nodeService.getRootNode(storeRef); + + properties = new PropertyMap(); + properties.put(IntegrityTest.TEST_PROP_TEXT_C, "abc"); + } + + public void tearDown() throws Exception + { + authenticationComponent.clearCurrentSecurityContext(); + txn.rollback(); + } + + /** + * Create a node of the given type, and hanging off the root node + */ + private NodeRef createNode(String name, QName type, PropertyMap properties) + { + return nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(IntegrityTest.NAMESPACE, name), + type, + properties + ).getChildRef(); + } + + public void testSetUp() throws Exception + { + assertNotNull("IncompleteNodeTagger not created", tagger); + } + + private void checkTagging(NodeRef nodeRef, boolean mustBeTagged) + { + tagger.beforeCommit(false); + assertEquals(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_INCOMPLETE), mustBeTagged); + } + + public void testCreateWithoutProperties() throws Exception + { + NodeRef nodeRef = createNode("abc", IntegrityTest.TEST_TYPE_WITH_PROPERTIES, null); + checkTagging(nodeRef, true); + } + + public void testCreateWithProperties() throws Exception + { + NodeRef nodeRef = createNode("abc", IntegrityTest.TEST_TYPE_WITH_PROPERTIES, properties); + checkTagging(nodeRef, false); + } +} diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java index 20e2efce0d..95a9d20322 100644 --- a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java +++ b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -39,12 +38,13 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Implementation of the {@link org.alfresco.repo.integrity.IntegrityService integrity service} - * that uses the domain persistence mechanism to store and recall integrity events. + * Component that generates and processes integrity events, enforcing the dictionary + * model's node structure. *

* In order to fulfill the contract of the interface, this class registers to receive notifications * pertinent to changes in the node structure. These are then store away in the persistent @@ -172,12 +172,9 @@ public class IntegrityChecker public void init() { // check that required properties have been set - if (dictionaryService == null) - throw new AlfrescoRuntimeException("IntegrityChecker property not set: dictionaryService"); - if (nodeService == null) - throw new AlfrescoRuntimeException("IntegrityChecker property not set: nodeService"); - if (policyComponent == null) - throw new AlfrescoRuntimeException("IntegrityChecker property not set: policyComponent"); + PropertyCheck.mandatory("IntegrityChecker", "dictionaryService", dictionaryService); + PropertyCheck.mandatory("IntegrityChecker", "nodeService", nodeService); + PropertyCheck.mandatory("IntegrityChecker", "policyComponent", policyComponent); if (enabled) // only register behaviour if integrity checking is on { @@ -276,6 +273,8 @@ public class IntegrityChecker /** * @see PropertiesIntegrityEvent + * @see AssocTargetRoleIntegrityEvent + * @see AssocTargetMultiplicityIntegrityEvent */ public void onCreateNode(ChildAssociationRef childAssocRef) { @@ -344,6 +343,7 @@ public class IntegrityChecker /** * @see PropertiesIntegrityEvent + * @see AssocTargetMultiplicityIntegrityEvent */ public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { @@ -382,6 +382,13 @@ public class IntegrityChecker { } + /** + * @see AssocSourceTypeIntegrityEvent + * @see AssocTargetTypeIntegrityEvent + * @see AssocSourceMultiplicityIntegrityEvent + * @see AssocTargetMultiplicityIntegrityEvent + * @see AssocTargetRoleIntegrityEvent + */ public void onCreateChildAssociation(ChildAssociationRef childAssocRef) { IntegrityEvent event = null; @@ -426,7 +433,8 @@ public class IntegrityChecker } /** - * @see CreateChildAssocIntegrityEvent + * @see AssocSourceMultiplicityIntegrityEvent + * @see AssocTargetMultiplicityIntegrityEvent */ public void onDeleteChildAssociation(ChildAssociationRef childAssocRef) { @@ -450,7 +458,10 @@ public class IntegrityChecker } /** - * @see AbstractAssocIntegrityEvent + * @see AssocSourceTypeIntegrityEvent + * @see AssocTargetTypeIntegrityEvent + * @see AssocSourceMultiplicityIntegrityEvent + * @see AssocTargetMultiplicityIntegrityEvent */ public void onCreateAssociation(AssociationRef nodeAssocRef) { @@ -488,7 +499,8 @@ public class IntegrityChecker } /** - * @see AbstractAssocIntegrityEvent + * @see AssocSourceMultiplicityIntegrityEvent + * @see AssocTargetMultiplicityIntegrityEvent */ public void onDeleteAssociation(AssociationRef nodeAssocRef) { @@ -583,7 +595,7 @@ public class IntegrityChecker private List processAllEvents() { // the results - ArrayList allIntegrityResults = new ArrayList(0); // generally unused + ArrayList allIntegrityResults = new ArrayList(0); // generally empty // get all the events for the transaction (or unit of work) // duplicates have been elimiated diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java b/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java index 108f9b44c9..8d4c7933c4 100644 --- a/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java +++ b/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java @@ -74,8 +74,10 @@ public class IntegrityTest extends TestCase public static final QName TEST_PROP_TEXT_A = QName.createQName(NAMESPACE, "prop-text-a"); public static final QName TEST_PROP_TEXT_B = QName.createQName(NAMESPACE, "prop-text-b"); + public static final QName TEST_PROP_TEXT_C = QName.createQName(NAMESPACE, "prop-text-c"); public static final QName TEST_PROP_INT_A = QName.createQName(NAMESPACE, "prop-int-a"); public static final QName TEST_PROP_INT_B = QName.createQName(NAMESPACE, "prop-int-b"); + public static final QName TEST_PROP_INT_C = QName.createQName(NAMESPACE, "prop-int-c"); private static ApplicationContext ctx; static diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml b/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml index 3ddf0c8dec..f39557d573 100644 --- a/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml +++ b/source/java/org/alfresco/repo/node/integrity/IntegrityTest_model.xml @@ -27,11 +27,15 @@ d:text - true + true d:text + + d:text + true + @@ -116,6 +120,10 @@ d:int + + d:int + true + diff --git a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java index c3dc77e811..60ba5c1fc5 100644 --- a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java +++ b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java @@ -126,8 +126,8 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent for (PropertyDefinition propertyDef : propertyDefs) { QName propertyQName = propertyDef.getName(); - // check that mandatory properties are set - if (propertyDef.isMandatory() && !nodeProperties.containsKey(propertyQName)) + // check that enforced, mandatoryproperties are set + if (propertyDef.isMandatory() && propertyDef.isMandatoryEnforced() && !nodeProperties.containsKey(propertyQName)) { IntegrityRecord result = new IntegrityRecord( "Mandatory property not set: \n" + diff --git a/source/java/org/alfresco/repo/transaction/TransactionListener.java b/source/java/org/alfresco/repo/transaction/TransactionListener.java index 7b9d191fde..690ddb4193 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionListener.java +++ b/source/java/org/alfresco/repo/transaction/TransactionListener.java @@ -29,6 +29,8 @@ public interface TransactionListener * Allows the listener to flush any consuming resources. This mechanism is * used primarily during long-lived transactions to ensure that system resources * are not used up. + *

+ * This method must not be used for implementing business logic. */ void flush(); diff --git a/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java index 4b38993c6f..f8a5d4c392 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java +++ b/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java @@ -72,6 +72,13 @@ public interface PropertyDefinition */ public boolean isMandatory(); + /** + * @return Returns true if the system enforces the presence of + * {@link #isMandatory() mandatory} properties, or false if the system + * just marks objects that don't have all mandatory properties present. + */ + public boolean isMandatoryEnforced(); + /** * @return true => system maintained, false => client may maintain */ diff --git a/source/java/org/alfresco/util/PropertyCheck.java b/source/java/org/alfresco/util/PropertyCheck.java new file mode 100644 index 0000000000..91464f34fc --- /dev/null +++ b/source/java/org/alfresco/util/PropertyCheck.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.util; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Helper class for for use when checking properties. This class uses + * I18N for its messages. + * + * @author Derek Hulley + */ +public class PropertyCheck +{ + public static final String ERR_PROPERTY_NOT_SET = "system.err.property_not_set"; + + /** + * Checks that the property with the given name is not null. + * + * @param target the object on which the property must have been set + * @param propertyName the name of the property + * @param value of the property value + */ + public static void mandatory(Object target, String propertyName, Object value) + { + if (value == null) + { + throw new AlfrescoRuntimeException( + ERR_PROPERTY_NOT_SET, + new Object[] {propertyName, target}); + } + } +}