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
+