Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (revision 841) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (working copy) @@ -128,6 +128,10 @@ public static I18n autocreatedPropertyNeedsDefault; public static I18n singleValuedPropertyNeedsSingleValuedDefault; + public static I18n noDefinition; + public static I18n noSnsDefinition; + public static I18n missingMandatoryItem; + static { try { I18n.initialize(JcrI18n.class); Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java (revision 841) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java (working copy) @@ -215,6 +215,25 @@ defaultPrimaryTypeName, required); } + + + @Override + public int hashCode() { + return getId().toString().hashCode(); + } + + @Override + public boolean equals( Object obj ) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + JcrNodeDefinition other = (JcrNodeDefinition)obj; + if (id == null) { + if (other.id != null) return false; + } else if (!id.equals(other.id)) return false; + return true; + } + /** * {@inheritDoc} * Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java (revision 841) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java (working copy) @@ -310,7 +310,25 @@ throw new IllegalStateException("Invalid property type: " + type); } } + + @Override + public int hashCode() { + return getId().toString().hashCode(); + } + @Override + public boolean equals( Object obj ) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + JcrPropertyDefinition other = (JcrPropertyDefinition)obj; + if (id == null) { + if (other.id != null) return false; + } else if (!id.equals(other.id)) return false; + return true; + } + + /** * Interface that encapsulates a reusable method that can test values to determine if they match a specific list of * constraints for the semantics associated with a single {@link PropertyType}. Index: dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java (revision 841) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java (working copy) @@ -74,7 +74,7 @@ private final String stringRepresentation; /** - * Create a new identifier for a propety definition. + * Create a new identifier for a property definition. * * @param nodeTypeName the name of the node type; may not be null * @param propertyDefinitionName the name of the property definition, which may be a {@link #ANY_NAME residual property}; may Index: dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (revision 841) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (working copy) @@ -355,12 +356,202 @@ } /** + * Checks that the child items of the node are consistent with the definitions required by the node's primary type and mixin + * types (if any). + *

+ * This method first checks that all of the child nodes and properties for the node have definitions based on the current + * primary and mixin node types for the node as held in the node type registry. The method then checks that all mandatory (and + * non-protected) items are populated. + *

+ * + * @param nodeUuid the UUID of the node to check + * @param checkSns if true indicates that this method should distinguish between child nodes that have no matching definition + * and child nodes that would have a definition that would match if it allowed same-name siblings. This flag determines + * which exception type should be thrown in that case. + * @throws ItemExistsException if checkSns is true and there is no definition that allows same-name siblings for one of the + * node's child nodes and the node already has a child node with the given name + * @throws ConstraintViolationException if one of the node's properties or child nodes does not have a matching definition for + * the name and type among the node's primary and mixin types; this should only occur if type definitions have been + * modified since the node was loaded or modified. + * @throws RepositoryException if any other error occurs + */ + private void checkAgainstTypeDefinitions( UUID nodeUuid, + boolean checkSns ) + throws ConstraintViolationException, ItemExistsException, RepositoryException { + + NodeInfo nodeInfo = findNodeInfo(nodeUuid); + AbstractJcrNode node = findJcrNode(nodeUuid); + + Name primaryTypeName = node.getPrimaryTypeName(); + List mixinTypeNames = node.getMixinTypeNames(); + Set satisfiedChildNodes = new HashSet(); + Set satisfiedProperties = new HashSet(); + + for (AbstractJcrProperty property : findJcrPropertiesFor(nodeUuid)) { + JcrPropertyDefinition definition = findBestPropertyDefintion(primaryTypeName, + mixinTypeNames, + property.property(), + property.getType(), + false); + if (definition == null) { + throw new ConstraintViolationException(JcrI18n.noDefinition.text("property", + property.getName(), + node.getPath(), + primaryTypeName, + mixinTypeNames)); + } + + satisfiedProperties.add(definition); + } + + Children children = nodeInfo.getChildren(); + for (ChildNode child : children) { + int snsCount = children.getCountOfSameNameSiblingsWithName(child.getName()); + NodeInfo childInfo = findNodeInfo(child.getUuid()); + JcrNodeDefinition definition = nodeTypes().findChildNodeDefinition(primaryTypeName, + mixinTypeNames, + child.getName(), + childInfo.getPrimaryTypeName(), + snsCount, + false); + if (definition == null) { + if (checkSns && snsCount > 1) { + definition = nodeTypes().findChildNodeDefinition(primaryTypeName, + mixinTypeNames, + child.getName(), + childInfo.getPrimaryTypeName(), + 1, + false); + + if (definition != null) { + throw new ItemExistsException(JcrI18n.noSnsDefinition.text(child.getName(), + node.getPath(), + primaryTypeName, + mixinTypeNames)); + } + } + throw new ConstraintViolationException(JcrI18n.noDefinition.text("child node", + child.getName(), + node.getPath(), + primaryTypeName, + mixinTypeNames)); + } + satisfiedChildNodes.add(definition); + } + + JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName); + for (JcrPropertyDefinition definition : primaryType.getPropertyDefinitions()) { + if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) { + throw new ConstraintViolationException(JcrI18n.noDefinition.text("property", + definition.getName(), + definition.getDeclaringNodeType().getName(), + node.getPath())); + } + } + for (JcrNodeDefinition definition : primaryType.getChildNodeDefinitions()) { + if (definition.isMandatory() && !definition.isProtected() && !satisfiedChildNodes.contains(definition)) { + throw new ConstraintViolationException(JcrI18n.noDefinition.text("child node", + definition.getName(), + definition.getDeclaringNodeType().getName(), + node.getPath())); + } + } + + for (Name mixinTypeName : mixinTypeNames) { + JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName); + for (JcrPropertyDefinition definition : mixinType.getPropertyDefinitions()) { + if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) { + throw new ConstraintViolationException(JcrI18n.noDefinition.text("property", + definition.getName(), + definition.getDeclaringNodeType().getName(), + node.getPath())); + } + } + for (JcrNodeDefinition definition : mixinType.getChildNodeDefinitions()) { + if (definition.isMandatory() && !definition.isProtected() && !satisfiedChildNodes.contains(definition)) { + throw new ConstraintViolationException(JcrI18n.noDefinition.text("child node", + definition.getName(), + definition.getDeclaringNodeType().getName(), + node.getPath())); + } + } + + } + } + + /** + * Find the best definition for the child node with the given name on the node with the given UUID. + * + * @param nodeUuid the parent node; may not be null + * @param newNodeName the name of the potential new child node; may not be null + * @param newNodePrimaryTypeName the primary type of the potential new child node; may not be null + * @return the definition that best fits the new node name and type + * @throws ItemExistsException if there is no definition that allows same-name siblings for the name and type and the parent + * node already has a child node with the given name + * @throws ConstraintViolationException if there is no definition for the name and type among the parent node's primary and + * mixin types + * @throws RepositoryException if any other error occurs + */ + private JcrNodeDefinition findBestNodeDefinition( UUID nodeUuid, + Name newNodeName, + Name newNodePrimaryTypeName ) + throws ItemExistsException, ConstraintViolationException, RepositoryException { + assert (nodeUuid != null); + assert (newNodeName != null); + assert (newNodePrimaryTypeName != null); + + NodeInfo nodeInfo = findNodeInfo(nodeUuid); + AbstractJcrNode node = findJcrNode(nodeUuid); + + Name primaryTypeName = node.getPrimaryTypeName(); + List mixinTypeNames = node.getMixinTypeNames(); + + Children children = nodeInfo.getChildren(); + int snsCount = children.getCountOfSameNameSiblingsWithName(newNodeName); + JcrNodeDefinition definition = nodeTypes().findChildNodeDefinition(primaryTypeName, + mixinTypeNames, + newNodeName, + newNodePrimaryTypeName, + snsCount, + true); + if (definition == null) { + if (snsCount > 1) { + definition = nodeTypes().findChildNodeDefinition(primaryTypeName, + mixinTypeNames, + newNodeName, + newNodePrimaryTypeName, + 1, + true); + + if (definition != null) { + throw new ItemExistsException(JcrI18n.noSnsDefinition.text(newNodeName, + node.getPath(), + primaryTypeName, + mixinTypeNames)); + } + } + + throw new ConstraintViolationException(JcrI18n.noDefinition.text("child node", + newNodeName, + node.getPath(), + primaryTypeName, + mixinTypeNames)); + } + + return definition; + } + + /** * Save any changes that have been accumulated by this session. * * @throws RepositoryException if any error resulting while saving the changes to the repository */ public void save() throws RepositoryException { if (operations.isExecuteRequired()) { + for (UUID changedUuid : changedNodes.keySet()) { + checkAgainstTypeDefinitions(changedUuid, false); + } + // Execute the batched operations ... try { operations.execute(); @@ -452,10 +644,33 @@ for (ChildNode childNode : changedNode.getChildren()) { uuidsUnderBranch.add(childNode.getUuid()); } + + Collection peers = changedNode.getPeers(); + if (peers != null) peersToCheck.addAll(peers); } } + /* + * Need to check that any peers in a Session.move operation are both in the save + */ + for (UUID peerUuid : peersToCheck) { + if (!uuidsUnderBranch.contains(peerUuid)) { + throw new ConstraintViolationException(); + } + } + + /* + * Also need to check that constraints are met + */ + for (UUID changedUuid : uuidsUnderBranch) { + NodeInfo deletedNodeInfo = deletedNodes.get(changedUuid); + if (deletedNodeInfo != null) { + // Need to check that the parent still matches + checkAgainstTypeDefinitions(deletedNodeInfo.getParent(), false); + } else checkAgainstTypeDefinitions(changedUuid, false); + } + // Now execute the branch ... Graph.Batch branchBatch = store.batch(new BatchRequestBuilder(branchRequests)); try { Index: dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties =================================================================== --- dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (revision 841) +++ dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (working copy) @@ -112,3 +112,7 @@ cannotRedefineProperty=Cannot redefine property '{0}' with new type '{1}' when existing property with same name in type '{2}' has incompatible type '{3}' autocreatedPropertyNeedsDefault=Auto-created property '{0}' in type '{1}' must specify a default value singleValuedPropertyNeedsSingleValuedDefault=Single-valued property '{0}' in type '{1}' cannot have multiple default values + +noDefinition=Cannot find a definition for the {0} named '{1}' on the node at '{2}' with primary type '{3}' and mixin types: {4} +noSnsDefinition=Cannot find a definition that allows same-name siblings for the child node named '{0}' on the node at '{1}' with primary type '{2}' and mixin types: {3} and a child node already exists with this name +missingMandatoryItem=The mandatory {0} named '{1}' defined in type '{2}' is missing from the node at '{4}' Index: dna-jcr/src/test/resources/repositoryStubImpl.properties =================================================================== --- dna-jcr/src/test/resources/repositoryStubImpl.properties (revision 841) +++ dna-jcr/src/test/resources/repositoryStubImpl.properties (working copy) @@ -9,11 +9,11 @@ javax.jcr.tck.workspacename= javax.jcr.tck.nodetype=dnatest\:referenceableUnstructured javax.jcr.tck.nodetypenochildren=dna:namespace -javax.jcr.tck.sourceFolderName=source -javax.jcr.tck.targetFolderName=target +javax.jcr.tck.sourceFolderName=source +javax.jcr.tck.targetFolderName=target javax.jcr.tck.rootNodeName=rootNode javax.jcr.tck.propertySkipped=propertySkipped -javax.jcr.tck.propertyValueMayChange=propertyValueMayChange +javax.jcr.tck.propertyValueMayChange=propertyValueMayChange javax.jcr.tck.nodeTypesTestNode=nodeTypesTestNode javax.jcr.tck.mixinTypeTestNode=mixinTypeTestNode javax.jcr.tck.propertyTypesTestNode=propertyTypesTestNode @@ -22,6 +22,8 @@ javax.jcr.tck.referenceableNodeTestNode=referenceableNodeTestNode javax.jcr.tck.orderChildrenTestNode=orderChildrenTestNode javax.jcr.tck.namespaceTestNode=namespaceTestNode +javax.jcr.tck.sameNameSibsTrueNodeType=nt\:unstructured +javax.jcr.tck.sameNameSibsFalseNodeType=dnatest\:noSameNameSibs javax.jcr.tck.sameNameSibsFalseChildNodeDefinition=dnatest\:noSameNameSibs javax.jcr.tck.stringTestProperty=stringTestProperty javax.jcr.tck.binaryTestProperty=binaryTestProperty @@ -33,4 +35,10 @@ javax.jcr.tck.pathTestProperty=pathTestProperty javax.jcr.tck.referenceTestProperty=referenceTestProperty javax.jcr.tck.multiValueTestProperty=multiValueTestProperty -javax.jcr.tck.NodeTest.testAddNodeItemExistsException.nodetype=dnatest\:noSameNameSibs \ No newline at end of file +javax.jcr.tck.NodeTest.testAddNodeItemExistsException.nodetype=dnatest\:noSameNameSibs +javax.jcr.tck.NodeOrderableChildNodesTest.nodetype2=dnatest\:referenceableUnstructured +javax.jcr.tck.SessionTest.testSaveContstraintViolationException.nodetype2=dnatest\:nodeWithMandatoryProperty +javax.jcr.tck.NodeTest.testRemoveMandatoryNode.nodetype2=dnatest\:nodeWithMandatoryChild +javax.jcr.tck.NodeTest.testRemoveMandatoryNode.nodename3=dnatest\:mandatoryChild +javax.jcr.tck.NodeTest.testSaveContstraintViolationException.nodetype2=dnatest\:nodeWithMandatoryProperty +